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 会做两步:
- 先把远程的
C、D拿下来。 - 再把你的
B暂时拿起,重新放到D后面。
结果变成:
text
A -- C -- D -- B' 你的本地分支注意这里是 B',不是原来的 B。因为它的父提交变了,所以 Git 会生成一个新的提交对象。
普通拉取会发生什么
如果你用普通 git pull,在默认 merge 策略下,大概率会变成这样:
text
B ---- M
/ /
A -- C -- D这里的 M 是一个合并提交。它的意思是:
- 你的提交
B保留在原位置 - 远程提交
C、D保留在原位置 - Git 新建一个 merge commit,把两条线合到一起
这种方式没有错,信息也完整。缺点是如果团队每个人都频繁这样拉取,历史里会出现很多“只是同步代码”的合并提交。
拉取(变基)为什么会让历史更直
git pull --rebase 不创建“同步用”的 merge commit,而是把你的本地提交排到远程提交后面。
所以历史从分叉图:
text
B
/
A -- C -- D变成直线:
text
A -- C -- D -- B'这就是它最大的价值:让主线历史更像一条按时间推进的直线。
读历史时,你看到的是:
- 远程已经发生了什么;
- 你的改动是在这些变化之后补上去的。
这对看 git log、排查问题、做代码回滚,都更清爽。
它和 git fetch、git merge、git rebase 的关系
可以直接这样理解:
| 操作 | 可以拆成什么 | 结果特点 |
|---|---|---|
git fetch | 只下载远程变化 | 不改你当前工作分支 |
git pull | git fetch + git merge | 把远程变化合进来,可能产生 merge commit |
git pull --rebase | git fetch + git rebase | 把本地提交重放到远程最新提交后面 |
fetch 最安全,因为它只是“拿消息”,不改你当前分支。
pull 和 pull --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'每应用一个提交,都可能和远程最新代码冲突。
所以你可能会看到:
- 处理
B1的冲突; git add标记已解决;git rebase --continue;- 又遇到
B2的冲突; - 继续处理。
这不是 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 --rebaseVS 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 trueVS 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。