【问题标题】:In what cases could `git pull` be harmful?在什么情况下 `git pull` 是有害的?
【发布时间】:2016-10-01 18:19:15
【问题描述】:

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

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

【问题讨论】:

  • 或者你也可以git pull --rebase并将这个策略设置为新分支的默认策略git config branch.autosetuprebase
  • knoopx 说得对,将--rebase 标志添加到git pull 可以使本地与远程同步,然后在更新的本地之上重放您的本地更改。然后,当您推送时,您所做的就是将新提交附加到远程的末尾。很简单。
  • 感谢@BenMcCormick。我已经这样做了,但是关于问题有效性的讨论似乎正在问题下方的这些 cmets 中进行。而且我认为提出问题以创建一个平台来表达您的个人观点,这并不是 SO 的问答结构的真正用途。
  • @RichardHansen,这似乎是一种欺骗积分系统的方式,尤其是在您的答案语气差异如此之大且时间间隔如此之短的情况下。使用您的问答模型,我们都可以使用我们以前的知识提出问题并自己回答问题。那时,您应该考虑写一篇博客文章,因为这样更合适。问答专门寻求其他人的知识。一篇博文展示了你自己的。

标签: git git-pull


【解决方案1】:

总结

默认情况下,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 配置为rebase 而不是merge,但这也有问题(稍后讨论)。相反,git pull 应配置为仅进行快进合并。

重新引入 Rebased-out Commits

假设有人重新设置分支并强制推送它。这通常不应该发生,但有时是必要的(例如,删除意外提交和推送的 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 选项)。

不幸的是,如果您想要保留一个未推送的合并提交(例如,将推送的功能分支合并到 master 的提交),那么 rebase-pull(git pullbranch.<branch>.rebase 设置为 @987654369 @) 也不是合并拉动(默认的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 pull 在获取上游提交后执行git rebase --preserve-merges。 (感谢funkaster 的提醒!)

清理已删除的分支

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

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

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

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

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

【讨论】:

  • @brianz: git remote update -p 等价于git fetch --all -p。我有输入git remote update -p 的习惯,因为曾几何时fetch 没有-p 选项。关于前导!,见git help configalias.*的描述。它说,“如果别名扩展以感叹号为前缀,它将被视为 shell 命令。”
  • Git 2.0 添加了一个pull.ff 配置,似乎实现了同样的事情,没有别名。
  • 有些原因听起来像是“当其他人做疯狂的事情时,拉动可能会导致问题”。不,这是疯狂的事情,比如从上游 repo 重新提交会导致问题。仅当您在尚未推送的提交上本地执行此操作时,IMO rebase 才是安全的。例如,当您在推送之前拉取时,重新定位本地提交有助于保持您的历史线性(尽管线性历史并不是什么大不了的事)。尽管如此,git up 听起来还是一个有趣的选择。
  • 您的大部分观点是因为您做错了事:您正试图在您自己的工作分支中审查代码。这不是一个好主意,只需创建一个新分支,拉出 --rebase=preserve 然后扔掉那个分支(或者如果你愿意,可以合并它)。
  • @funkaster 的观点很有意义,尤其是关于:“难以审查其他人的提交”。这不是大多数 Git 用户使用的审查流程,这是我从未在任何地方看到推荐的东西,它是导致标题下方描述的所有不必要的额外工作的原因,而不是 git pull
【解决方案2】:

我的回答来自 HackerNews 上arose 的讨论:

我很想用贝特里奇头条定律来回答这个问题:为什么git pull 被认为是有害的?不是。

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

【讨论】:

  • 我同意。 git pull 本身并没有什么害处。然而,它可能会与一些有害的做法相冲突,比如想要重写历史而不是严格必要的。但是 git 是灵活的,所以如果你想以不同的方式使用它,一定要这样做。但那是因为(嗯,@Richard Hansen)想要在 git 中做一些不寻常的事情,而不是因为 git pull 有害。
  • 不能再同意了。人们提倡git rebase 并认为git pull 有害?真的吗?
  • 很高兴看到有人创建了一个图表,以道德为轴,并将 git 命令分类为好、坏或介于两者之间的某个地方。这个图表在不同的开发者之间会有所不同,尽管它会说明很多人使用 git。
  • 我对@9​​87654327@ 没有--rebase 选项的问题是它创建的合并方向。当您查看差异时,该合并中的所有更改现在都属于拉动的人,而不是做出更改的人。我喜欢一个工作流程,其中合并保留给两个单独的分支(A -> B),因此合并提交清楚地介绍了引入的内容,并且保留重新定位以在同一分支上保持最新(远程 A -> 本地 A )
  • 那么,如果有人在其他人之前几秒钟或相反地进行了拉动,您会获得什么?我认为这只是噪音,只是混淆了真正相关的历史。这甚至降低了历史的价值。一个好的历史应该是 a) 干净 b) 实际上有重要的历史。
【解决方案3】:

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

【讨论】:

  • 详细说明:如果每个人都在自己的分支上工作(我认为这是使用 git 的正确方法),git pull 不是任何问题。在 git 中分支很便宜。
【解决方案4】:

接受的答案声明

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

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

git pull --rebase=preserve

git config --global pull.rebase preserve

git config branch.<name>.rebase preserve

docs

preserve, 也将--preserve-merges 传递给'git rebase' 时,本地提交的合并提交不会被运行'git pull' 弄平。

之前的讨论有更详细的信息和图表:git pull --rebase --preserve-merges。这也解释了为什么git pull --rebase=preservegit pull --rebase --preserve-merges 不一样,后者做的不对。

之前的另一个讨论解释了 rebase 的 preserve-merges 变体实际上做了什么,以及它如何比常规 rebase 复杂得多:What exactly does git's "rebase --preserve-merges" do (and why?)

【讨论】:

    【解决方案5】:

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

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

    这对我来说很完美。

    【讨论】:

      猜你喜欢
      • 2021-11-06
      • 2022-10-21
      • 2021-08-03
      • 2019-11-17
      • 1970-01-01
      • 1970-01-01
      • 2015-04-20
      • 2012-12-22
      相关资源
      最近更新 更多