feat(git): PR 目標分支改走階梯推導,既有 PR 改為校準不重開 #12
+131
-21
@@ -1,27 +1,51 @@
|
|||||||
#!/usr/bin/env sh
|
#!/usr/bin/env sh
|
||||||
# base-branch.sh — 決定 PR 的基底分支。
|
# base-branch.sh — 決定 PR 的基底分支。
|
||||||
# 用法: base-branch.sh [caller-base]
|
# 用法: base-branch.sh [caller-base]
|
||||||
# 規則: 呼叫方傳入的分支最優先;呼叫方沒傳,才依序試 develop、main、master。
|
# base-branch.sh --derive [branch]
|
||||||
# 先 git fetch --prune origin,再確認選中的分支存在於遠端。
|
#
|
||||||
|
# 模式一(既有): 呼叫方傳入的分支最優先;呼叫方沒傳,才依序試 develop、main、master。
|
||||||
# 「有沒有傳」看參數個數,不看參數內容:呼叫方寫 base-branch.sh "$SOURCE_BRANCH"
|
# 「有沒有傳」看參數個數,不看參數內容:呼叫方寫 base-branch.sh "$SOURCE_BRANCH"
|
||||||
# 而變數沒設定時,那是空字串,不是沒傳。若當成沒傳就會悄悄退回 develop,
|
# 而變數沒設定時,那是空字串,不是沒傳。若當成沒傳就會悄悄退回 develop,
|
||||||
# 把單一工作包直接合進 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 分支名。
|
||||||
|
#
|
||||||
# 輸出: 選中的分支名(一行)。
|
# 輸出: 選中的分支名(一行)。
|
||||||
# 護欄: 參數過多回傳 2;抓不到遠端回傳 3;呼叫方指定的分支不在遠端回傳 4;
|
# 護欄: 參數過多回傳 2;抓不到遠端回傳 3;呼叫方指定的分支不在遠端回傳 4;
|
||||||
# develop、main、master 都不在遠端回傳 5;呼叫方傳入空字串回傳 6。
|
# develop、main、master 都不在遠端回傳 5;傳入空字串回傳 6;
|
||||||
|
# 推不出唯一合法基底回傳 7;推導出的基底不在遠端又不能自動建立回傳 8;
|
||||||
|
# 自動建立功能主幹失敗回傳 9。
|
||||||
# 錯誤訊息一律印繁中到 stderr。
|
# 錯誤訊息一律印繁中到 stderr。
|
||||||
set -u
|
set -u
|
||||||
|
|
||||||
|
MODE=caller
|
||||||
|
if [ "$#" -ge 1 ] && [ "$1" = "--derive" ]; then
|
||||||
|
MODE=derive
|
||||||
|
shift
|
||||||
|
fi
|
||||||
|
|
||||||
if [ "$#" -gt 1 ]; then
|
if [ "$#" -gt 1 ]; then
|
||||||
echo "用法: base-branch.sh [caller-base]" >&2
|
echo "用法: base-branch.sh [caller-base] 或 base-branch.sh --derive [branch]" >&2
|
||||||
exit 2
|
exit 2
|
||||||
fi
|
fi
|
||||||
|
|
||||||
ARGC=$#
|
ARGC=$#
|
||||||
CALLER=${1:-}
|
ARG=${1:-}
|
||||||
|
|
||||||
if [ "$ARGC" -eq 1 ] && [ -z "$CALLER" ]; then
|
if [ "$ARGC" -eq 1 ] && [ -z "$ARG" ]; then
|
||||||
echo "錯誤: 呼叫方傳入空字串當基底分支。請確認來源分支變數有值,或整個參數不要傳。" >&2
|
echo "錯誤: 傳入空字串當分支名。請確認分支變數有值,或整個參數不要傳。" >&2
|
||||||
exit 6
|
exit 6
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -34,21 +58,107 @@ remote_has() {
|
|||||||
[ -n "$(git ls-remote --heads origin "refs/heads/$1" 2>/dev/null)" ]
|
[ -n "$(git ls-remote --heads origin "refs/heads/$1" 2>/dev/null)" ]
|
||||||
}
|
}
|
||||||
|
|
||||||
if [ "$ARGC" -eq 1 ]; then
|
if [ "$MODE" = "caller" ]; then
|
||||||
if remote_has "$CALLER"; then
|
if [ "$ARGC" -eq 1 ]; then
|
||||||
printf '%s\n' "$CALLER"
|
if remote_has "$ARG"; then
|
||||||
exit 0
|
printf '%s\n' "$ARG"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
echo "錯誤: 呼叫方指定的基底分支 $ARG 不在 origin 上。請確認分支名,不要自行改用其他分支。" >&2
|
||||||
|
exit 4
|
||||||
fi
|
fi
|
||||||
echo "錯誤: 呼叫方指定的基底分支 $CALLER 不在 origin 上。請確認分支名,不要自行改用其他分支。" >&2
|
|
||||||
exit 4
|
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
|
fi
|
||||||
|
|
||||||
for candidate in develop main master; do
|
# 以下是 --derive 模式。
|
||||||
if remote_has "$candidate"; then
|
BRANCH=$ARG
|
||||||
printf '%s\n' "$candidate"
|
if [ -z "$BRANCH" ]; then
|
||||||
exit 0
|
BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || true)
|
||||||
fi
|
fi
|
||||||
done
|
|
||||||
|
|
||||||
echo "錯誤: origin 上找不到 develop、main、master。請由呼叫方指定基底分支。" >&2
|
if [ -z "$BRANCH" ] || [ "$BRANCH" = "HEAD" ]; then
|
||||||
exit 5
|
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
|
||||||
|
|||||||
Reference in New Issue
Block a user