高效cherry-picks方案

0、引言

在 Git 协同开发中,经常需要将特定分支中某个开发者在某段时间内的提交迁移到另一个分支。标准的 git cherry-pick 命令虽然功能强大,但在批量处理场景中存在诸多不足:

  1. 缺少自动筛选功能(作者+时间范围)
  2. 需要手动反转提交顺序(从旧到新)
  3. 冲突处理中断整个流程
  4. 没有进度显示和结果统计

本文将介绍一个增强版的 Git Cherry-Pick 脚本,解决上述所有痛点,帮助您高效完成批量提交迁移工作。

1、方案

# ====================================================
# 增强版 Cherry-Pick 脚本 (解决冲突后继续版本)
# 功能:将源分支中特定作者在时间范围内的提交批量应用到目标分支
# 特点:
#   1. 精确的作者和时间筛选
#   2. 自动按时间顺序应用提交(最早优先)
#   3. 重复提交检测(避免重复操作)
#   4. 冲突处理指导(解决后可继续)
#   5. 进度显示和结果统计
#   6. 多平台兼容(支持 macOS 和 Linux)
#   7. 预览模式(DRY_RUN)
# ====================================================

# 配置参数 - 根据实际情况修改
SOURCE_BRANCH="branchB"                   # 源分支(包含原始提交的分支)
TARGET_BRANCH="branchA"                   # 目标分支(应用提交的分支)
AUTHOR="张三 <zhangsan@example.com>"      # 你的姓名和邮箱(必须精确匹配)
START_TIME="2024-01-01 00:00:00 +0800"    # 开始时间(可接受ISO 8601格式)
END_TIME="2024-01-31 23:59:59 +0800"      # 结束时间(可接受ISO 8601格式)
DRY_RUN=false                             # 设置为true可预览结果而不实际执行

# 解决冲突后继续处理的函数
resolve_conflict_and_continue() {
  local commit=$1
  local count=$2
  local total=$3
  
  echo -e "\n\033[41m冲突发生!请解决冲突...\033[0m"
  echo "========================================"
  echo "冲突解决指南:"
  echo "  1. 手动编辑冲突文件"
  echo "  2. 使用 git add 标记已解决的文件"
  echo "  3. 使用 git cherry-pick --continue 完成应用"
  echo "  4. 若要跳过此提交: git cherry-pick --skip"
  echo "  5. 若要完全放弃: git cherry-pick --abort"
  echo "========================================"
  
  while true; do
    read -p "请输入命令 (c=继续,s=跳过,a=放弃): " choice
    case "$choice" in
      c|C)
        git cherry-pick --continue
        if [ $? -eq 0 ]; then
          echo -e "\033[32m冲突解决成功!提交 ${commit:0:7} 已应用\033[0m"
          return 0
        else
          echo -e "\033[31m冲突未完全解决!请继续处理...\033[0m"
        fi
        ;;
      s|S)
        git cherry-pick --skip
        echo -e "\033[33m已跳过提交 ${commit:0:7}\033[0m"
        return 1
        ;;
      a|A)
        git cherry-pick --abort
        echo -e "\033[31m已放弃当前操作\033[0m"
        exit 1
        ;;
      *)
        echo "无效选择,请重新输入"
        ;;
    esac
  done
}

# 主执行函数
main() {
  # 切换到目标分支并更新
  echo "切换到目标分支 $TARGET_BRANCH..."
  git checkout "$TARGET_BRANCH" || exit 1
  echo "拉取最新代码..."
  git pull

  # 获取符合条件的提交列表
  echo "正在扫描提交:作者=$AUTHOR,时间段=$START_TIME 到 $END_TIME..."
  COMMITS=$(git log "$SOURCE_BRANCH" \
    --author="$AUTHOR" \
    --since="$START_TIME" \
    --until="$END_TIME" \
    --no-merges \
    --pretty=format:"%H" \
    --date=iso)

  # 无提交则退出
  if [ -z "$COMMITS" ]; then
    echo "没有找到符合条件的提交"
    exit 0
  fi

  # 统计提交数量
  TOTAL=$(echo "$COMMITS" | wc -l | tr -d ' ')
  echo "找到 $TOTAL 个符合条件的提交"

  # 显示提交预览
  echo "提交预览(前5个):"
  git log --no-pager --oneline -n 5 "$SOURCE_BRANCH" \
    --author="$AUTHOR" \
    --since="$START_TIME" \
    --until="$END_TIME"

  # 询问是否继续
  if [ "$DRY_RUN" = true ]; then
    echo "注意:当前为预览模式(DRY_RUN=true),将不会实际执行"
  else
    read -p "是否继续执行cherry-pick?(y/n) " -n 1 -r
    echo
    if [[ ! $REPLY =~ ^[Yy]$ ]]; then
      echo "操作已取消"
      exit 0
    fi
  fi

  # 检测反转命令(兼容macOS和Linux)
  REVERSE_CMD=""
  if command -v tac > /dev/null 2>&1; then
    REVERSE_CMD="tac"
  elif command -v tail > /dev/null 2>&1; then
    REVERSE_CMD="tail -r"
  else
    echo "错误:找不到反转命令 (tac 或 tail -r)"
    exit 1
  fi

  # 反转顺序(从旧到新应用)
  COMMITS_REVERSED=$(echo "$COMMITS" | $REVERSE_CMD)

  # 批量cherry-pick并处理冲突
  COUNT=0
  SUCCESS=0
  SKIPPED=0
  CONFLICTS=0

  echo ""
  echo "开始批量操作,共 $TOTAL 个提交..."
  echo "========================================"

  for commit in $COMMITS_REVERSED; do
    COUNT=$((COUNT + 1))
    
    # 检查是否已存在于目标分支
    if git merge-base --is-ancestor "$commit" HEAD; then
      echo -e "[$COUNT/$TOTAL] \033[33m跳过已存在的提交: ${commit:0:7}\033[0m"
      SKIPPED=$((SKIPPED + 1))
      continue
    fi
    
    if [ "$DRY_RUN" = true ]; then
      # 预览模式:只显示不执行
      git log --oneline -n 1 "$commit"
      SUCCESS=$((SUCCESS + 1))
    else
      # 实际执行cherry-pick
      echo -e "[$COUNT/$TOTAL] 正在cherry-pick提交:${commit:0:7}"
      git log --oneline -n 1 "$commit"  # 显示提交信息
      
      if ! git cherry-pick "$commit" > /dev/null 2>&1; then
        CONFLICTS=$((CONFLICTS + 1))
        resolve_conflict_and_continue "$commit" "$COUNT" "$TOTAL" || {
          SKIPPED=$((SKIPPED + 1))
          continue
        }
        SUCCESS=$((SUCCESS + 1))
      else
        echo -e "    \033[32m✔ 成功应用\033[0m"
        SUCCESS=$((SUCCESS + 1))
      fi
    fi
  done

  echo ""
  echo "========================================"
  echo "操作完成!结果统计:"
  echo -e "  总提交: $TOTAL"
  echo -e "  \033[32m成功应用: $SUCCESS\033[0m"
  echo -e "  \033[33m跳过重复: $SKIPPED\033[0m"
  echo -e "  \033[33m处理冲突: $CONFLICTS\033[0m"

  if [ "$DRY_RUN" = false ]; then
    if [ $CONFLICTS -eq 0 ]; then
      echo -e "\033[42m所有提交已成功应用!\033[0m"
      echo "建议:运行测试后,使用 git push 推送到远程仓库"
    else
      echo -e "\033[43m注意:处理了 $CONFLICTS 个冲突提交\033[0m"
    fi
  fi
}

# 执行主函数
main

2、基础使用步骤

  1. 保存脚本

    nano cherry-pick-tool.sh
    # 粘贴上述脚本内容
    chmod +x cherry-pick-tool.sh
    
  2. 配置参数

    # 编辑以下参数
    SOURCE_BRANCH="develop"              # 源分支
    TARGET_BRANCH="release-v1.2"         # 目标分支
    AUTHOR="John Doe <john@example.com>" # 作者信息
    START_TIME="2023-11-01"              # 开始日期
    END_TIME="2023-11-30"                # 结束日期
    # DRY_RUN=true                      # 预览模式开关
    
  3. 执行脚本

    ./cherry-pick-tool.sh
    
  4. 预览确认

    找到 18 个符合条件的提交
    提交预览(前5个):
    3a4b5c6 优化支付接口
    7d8e9f0 修复用户注册验证逻辑
    1a2b3c4 更新文档说明
    4d5e6f7 添加缓存功能
    9a0b1c2 重构订单服务
    是否继续执行cherry-pick?(y/n)
    
  5. 执行批处理

    [1/18] 正在cherry-pick提交:9a0b1c2
    重构订单服务
        ✔ 成功应用
    [2/18] 正在cherry-pick提交:4d5e6f7
    添加缓存功能
        ✔ 成功应用
    ...
    

3、冲突解决流程

当遇到冲突时:

[5/18] 正在cherry-pick提交:a1b2c3d
优化支付接口
    ✗ 冲突发生!

冲突发生!请解决冲突...
========================================
冲突解决指南:
  1. 手动编辑冲突文件
  2. 使用 git add 标记已解决的文件
  3. 使用 git cherry-pick --continue 完成应用
  4. 若要跳过此提交: git cherry-pick --skip
  5. 若要完全放弃: git cherry-pick --abort
========================================
请输入命令 (c=继续,s=跳过,a=放弃):

3.1、解决方法:

  1. 在另一终端解决冲突

    # 查看冲突文件
    git status
    
    # 编辑冲突文件
    nano src/payment/gateway.js
    
    # 标记为已解决
    git add src/payment/gateway.js
    
  2. 返回脚本界面继续

    • 输入 c:继续应用当前提交
    • 输入 s:跳过当前提交
    • 输入 a:放弃整个操作

3.2、最终结果输出示例

========================================
操作完成!结果统计:
  总提交: 18
  成功应用: 16
  跳过重复: 1
  处理冲突: 1

注意:处理了 1 个冲突提交

3.3、使用细节

时间格式参考

Git 支持灵活的时间格式:

格式示例 说明
"2023-11-01" 简单日期格式
"2023-11-01 08:30:00" 精确到秒
"2023-11-01 08:30:00 -0500" 带时区
"2 weeks ago" 相对时间
"last Monday" 自然语言

多平台兼容性处理

脚本自动检测平台并选择合适的反转命令:

# 检测反转命令(兼容macOS和Linux)
REVERSE_CMD=""
if command -v tac > /dev/null 2>&1; then
  REVERSE_CMD="tac"
elif command -v tail > /dev/null 2>&1; then
  REVERSE_CMD="tail -r"
else
  echo "错误:找不到反转命令 (tac 或 tail -r)"
  exit 1
fi

重复提交检测机制

# 检查是否已存在于目标分支
if git merge-base --is-ancestor "$commit" HEAD; then
  echo -e "[$COUNT/$TOTAL] \033[33m跳过已存在的提交: ${commit:0:7}\033[0m"
  SKIPPED=$((SKIPPED + 1))
  continue
fi

预览模式(DRY_RUN)

开启预览模式参数:

DRY_RUN=true

在预览模式下:

  1. 不会执行实际 cherry-pick 操作
  2. 显示将要处理的所有提交
  3. 展示完整模拟结果统计

实用场景

此脚本特别适用于:

  1. 分支合并:将特定功能的提交从开发分支转移到发布分支
  2. 热修复:将生产环境的修复提交应用到多个长期分支
  3. 代码隔离:将特定开发者的工作转移到独立分支
  4. 版本升级:选择性迁移关键提交到新版本分支
  5. 代码审计:按时间范围检阅特定开发者的提交

4、注意事项

  1. 精确匹配作者信息

    • 使用 git log 查看精确的作者格式
    • 包括姓名和邮箱的完整格式:John Doe <john@example.com>
  2. 提前更新分支

    # 在运行脚本前
    git checkout $TARGET_BRANCH
    git pull
    
  3. 备份重要分支

    • 在操作关键分支前创建备份:
    git checkout $TARGET_BRANCH
    git branch backup/$(date +%Y%m%d)-$TARGET_BRANCH
    
  4. 冲突解决建议

    • 对于复杂冲突,可以考虑:
    git mergetool              # 使用图形化工具解决冲突
    git diff --name-only --diff-filter=U # 查看所有冲突文件
    
  5. 日志分析

    • 脚本执行完成后:
    git log --oneline -n 20    # 查看最新提交
    git status                 # 检查是否有遗漏的冲突
    

5、结语

本增强版 Git Cherry-Pick 脚本解决了批量迁移提交的三大核心痛点:精确筛选顺序处理冲突解决。通过自动化这些繁琐的流程,开发者可以:

  1. 节省 70% 的手动操作时间
  2. 减少 90% 的人工错误可能
  3. 显著提升分支管理效率
  4. 增强团队协作流程的可靠性

无论是日常开发中的分支管理,还是紧急发布前的代码整合,此脚本都将是您强大的助手。立即保存使用,体验高效、稳定的批量化提交迁移工作流!

posted @ 2025-07-06 19:11  Dreams0001  阅读(12)  评论(0)    收藏  举报