跳转到内容
关系图谱

Git cherry-pick 详解

5,074 字 19 分钟

属性 1
status
学习中

Git cherry-pick 详解

git cherry-pick 是 Git 中用于将指定提交应用到当前分支的命令。它会复制一个提交的修改内容,在当前分支上创建一个新的提交。与 mergerebase 不同,cherry-pick 只选取特定的提交,而非整个分支的所有提交。

一句话理解

cherry-pick 就像「从其他分支摘一颗樱桃」——只拿你想要的那个提交,不影响其他。

基本语法

bash
git cherry-pick <commit>
git cherry-pick --no-commit <commit>
git cherry-pick --continue
git cherry-pick --abort
git cherry-pick --quit

核心概念

cherry-pick 的工作原理

git cherry-pick 会提取指定提交引入的变更(diff),然后在当前分支上重新应用这些变更,创建一个新的提交(新的 SHA)。

cherry-pick 生成新提交

C5'C5代码修改内容相同,但 SHA 不同(因为父提交不同)。cherry-pick 不会移动或删除原始提交。

cherry-pick vs 其他命令

对比项cherry-pickmergerebase
选取范围指定的一个或多个提交整个分支的所有提交整个分支的所有提交
新提交创建新提交(新 SHA)创建合并提交重写所有提交(新 SHA)
原始提交保留不变保留不变原始提交变为悬空对象
历史关系无父子关系保留分支合并关系线性历史
适用场景跨分支移植少量提交合并整个分支整理历史 / 同步上游

基本用法

摘取单个提交

bash
# 将 feature 分支上的某个提交应用到当前分支
git cherry-pick abc1234

# 不打开编辑器,使用原始提交信息
git cherry-pick --no-edit abc1234

摘取多个提交

bash
# 摘取多个不连续的提交(按顺序应用)
git cherry-pick abc1234 def5678 ghi9012

# 摘取一个范围(不含起始提交,含结束提交)
git cherry-pick abc1234..ghi9012

# 摘取一个范围(含起始提交)
git cherry-pick abc1234^..ghi9012
范围语法
  • A..B — 从 A(不含)到 B(含),即 A 之后到 B 的所有提交
  • A^..B — 从 A(含)到 B(含),即 A 到 B 的所有提交
# C1 → C2 → C3 → C4 → C5
git cherry-pick C2..C5    # 摘取 C3, C4, C5
git cherry-pick C2^..C5   # 摘取 C2, C3, C4, C5

--no-commit:只应用修改不提交

bash
# 将修改应用到暂存区,但不自动创建提交
git cherry-pick --no-commit abc1234

# 可以继续修改后手动提交
git commit -m "feat: 从 feature 分支移植的功能"
何时使用 --no-commit
  • 需要合并多个 cherry-pick 为一个提交时
  • 需要在 cherry-pick 的基础上做额外修改时
  • 想自定义提交信息时

保留原始提交信息

bash
# cherry-pick 默认会保留原始提交信息
# 但会在末尾添加 "(cherry picked from commit xxx)"

# 添加 -x 选项,在提交信息中记录原始 SHA
git cherry-pick -x abc1234
# 提交信息会包含:(cherry picked from commit abc1234)

# 不添加 -x 时,提交信息不包含原始引用
git cherry-pick abc1234
建议始终使用 -x

-x 添加的原始 SHA 引用让你以后能追溯这个提交的来源,特别是在多分支维护的项目中非常有用。

处理冲突

cherry-pick 时如果目标分支和源分支的代码有冲突,需要手动解决。

冲突解决流程

bash
# 1. cherry-pick 遇到冲突
git cherry-pick abc1234
# CONFLICT (content): Merge conflict in src/main.ts

# 2. 解决冲突
# 编辑冲突文件,删除冲突标记

# 3. 标记已解决
git add src/main.ts

# 4. 继续
git cherry-pick --continue

# 或完全放弃
git cherry-pick --abort

--abort vs --quit vs --continue

命令效果
git cherry-pick --continue冲突解决后继续处理下一个提交
git cherry-pick --abort完全取消,恢复到 cherry-pick 前的状态
git cherry-pick --quit退出但保留已成功 cherry-pick 的提交

使用策略选项解决冲突

bash
# 冲突时自动采用「当前分支」的版本
git cherry-pick -X ours abc1234

# 冲突时自动采用「被 cherry-pick 的提交」的版本
git cherry-pick -X theirs abc1234
-X ours / -X theirs 的含义

在 cherry-pick 中:

  • ours = 当前分支(你正在工作的分支)
  • theirs = 被 cherry-pick 的提交(来源提交)

这与 merge 中的含义一致,但与 rebase 中的含义相反

cherry-pick 合并提交

git revert 类似,cherry-pick 合并提交需要指定 --mainline-m)参数:

bash
# 合并提交有两个父提交
# -m 1:保留第一个父提交的差异(通常是目标分支)
# -m 2:保留第二个父提交的差异(通常是被合并的分支)

git cherry-pick -m 1 <merge-commit-sha>
通常用 -m 1

大多数情况下,你想 cherry-pick 的是「被合并进来的分支引入的修改」,这对应 -m 1(相对于第一个父提交的差异)。

实际项目中的 cherry-pick 场景

场景 1:Hotfix 跨分支移植

最经典的 cherry-pick 场景——在 develop 上修复了 bug,需要把修复移植到 release 分支:

bash
# 1. 在 develop 上修复 bug 并提交
git checkout develop
# ... 修复代码 ...
git commit -m "fix: 修复用户登录超时问题 (#789)"

# 2. 将修复 cherry-pick 到 release 分支
git checkout release/v2.0
git cherry-pick -x <fix-commit-sha>

# 3. 推送
git push origin release/v2.0

场景 2:多版本维护

同时维护 v1.x 和 v2.x 版本线时,安全补丁需要应用到所有维护中的版本:

bash
# 安全修复提交在 main 上
git log --oneline -1
# abc1234 fix: 修复 SQL 注入漏洞 (CVE-2026-xxxx)

# 依次移植到所有维护版本
git checkout release/v2.0
git cherry-pick -x abc1234

git checkout release/v1.5
git cherry-pick -x abc1234

git checkout release/v1.4
git cherry-pick -x abc1234
# 注意:版本越旧,冲突的可能性越大

场景 3:从废弃分支抢救代码

功能分支被废弃了,但其中某些提交仍然有用:

bash
# 查看废弃分支的提交历史
git log --oneline abandoned-feature
# abc1234 feat: 有用的工具函数
# def5678 WIP: 半成品功能
# ghi9012 feat: 另一个有用的改动

# 只摘取有价值的提交
git checkout main
git cherry-pick -x abc1234 ghi9012

场景 4:将 PR 中的部分提交合入

一个大 PR 中只有部分提交已经通过审查,想先合入已审查的部分:

bash
# PR 包含 5 个提交,前 3 个已审查通过
git log --oneline feature-branch
# e5f6a7b feat: 功能 C(未审查)
# c3d4e5f feat: 功能 B(未审查)
# a1b2c3d feat: 功能 A(已通过)
# 9e8f7a6 refactor: 代码重构(已通过)
# 7c6d5e4 fix: 修复 bug(已通过)

# 只 cherry-pick 已审查通过的提交
git checkout main
git cherry-pick -x 7c6d5e4 9e8f7a6 a1b2c3d

场景 5:在 CI/CD 中自动化 cherry-pick

一些团队使用标签或评论触发自动 cherry-pick:

bash
# GitHub Actions 示例:当 PR 有 "backport-v2" 标签时自动 cherry-pick
# .github/workflows/backport.yml
# 当 PR 合并后,自动将合并提交 cherry-pick 到 release/v2 分支

# 手动触发的脚本
#!/bin/bash
COMMIT=$1
TARGET_BRANCH=$2

git fetch origin
git checkout $TARGET_BRANCH
git cherry-pick -x $COMMIT

if [ $? -ne 0 ]; then
    echo "Cherry-pick 冲突,需要手动处理"
    git cherry-pick --abort
    exit 1
fi

git push origin $TARGET_BRANCH

场景 6:紧急修复部署到特定环境

在灰度发布或多环境部署中,某个修复只需要部署到特定环境:

bash
# staging 环境发现 bug,需要紧急修复
# 但不想把 develop 的其他未完成功能一起部署

# 1. 在 develop 上修复
git checkout develop
git commit -m "fix: 修复支付金额计算错误"

# 2. 只将修复 cherry-pick 到 staging 分支
git checkout staging
git cherry-pick -x <fix-sha>
git push origin staging
# staging 环境自动部署,只包含这个修复

实用技巧

技巧 1:批量 cherry-pick 合并为单个提交

bash
# 将多个提交合并为一个
git cherry-pick --no-commit abc1234 def5678 ghi9012
git commit -m "feat: 从 feature 分支移植的核心功能"

技巧 2:cherry-pick 前确认提交内容

bash
# 查看提交修改了什么
git show abc1234

# 只看文件列表
git show --name-only abc1234

# 查看统计信息
git show --stat abc1234

# 模拟 cherry-pick(不实际执行)
git cherry-pick --no-commit abc1234
git diff --cached --stat
git cherry-pick --abort

技巧 3:找到两个分支间的差异提交

bash
# 查看 feature 有但 main 没有的提交
git log --oneline main..feature

# 查看 main 有但 feature 没有的提交
git log --oneline feature..main

# 查看两个分支的完整差异
git log --oneline --left-right main...feature
# < 开头的是 main 独有的
# > 开头的是 feature 独有的

技巧 4:检测重复 cherry-pick

bash
# 查看某个提交是否已经被 cherry-pick 过
# 方式 1:使用 git log 搜索提交信息
git log --grep="cherry picked from commit abc1234" --oneline

# 方式 2:使用 git log 搜索原始提交信息
git log --grep="原始提交信息的关键字" --oneline

# 方式 3:使用 patch-id 对比
git show abc1234 | git patch-id
# 对比两个提交的 patch-id 是否相同

技巧 5:配置 Git alias

bash
# 在 ~/.gitconfig 中添加
[alias]
    # 快速 cherry-pick(带 -x 记录来源)
    cp = cherry-pick -x
    # cherry-pick 不提交
    cpn = cherry-pick --no-commit
    # 继续 / 放弃
    cpc = cherry-pick --continue
    cpa = cherry-pick --abort

# 使用
git cp abc1234            # cherry-pick 并记录来源
git cpn abc1234 def5678   # 多个 cherry-pick 不提交

技巧 6:cherry-pick 后验证

bash
# cherry-pick 后对比两个提交的 diff 是否一致
git diff abc1234 HEAD
# 如果输出为空,说明 cherry-pick 完全一致
# 如果有输出,说明有冲突解决导致的差异

# 运行测试确认
npm test

更多实战场景

场景 7:开源项目的 Backport 工作流

大型开源项目(如 Node.js、Linux 内核)使用标签驱动的 backport 流程:

bash
# 1. 修复合入 main(最新开发分支)
git checkout main
git commit -m "fix: 修复内存泄漏 (#1234)"

# 2. 在 PR/Issue 上添加标签 "backport-v18.x"

# 3. 手动(或 bot 自动)将修复 cherry-pick 到旧版本分支
git checkout v18.x-staging
git cherry-pick -x <commit-sha>

# 4. 如果有冲突,手动适配旧版本的 API
# 旧版本可能没有新版本中引入的辅助函数
# 需要手动调整代码使其兼容
GitHub 上的自动 Backport Bot

很多项目使用 Mergifygithub-backport 等工具自动执行 cherry-pick backport。当 PR 合并且包含特定标签时,bot 自动创建 backport PR。

场景 8:修复提交到错误分支后的补救

不小心把修改提交到了错误的分支:

bash
# 场景:本该提交到 feature-payment 的代码,提交到了 main
git log --oneline -3
# abc1234 (HEAD -> main) feat: 支付模块逻辑    ← 提交错了!
# def5678 fix: 之前的修复
# ghi9012 上一个正式提交

# 1. 先 cherry-pick 到正确的分支
git checkout feature-payment
git cherry-pick abc1234

# 2. 回到 main 撤销错误的提交
git checkout main
git reset --hard HEAD~1

# 现在修改在正确的 feature-payment 分支上了
这是 cherry-pick 最实用的日常场景之一

每个开发者都会偶尔提交到错误分支。记住这个 cherry-pick + reset 的组合技。

场景 9:跨 fork 仓库 cherry-pick

在开源贡献中,经常需要从上游仓库 cherry-pick 修复到自己的 fork:

bash
# 1. 添加上游仓库为 remote
git remote add upstream https://github.com/original/repo.git

# 2. 获取上游的提交
git fetch upstream

# 3. 找到需要的提交
git log --oneline upstream/main -10

# 4. cherry-pick 到自己的分支
git checkout my-feature
git cherry-pick -x <upstream-commit-sha>

场景 10:使用 cherry-pick 拆分大提交

一个巨大的提交包含了多个不相关的修改,需要拆分到不同分支:

bash
# 原始提交 abc1234 包含了 3 个功能的修改
git show --stat abc1234
# src/auth/login.ts      | 50 +
# src/payment/checkout.ts | 30 +
# src/ui/button.css       | 10 +

# 对每个功能分别 cherry-pick 并只保留相关文件

# 分支 1:auth 功能
git checkout -b feature-auth main
git cherry-pick --no-commit abc1234
git checkout HEAD -- src/payment/ src/ui/
git commit -m "feat: 登录功能"

# 分支 2:payment 功能
git checkout -b feature-payment main
git cherry-pick --no-commit abc1234
git checkout HEAD -- src/auth/ src/ui/
git commit -m "feat: 支付功能"

# 分支 3:UI 修改
git checkout -b feature-ui main
git cherry-pick --no-commit abc1234
git checkout HEAD -- src/auth/ src/payment/
git commit -m "style: 按钮样式更新"

场景 11:cherry-pick 用于创建最小复现

当需要为 bug 报告创建最小复现时:

bash
# 1. 创建一个干净的分支
git checkout -b reproduce/bug-123 v2.0.0

# 2. 只 cherry-pick 与 bug 相关的最少提交
git cherry-pick abc1234   # 引入了有问题的依赖
git cherry-pick def5678   # 修改了配置

# 3. 现在有了一个只包含必要修改的最小复现环境
npm test
# 确认 bug 可以在这个最小环境中复现

冲突解决进阶

常见冲突模式及解决方案

bash
# 模式 1:上下文偏移(最常见)
# 原因:cherry-pick 的提交周围的代码在目标分支中已被修改
# 解决:检查冲突标记,通常只需要保留正确的上下文

# 模式 2:已删除的文件
# 原因:cherry-pick 修改了一个在目标分支中已被删除的文件
git cherry-pick abc1234
# CONFLICT (modify/delete): src/old-file.ts deleted in HEAD
# 解决:决定是否需要这个文件
git rm src/old-file.ts         # 确认删除
# 或
git add src/old-file.ts        # 保留文件

# 模式 3:重命名冲突
# 原因:文件在两个分支中被重命名为不同的名字
git cherry-pick abc1234
# CONFLICT (rename/rename): src/utils.ts renamed to...
# 解决:选择正确的文件名
git add src/correct-name.ts
git rm src/wrong-name.ts

冲突解决的验证清单

bash
# cherry-pick 冲突解决后,执行以下检查
# 1. 确认没有遗留的冲突标记
grep -rn "<<<<<<" src/
grep -rn "======" src/
grep -rn ">>>>>>" src/

# 2. 对比与原始提交的差异
git diff --cached               # 查看暂存区变更
git show <original-sha> --stat  # 对比原始提交的文件列表

# 3. 运行测试
npm test

# 4. 继续
git cherry-pick --continue

常见陷阱

陷阱 1:重复 cherry-pick 导致重复修改

同一个提交被 cherry-pick 两次,虽然不会报错(因为 SHA 不同),但代码中会有重复的修改:

bash
# ❌ 不要这样做
git cherry-pick abc1234      # 第一次
git cherry-pick abc1234      # 第二次 → 可能产生冲突或重复代码

# ✅ cherry-pick 前先检查
git log --grep="cherry picked from commit abc1234"
陷阱 2:cherry-pick 后 merge 时出现重复

如果你 cherry-pick 了 feature 的提交到 main,之后再 merge feature 到 main,Git 通常能正确处理(不会重复应用),但在某些情况下可能产生冲突。

解决方案:如果计划之后要 merge 整个分支,考虑是否真的需要 cherry-pick。

陷阱 3:cherry-pick 不带依赖

如果提交 B 依赖于提交 A(例如 B 使用了 A 中定义的函数),只 cherry-pick B 会导致编译错误。

bash
# A: 定义了 processPayment() 函数
# B: 调用了 processPayment() 函数

# ❌ 只 cherry-pick B → 编译错误(找不到 processPayment)
git cherry-pick B

# ✅ 同时 cherry-pick A 和 B
git cherry-pick A B
陷阱 4:cherry-pick 合并提交忘记 -m
bash
# 报错
git cherry-pick <merge-commit>
# error: commit is a merge but no -m option was given

# 正确
git cherry-pick -m 1 <merge-commit>

常见问题

cherry-pick 和手动复制代码有什么区别?

cherry-pick 会保留原始提交的作者信息提交信息,并且使用 Git 的合并引擎来应用修改,能正确处理文件重命名、上下文偏移等情况。手动复制容易出错且没有追溯性。

cherry-pick 后原始提交会被删除吗?

不会。cherry-pick 只是复制修改内容创建新提交,原始提交在其所在分支上完全不受影响。

能否 cherry-pick 到同一个分支上?

技术上可以(比如 cherry-pick 一个已被 reset 掉的提交),但通常没有意义。如果你想恢复被撤销的提交,使用 git reflog + git reset 更合适。

cherry-pick 多个提交时,其中一个冲突了怎么办?

冲突的提交会暂停,你有三个选择:

  1. 解决冲突后 git cherry-pick --continue(继续处理后续提交)
  2. git cherry-pick --abort(完全取消,已成功的也回滚)
  3. git cherry-pick --quit(退出但保留已成功的提交)
cherry-pick 和 git format-patch + git am 有什么区别?

功能类似,但适用场景不同:

对比项cherry-pickformat-patch + am
适用场景同一仓库的不同分支跨仓库移植
使用方式直接引用 SHA生成 patch 文件再应用
网络需求需要在同一仓库中可通过邮件/文件传输 patch
保留信息作者、时间、提交信息作者、时间、提交信息

团队协作中的 cherry-pick

cherry-pick 审批流程

在严格管控的团队中,cherry-pick 到 release 分支需要审批:

bash
# 1. 开发者:创建 cherry-pick PR 而非直接操作
git checkout -b cherry-pick/fix-1234 release/v2.0
git cherry-pick -x abc1234
git push origin cherry-pick/fix-1234
# 创建 PR:cherry-pick/fix-1234 → release/v2.0

# 2. 审查者:确认修改内容和适用性
# - 这个修复是否适用于 v2.0?
# - 是否有 API 不兼容问题?
# - 测试是否覆盖?

# 3. 合并后打补丁标签
git checkout release/v2.0
git tag -a v2.0.1 -m "Hotfix v2.0.1"

cherry-pick 追踪与审计

bash
# 查看所有 cherry-pick 过来的提交
git log --grep="cherry picked from" --oneline

# 统计每个分支被 cherry-pick 的次数
git log --grep="cherry picked from" --format="%s" | \
    grep -oP 'cherry picked from commit \K[a-f0-9]+' | \
    while read sha; do
        branch=$(git branch --contains $sha --format='%(refname:short)' 2>/dev/null | head -1)
        echo "$sha$branch"
    done | sort | uniq -c | sort -rn

# 检查某个修复是否已经 cherry-pick 到所有需要的分支
FIX_SHA=abc1234
for branch in release/v1.0 release/v2.0 release/v3.0; do
    if git log $branch --grep="cherry picked from commit $FIX_SHA" --oneline | grep -q .; then
        echo "✅ $branch 已包含修复"
    else
        echo "❌ $branch 缺少修复"
    fi
done

cherry-pick 自动化

批量 backport 脚本

bash
#!/bin/bash
# backport.sh — 自动 cherry-pick 到多个 release 分支
# 用法:./backport.sh <commit-sha> release/v1.0 release/v2.0 release/v3.0

COMMIT=$1
shift
BRANCHES="$@"

if [ -z "$COMMIT" ] || [ -z "$BRANCHES" ]; then
    echo "用法: $0 <commit-sha> <branch1> [branch2] ..."
    exit 1
fi

echo "准备将 $COMMIT 移植到以下分支:"
for branch in $BRANCHES; do
    echo "  - $branch"
done
echo ""

ORIGINAL_BRANCH=$(git branch --show-current)
FAILED=""

for branch in $BRANCHES; do
    echo "=== 移植到 $branch ==="
    git checkout $branch 2>/dev/null
    if [ $? -ne 0 ]; then
        echo "❌ 无法切换到 $branch"
        FAILED="$FAILED $branch"
        continue
    fi

    git pull origin $branch
    git cherry-pick -x $COMMIT

    if [ $? -ne 0 ]; then
        echo "❌ $branch 有冲突,需要手动解决"
        git cherry-pick --abort
        FAILED="$FAILED $branch"
    else
        echo "✅ $branch 移植成功"
        git push origin $branch
    fi
    echo ""
done

git checkout $ORIGINAL_BRANCH

if [ -n "$FAILED" ]; then
    echo "⚠️ 以下分支需要手动处理:$FAILED"
    exit 1
fi
echo "🎉 全部移植完成"

配合 GitHub Actions 自动 backport

yaml
# .github/workflows/backport.yml
name: Auto Backport
on:
  pull_request:
    types: [closed]

jobs:
  backport:
    if: github.event.pull_request.merged == true
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - name: Backport
        uses: sqren/backport-github-action@v9
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          auto_backport_label_prefix: backport-

cherry-pick 的内部原理

Git 如何执行 cherry-pick

bash
# cherry-pick 本质上等价于:
git diff abc1234^..abc1234 | git apply --3way
# 然后创建一个新提交,保留原始作者信息

cherry-pick vs rebase 的内部区别

对比项cherry-pickrebase
操作粒度选择性地挑选提交整个分支的所有提交
基准点当前 HEAD目标分支的最新提交
提交关系新提交与原始提交无 Git 关联新提交替代原始提交
原始分支不受影响原始提交被「抛弃」
适用场景跨分支移植少量提交同一分支上整理历史

速查表

目标命令
摘取单个提交git cherry-pick <sha>
摘取并记录来源git cherry-pick -x <sha>
摘取多个提交git cherry-pick <sha1> <sha2>
摘取范围(不含起始)git cherry-pick <A>..<B>
摘取范围(含起始)git cherry-pick <A>^..<B>
只应用不提交git cherry-pick --no-commit <sha>
摘取合并提交git cherry-pick -m 1 <merge-sha>
冲突时用当前分支版本git cherry-pick -X ours <sha>
冲突时用来源提交版本git cherry-pick -X theirs <sha>
冲突解决后继续git cherry-pick --continue
放弃 cherry-pickgit cherry-pick --abort
保留已成功的退出git cherry-pick --quit

决策流程图

相关命令

  • git rebase — 整体移动分支提交,cherry-pick 的批量版本
  • git revert — 撤销提交(创建反向提交),与 cherry-pick 方向相反
  • git reset — 重置 HEAD,可用于撤销 cherry-pick
  • git stash — 临时保存修改,cherry-pick 前保存工作区
  • git format-patch / git am — 通过 patch 文件跨仓库移植提交

参考资料