Skip to content

Git 拉取(变基)说明

这篇文章解决什么问题

很多 Git 图形工具里会有一个按钮,名字叫“拉取(变基)”或 Pull with Rebase

它看起来像普通“拉取”,但点完以后,提交历史可能会变得不一样。很多人困惑的点不是命令怎么输,而是:

  • 它和普通 git pull 有什么区别
  • 它到底改了本地提交,还是改了远程提交
  • 为什么它能让历史更直
  • 冲突时为什么会一遍又一遍让你处理
  • 什么时候该用,什么时候不该用

这篇文章只讲日常开发最常遇到的场景。

先说结论

  • “拉取(变基)”本质上是:先获取远程最新代码,再把你本地还没推送的提交重新放到远程最新提交后面
  • 对应命令通常是 git pull --rebase
  • 普通 git pull 默认更像 git fetch + git merge,可能生成一个合并提交。
  • git pull --rebase 更像 git fetch + git rebase,通常不会额外生成合并提交,提交线更直。
  • 如果希望以后普通 git pull 默认也按 rebase 方式拉取,核心配置是 git config --global pull.rebase true
  • 它主要影响的是你本地未推送的提交,不会直接改远程仓库历史。
  • 如果你的本地提交已经推送并被别人基于它开发,不要随便对这些提交做 rebase。

一句话记:

text
普通拉取:把远程变化合进来。
拉取(变基):把我的本地提交挪到远程最新代码后面重新排一遍。

先看一个最常见的场景

假设你和同事都从同一个提交 A 开始开发。

你本地写了一个提交:

text
A -- B   你的本地分支

同事先推了两个提交到远程:

text
A -- C -- D   远程分支

这时你的本地分支和远程分支分叉了:

text
      B        你的本地提交
     /
A -- C -- D    远程最新提交

现在你点“拉取(变基)”,Git 会做两步:

  1. 先把远程的 CD 拿下来。
  2. 再把你的 B 暂时拿起,重新放到 D 后面。

结果变成:

text
A -- C -- D -- B'   你的本地分支

注意这里是 B',不是原来的 B。因为它的父提交变了,所以 Git 会生成一个新的提交对象。

普通拉取会发生什么

如果你用普通 git pull,在默认 merge 策略下,大概率会变成这样:

text
      B ---- M
     /      /
A -- C -- D

这里的 M 是一个合并提交。它的意思是:

  • 你的提交 B 保留在原位置
  • 远程提交 CD 保留在原位置
  • Git 新建一个 merge commit,把两条线合到一起

这种方式没有错,信息也完整。缺点是如果团队每个人都频繁这样拉取,历史里会出现很多“只是同步代码”的合并提交。

拉取(变基)为什么会让历史更直

git pull --rebase 不创建“同步用”的 merge commit,而是把你的本地提交排到远程提交后面。

所以历史从分叉图:

text
      B
     /
A -- C -- D

变成直线:

text
A -- C -- D -- B'

这就是它最大的价值:让主线历史更像一条按时间推进的直线

读历史时,你看到的是:

  1. 远程已经发生了什么;
  2. 你的改动是在这些变化之后补上去的。

这对看 git log、排查问题、做代码回滚,都更清爽。

它和 git fetchgit mergegit rebase 的关系

可以直接这样理解:

操作可以拆成什么结果特点
git fetch只下载远程变化不改你当前工作分支
git pullgit fetch + git merge把远程变化合进来,可能产生 merge commit
git pull --rebasegit fetch + git rebase把本地提交重放到远程最新提交后面

fetch 最安全,因为它只是“拿消息”,不改你当前分支。

pullpull --rebase 都会修改你当前分支,只是修改方式不同:

  • merge 是“合并两条历史”
  • rebase 是“搬动本地提交,让它接在新基础后面”

“变基”到底变的是什么

“基”可以理解成你这批本地提交的起点。

还是这个例子:

text
      B
     /
A -- C -- D

你的提交 B 原来基于 A
变基之后,B' 基于 D

text
A -- C -- D -- B'

所以“变基”不是把远程代码硬塞进你的提交里,而是让你的提交换一个更新的起点。

冲突时为什么会反复出现

普通 merge 冲突时,Git 通常是在最后合并点上一次性处理冲突。

rebase 不一样。它会按顺序“重放”你的本地提交:

text
B1 -> B2 -> B3

如果你本地有三个提交,Git 会一个一个重新应用:

text
A -- C -- D -- B1' -- B2' -- B3'

每应用一个提交,都可能和远程最新代码冲突。
所以你可能会看到:

  1. 处理 B1 的冲突;
  2. git add 标记已解决;
  3. git rebase --continue
  4. 又遇到 B2 的冲突;
  5. 继续处理。

这不是 Git 出错,而是 rebase 的工作方式。

冲突时怎么处理

遇到冲突后,常见流程是:

sh
git status

先看哪些文件冲突。

改完冲突文件后:

sh
git add 冲突文件
git rebase --continue

如果后面还有冲突,继续重复。

如果你发现这次 rebase 不该继续,可以中止:

sh
git rebase --abort

中止后,Git 会尽量回到 rebase 开始前的状态。

什么时候适合用拉取(变基)

适合用在这些场景:

  • 你在自己的 feature 分支上开发;
  • 你本地有几个提交,但还没推送;
  • 远程主分支或目标分支更新了;
  • 你想把自己的提交放到最新代码后面;
  • 团队希望历史保持线性,少一些同步用 merge commit。

典型命令是:

sh
git pull --rebase origin main

如果当前分支已经跟踪远程分支,也可以直接:

sh
git pull --rebase

什么时候不要随便用

不要随便对这些提交做 rebase:

  • 已经推送到共享分支的提交;
  • 别人已经基于它继续开发的提交;
  • 发布分支、主分支里需要保留真实合并关系的提交;
  • 你不确定当前分支是否只有自己在用。

原因很简单:rebase 会重写提交对象。

原来的:

text
B

变基后会变成:

text
B'

内容可能一样,但提交 ID 不一样。
如果别人手里还拿着旧的 B,你这里变成 B',后面同步时就容易产生混乱。

和“强制推送”是什么关系

只做 git pull --rebase,通常只是改你本地分支。

但如果你 rebase 的提交已经推送过,再想把重写后的历史推到远程,Git 可能会拒绝。
这时有人会用:

sh
git push --force-with-lease

这就涉及重写远程历史了,风险比本地 rebase 高很多。

日常建议是:

  • 本地未推送提交:可以比较放心地 rebase;
  • 已推送但只有你自己用的分支:谨慎 rebase,推送时用 --force-with-lease 比裸 --force 稳;
  • 公共分支:不要随便 rebase 后强推。

常见配置

让命令行里的 git pull 默认变基

如果你希望以后直接执行 git pull 时,默认就等价于 git pull --rebase,最常用的是全局配置:

sh
git config --global pull.rebase true

这个配置会写到你的全局 Git 配置里,影响你当前用户下的大多数仓库。

如果只想对当前仓库生效,不想影响别的项目,可以用本地配置:

sh
git config --local pull.rebase true

--local 只写当前仓库的 .git/config。不写 --local 时,只要你人在某个 Git 仓库里,默认也是写当前仓库配置。

如果只想让某个分支默认变基,可以单独配置这个分支:

sh
git config branch.main.rebase true

这里的 main 要换成你的分支名。这个写法适合某个分支要特殊处理的情况,日常一般先用 pull.rebase true 就够了。

如果只想偶尔用一次,不建议先改全局配置,直接用命令更清楚:

sh
git pull --rebase

VS Code 里怎么配

VS Code 里有几个容易混在一起的操作:

你点的操作更接近什么
Git: Pull执行普通 git pull,会受 Git 的 pull.rebase 配置影响
Git: Pull (Rebase)明确执行变基拉取
状态栏同步按钮 / Git: Sync先拉取再推送,是否 rebase 还要看 VS Code 的同步配置

如果你已经执行了下面这个配置:

sh
git config --global pull.rebase true

那么在终端里执行 git pull,以及很多调用系统 Git 的图形工具,都会默认按 rebase 拉取。

如果你希望 VS Code 的同步按钮也强制使用 rebase,可以在 VS Code 的 settings.json 里加:

json
{
  "git.rebaseWhenSync": true
}

也可以在设置界面搜索 Git: Rebase When Sync,然后勾选它。

要注意:git.rebaseWhenSync 是 VS Code 自己的设置,主要影响 VS Code 的 Sync 操作;pull.rebase 是 Git 配置,影响命令行和更多 Git 客户端。两者不是同一个配置。

有未提交改动时怎么办

rebase 对工作区状态更挑剔。如果你本地有未提交修改,git pull --rebase 可能会因为工作区不干净而中断。

如果你希望 pull 前自动临时保存修改,pull 完再恢复,可以配置:

sh
git config --global pull.autoStash true

VS Code 也有类似设置:

json
{
  "git.autoStash": true
}

这个配置只是减少“工作区不干净导致无法拉取”的打断,不等于完全没有冲突。远程变化和你的本地修改撞上时,还是要正常解决冲突。

怎么检查配置是否生效

查看当前 Git 最终读到的配置:

sh
git config --show-origin --get pull.rebase

如果输出里看到 true,说明默认 rebase 已经打开。--show-origin 会顺便告诉你这个配置来自哪个文件,方便判断是全局配置还是当前仓库配置。

查看自动 stash:

sh
git config --show-origin --get pull.autoStash

如果你想看所有和 pull 相关的配置:

sh
git config --show-origin --get-regexp '^pull\.'

怎么取消默认变基

如果你后来不想让 git pull 默认 rebase,可以改回 merge:

sh
git config --global pull.rebase false

如果是当前仓库单独配过,可以取消当前仓库配置:

sh
git config --local --unset pull.rebase

如果也想取消自动 stash:

sh
git config --global --unset pull.autoStash

一个日常推荐流程

如果你在自己的功能分支上开发,可以这样做:

sh
git status
git fetch origin
git rebase origin/main

这和 git pull --rebase origin main 很接近,但拆开后更容易看清每一步。

如果你只是想日常快速同步:

sh
git pull --rebase

如果冲突了:

sh
git status
git add 冲突文件
git rebase --continue

如果不想继续:

sh
git rebase --abort

和普通拉取怎么选

场景更推荐
自己的本地提交还没推送git pull --rebase
团队要求线性历史git pull --rebase
想保留真实分支合并关系普通 git pull / git merge
公共分支多人共同提交不要随便 rebase,按团队规范来
不确定当前状态git fetch,再看 git log --oneline --graph --decorate --all

最后记住这几句话

  • git pull --rebase 不是“更高级的 pull”,它是另一种整合远程变化的方式。
  • 它会让本地提交换一个更新的基础,所以历史更直。
  • 它适合整理自己的本地提交,不适合随便改公共历史。
  • 冲突时按提交逐个解决,解决后用 git rebase --continue
  • 不确定时,先 git fetch,看清分支图,再决定 merge 还是 rebase。

基于 VitePress 的个人知识库骨架