技能以前只寫成功路徑。腳本回非 0 時,模型得自己猜下一步,猜錯就是靜靜 往下走。現在每一支腳本在檔頭宣告自己的結束碼,技能也逐碼寫明要停、要 問、還是要改參數再呼叫一次。兩支技能補上連線變數的解析步驟,讓缺值在 第一步就浮出來,而不是在中途撞出一行英文錯誤。 流程也拉平了。取內容、選範本、問輸出位置這幾件事彼此不相依,改成同一批 送出;存取庫批次同步從逐一處理改成各存取庫同時進行,一個 owner 底下有 上百個存取庫時差距最明顯。 相依的技能組下限寫進外掛設定,版本推進。
84 lines
3.3 KiB
Bash
Executable File
84 lines
3.3 KiB
Bash
Executable File
#!/usr/bin/env sh
|
||
# repo-sync.sh — 把單一存取庫同步到本機。
|
||
# 用法: repo-sync.sh <owner>/<repo> [target-dir] # target-dir 預設為 <repo>
|
||
# 目錄不存在 → git clone(clone URL 取自 gitea.sh clone-url)
|
||
# 目錄已存在 → git fetch、切到基準分支、git pull --ff-only
|
||
# 有未提交變更 → 只回報 dirty 與基準分支,不動分支也不 pull
|
||
# 基準分支的唯一優先序(呼叫端不必再判斷,也不要自己再推導一次):
|
||
# 1. gitea.sh default-branch <owner>/<repo>(該分支要在遠端存在)
|
||
# 2. 查不到時依序取遠端的 develop、main、master
|
||
# 3. 都沒有 → failed no default branch
|
||
# 輸出: 恰好一行,cloned、updated、dirty {分支} 或 failed {原因}。
|
||
# 前三種 exit 0;failed exit 1。
|
||
# dirty 會把解析好的基準分支一起帶出來,呼叫端直接拿去當 PR 的 base。
|
||
# 結束碼: 0=同步完成,輸出是 cloned、updated 或 dirty {分支}
|
||
# 1=失敗,輸出是 failed {原因};參數錯誤、clone-url 取不到、fetch/checkout/pull 失敗、
|
||
# 以及找不到基準分支都走這一碼。只有 0 與 1 兩種。
|
||
set -u
|
||
|
||
script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||
gitea="$script_dir/gitea.sh"
|
||
|
||
fail() { # 原因壓成一行,避免呼叫端解析多行輸出
|
||
printf 'failed %s\n' "$(printf '%s' "$1" | tr '\n\r\t' ' ')"
|
||
exit 1
|
||
}
|
||
|
||
full="${1:-}"
|
||
[ -n "$full" ] || fail "usage: repo-sync.sh <owner>/<repo> [target-dir]"
|
||
case "$full" in
|
||
*/*/*|/*|*/) fail "not owner/repo: $full" ;;
|
||
*/*) ;;
|
||
*) fail "not owner/repo: $full" ;;
|
||
esac
|
||
repo="${full#*/}"
|
||
target="${2:-$repo}"
|
||
|
||
remote_has() { # 遠端是否有這個分支
|
||
git -C "$target" rev-parse --verify --quiet "refs/remotes/origin/$1" >/dev/null 2>&1
|
||
}
|
||
|
||
if [ ! -e "$target" ]; then
|
||
# clone URL 只收 stdout:把 stderr 併進來會讓任何雜訊變成網址的一部分
|
||
err=$(mktemp)
|
||
url=$("$gitea" clone-url "$full" 2>"$err") || url=''
|
||
if [ -z "$url" ]; then
|
||
reason=$(cat "$err"); rm -f "$err"
|
||
fail "clone-url failed for $full: ${reason:-no clone URL}"
|
||
fi
|
||
rm -f "$err"
|
||
out=$(git clone "$url" "$target" 2>&1) || fail "clone: $out"
|
||
printf 'cloned\n'
|
||
exit 0
|
||
fi
|
||
|
||
git -C "$target" rev-parse --git-dir >/dev/null 2>&1 || fail "$target is not a git repo"
|
||
|
||
dirty=0
|
||
[ -z "$(git -C "$target" status --porcelain 2>/dev/null)" ] || dirty=1
|
||
|
||
# dirty 的存取庫也要解析基準分支:呼叫端拿它當 PR 的 base,自己再推導一次就會
|
||
# 出現第二套優先序。fetch 只更新 refs,不動工作目錄,dirty 時照樣安全;但 dirty
|
||
# 時網路失敗不算致命,還是要把 dirty 回報出去,改用本機既有的 remote refs 判斷。
|
||
if ! out=$(git -C "$target" fetch --prune origin 2>&1); then
|
||
[ "$dirty" -eq 1 ] || fail "fetch: $out"
|
||
fi
|
||
|
||
branch=$("$gitea" default-branch "$full" 2>/dev/null) || branch=''
|
||
if [ -z "$branch" ] || ! remote_has "$branch"; then
|
||
branch=''
|
||
for b in develop main master; do
|
||
if remote_has "$b"; then branch="$b"; break; fi
|
||
done
|
||
fi
|
||
[ -n "$branch" ] || fail "no default branch"
|
||
|
||
if [ "$dirty" -eq 1 ]; then
|
||
printf 'dirty %s\n' "$branch"
|
||
exit 0
|
||
fi
|
||
|
||
out=$(git -C "$target" checkout "$branch" 2>&1) || fail "checkout $branch: $out"
|
||
out=$(git -C "$target" pull --ff-only origin "$branch" 2>&1) || fail "pull $branch: $out"
|
||
printf 'updated\n'
|