#!/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