git之路:rebase & cherry-pick

在团队协作和日常开发中,rebasecherry-pick 是 Git 中非常强大的两个命令。它们不仅能让你的提交历史更加清晰,还能灵活地“搬运”代码。本文结合一张可视化分支图,带你深入理解这两者的用法与区别,并掌握团队协作中的最佳实践。

🧩 一、核心概念

  • rebase (变基):将当前分支的一系列提交“重放”到另一个目标分支之上。其核心用途是整理个人分支的提交历史,使其在并入主干时保持线性、清晰。
  • cherry-pick (拣选):将某个(或某些)特定的提交复制到当前分支。它像摘樱桃一样,适合“挑着搬运”某个功能或紧急修复。

🗺️ 二、图解分支操作流程

下面的两幅 Mermaid 图示分别演示了 Rebase 和 Cherry-pick 在典型协作场景中的作用。将代码块直接放入支持 Mermaid 的 Markdown 环境即可自动渲染。

图 1 · Rebase 工作流(主干更新后将功能分支变基到最新 main)



flowchart TD
    subgraph "main 分支"
        c0((c0)) --> c1((c1)) --> c3((c3))
    end

    subgraph "feature/a (rebase 前)"
        c2_before((c2)) --> c4_before((c4))
    end

    subgraph "feature/a (rebase 后)"
        c2_after(("c2'")) --> c4_after(("c4'"))
    end

    c1 --> c2_before
    c3 --> c2_after

图 2 · Cherry-pick 工作流(从 feature/a 拣选 c2 到 main)



flowchart TD
    subgraph "初始历史"
        c0((c0)) --> c1((c1))
    end

    subgraph "main 分支"
        c3((c3)) --> c2_prime(("c2'"))
        style c2_prime fill:#bbf,stroke:#333,stroke-width:2px
    end

    subgraph "feature/a 分支"
        c2((c2)) --> c4((c4))
    end
    
    c1 --> c3
    c1 --> c2

    c2 -.-> c2_prime

场景设定

  • main 是我们的主分支,开发主线从 c0 -> c1 开始。
  • c1 拉取了一个 feature/a 分支进行功能开发,并提交了 c2c4
  • 在此期间,另一位同事完成了 fixbug/b 分支,并将其合并回 main,使得 main 分支前进到 c3

此时,你的 feature/a 分支就“落后”于 main 分支了。在准备合并你的功能之前,最好的做法是先将 main 的最新更改同步到 feature/a 分支。这里,rebase 就是最佳选择。

1. Rebase:同步主干,保持线性历史

为了让 feature/a 的历史记录更清晰,我们执行变基操作,将 feature/a 的提交“重放”到最新的 main 分支上。

1
2
3
4
5
6
7
8
# 1. 确保你的功能分支是当前分支
git checkout feature/a

# 2. 拉取远程主分支的最新代码(好习惯)
git fetch origin main

# 3. 将当前分支变基到最新的 main 分支上
git rebase origin/main # 或者 git rebase main

执行后,Git 会在内部执行以下操作:

  1. 找到 feature/amain 的共同祖先 c1
  2. 暂存 feature/ac1 之后独有的提交(c2, c4)。
  3. feature/a 分支的指针指向 main 的最新提交 c3
  4. 将刚刚暂存的提交 c2c4 重新应用(Replay)在 c3 之后,生成两个内容相同但ID不同的新提交 c2'c4'

最终,feature/a 的历史就变成了 c0 -> c1 -> c3 -> c2' -> c4'。它看起来就像是你从最新的 main 分支拉取代码然后才开始开发的一样,历史记录干净、线性,非常便于后续的合并和代码审查。

这个操作完美地诠释了 rebase 在个人开发分支上的核心用途:保持与主干同步,并维持一个清晰的线性历史。

2. Cherry-pick:挑选特定提交

现在,我们换一个场景。假设 feature/a 上的 c2 是一个非常紧急的修复,需要立即上线,但 c4 还没开发完。这时就可以用 cherry-pick

1
2
3
4
5
# 1. 切换到需要接收修复的主分支
git checkout main

# 2. 从 feature/a 分支拣选 c2 提交
git cherry-pick c2

如图右侧所示,main 分支通过 cherry-pick,将 feature/a 上的 c2 提交“拣选”过来,生成了一个新的提交 c5。其他提交 (c4) 不会被带过来,完美满足了只需要部分功能/修复的场景。


🛠️ 三、rebase 用法详解

1. 基本用法

1
2
# 将当前分支变基到 <目标分支> 上
git rebase <目标分支>

2. 交互式 rebase

这是 rebase 的一大神器,常用于合并、修改、整理当前分支的提交。

1
2
3
# -i 代表 --interactive(交互式)
# HEAD~3 表示整理当前分支最新的 3 个提交
git rebase -i HEAD~3

执行后会打开一个编辑器,你可以对指定范围的提交进行 squash (合并)、edit (修改)、reword (修改提交信息) 等精细化操作,极大提升提交历史的可读性。

3. 注意事项

  • rebase 会重写历史,永远不要对已经推送并被他人使用的公共分支(如 main)进行 rebase
  • rebase 过程遇到冲突需手动解决,解决后用 git add . 将文件标记为已解决,然后用 git rebase --continue 继续。

🍒 四、cherry-pick 用法详解

1. 基本用法

1
git cherry-pick <commit-hash>

将指定的 <commit-hash> 应用到当前分支,生成一个新的提交。

2. 批量 cherry-pick

1
2
3
4
5
# 拣选多个不连续的提交
git cherry-pick <commit-1> <commit-2>

# 拣选一个连续的提交范围(不包含 commit-A)
git cherry-pick <commit-A>..<commit-B>

3. 注意事项

  • cherry-pick 也可能遇到冲突,需手动解决并 commit
  • cherry-pick 会生成新的 commit id,因为它是在新的分支上下文中应用变更。

👑 Rebase vs. Merge:黄金准则与团队协作

我们已经看到了 rebase 的强大之处,但它也是一把双刃剑,因为它会重写提交历史。这就引出了 Git 协作中一条最重要的准则:

黄金准则:在自己的私有分支上自由地 rebase,但永远不要 rebase 一个公共的、共享的分支。

换句话说:

  • rebase 用来“整理”自己:在你的个人功能分支(如 feature/a)上,使用 rebase 来同步 main 分支的更新。这属于你自己的“私事”,目的是让你的工作成果能干净地对接到主线上。
  • merge 用来“汇合”他人:当你的功能分支准备好被合并到 maindevelop 这样的公共分支时,应该使用 git merge(通常是通过 GitHub/GitLab 上的 Pull Request/Merge Request)来完成。这次合并会产生一个合并提交(Merge Commit),清晰地记录了“在某个时间点,我们将 feature/a 的所有成果汇入了主线”这一事实。

为什么这是最佳实践?

  1. 清晰且可追溯的历史:通过 rebase 保持功能分支的线性,使得 main 分支的历史由一个个清晰的功能块(通过 merge commit 连接)组成,而不是一个混乱交织的网络。阅读 git log 会非常轻松。
  2. 避免团队混乱:如果你 rebase 了一个公共分支(例如 main),所有基于旧的 main 进行开发的同事都会在下一次 git pull 时遇到严重的问题。他们的本地历史和被你重写过的远程历史产生了冲突,需要非常复杂的操作才能修复,这会给整个团队带来灾难。

推荐的开发工作流

一个标准的、健壮的开发工作流如下:

  1. 开始新任务
    1
    2
    3
    git checkout main
    git pull origin main
    git checkout -b feature/new-login
  2. 开心写代码并提交
    1
    2
    3
    # ... coding and committing ...
    git commit -m "feat: add username field"
    git commit -m "feat: add password field"
  3. 定期与主干同步(使用 Rebase):
    1
    2
    3
    4
    # 当 main 分支有更新时
    git fetch origin main
    git rebase origin/main
    # 如果有冲突,解决冲突后 git rebase --continue
  4. 发起合并请求
    • 将你“整理”好的分支推送到远程:git push origin feature/new-login --force-with-lease (--force-with-lease 是比 --force 更安全的选择,因为它不会覆盖他人的工作)。
    • 在代码托管平台(如 GitHub)上创建 Pull Request,请求将 feature/new-login 合并到 main
    • 团队成员进行代码审查,最终由维护者点击“Merge”按钮,将你的功能安全地汇入主线。

⚡ 五、实战小结

  • rebase:核心用途是在个人开发分支上同步主干分支的变更,以保持提交历史的整洁和线性。它是合并到公共分支前的“自我修养”。
  • cherry-pick:核心用途是精确复制一个或多个提交到其他分支,适合跨分支应用热修复或挑选特定功能。
  • 黄金准则:永远记住在私有分支上 rebase,在公共分支上 merge。

📝 六、实用建议

1. 分支命名规范

  • 功能开发:feature/功能简述 如:feature/login-page
  • Bug 修复:fix/问题简述 如:fix/login-crash
  • 热修复:hotfix/问题简述 如:hotfix/urgent-bug
  • 其他类型:chore/test/docs/

2. Commit 信息规范

  • 推荐格式(Angular 规范):type(scope): subject
    • type: feat, fix, docs, style, refactor, test, chore
    • scope: 影响范围(可选)
    • subject: 简短描述
  • 一次 commit 只做一件事,保持原子性。

3. 团队协作建议

  • 合并前先同步:在将功能分支合并到 main 之前,应先在自己的分支上执行 git rebase main 来同步最新代码,这能极大减少最终合并时的冲突。
  • 保护公共分支:像 mainrelease 这样的重要分支应设置为受保护状态,禁止 force push,所有变更都必须通过 Pull Request (PR) 进行。
  • 拥抱代码评审:充分利用 Pull Request 进行代码评审,这是保证代码质量和知识共享的最佳途径。
  • 勤于观察状态:养成良好习惯,多用 git statusgit log --oneline --graph 观察分支状态,做到心中有数。

希望这些建议能帮助你和团队更高效地使用 Git,写出更优雅、可维护的代码历史!