Files
git/tools/base-branch.sh
T
jiantw83 25792fd279 docs(tools): 補上腳本結束碼說明,並改指階梯規則正本
腳本標頭沒把每個結束碼代表什麼、該怎麼處理寫清楚,呼叫端只能用猜的。階梯表又同時抄在說明檔與技能內文,改規則時兩邊容易不同步。

- 推導基底的腳本標頭逐碼說明狀況與處置,並標明該碼屬於哪一種模式。
- 產生分支名的腳本標頭補上參數不足的結束碼。
- 說明檔刪掉重複的階梯表,改指向指引的階梯章節。
- 說明檔補上型別優先序腳本的說明,並更新兩支技能的摘要。
2026-08-31 11:05:49 +08:00

176 lines
7.2 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env sh
# base-branch.sh — 決定 PR 的基底分支。
# 用法: base-branch.sh [caller-base]
# base-branch.sh --derive [branch]
#
# 模式一(既有): 呼叫方傳入的分支最優先;呼叫方沒傳,才依序試 develop、main、master。
# 「有沒有傳」看參數個數,不看參數內容:呼叫方寫 base-branch.sh "$SOURCE_BRANCH"
# 而變數沒設定時,那是空字串,不是沒傳。若當成沒傳就會悄悄退回 develop,
# 把單一工作包直接合進 develop——這支腳本存在的目的就是擋這件事。
#
# 模式二(--derive): 從分支名推出唯一合法的上一階基底,禁止越級。
# 沒傳 branch 就取目前分支。階梯如下:
# feat、docs、style、refactor、perf、test、chore、revert:
# {類型}/{功能}/{子功能} → {類型}/{功能}/main → develop → master
# {子功能} 可以多層(例:feat/a/b/c),推導一律「去掉最後一段後接 /main」。
# fix:
# fix/{修改} → develop → master
# 推不出唯一合法基底就中止(退出碼 7),由呼叫端問使用者,不猜、也不退回 develop。
# 功能主幹 {類型}/{功能}/main 不在 origin 時,自動以 origin/develop 為起點建立並推上去,
# 再把建立了哪一條分支印到 stderr。
#
# 分支名只允許 ASCII(a-z0-9 與 /、-)。中文簡述先交給同目錄的 slugify.sh 轉成 ASCII slug,
# 再組成分支名,這支腳本不接受非 ASCII 分支名。
#
# 輸出: 選中的分支名(一行)。錯誤訊息一律印繁中到 stderr。
# 結束碼: 0=stdout 印出一個基底分支名,兩種模式共用。直接拿它開 PR。
# 2=參數過多(兩種模式共用)。分支名要用引號包成單一參數,再重跑。
# 3=連不上 origin,git fetch 失敗(兩種模式共用)。先確認遠端可以連線,再重跑。
# 4=(呼叫方模式)呼叫方指定的分支不在 origin 上。停下來問使用者原本要的是哪一條,
# 不要自行改用其他分支。
# 5=(呼叫方模式)origin 上找不到 develop、main、master。這個碼只在完全沒傳參數時
# 才會出現,所以真正的問題通常是基底參數在路上掉了。請由呼叫方指定基底分支。
# 6=(呼叫方模式)傳進來的是空字串。回去補上分支變數的值,不要改成整個參數不傳——
# 不傳會悄悄退回 develop。
# 7=(--derive 模式)推不出唯一合法基底:站在斷頭狀態、站在 master、分支名含非 ASCII
# 或其他不允許的字元、類型不在階梯表內、feat 這類階梯少了功能層,或 fix 寫成多層。
# 照 stderr 的訊息修分支名再重跑,不要退回 develop。
# 8=(--derive 模式)推導出的基底不在 origin 上,而且它不是可以自動建立的功能主幹。
# 先把那條分支建出來並推上 origin,再重跑。
# 9=(--derive 模式)自動建立功能主幹失敗:origin 上沒有 develop,或推送被拒。
# 先建好 develop,或確認推送權限,再重跑。
set -u
MODE=caller
if [ "$#" -ge 1 ] && [ "$1" = "--derive" ]; then
MODE=derive
shift
fi
if [ "$#" -gt 1 ]; then
echo "用法: base-branch.sh [caller-base] 或 base-branch.sh --derive [branch]" >&2
exit 2
fi
ARGC=$#
ARG=${1:-}
if [ "$ARGC" -eq 1 ] && [ -z "$ARG" ]; then
echo "錯誤: 傳入空字串當分支名。請確認分支變數有值,或整個參數不要傳。" >&2
exit 6
fi
if ! git fetch --prune origin >/dev/null 2>&1; then
echo "錯誤: 無法向 origin 取得遠端分支。請確認遠端可以連線,再重試。" >&2
exit 3
fi
remote_has() {
[ -n "$(git ls-remote --heads origin "refs/heads/$1" 2>/dev/null)" ]
}
if [ "$MODE" = "caller" ]; then
if [ "$ARGC" -eq 1 ]; then
if remote_has "$ARG"; then
printf '%s\n' "$ARG"
exit 0
fi
echo "錯誤: 呼叫方指定的基底分支 $ARG 不在 origin 上。請確認分支名,不要自行改用其他分支。" >&2
exit 4
fi
for candidate in develop main master; do
if remote_has "$candidate"; then
printf '%s\n' "$candidate"
exit 0
fi
done
echo "錯誤: origin 上找不到 develop、main、master。請由呼叫方指定基底分支。" >&2
exit 5
fi
# 以下是 --derive 模式。
BRANCH=$ARG
if [ -z "$BRANCH" ]; then
BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || true)
fi
if [ -z "$BRANCH" ] || [ "$BRANCH" = "HEAD" ]; then
echo "錯誤: 取不到目前分支名(可能在斷頭狀態)。請先切到分支,或直接把分支名當參數傳進來。" >&2
exit 7
fi
if printf '%s' "$BRANCH" | LC_ALL=C grep -q '[^a-z0-9/-]'; then
echo "錯誤: 分支名 $BRANCH 含分支名不允許的字元。分支名只允許 ASCII 小寫、數字、連字號與斜線;中文簡述請先交給 slugify.sh。" >&2
exit 7
fi
TYPE=${BRANCH%%/*}
SEGMENTS=$(printf '%s' "$BRANCH" | tr '/' '\n' | grep -c '')
LAST=${BRANCH##*/}
HEAD_PART=${BRANCH%/*}
ladder_type() {
case "$1" in
feat|docs|style|refactor|perf|test|chore|revert) return 0 ;;
*) return 1 ;;
esac
}
BASE=""
TRUNK_AUTOCREATE=no
if [ "$BRANCH" = "develop" ]; then
BASE=master
elif [ "$BRANCH" = "master" ]; then
echo "錯誤: master 已經是階梯頂端,推不出上一階基底分支。請確認是不是站錯分支。" >&2
exit 7
elif [ "$TYPE" = "fix" ]; then
if [ "$SEGMENTS" -eq 2 ]; then
BASE=develop
else
echo "錯誤: 分支 $BRANCH 不符合 fix 階梯。fix 只有 fix/{修改} → develop → master 一條路,不接受多層分支名。" >&2
exit 7
fi
elif ladder_type "$TYPE"; then
if [ "$SEGMENTS" -lt 3 ]; then
echo "錯誤: 分支 $BRANCH 少了功能層,推不出唯一合法基底。$TYPE 階梯為 $TYPE/{功能}/{子功能} → $TYPE/{功能}/main → develop → master,請補上功能層再重試。" >&2
exit 7
elif [ "$LAST" = "main" ]; then
BASE=develop
else
BASE="$HEAD_PART/main"
TRUNK_AUTOCREATE=yes
fi
else
echo "錯誤: 分支 $BRANCH 的類型 $TYPE 不在階梯表內,推不出唯一合法基底。請改用 feat、fix、docs、style、refactor、perf、test、chore、revert 其中一種類型。" >&2
exit 7
fi
if remote_has "$BASE"; then
printf '%s\n' "$BASE"
exit 0
fi
if [ "$TRUNK_AUTOCREATE" = "no" ]; then
echo "錯誤: 推導出的基底分支 $BASE 不在 origin 上,而且它不是可以自動建立的功能主幹。請先建立 $BASE,再重試。" >&2
exit 8
fi
# 功能主幹不存在就自動補一條:直接把 origin/develop 推成新分支,
# 不動本機工作區,省下切分支與切回來的來回。
if ! remote_has develop; then
echo "錯誤: origin 上沒有 develop,無法自動建立功能主幹 $BASE。請先建立 develop,再重試。" >&2
exit 9
fi
if ! git push origin "refs/remotes/origin/develop:refs/heads/$BASE" >/dev/null 2>&1; then
echo "錯誤: 自動建立功能主幹 $BASE 失敗。請確認遠端推送權限,或手動從 develop 建立 $BASE。" >&2
exit 9
fi
echo "已自動從 develop 建立功能主幹 $BASE,並推上 origin。" >&2
printf '%s\n' "$BASE"
exit 0