在什么情况下`git pull`是有害的?

Ric*_*sen 404 git git-pull

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

git pull命令似乎是更新本地存储库的规范方法.使用git pull创建问题?它创造了什么问题?有更好的方法来更新git存储库吗?

Ric*_*sen 543

摘要

默认情况下,git pull创建合并提交,这会增加代码历史记录的噪音和复杂性.此外,还pull可以轻松地不考虑您的更改如何受到传入更改的影响.

git pull命令是安全的,只要它只执行快进合并.如果git pull配置为仅执行快进合并,并且当无法进行快进合并时,则Git将退出并显示错误.这将使您有机会研究传入的提交,考虑它们可能如何影响您的本地提交,并确定最佳的操作过程(合并,变基,重置等).

使用Git 2.0和更新版本,您可以运行:

git config --global pull.ff only
Run Code Online (Sandbox Code Playgroud)

将默认行为更改为仅快进.使用1.6.6和1.9.x之间的Git版本,你将不得不养成输入的习惯:

git pull --ff-only
Run Code Online (Sandbox Code Playgroud)

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

git config --global alias.up '!git remote update -p; git merge --ff-only @{u}'
Run Code Online (Sandbox Code Playgroud)

并使用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为rebase而不是merge,但这也存在问题(稍后讨论).相反,git pull应配置为仅执行快进合并.

重新引入重新提交的提案

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

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

惊喜工作目录修改

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

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

难以回顾其他人的提交

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

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

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

重新定位到远程分支

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

不幸的是,如果你有一个unpushed合并提交要保存(如提交合并推送的特性分支成master),既不是底垫式(git pullbranch.<branch>.rebase设定为true),也不是合并式(默认git pull行为),随后是rebase将起作用.这是因为git rebase在没有--preserve-merges选项的情况下消除了合并(它使DAG线性化).不能将rebase-pull操作配置为保留合并,并且合并拉动后跟a git rebase -p @{u}将不会消除merge-pull引起的合并. 更新: 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}'
Run Code Online (Sandbox Code Playgroud)

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

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

另外一个选项: git pull --ff-only --all -p

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

git config --global alias.up 'pull --ff-only --all -p'
Run Code Online (Sandbox Code Playgroud)

此版本git up与前一个git up别名具有相同的行为,但以下情况除外:

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

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

使用Git 2.0和更新版本,您可以配置git pull为默认只执行快进合并:

git config --global pull.ff only
Run Code Online (Sandbox Code Playgroud)

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

  • 一些原因听起来像"拉动可能会导致其他人做疯狂的事情".不,这是一个疯狂的事情,比如从上游回购中修改一个导致问题的提交.当你在尚未推送的提交上本地执行时,IMO rebase是安全的.例如,当您在推送之前拉动时,重新定位本地提交有助于保持您的历史线性(尽管线性历史记录不是那么重要).不过,"git up"听起来像是一个有趣的选择. (51认同)
  • 你的大多数观点都是因为你做错了什么:你试图在你自己的工作分支**中复习代码**.这不是一个好主意,只需创建一个新的分支,拉--rebase =保留然后折腾该分支(或者如果你想要合并它). (16认同)
  • Git 2.0添加了一个`pull.ff`配置,似乎可以实现相同的功能,没有别名. (13认同)
  • @brianz:`git remote update -p`相当于`git fetch --all -p`.我习惯输入`git remote update -p`,因为曾经的`fetch`没有`-p`选项.关于前导`!`,请参阅`git help config`中`alias.*`的描述.它说,"如果别名扩展以感叹号为前缀,它将被视为shell命令." (6认同)
  • @ funkaster的观点非常有意义,尤其是:"难以回顾其他人的提交".这不是大多数Git用户使用的评论流程,这是我从未在任何地方看到的建议,它是导致标题下面描述的所有不必要的额外工作的原因,而不是`git pull`. (5认同)
  • 对于经常被忽视的主题,这是一个很好的答案.我见过vanialla`git pull`在Git新手的实践中引起了很多问题.问题:为什么`远程更新'超过`fetch`?领先的是什么!在别名命令中执行?你能不能别名:`git fetch --all -p; git merge --ff-only @ {u}` (3认同)
  • 有人可以解释@ {u}的含义吗? (2认同)

Sér*_*lho 194

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

我觉得我很想用热线新闻头条法回答这个问题:为什么被git pull认为有害?事实并非如此.

  • 非线性不是本质上不好的.如果他们代表了实际的历史,他们就可以.
  • 意外重新引入上游的提交上游错误重写历史的结果.当历史沿着几个回购复制时,您无法重写历史记录.
  • 修改工作目录是预期的结果; 有争议的有用性,即面对hg/monotone/darcs/other_dvcs_predating_git的行为,但同样不是本质上不好的.
  • 合并需要暂停审查其他人的工作,并且再次成为git pull的预期行为.如果您不想合并,则应使用git fetch.同样,与以前流行的dvcs相比,这是git的特质,但它是预期的行为而不是本质上不好的.
  • 难以对远程分支进行折旧是很好的.除非你绝对需要,否则不要重写历史.我不能为我的生活理解这种(假的)线性历史的追求
  • 不清理分支是好的.每个回购都知道它想要持有什么.Git没有主从关系的概念.

  • 不能同意更多.人们主张`git rebase`并考虑`git pull`有害吗?真? (28认同)
  • 我同意.对于'git pull`没有任何本质上有害的东西.但是,它可能与一些有害的做法相冲突,比如想要重写历史而不是严格必要的.但是git很灵活,所以如果你想以不同的方式使用它,一定要这样做.但那是因为*你*(好吧,@ Richard Harsen)想要在git中做一些不寻常的事情,而不是因为`git pull`是有害的. (13认同)
  • 很高兴看到有人创建一个图形,以道德为轴,并将git命令分为好,坏,或介于两者之间.这个图表在开发人员之间会有所不同,尽管它会说很多人使用git. (10认同)
  • 没有`--rebase`选项的`git pull`的问题是它创建的合并方向.当您查看差异时,该合并中的所有更改现在都属于拉取的人,而不是进行更改的人.我喜欢一个工作流程,其中合并是为两个独立的分支(A - > B)保留的,因此合并提交很清楚引入的内容,并且保留变基以在同一分支上获取最新信息(远程A - >本地A ) (5认同)
  • 那么,如果有人在其他人之前或者其他方面做了一次拉动,你会得到什么呢?我认为这只是噪音而且只是模糊了真正相关的历史.这甚至减少了历史的价值.一个好的历史应该是a)清洁和b)实际上有重要的历史. (4认同)

Hun*_*ick 26

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

  • 详细说明一下:如果每个人都在他们自己的分支上工作(在我看来这是使用git的正确方法),`git pull`不是任何问题.在git中分支很便宜. (10认同)

Mar*_*age 18

接受的答案声称

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

但是从Git 1.8.5开始,这个帖子就是这个答案,你可以做到

git pull --rebase=preserve
Run Code Online (Sandbox Code Playgroud)

要么

git config --global pull.rebase preserve
Run Code Online (Sandbox Code Playgroud)

要么

git config branch.<name>.rebase preserve
Run Code Online (Sandbox Code Playgroud)

文件说,

preserve,也传递--preserve-merges给'git rebase'时,运行'git pull'不会使本地提交的合并提交变平.

前面的讨论有更详细的信息和图表:git pull --rebase --preserve-merges.它也解释了为什么git pull --rebase=preserve不相同git pull --rebase --preserve-merges,哪些不正确.

之前的其他讨论解释了rebase的保留合并变体实际上做了什么,以及它如何比常规rebase复杂得多:git的"rebase --preserve-merges"究竟做了什么(以及为什么?)