common.sh 4.68 KB
#!/usr/bin/env bash
# 所有脚本共用的函数与变量

# 获取仓库根目录(非 git 仓库时提供兜底)
get_repo_root() {
    if git rev-parse --show-toplevel >/dev/null 2>&1; then
        git rev-parse --show-toplevel
    else
        # 非 git 仓库:回退为脚本所在目录向上查找
        local script_dir="$(CDPATH="" cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
        (cd "$script_dir/../../.." && pwd)
    fi
}

# 获取当前分支(非 git 仓库时提供兜底)
get_current_branch() {
    # 优先使用 SPECIFY_FEATURE 环境变量
    if [[ -n "${SPECIFY_FEATURE:-}" ]]; then
        echo "$SPECIFY_FEATURE"
        return
    fi

    # 其次尝试从 git 读取
    if git rev-parse --abbrev-ref HEAD >/dev/null 2>&1; then
        git rev-parse --abbrev-ref HEAD
        return
    fi

    # 非 git 仓库:尝试找到最新的功能目录
    local repo_root=$(get_repo_root)
    local specs_dir="$repo_root/specs"

    if [[ -d "$specs_dir" ]]; then
        local latest_feature=""
        local highest=0

        for dir in "$specs_dir"/*; do
            if [[ -d "$dir" ]]; then
                local dirname=$(basename "$dir")
                if [[ "$dirname" =~ ^([0-9]{3})- ]]; then
                    local number=${BASH_REMATCH[1]}
                    number=$((10#$number))
                    if [[ "$number" -gt "$highest" ]]; then
                        highest=$number
                        latest_feature=$dirname
                    fi
                fi
            fi
        done

        if [[ -n "$latest_feature" ]]; then
            echo "$latest_feature"
            return
        fi
    fi

    echo "main"  # 最终兜底
}

# 检查是否为 git 仓库
has_git() {
    git rev-parse --show-toplevel >/dev/null 2>&1
}

check_feature_branch() {
    local branch="$1"
    local has_git_repo="$2"

    # 非 git 仓库无法强制分支命名,但仍输出提示
    if [[ "$has_git_repo" != "true" ]]; then
        echo "[specify] 警告:未检测到 Git 仓库;已跳过分支校验" >&2
        return 0
    fi

    if [[ ! "$branch" =~ ^[0-9]{3}- ]]; then
        echo "错误:当前不在功能分支。当前分支:$branch" >&2
        echo "功能分支命名应类似:001-feature-name" >&2
        return 1
    fi

    return 0
}

get_feature_dir() { echo "$1/specs/$2"; }

# 按数字前缀查找功能目录,而不是严格按分支名匹配
# 这样允许多个分支指向同一份规格(例如 004-fix-bug、004-add-feature)
find_feature_dir_by_prefix() {
    local repo_root="$1"
    local branch_name="$2"
    local specs_dir="$repo_root/specs"

    # 从分支名提取数字前缀(例如从 "004-whatever" 提取 "004")
    if [[ ! "$branch_name" =~ ^([0-9]{3})- ]]; then
        # 分支名不含数字前缀:回退为按分支名拼路径
        echo "$specs_dir/$branch_name"
        return
    fi

    local prefix="${BASH_REMATCH[1]}"

    # 在 specs/ 下查找以该前缀开头的目录
    local matches=()
    if [[ -d "$specs_dir" ]]; then
        for dir in "$specs_dir"/"$prefix"-*; do
            if [[ -d "$dir" ]]; then
                matches+=("$(basename "$dir")")
            fi
        done
    fi

    # 处理查找结果
    if [[ ${#matches[@]} -eq 0 ]]; then
        # 未找到:返回按分支名拼的路径(后续会给出更明确的错误)
        echo "$specs_dir/$branch_name"
    elif [[ ${#matches[@]} -eq 1 ]]; then
        # 仅一个匹配:直接返回
        echo "$specs_dir/${matches[0]}"
    else
        # 多个匹配:命名规范正确时不应发生
        echo "错误:发现多个同前缀规格目录 '$prefix':${matches[*]}" >&2
        echo "请确保每个数字前缀只对应一个规格目录。" >&2
        echo "$specs_dir/$branch_name"  # 返回一个路径以避免脚本中断
    fi
}

get_feature_paths() {
    local repo_root=$(get_repo_root)
    local current_branch=$(get_current_branch)
    local has_git_repo="false"

    if has_git; then
        has_git_repo="true"
    fi

    # 使用前缀匹配以支持“多分支对应同一份规格”的场景
    local feature_dir=$(find_feature_dir_by_prefix "$repo_root" "$current_branch")

    cat <<EOF
REPO_ROOT='$repo_root'
CURRENT_BRANCH='$current_branch'
HAS_GIT='$has_git_repo'
FEATURE_DIR='$feature_dir'
FEATURE_SPEC='$feature_dir/spec.md'
IMPL_PLAN='$feature_dir/plan.md'
TASKS='$feature_dir/tasks.md'
RESEARCH='$feature_dir/research.md'
DATA_MODEL='$feature_dir/data-model.md'
QUICKSTART='$feature_dir/quickstart.md'
CONTRACTS_DIR='$feature_dir/contracts'
EOF
}

check_file() { [[ -f "$1" ]] && echo "  ✓ $2" || echo "  ✗ $2"; }
check_dir() { [[ -d "$1" && -n $(ls -A "$1" 2>/dev/null) ]] && echo "  ✓ $2" || echo "  ✗ $2"; }