腳本標頭沒把每個結束碼代表什麼、該怎麼處理寫清楚,呼叫端只能用猜的。階梯表又同時抄在說明檔與技能內文,改規則時兩邊容易不同步。 - 推導基底的腳本標頭逐碼說明狀況與處置,並標明該碼屬於哪一種模式。 - 產生分支名的腳本標頭補上參數不足的結束碼。 - 說明檔刪掉重複的階梯表,改指向指引的階梯章節。 - 說明檔補上型別優先序腳本的說明,並更新兩支技能的摘要。
176 lines
7.2 KiB
Bash
Executable File
176 lines
7.2 KiB
Bash
Executable File
#!/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
|