git pull 到底做了什么?

562 阅读3分钟

git pull + push 条件反射陷阱

如果我们在一个分支上不需要协同,一切都非常简单:我们所有的git push都能成功,不需要频繁的git pull操作。但是只要有其他人同样在我们共同的分支上工作(常见的工作模式),我们就非常普遍地遇到下面的场景:在我们的最近一次同步(使用git pull)和我们需要发布local history(要使用git push)的这个时刻,另外一个同事已经分享了他们的工作到远程仓库中,所以remote branch(比如说origin/feature分支上)是比我们的本地拷贝要更新一些。 这种情况,git push的时候,git就会拒绝接受(因为如果接受就会丢失历史)

image.png

在这种情况,大多数人都会接受git的建议,先做一次 git pull,随后再 git push,这看起来没有问题,但是还是有些需要思考的...

git pull 到底做了什么?

pull 实际上包含两项顺序执行的操作

1、将 local copy repo 和 remote repo 做一次网络同步。这实际上就是一次 git fetch,也只有这次我们需要有和 remote repo 的网络连接

2、默认的,一个 git merge 操作(将 remote tracked branch merge 到我们的 local tracking branch , 比如说 origin/feature -> feature)

为了方便理解,我们假设我当前在 feature 分支上,而它的 remote track branch 是 origin/feature,那么一个 git pull 操作就等效于: 1、git fetch 2、git merge origin/feature

git pull 自带 merge 动作的问题

由于我有了 local 的变更,而 remote 又有另外的一些变更,这样由于 local 和 remote 有了分叉,因此 git pull 的 merge 就会产生一个真实的 merge,repo库的历史图谱就非线性:

image.png

而这种结果并非我们想要,一个 merge 动作代表了我们将一个 well-known branch 需要合并融合主流的动作,而不是一次繁文缛节的技术动作。

执行一个 git pull 操作动作时保持这个分支历史信息的线性化,往往是我们希望达到的。而要达到这一点,我们唯一需要做的就是要求 git pull 操作时不要执行 merge 操作,而是执行 rebase 操作,所以 git pull 执行的结果就是让你的 local commits 一个一个地在新拉下来的 base 基础上重新 run 一遍

git pull 时使用 rebase

  • 我们可以通过 git pull --rebase 来明确要求git,但是这不是一个很可靠的解决方案,因为这需要我们在 git pull 操作时时刻保持警惕,但这往往不太可能,因为只要是人就容易犯错误。

image.png

  • 我们可以通过一个配置项来保证我们不会忘记这件事儿(要求git pull时使用rebase而不是merge),global 级别配置: git config --global pull.rebase true,但此配置会将已有的分支合并记录全部转变为线性历史

image.png

  • global 级别配置: git config --global pull.rebase preserve,使用 pull with rebase 策略,保留 local merge,在实际工作中更为常用