在什么情况下,“ git pull”会有害吗?


409

我有一个同事声称这git pull是有害的,每当有人使用它都会感到沮丧。

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



8
或者,您也可以git pull --rebase将此策略设置为新分支的默认策略 git config branch.autosetuprebase
knoopx 2014年

4
knoopx正确,添加--rebase标志以git pull将本地与远程同步,然后在更新的本地上重放本地更改。然后,当您按入所有操作时,就是将新提交追加到远程终端的末尾。很简单
Heath Lilley 2014年

4
谢谢@BenMcCormick。我已经做了,但是关于问题有效性的讨论似乎在问题下方的这些注释中进行。我认为,提出一个问题以创建一个平台来表达您的个人观点,实际上这并不是SO的Q&A结构的真正目的。
mcv 2014年

4
@RichardHansen,这似乎是一种欺骗分数系统的方法,尤其是您的答案在语气上有如此大的差异,而且时间间隔如此之短。使用您的问答模型,我们所有人都可以使用我们以前的知识提出问题并自己回答。到那时,您应该考虑撰写博客文章,因为这样做更为合适。问答专门寻求他人的知识。博客文章展示了您自己的博客。
乔什·布朗

Answers:


546

摘要

默认情况下,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命令以及创建该命令的原因。)创建的合并提交git pull不会将有用的语义传达给其他人-他们只是说,在完成更改之前,其他人碰巧将其推送到存储库。如果这些合并提交对其他人没有意义并且可能很危险,为什么还要合并?

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

重新引入重新定基的提交

假设有人重新设置了分支的基础并用力推动了它。通常不应发生这种情况,但有时是有必要的(例如,删除意外提交和推送的50GiB日志文件)。完成的合并git pull会将上游分支的新版本合并为本地存储库中仍然存在的旧版本。如果您推动结果,则音叉和火把将开始向您行驶。

有人可能会认为真正的问题是强制更新。是的,通常建议尽可能避免用力推动,但有时不可避免。开发人员必须准备好应对强制更新,因为它们有时会发生。这意味着不要通过普通方法盲目地合并旧提交git pull

惊喜工作目录修改

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

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

审查他人承诺的困难

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

您可以使用git stash然后git pull,然后使用,但是完成检查后您会怎么做?要回到原来的位置,您必须撤消创建的合并git pull并应用存储。

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

迁移到远程分支

常见的Git使用模式是先导入git pull最新的更改,然后git rebase @{u}再删除以消除git pull引入的合并提交。这是常见的,以至于Git有一些配置信息,告诉降低这两个步骤,一个步骤git pull来执行,而不是合并衍合(见的branch.<branch>.rebasebranch.autosetuprebasepull.rebase选项)。

不幸的是,如果你有一个unpushed合并提交要保存(如提交合并推送的特性分支成master),既不是底垫式(git pullbranch.<branch>.rebase设定为true),也不是合并式(默认git pull后跟一个行为)重新设置将起作用。这是因为git rebase无需--preserve-merges选项即可消除合并(线性化DAG)。无法将rebase-pull操作配置为保留合并,并且合并合并后跟合并git rebase -p @{u}将不会消除由合并合并引起的合并。 更新: 添加了Git v1.8.5 git 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


6
@brianz:git remote update -p等同于git fetch --all -p。我有打字的习惯,git remote update -p因为从前fetch没有-p选择。至于领导!,看到的描述alias.*git help config。它说:“如果别名扩展名带有感叹号作为前缀,它将被视为shell命令。”
理查德·汉森

13
Git 2.0添加了一个pull.ff配置,该配置看起来可以实现相同的功能,而没有别名。
丹尼·托马斯

51
有些原因听起来像是“当其他人做疯狂的事情时,拉动可能会引起问题”。不,这是令人发指的事情,例如从上游回购中撤回提交,导致问题。仅当您尚未提交的本地提交时,IMO rebase才是安全的。例如,当您在推入之前拉动时,对本地提交进行基础调整有助于保持历史记录的线性(尽管线性历史记录没什么大不了的)。尽管如此,git up听起来还是一个有趣的选择。
mcv 2014年

16
您的大部分观点是因为您做错了什么:您正在尝试在自己的工作分支中查看代码。这不是一个好主意,只需创建一个新分支,拉--rebase = preserve然后扔掉该分支(或合并即可)。
funkaster 2014年

5
@funkaster在这里的观点很有道理,尤其是关于:“很难检查其他人的承诺”。这不是大多数Git用户使用的审阅流程,这是我从未在任何地方推荐的东西,这是导致标题下所述的所有不必要的额外工作的原因,而不是git pull
Ben Regenspan 2014年

195

我的答案,从讨论中拉起来的HackerNews:

我很想用《贝特里奇新闻标题法》回答这个问题:为什么被git pull认为有害?不是。

  • 非线性本质上不是坏的。如果它们代表实际的历史,则可以。
  • 意外重新引入以上游为基础的提交是错误地重写上游历史记录的结果。当沿几个存储库复制历史记录时,您将无法重写历史记录。
  • 修改工作目录是预期的结果;值得商useful的有用性,即面对hg / monotone / darcs / other_dvcs_predating_git的行为,但本质上也不错。
  • 合并需要暂停查看他人的工作,这也是git pull上的预期行为。如果您不想合并,则应使用git fetch。同样,与以前流行的dvc相比,这是git的特质,但这是预期的行为,本质上不是坏的。
  • 使其难以根据远程分支进行基础是好的。除非绝对必要,否则不要重写历史记录。我一生无法理解对(假)线性历史的这种追求
  • 不清理分支是好的。每个回购都知道它想持有什么。Git没有主从关系的概念。

13
我同意。根本没有什么害处git pull。但是,它可能与某些有害的做法相冲突,例如想要重写历史记录超出严格的要求。但是git是灵活的,因此,如果您想以其他方式使用它,则一定要这样做。但这是因为(嗯,@ Richard Hansen)想在git中做一些不寻常的事情,而不是因为这样git pull做有害。
mcv 2014年

28
完全同意。人们提倡git rebase并考虑git pull有害吗?真?
维克多·莫罗兹

10
很高兴看到有人创建一个以道德为轴的图表,并将git命令分类为好,坏或介于两者之间。尽管开发人员会说很多使用git的情况,但该图表在开发人员之间会有所不同。
michaelt 2014年

5
git pull没有--rebase选项的我的问题是它创建的合并方向。当您查看差异时,合并中的所有更改现在都属于拉动的人,而不是进行更改的人。我喜欢一个工作流,其中为两个单独的分支(A-> B)保留了合并,因此合并提交很清楚引入的内容,并且保留了重新定基以在同一分支上获取最新信息(远程A->本地A )
Craig Kochis 2014年

4
那么,让您知道是否有人在其他人之前几秒钟就做出了拉动,或者相反,这对您有什么好处?我认为这只是噪音,只会混淆真正相关的历史。这甚至会降低历史记录的价值。良好的历史应该是:a)干净,并且b)实际上具有重要的历史。
David Ongaro 2014年

26

如果正确使用Git,则认为这不是有害的。在给定用例的情况下,我看到了它对您的负面影响,但是您可以通过不修改共享历史记录来避免问题。


10
详细说明:如果每个人都在自己的分支上工作(我认为这是使用git的正确方法),那git pull不是什么问题。在git中分支很便宜。
AlexQueue 2014年

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的save-merges变体实际上做了什么,以及它比常规rebase复杂得多:git的“ rebase --preserve-merges”究竟做了什么(为什么?)


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.