aboutsummaryrefslogtreecommitdiffstats
path: root/examples/tools/clone.sh
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-10-19 19:49:08 +0200
committerDanilo M. <danix@danix.xyz>2026-10-19 19:49:08 +0200
commitb6965ac87bfffb610bf099812562951cc154738e (patch)
tree0431d2466731a9bc4084843298afb5268ca38985 /examples/tools/clone.sh
parent6cb503a50fc49ec26af0f0624141a0c97e75cca4 (diff)
downloaddesktop-assistant-b6965ac87bfffb610bf099812562951cc154738e.tar.gz
desktop-assistant-b6965ac87bfffb610bf099812562951cc154738e.zip
clone tool: clone a public repo into the host's folder
A public URL is cloned into ~/Programming/GIT/<host-folder>/<repo>, the folder chosen by host from ~/.config/desktop-assistant/clone-hosts (github.com, gitlab.com, forge.slackware.nl seeded). An unknown host or an already-cloned repo stops and asks with a concrete action; approving a new host folder appends it to the map, so later clones of that host ask nothing. Update pulls with --ff-only, never clones over the folder. test-clone.sh stubs git and uses a scratch tree and map.
Diffstat (limited to 'examples/tools/clone.sh')
-rwxr-xr-xexamples/tools/clone.sh113
1 files changed, 113 insertions, 0 deletions
diff --git a/examples/tools/clone.sh b/examples/tools/clone.sh
new file mode 100755
index 0000000..e4051fc
--- /dev/null
+++ b/examples/tools/clone.sh
@@ -0,0 +1,113 @@
+#!/bin/bash
+# desktop-assistant tool: clone a public git repository into the project tree.
+# Copyright (C) 2026 Danilo M. <danix@danix.xyz>
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 2 as published
+# by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+# Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# clone.sh <url> [to] [update]: clone into <root>/<folder>/<repo>, the folder
+# chosen by host from the clone-hosts map (github.com -> GITHUB, ...). An
+# unknown host stops and says which folder to offer; passing that folder as
+# <to> clones there and remembers the mapping. <update>=1 pulls an existing
+# clone instead. Check: test-clone.sh.
+
+set -uo pipefail
+
+url=${1:-}
+to=${2:-}
+update=${3:-}
+root=${CLONE_ROOT:-$HOME/Programming/GIT}
+map=${CLONE_MAP:-${XDG_CONFIG_HOME:-$HOME/.config}/desktop-assistant/clone-hosts}
+
+[ -n "$url" ] || { echo "url is empty" >&2; exit 1; }
+# The fragment and query are not part of a repository; a trailing slash is not
+# either. The repo name is the last path segment with a .git suffix removed.
+url=${url%%\#*}
+url=${url%%\?*}
+url=${url%/}
+
+case "$url" in
+ *://*)
+ rest=${url#*://}
+ rest=${rest#*@}
+ host=${rest%%/*}
+ path=${rest#*/}
+ ;;
+ *@*:*)
+ rest=${url#*@}
+ host=${rest%%:*}
+ path=${rest#*:}
+ ;;
+ *)
+ echo "not a git URL: $url" >&2; exit 1
+ ;;
+esac
+host=${host%%:*}
+path=${path%/}
+repo=${path##*/}
+repo=${repo%.git}
+
+# Both names land in a path, so neither may escape the tree.
+[[ $host =~ ^[A-Za-z0-9][A-Za-z0-9.-]*$ ]] ||
+ { echo "bad host: $host" >&2; exit 1; }
+[[ $repo =~ ^[A-Za-z0-9][A-Za-z0-9._-]*$ ]] ||
+ { echo "bad repository name: $repo" >&2; exit 1; }
+
+# host -> folder, one tab-separated pair per line, seeded once. Appending the
+# approved folder is how a new host is remembered, so the next clone of that
+# host goes straight there.
+if [ ! -e "$map" ]; then
+ mkdir -p "$(dirname "$map")" || exit 1
+ printf '%s\t%s\n' github.com GITHUB gitlab.com GITLAB \
+ forge.slackware.nl FORGE > "$map" || exit 1
+fi
+folder=$(awk -v h="$host" '$1 == h { f = $2 } END { print f }' "$map")
+
+if [ -n "$to" ]; then
+ [[ $to =~ ^[A-Za-z0-9][A-Za-z0-9._-]*$ ]] ||
+ { echo "bad folder name: $to" >&2; exit 1; }
+ [ -n "$folder" ] || printf '%s\t%s\n' "$host" "$to" >> "$map" || exit 1
+ folder=$to
+fi
+
+if [ -z "$folder" ]; then
+ label=${host%.*}
+ label=$(printf '%s' "$label" | tr '[:lower:]' '[:upper:]')
+ echo "no folder for host $host; ask the user: create $label/ and clone $repo there? if yes, call again with to=$label"
+ exit 0
+fi
+
+target=$root/$folder/$repo
+
+# Never clone over an existing folder, and never under another name: offer an
+# update instead. ff-only so a diverged clone is reported, not merged.
+if [ -e "$target" ]; then
+ if [ -n "$update" ]; then
+ out=$(git -C "$target" pull --ff-only 2>&1) && {
+ echo "updated $target: $(tail -1 <<< "$out")"
+ exit 0
+ }
+ echo "could not update $target: $(tail -1 <<< "$out")" >&2
+ exit 1
+ fi
+ echo "already cloned at $target; ask the user: update it? if yes, call again with update=1"
+ exit 0
+fi
+
+mkdir -p "$root/$folder" || exit 1
+if out=$(git clone -- "$url" "$target" 2>&1); then
+ echo "cloned $url into $target"
+else
+ echo "clone failed: $(tail -1 <<< "$out")" >&2
+ exit 1
+fi