git-clone-path.sh
· 3.7 KiB · Bash
Raw
#!/usr/bin/env bash
#
# git-clone-path.sh — clone a shallow Git subdirectory to a target path.
#
# Usage:
# ./git-clone-path.sh [options] <repo_url> <repo_path>
#
# Options:
# -r, --ref <ref> Branch, tag, or commit to check out (default: default branch)
# -o, --output <dir> Destination directory (default: current directory)
# -f, --flatten Place the subdirectory's contents directly into <dir> (requires -o)
# instead of <dir>/<basename>
# -h, --help Show this help
#
# Examples:
# ./git-clone-path.sh -rmain https://github.com/agent-sh/computer-use-linux \
# skills/computer-use-linux -o./skills/
# # -> ./skills/computer-use-linux
#
# ./git-clone-path.sh -rmain https://github.com/agent-sh/computer-use-linux \
# skills/computer-use-linux -o./skills/ -f
# # -> ./skills/ (contents placed directly)
set -euo pipefail
usage() {
sed -n '2,20p' "$0" | sed 's/^# \{0,1\}//'
}
command -v git >/dev/null 2>&1 || { echo "error: git is required but not found" >&2; exit 1; }
REF=""
OUTPUT=""
FLATTEN=0
# ---- argument parsing -------------------------------------------------------
# Options may appear before, between, or after the positional args.
POSITIONAL=()
while [[ $# -gt 0 ]]; do
case "$1" in
-r|--ref)
[[ $# -ge 2 ]] || { echo "error: $1 requires a value" >&2; exit 1; }
REF="$2"; shift 2 ;;
--ref=*) REF="${1#*=}"; shift ;;
-r?*) REF="${1#-r}"; shift ;; # -rmain
-o|--output)
[[ $# -ge 2 ]] || { echo "error: $1 requires a value" >&2; exit 1; }
OUTPUT="$2"; shift 2 ;;
--output=*) OUTPUT="${1#*=}"; shift ;;
-o?*) OUTPUT="${1#-o}"; shift ;; # -o./skills/
-f|--flatten) FLATTEN=1; shift ;;
-h|--help) usage; exit 0 ;;
-*) echo "error: unknown option: $1" >&2; usage >&2; exit 1 ;;
*) POSITIONAL+=("$1"); shift ;;
esac
done
if [[ ${#POSITIONAL[@]} -ne 2 ]]; then
echo "error: expected <repo_url> and <repo_path>" >&2
usage >&2
exit 1
fi
if [[ "$FLATTEN" -eq 1 && -z "$OUTPUT" ]]; then
echo "error: -f/--flatten requires -o/--output" >&2
exit 1
fi
REPO_URL="${POSITIONAL[0]}"
REPO_PATH="${POSITIONAL[1]#./}" # strip leading ./
REPO_PATH="${REPO_PATH#/}" # strip leading /
REPO_PATH="${REPO_PATH%/}" # strip trailing /
# ---- resolve basename & target ---------------------------------------------
if [[ -z "$REPO_PATH" || "$REPO_PATH" == "." ]]; then
REPO_PATH="."
BASENAME="$(basename "${REPO_URL%%\?*}" .git)"
else
BASENAME="$(basename "$REPO_PATH")"
fi
if [[ -n "$OUTPUT" ]]; then
OUTDIR="${OUTPUT%/}"
else
OUTDIR="."
fi
if [[ "$FLATTEN" -eq 1 ]]; then
TARGET="$OUTDIR"
else
if [[ -n "$OUTPUT" ]]; then
TARGET="$OUTDIR/$BASENAME"
else
TARGET="$BASENAME"
fi
fi
# ---- shallow sparse clone ---------------------------------------------------
TMP_DIR="$(mktemp -d)"
trap 'rm -rf "$TMP_DIR"' EXIT
echo "Cloning $REPO_URL (path: $REPO_PATH) ..."
git clone --depth 1 --filter=blob:none --no-checkout "$REPO_URL" "$TMP_DIR/repo"
git -C "$TMP_DIR/repo" sparse-checkout init --cone
git -C "$TMP_DIR/repo" sparse-checkout set "$REPO_PATH"
if [[ -n "$REF" ]]; then
git -C "$TMP_DIR/repo" fetch --depth 1 origin "$REF"
git -C "$TMP_DIR/repo" checkout --detach FETCH_HEAD
else
git -C "$TMP_DIR/repo" checkout
fi
SRC="$TMP_DIR/repo/$REPO_PATH"
if [[ ! -e "$SRC" ]]; then
echo "error: path '$REPO_PATH' not found in repository" >&2
exit 1
fi
# ---- place into target ------------------------------------------------------
if [[ "$FLATTEN" -eq 1 ]]; then
mkdir -p "$TARGET"
cp -a "$SRC"/. "$TARGET"/
else
mkdir -p "$(dirname "$TARGET")"
rm -rf "$TARGET"
mv "$SRC" "$TARGET"
fi
echo "Done -> $TARGET"
| 1 | #!/usr/bin/env bash |
| 2 | # |
| 3 | # git-clone-path.sh — clone a shallow Git subdirectory to a target path. |
| 4 | # |
| 5 | # Usage: |
| 6 | # ./git-clone-path.sh [options] <repo_url> <repo_path> |
| 7 | # |
| 8 | # Options: |
| 9 | # -r, --ref <ref> Branch, tag, or commit to check out (default: default branch) |
| 10 | # -o, --output <dir> Destination directory (default: current directory) |
| 11 | # -f, --flatten Place the subdirectory's contents directly into <dir> (requires -o) |
| 12 | # instead of <dir>/<basename> |
| 13 | # -h, --help Show this help |
| 14 | # |
| 15 | # Examples: |
| 16 | # ./git-clone-path.sh -rmain https://github.com/agent-sh/computer-use-linux \ |
| 17 | # skills/computer-use-linux -o./skills/ |
| 18 | # # -> ./skills/computer-use-linux |
| 19 | # |
| 20 | # ./git-clone-path.sh -rmain https://github.com/agent-sh/computer-use-linux \ |
| 21 | # skills/computer-use-linux -o./skills/ -f |
| 22 | # # -> ./skills/ (contents placed directly) |
| 23 | |
| 24 | set -euo pipefail |
| 25 | |
| 26 | usage() { |
| 27 | sed -n '2,20p' "$0" | sed 's/^# \{0,1\}//' |
| 28 | } |
| 29 | |
| 30 | command -v git >/dev/null 2>&1 || { echo "error: git is required but not found" >&2; exit 1; } |
| 31 | |
| 32 | REF="" |
| 33 | OUTPUT="" |
| 34 | FLATTEN=0 |
| 35 | |
| 36 | # ---- argument parsing ------------------------------------------------------- |
| 37 | # Options may appear before, between, or after the positional args. |
| 38 | POSITIONAL=() |
| 39 | while [[ $# -gt 0 ]]; do |
| 40 | case "$1" in |
| 41 | -r|--ref) |
| 42 | [[ $# -ge 2 ]] || { echo "error: $1 requires a value" >&2; exit 1; } |
| 43 | REF="$2"; shift 2 ;; |
| 44 | --ref=*) REF="${1#*=}"; shift ;; |
| 45 | -r?*) REF="${1#-r}"; shift ;; # -rmain |
| 46 | -o|--output) |
| 47 | [[ $# -ge 2 ]] || { echo "error: $1 requires a value" >&2; exit 1; } |
| 48 | OUTPUT="$2"; shift 2 ;; |
| 49 | --output=*) OUTPUT="${1#*=}"; shift ;; |
| 50 | -o?*) OUTPUT="${1#-o}"; shift ;; # -o./skills/ |
| 51 | -f|--flatten) FLATTEN=1; shift ;; |
| 52 | -h|--help) usage; exit 0 ;; |
| 53 | -*) echo "error: unknown option: $1" >&2; usage >&2; exit 1 ;; |
| 54 | *) POSITIONAL+=("$1"); shift ;; |
| 55 | esac |
| 56 | done |
| 57 | |
| 58 | if [[ ${#POSITIONAL[@]} -ne 2 ]]; then |
| 59 | echo "error: expected <repo_url> and <repo_path>" >&2 |
| 60 | usage >&2 |
| 61 | exit 1 |
| 62 | fi |
| 63 | |
| 64 | if [[ "$FLATTEN" -eq 1 && -z "$OUTPUT" ]]; then |
| 65 | echo "error: -f/--flatten requires -o/--output" >&2 |
| 66 | exit 1 |
| 67 | fi |
| 68 | |
| 69 | REPO_URL="${POSITIONAL[0]}" |
| 70 | REPO_PATH="${POSITIONAL[1]#./}" # strip leading ./ |
| 71 | REPO_PATH="${REPO_PATH#/}" # strip leading / |
| 72 | REPO_PATH="${REPO_PATH%/}" # strip trailing / |
| 73 | |
| 74 | # ---- resolve basename & target --------------------------------------------- |
| 75 | if [[ -z "$REPO_PATH" || "$REPO_PATH" == "." ]]; then |
| 76 | REPO_PATH="." |
| 77 | BASENAME="$(basename "${REPO_URL%%\?*}" .git)" |
| 78 | else |
| 79 | BASENAME="$(basename "$REPO_PATH")" |
| 80 | fi |
| 81 | |
| 82 | if [[ -n "$OUTPUT" ]]; then |
| 83 | OUTDIR="${OUTPUT%/}" |
| 84 | else |
| 85 | OUTDIR="." |
| 86 | fi |
| 87 | |
| 88 | if [[ "$FLATTEN" -eq 1 ]]; then |
| 89 | TARGET="$OUTDIR" |
| 90 | else |
| 91 | if [[ -n "$OUTPUT" ]]; then |
| 92 | TARGET="$OUTDIR/$BASENAME" |
| 93 | else |
| 94 | TARGET="$BASENAME" |
| 95 | fi |
| 96 | fi |
| 97 | |
| 98 | # ---- shallow sparse clone --------------------------------------------------- |
| 99 | TMP_DIR="$(mktemp -d)" |
| 100 | trap 'rm -rf "$TMP_DIR"' EXIT |
| 101 | |
| 102 | echo "Cloning $REPO_URL (path: $REPO_PATH) ..." |
| 103 | git clone --depth 1 --filter=blob:none --no-checkout "$REPO_URL" "$TMP_DIR/repo" |
| 104 | |
| 105 | git -C "$TMP_DIR/repo" sparse-checkout init --cone |
| 106 | git -C "$TMP_DIR/repo" sparse-checkout set "$REPO_PATH" |
| 107 | |
| 108 | if [[ -n "$REF" ]]; then |
| 109 | git -C "$TMP_DIR/repo" fetch --depth 1 origin "$REF" |
| 110 | git -C "$TMP_DIR/repo" checkout --detach FETCH_HEAD |
| 111 | else |
| 112 | git -C "$TMP_DIR/repo" checkout |
| 113 | fi |
| 114 | |
| 115 | SRC="$TMP_DIR/repo/$REPO_PATH" |
| 116 | if [[ ! -e "$SRC" ]]; then |
| 117 | echo "error: path '$REPO_PATH' not found in repository" >&2 |
| 118 | exit 1 |
| 119 | fi |
| 120 | |
| 121 | # ---- place into target ------------------------------------------------------ |
| 122 | if [[ "$FLATTEN" -eq 1 ]]; then |
| 123 | mkdir -p "$TARGET" |
| 124 | cp -a "$SRC"/. "$TARGET"/ |
| 125 | else |
| 126 | mkdir -p "$(dirname "$TARGET")" |
| 127 | rm -rf "$TARGET" |
| 128 | mv "$SRC" "$TARGET" |
| 129 | fi |
| 130 | |
| 131 | echo "Done -> $TARGET" |
| 132 |