diff options
| author | Danilo M. <danix@danix.xyz> | 2026-10-19 19:49:08 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-10-19 19:49:08 +0200 |
| commit | b6965ac87bfffb610bf099812562951cc154738e (patch) | |
| tree | 0431d2466731a9bc4084843298afb5268ca38985 /examples/tools/clone.sh | |
| parent | 6cb503a50fc49ec26af0f0624141a0c97e75cca4 (diff) | |
| download | desktop-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-x | examples/tools/clone.sh | 113 |
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 |
