aboutsummaryrefslogtreecommitdiffstats
path: root/examples
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
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')
-rwxr-xr-xexamples/tools/clone.sh113
-rw-r--r--examples/tools/clone.toml15
-rwxr-xr-xexamples/tools/test-clone.sh95
3 files changed, 223 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
diff --git a/examples/tools/clone.toml b/examples/tools/clone.toml
new file mode 100644
index 0000000..0862165
--- /dev/null
+++ b/examples/tools/clone.toml
@@ -0,0 +1,15 @@
+description = "Clone a public git repository into the user's project tree. The folder comes from the host: github.com -> GITHUB, gitlab.com -> GITLAB, forge.slackware.nl -> FORGE, plus hosts remembered from earlier clones. Call it with url and nothing else; when the host is known and the repo is not there yet it clones straight away. If it answers that the host has no folder, or that the repo is already cloned, it did nothing: ask the user exactly as that answer says, then call again with the to or update it names. Never clone a repo under a different name when it is already there; offer to update it."
+command = ["clone.sh", "{url}", "{to}", "{update}"]
+timeout = 300
+
+[params.url]
+type = "string"
+description = "Repository URL, e.g. https://github.com/owner/repo.git"
+
+[params.to]
+type = "string"
+description = "Only when the user approved a new host folder: the folder name (e.g. CODEBERG). Empty otherwise."
+
+[params.update]
+type = "string"
+description = "1 to pull an already-cloned repository. Empty otherwise."
diff --git a/examples/tools/test-clone.sh b/examples/tools/test-clone.sh
new file mode 100755
index 0000000..d84a157
--- /dev/null
+++ b/examples/tools/test-clone.sh
@@ -0,0 +1,95 @@
+#!/bin/bash
+# desktop-assistant tool check: clone.sh against a scratch tree and a stub git.
+# 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.
+#
+# test-clone.sh: CLONE_ROOT and CLONE_MAP point at a throwaway dir, and git is
+# a stub that records its argv and creates the clone target, so nothing hits
+# the network or the real tree. Prints "ok" or one FAIL line per broken case.
+
+set -u
+here=$(dirname "$0")
+tmp=$(mktemp -d)
+trap 'rm -rf "$tmp"' EXIT
+export CLONE_ROOT=$tmp/git
+export CLONE_MAP=$tmp/clone-hosts
+export GIT_LOG=$tmp/git.log
+: > "$GIT_LOG"
+mkdir -p "$tmp/bin"
+
+cat > "$tmp/bin/git" <<'EOF'
+#!/bin/bash
+printf '%s\n' "$*" >> "$GIT_LOG"
+[ "$1" = clone ] && mkdir -p "${@: -1}"
+exit 0
+EOF
+chmod +x "$tmp/bin/git"
+PATH=$tmp/bin:$PATH
+export PATH
+
+fail=0
+check() { [ "$2" = "$3" ] || { echo "FAIL $1: got '$2', want '$3'"; fail=1; }; }
+contains() { case "$2" in *"$3"*) ;; *) echo "FAIL $1: '$2' lacks '$3'"; fail=1 ;; esac; }
+count() { grep -cF -- "$1" "$GIT_LOG" || true; }
+mapcount() { grep -cF -- "$1" "$CLONE_MAP" || true; }
+
+# Known host, clean copy: seeded map, straight clone, no question.
+out=$("$here/clone.sh" https://github.com/a/b.git)
+check "github clone" "$out" "cloned https://github.com/a/b.git into $CLONE_ROOT/GITHUB/b"
+check "seed" "$(cat "$CLONE_MAP")" \
+ "$(printf '%s\t%s\n' github.com GITHUB gitlab.com GITLAB forge.slackware.nl FORGE)"
+check "clone argv" "$(count "clone -- https://github.com/a/b.git $CLONE_ROOT/GITHUB/b")" "1"
+
+# Trailing slash and .git are not part of the repo name.
+out=$("$here/clone.sh" https://gitlab.com/a/c.git/)
+check "gitlab repo" "$out" "cloned https://gitlab.com/a/c.git into $CLONE_ROOT/GITLAB/c"
+
+# Unknown host: ask, do nothing, name the folder to offer.
+out=$("$here/clone.sh" https://codeberg.org/a/d)
+contains "unknown asks" "$out" "no folder for host codeberg.org"
+contains "unknown label" "$out" "to=CODEBERG"
+check "unknown no clone" "$(count "$CLONE_ROOT/CODEBERG/d")" "0"
+
+# Approved folder: clone there and remember it.
+out=$("$here/clone.sh" https://codeberg.org/a/d CODEBERG)
+check "approved clone" "$out" "cloned https://codeberg.org/a/d into $CLONE_ROOT/CODEBERG/d"
+check "remembered" "$(mapcount 'codeberg.org')" "1"
+check "remembered pair" "$(grep -F 'codeberg.org' "$CLONE_MAP")" \
+ "$(printf 'codeberg.org\tCODEBERG')"
+
+# Next clone of that host asks nothing.
+out=$("$here/clone.sh" https://codeberg.org/a/e)
+check "remembered clone" "$out" "cloned https://codeberg.org/a/e into $CLONE_ROOT/CODEBERG/e"
+
+# Re-approving the same folder does not duplicate the map line.
+"$here/clone.sh" https://codeberg.org/a/f CODEBERG >/dev/null
+check "no duplicate map line" "$(mapcount 'codeberg.org')" "1"
+
+# Already cloned: ask, do not clone again; update pulls.
+out=$("$here/clone.sh" https://github.com/a/b.git)
+contains "already asks" "$out" "already cloned at $CLONE_ROOT/GITHUB/b"
+check "already no reclone" "$(count "clone -- https://github.com/a/b.git")" "1"
+out=$("$here/clone.sh" https://github.com/a/b.git "" 1)
+contains "update says" "$out" "updated $CLONE_ROOT/GITHUB/b"
+check "update argv" "$(count "-C $CLONE_ROOT/GITHUB/b pull --ff-only")" "1"
+
+# Bad input is refused before anything is created.
+"$here/clone.sh" "" >/dev/null 2>&1; check "empty url" "$?" "1"
+"$here/clone.sh" not-a-url >/dev/null 2>&1; check "not a url" "$?" "1"
+"$here/clone.sh" https://github.com/a/.. >/dev/null 2>&1; check "bad repo" "$?" "1"
+"$here/clone.sh" https://codeberg.org/a/z ../escape >/dev/null 2>&1; check "bad to" "$?" "1"
+
+[ $fail = 0 ] && echo "ok"
+exit $fail