426

我有一位同事声称这git pull是有害的,并且每当有人使用它时都会感到不安。

git pull命令似乎是更新本地存储库的规范方式。使用会git pull产生问题吗?它会产生什么问题?有没有更好的方法来更新 git 存储库?

4

5 回答 5

565

概括

默认情况下,git pull创建合并提交,这会给代码历史添加噪音和复杂性。此外,pull您无需考虑您的更改可能会如何受到传入更改的影响。

git pull命令是安全的,只要它只执行快进合并。如果git pull配置为仅进行快进合并并且无法进行快进合并时,Git 将退出并出现错误。这将使您有机会研究传入的提交,考虑它们如何影响您的本地提交,并决定最佳的行动方案(合并、变基、重置等)。

使用 Git 2.0 及更高版本,您可以运行:

git config --global pull.ff only

将默认行为更改为仅快进。对于 1.6.6 和 1.9.x 之间的 Git 版本,您必须养成输入的习惯:

git pull --ff-only

但是,对于所有版本的 Git,我建议配置这样的git up别名:

git config --global alias.up '!git remote update -p; git merge --ff-only @{u}'

并使用git up而不是git pull. 我更喜欢这个别名,git pull --ff-only因为:

  • 它适用于所有(非古代)版本的 Git,
  • 它获取所有上游分支(不仅仅是您当前正在处理的分支),并且
  • 它清除origin/*上游不再存在的旧分支。

问题git pull

git pull如果使用得当,还不错。最近对 Git 的一些更改使其更易于git pull正确使用,但不幸的是,普通的默认行为git pull有几个问题:

  • 它在历史上引入了不必要的非线性
  • 它可以很容易地意外地重新引入在上游故意重新设置的提交
  • 它以不可预知的方式修改您的工作目录
  • 暂停你正在做的事情来审查别人的工作很烦人git pull
  • 这使得很难正确地重新定位到远程分支
  • 它不会清理在远程仓库中删除的分支

下面更详细地描述这些问题。

非线性历史

默认情况下,该git pull命令相当于运行git fetch后跟git merge @{u}. 如果本地存储库中有未推送的提交,则合并部分git pull会创建合并提交。

合并提交本身并没有什么坏处,但它们可能很危险,应该受到尊重:

  • 合并提交本质上很难检查。要了解合并在做什么,您必须了解所有父母的差异。传统的差异不能很好地传达这种多维信息。相比之下,一系列正常的提交很容易审查。
  • 合并冲突的解决很棘手,而且由于合并提交很难审查,错误往往会在很长一段时间内未被发现。
  • 合并可以悄悄地取代常规提交的影响。代码不再是增量提交的总和,导致对实际更改内容的误解。
  • 合并提交可能会破坏一些持续集成方案(例如,在假定的约定下,仅自动构建第一父路径,第二父路径指向正在进行的未完成工作)。

当然,合并有时间和地点,但是了解何时应该使用和不应该使用合并可以提高存储库的实用性。

请注意,Git 的目的是让共享和使用代码库的演变变得容易,而不是准确地记录历史的展开。(如果您不同意,请考虑该rebase命令及其创建原因。) by 创建的合并提交git pull不会向其他人传达有用的语义——他们只是说其他人碰巧在您完成更改之前推送到存储库。如果这些合并提交对其他人没有意义并且可能很危险,为什么还要进行这些合并提交?

可以配置git pull为变基而不是合并,但这也有问题(稍后讨论)。相反,git pull应配置为仅进行快进合并。

重新引入 Rebased-out Commits

假设有人重新设置分支并强制推送它。这通常不应该发生,但有时是必要的(例如,删除一个意外提交和推送的 50GiB 日志文件)。完成的合并git pull会将上游分支的新版本合并到本地存储库中仍然存在的旧版本中。如果你推动结果,音叉和火炬将开始向你靠近。

有些人可能会争辩说,真正的问题是强制更新。是的,通常建议尽可能避免用力推动,但有时它们是不可避免的。开发人员必须准备好应对强制更新,因为它们有时会发生。这意味着不要通过普通的git pull.

惊喜工作目录修改

在完成之前,无法预测工作目录或索引的外观git pull。在执行其他任何操作之前,您可能必须解决合并冲突,它可能会在您的工作目录中引入一个 50GiB 的日志文件,因为有人不小心推送了它,它可能会重命名您正在工作的目录等。

git remote update -p(或git fetch --all -p)允许您在决定合并或变基之前查看其他人的提交,从而允许您在采取行动之前制定计划。

难以审查其他人的提交

假设您正在进行一些更改,而其他人希望您查看他们刚刚推送的一些提交。 git pull的合并(或变基)操作会修改工作目录和索引,这意味着您的工作目录和索引必须是干净的。

你可以使用git stashand then git pull,但是当你完成复习时你会做什么?要回到原来的位置,您必须撤消由创建的合并git pull并应用存储。

git remote update -p(or git fetch --all -p) 不会修改工作目录或索引,因此可以随时安全地运行——即使您已暂存和/或未暂存更改。您可以暂停正在执行的操作并查看其他人的提交,而无需担心存储或完成您正在处理的提交。git pull没有给你那种灵活性。

重新定位到远程分支

一个常见的 Git 使用模式是执行 agit pull以引入最新的更改,然后执行 agit rebase @{u}以消除git pull引入的合并提交。git pullGit 有一些配置选项可以通过告诉执行变基而不是合并来将这两个步骤减少到一个步骤,这很常见(请参阅branch.<branch>.rebasebranch.autosetuprebasepull.rebase选项)。

不幸的是,如果您想要保留一个未推送的合并提交(例如,将推送的功能分支合并到的提交master),则既不是 rebase-pull(git pull设置branch.<branch>.rebasetrue)也不是 merge-pull(默认git pull行为),然后是变基将起作用。这是因为在没有选项git rebase的情况下消除了合并(它使 DAG 线性化) 。--preserve-mergesrebase-pull 操作不能配置为保留合并,并且后跟 a 的合并拉取git rebase -p @{u}不会消除由合并拉取引起的合并。 更新: 添加了 Git v1.8.5git pull --rebase=preservegit config pull.rebase preserve. 这些原因在获取上游提交后执行git pullgit rebase --preserve-merges(感谢funkaster的提醒!)

清理已删除的分支

git pull不会修剪与从远程存储库中删除的分支相对应的远程跟踪分支。例如,如果有人foo从远程仓库中删除分支,您仍然会看到origin/foo.

这会导致用户意外地复活被杀死的分支,因为他们认为它们仍然处于活动状态。

更好的选择:使用git up而不是git pull

而不是git pull,我建议创建和使用以下git up别名:

git config --global alias.up '!git remote update -p; git merge --ff-only @{u}'

此别名从所有上游分支下载所有最新提交(修剪死分支)并尝试将本地分支快进到上游分支上的最新提交。如果成功,则没有本地提交,因此不存在合并冲突的风险。如果有本地(未推送)提交,快进将失败,让您有机会在采取行动之前查看上游提交。

这仍然会以不可预知的方式修改您的工作目录,但前提是您没有任何本地更改。不像git pull,git up永远不会让你看到一个提示你修复合并冲突的提示。

另外的选择:git pull --ff-only --all -p

以下是上述git up别名的替代方法:

git config --global alias.up 'pull --ff-only --all -p'

此版本的具有与以前的别名git up相同的行为,除了:git up

  • 如果您的本地分支未配置上游分支,则错误消息会更加神秘
  • 它依赖于一个未记录的特性(-p传递给的参数fetch),该特性可能会在未来的 Git 版本中发生变化

如果您正在运行 Git 2.0 或更高版本

使用 Git 2.0 及更高版本,您可以配置git pull为默认仅进行快进合并:

git config --global pull.ff only

这会导致git pull行为类似git pull --ff-only,但它仍然不会获取所有上游提交或清除旧origin/*分支,所以我仍然更喜欢git up

于 2013-03-09T22:23:10.533 回答
198

我的回答来自 HackerNews 上的讨论

我很想用 Betteridge Law of Headlines 来回答这个问题:为什么被git pull认为是有害的?它不是。

  • 非线性本质上并不坏。如果它们代表实际历史,它们就可以了。
  • 意外重新引入上游rebase的提交是错误地重写上游历史的结果。当历史沿多个存储库复制时,您无法重写历史记录。
  • 修改工作目录是预期结果;有争议的有用性,即面对 hg/monotone/darcs/other_dvcs_predating_git 的行为,但本质上也不是坏的。
  • 合并需要暂停以查看其他人的工作,这也是 git pull 上的预期行为。如果你不想合并,你应该使用 git fetch。同样,与以前流行的 dvcs 相比,这是 git 的一个特性,但它是预期的行为,本质上并不坏。
  • 很难针对远程分支进行 rebase 是好的。除非绝对需要,否则不要重写历史。我一辈子都无法理解这种对(假)线性历史的追求
  • 不清理树枝是好的。每个回购都知道它想要持有什么。Git 没有主从关系的概念。
于 2014-03-12T15:15:03.797 回答
26

如果你正确使用 Git,它不会被认为是有害的。鉴于您的用例,我看到它如何对您产生负面影响,但是您可以通过不修改共享历史来避免问题。

于 2014-03-12T15:43:46.817 回答
18

接受的答案声称

无法将 rebase-pull 操作配置为保留合并

但从Git 1.8.5开始,你可以这样做

git pull --rebase=preserve

或者

git config --global pull.rebase preserve

或者

git config branch.<name>.rebase preserve

文档说_

preserve,也传递--preserve-merges给'git rebase',以便本地提交的合并提交不会通过运行'git pull'而变平。

之前的讨论有更详细的信息和图表:git pull --rebase --preserve-merges。它还解释了为什么git pull --rebase=preserve不一样git pull --rebase --preserve-merges,这不正确。

之前的另一个讨论解释了rebase的preserve-merges变体实际上做了什么,以及它如何比常规rebase复杂得多:git的“rebase --preserve-merges”到底做了什么(为什么?)

于 2014-03-12T16:38:35.023 回答
-1

如果你去旧的 git 存储库git up他们建议的别名是不同的。 https://github.com/aanand/git-up

git config --global alias.up 'pull --rebase --autostash'

这对我来说很完美。

于 2019-08-13T21:56:58.690 回答