#!/bin/sh # ============================================================================== # 脚本名称: git-restore-all-remote-branches.sh # 描述: 基于本地远程引用批量恢复本地跟踪分支 # 兼容性: 兼容所有 POSIX sh 环境,Git 1.7+ # ============================================================================== set -e # -------------------------- 变量定义 ---------------------------------------- DRY_RUN="false" NO_FETCH="false" PRUNE_REMOTE="false" REMOTE_NAME="" FETCH_FAIL_EXIT="false" total_refs=0 would_create=0 created=0 failed=0 skipped=0 illegal_names=0 # -------------------------- 帮助函数 ---------------------------------------- usage() { cat <&2 usage 1 fi REMOTE_NAME="$2" shift 2 ;; *) echo "未知参数: $1" >&2 usage 1 ;; esac done # -------------------------- 自动检测远程名称 -------------------------------- if [ -z "${REMOTE_NAME}" ]; then REMOTE_NAME=$(git remote | head -n1) if [ -z "${REMOTE_NAME}" ]; then echo "错误: 当前仓库没有配置任何远程仓库" >&2 exit 1 fi echo "自动检测到远程: ${REMOTE_NAME}" fi # 校验remote是否真实存在 if ! git remote get-url "${REMOTE_NAME}" >/dev/null 2>&1; then echo "错误: 远程 '${REMOTE_NAME}' 不存在" >&2 exit 1 fi # -------------------------- Fetch同步远程引用 ------------------------------- if [ "${NO_FETCH}" != "true" ]; then echo "正在同步远程引用: git fetch ${REMOTE_NAME}" fetch_args="${REMOTE_NAME}" if [ "${PRUNE_REMOTE}" = "true" ]; then fetch_args="${fetch_args} --prune" fi if ! git fetch ${fetch_args} 2>&1; then echo "警告: git fetch 执行失败!" >&2 echo "警告: 后续将使用本地过期缓存,分支列表可能不一致!" >&2 if [ "${FETCH_FAIL_EXIT}" = "true" ]; then echo "错误: 用户指定 --fetch-fail-exit,脚本终止" >&2 exit 1 fi fi fi # -------------------------- 读取远程引用列表 -------------------------------- ref_list=$(git for-each-ref --format='%(refname)' "refs/remotes/${REMOTE_NAME}/" 2>/dev/null) # 校验是否扫描到任何远程引用 if [ -z "${ref_list}" ]; then echo "警告: 远程 '${REMOTE_NAME}' 下没有任何远程引用" >&2 echo "提示: 如果远端确实有分支,请检查网络后重试,或移除 --no-fetch 参数" >&2 echo "" echo "===== 统计汇总 =====" echo "扫描总远程引用: 0" echo "========================" exit 0 fi echo "开始扫描远程引用: refs/remotes/${REMOTE_NAME}/" # -------------------------- 逐行处理远程引用 -------------------------------- # 【修复Bug2】使用 here-doc 替代 pipe,避免 while 在子shell中运行导致变量丢失 while IFS= read -r ref; do # 跳过空行 if [ -z "${ref}" ]; then continue fi total_refs=$((total_refs + 1)) # 从完整引用路径提取分支名 branch="${ref#refs/remotes/${REMOTE_NAME}/}" # 跳过特殊HEAD引用 case "${branch}" in HEAD) echo "跳过特殊引用 HEAD" skipped=$((skipped + 1)) continue ;; esac # 校验本地分支是否已存在 if git show-ref --verify --quiet "refs/heads/${branch}" 2>/dev/null; then echo "本地分支【${branch}】已存在,跳过" skipped=$((skipped + 1)) continue fi # 使用 git check-ref-format 检测非法分支名 if ! git check-ref-format --allow-onelevel "${branch}" >/dev/null 2>&1; then echo "警告: 分支【${branch}】名称不符合Git规范,跳过创建" >&2 illegal_names=$((illegal_names + 1)) skipped=$((skipped + 1)) continue fi # 斜杠分支冲突告警 case "${branch}" in */*) prefix="${branch%%/*}" if git show-ref --verify --quiet "refs/heads/${prefix}" 2>/dev/null; then echo "警告: 分支【${branch}】含斜杠,前缀【${prefix}】已是文件分支,可能创建失败" fi ;; esac # 预演模式 if [ "${DRY_RUN}" = "true" ]; then would_create=$((would_create + 1)) echo "[DRY-RUN] 准备创建: git branch --track ${branch} ${ref}" continue fi # 实际创建分支 # 【修复Bug1】使用 if 判断退出码(兼容 set -e),避免 git branch 失败时脚本直接退出 # git branch --track 成功时向 stdout 输出信息(如 "branch 'xxx' set up to track..."), # 失败时向 stderr 输出错误信息。必须检查退出码而非输出内容是否为空。 if err_msg=$(git branch --track "${branch}" "${ref}" 2>&1); then created=$((created + 1)) echo "分支【${branch}】创建成功" else failed=$((failed + 1)) echo "分支【${branch}】创建失败" >&2 # 根据错误信息分类打印根因 case "${err_msg}" in *"already exists"*) echo " 原因: 本地分支已存在" >&2 ;; *"not a valid ref"*) echo " 原因: 分支名不符合Git规范" >&2 ;; *"exists"*) echo " 原因: Git ref文件/目录冲突(前缀分支已存在)" >&2 ;; *"Permission denied"*|*"Authentication failed"*) echo " 原因: 权限或认证问题" >&2 ;; *) echo " 详细错误: ${err_msg}" >&2 ;; esac fi done <