在什么情况下 `git pull` 可能有害?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/15316601/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-10 15:42:28  来源:igfitidea点击:

In what cases could `git pull` be harmful?

gitgit-pull

提问by Richard Hansen

I have a colleague who claims that git pullis harmful, and gets upset whenever someone uses it.

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

The git pullcommand seems to be the canonical way to update your local repository. Does using git pullcreate problems? What problems does it create? Is there a better way to update a git repository?

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

回答by Richard Hansen

Summary

概括

By default, git pullcreates merge commits which add noise and complexity to the code history. In addition, pullmakes it easy to not think about how your changes might be affected by incoming changes.

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

The git pullcommand is safe so long as it only performs fast-forward merges. If git pullis configured to only do fast-forward merges and when a fast-forward merge isn't possible, then Git will exit with an error. This will give you an opportunity to study the incoming commits, think about how they might affect your local commits, and decide the best course of action (merge, rebase, reset, etc.).

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

With Git 2.0 and newer, you can run:

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

git config --global pull.ff only

to alter the default behavior to only fast-forward. With Git versions between 1.6.6 and 1.9.x you'll have to get into the habit of typing:

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

git pull --ff-only

However, with all versions of Git, I recommend configuring a git upalias like this:

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

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

and using git upinstead of git pull. I prefer this alias over git pull --ff-onlybecause:

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

  • it works with all (non-ancient) versions of Git,
  • it fetches all upstream branches (not just the branch you're currently working on), and
  • it cleans out old origin/*branches that no longer exist upstream.
  • 它适用于所有(非古代)版本的 Git,
  • 它获取所有上游分支(不仅仅是您当前正在处理的分支),并且
  • 它清除origin/*上游不再存在的旧分支。

Problems with git pull

问题 git pull

git pullisn't bad if it is used properly. Several recent changes to Git have made it easier to use git pullproperly, but unfortunately the default behavior of a plain git pullhas several problems:

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

  • it introduces unnecessary nonlinearities in the history
  • it makes it easy to accidentally reintroduce commits that were intentionally rebased out upstream
  • it modifies your working directory in unpredictable ways
  • pausing what you are doing to review someone else's work is annoying with git pull
  • it makes it hard to correctly rebase onto the remote branch
  • it doesn't clean up branches that were deleted in the remote repo
  • 它在历史中引入了不必要的非线性
  • 它很容易意外地重新引入故意在上游重新定位的提交
  • 它以不可预测的方式修改您的工作目录
  • 暂停你正在做的事情来别人的工作很烦人 git pull
  • 这使得很难正确地重新定位到远程分支
  • 它不会清理在远程仓库中删除的分支

These problems are described in greater detail below.

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

Nonlinear History

非线性历史

By default, the git pullcommand is equivalent to running git fetchfollowed by git merge @{u}. If there are unpushed commits in the local repository, the merge part of git pullcreates a merge commit.

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

There is nothing inherently bad about merge commits, but they can be dangerous and should be treated with respect:

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

  • Merge commits are inherently difficult to examine. To understand what a merge is doing, you have to understand the differences to all parents. A conventional diff doesn't convey this multi-dimensional information well. In contrast, a series of normal commits is easy to review.
  • Merge conflict resolution is tricky, and mistakes often go undetected for a long time because merge commits are difficult to review.
  • Merges can quietly supersede the effects of regular commits. The code is no longer the sum of incremental commits, leading to misunderstandings about what actually changed.
  • Merge commits may disrupt some continuous integration schemes (e.g., auto-build only the first-parent path under the assumed convention that second parents point to incomplete works in progress).
  • 合并提交本质上很难检查。要了解合并正在做什么,您必须了解与所有父级的差异。传统的差异不能很好地传达这种多维信息。相比之下,一系列正常的提交很容易。
  • 合并冲突的解决很棘手,而且由于合并提交难以,错误常常在很长一段时间内未被发现。
  • 合并可以悄悄地取代常规提交的影响。代码不再是增量提交的总和,导致对实际更改内容的误解。
  • 合并提交可能会破坏一些持续集成方案(例如,在第二个父项指向正在进行的未完成工作的假定约定下,仅自动构建第一个父级路径)。

Of course there is a time and a place for merges, but understanding when merges should and should not be used can improve the usefulness of your repository.

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

Note that the purpose of Git is to make it easy to share and consume the evolution of a codebase, not to precisely record history exactly as it unfolded. (If you disagree, consider the rebasecommand and why it was created.) The merge commits created by git pulldo not convey useful semantics to others—they just say that someone else happened to push to the repository before you were done with your changes. Why have those merge commits if they aren't meaningful to others and could be dangerous?

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

It is possible to configure git pullto rebase instead of merge, but this also has problems (discussed later). Instead, git pullshould be configured to only do fast-forward merges.

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

Reintroduction of Rebased-out Commits

重新引入重新定位的提交

Suppose someone rebases a branch and force pushes it. This generally shouldn't happen, but it's sometimes necessary (e.g., to remove a 50GiB log file that was accidentally comitted and pushed). The merge done by git pullwill merge the new version of the upstream branch into the old version that still exists in your local repository. If you push the result, pitch forks and torches will start coming your way.

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

Some may argue that the real problem is force updates. Yes, it's generally advisable to avoid force pushes whenever possible, but they are sometimes unavoidable. Developers must be prepared to deal with force updates, because they will happen sometimes. This means not blindly merging in the old commits via an ordinary git pull.

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

Surprise Working Directory Modifications

惊喜工作目录修改

There's no way to predict what the working directory or index will look like until git pullis done. There might be merge conflicts that you have to resolve before you can do anything else, it might introduce a 50GiB log file in your working directory because someone accidentally pushed it, it might rename a directory you are working in, etc.

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

git remote update -p(or git fetch --all -p) allows you to look at other people's commits before you decide to merge or rebase, allowing you to form a plan before taking action.

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

Difficulty Reviewing Other People's Commits

难以其他人的提交

Suppose you are in the middle of making some changes and someone else wants you to review some commits they just pushed. git pull's merge (or rebase) operation modifies the working directory and index, which means your working directory and index must be clean.

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

You could use git stashand then git pull, but what do you do when you're done reviewing? To get back to where you were you have to undo the merge created by git pulland apply the stash.

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

git remote update -p(or git fetch --all -p) doesn't modify the working directory or index, so it's safe to run at any time—even if you have staged and/or unstaged changes. You can pause what you're doing and review someone else's commit without worrying about stashing or finishing up the commit you're working on. git pulldoesn't give you that flexibility.

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

Rebasing onto a Remote Branch

重新定位到远程分支

A common Git usage pattern is to do a git pullto bring in the latest changes followed by a git rebase @{u}to eliminate the merge commit that git pullintroduced. It's common enough that Git has some configuration options to reduce these two steps to a single step by telling git pullto perform a rebase instead of a merge (see the branch.<branch>.rebase, branch.autosetuprebase, and pull.rebaseoptions).

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

Unfortunately, if you have an unpushed merge commit that you want to preserve (e.g., a commit merging a pushed feature branch into master), neither a rebase-pull (git pullwith branch.<branch>.rebaseset to true) nor a merge-pull (the default git pullbehavior) followed by a rebase will work. This is because git rebaseeliminates merges (it linearizes the DAG) without the --preserve-mergesoption. The rebase-pull operation can't be configured to preserve merges, and a merge-pull followed by a git rebase -p @{u}won't eliminate the merge caused by the merge-pull. Update:Git v1.8.5 added git pull --rebase=preserveand git config pull.rebase preserve. These cause git pullto do git rebase --preserve-mergesafter fetching the upstream commits. (Thanks to funkasterfor the heads-up!)

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

Cleaning Up Deleted Branches

清理已删除的分支

git pulldoesn't prune remote tracking branches corresponding to branches that were deleted from the remote repository. For example, if someone deletes branch foofrom the remote repo, you'll still see origin/foo.

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

This leads to users accidentally resurrecting killed branches because they think they're still active.

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

A Better Alternative: Use git upinstead of git pull

更好的选择:使用git up代替git pull

Instead of git pull, I recommend creating and using the following git upalias:

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

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

This alias downloads all of the latest commits from all upstream branches (pruning the dead branches) and tries to fast-forward the local branch to the latest commit on the upstream branch. If successful, then there were no local commits, so there was no risk of merge conflict. The fast-forward will fail if there are local (unpushed) commits, giving you an opportunity to review the upstream commits before taking action.

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

This still modifies your working directory in unpredictable ways, but only if you don't have any local changes. Unlike git pull, git upwill never drop you to a prompt expecting you to fix a merge conflict.

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

Another Option: git pull --ff-only --all -p

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

The following is an alternative to the above git upalias:

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

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

This version of git uphas the same behavior as the previous git upalias, except:

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

  • the error message is a bit more cryptic if your local branch isn't configured with an upstream branch
  • it relies on an undocumented feature (the -pargument, which is passed to fetch) that may change in future versions of Git
  • 如果您的本地分支未配置上游分支,则错误消息会更加神秘
  • 它依赖于一个未记录的特性(-p传递给的参数fetch),该特性可能会在 Git 的未来版本中发生变化

If you are running Git 2.0 or newer

如果您运行的是 Git 2.0 或更新版本

With Git 2.0 and newer you can configure git pullto only do fast-forward merges by default:

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

git config --global pull.ff only

This causes git pullto act like git pull --ff-only, but it still doesn't fetch all upstream commits or clean out old origin/*branches so I still prefer git up.

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

回答by Sérgio Carvalho

My answer, pulled from the discussion that aroseon HackerNews:

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

I feel tempted to just answer the question using the Betteridge Law of Headlines: Why is git pullconsidered harmful? It isn't.

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

  • Nonlinearities aren't intrinsically bad. If they represent the actual history they are ok.
  • Accidental reintroduction of commits rebasedupstream is the result of wrongly rewriting history upstream. You can't rewrite history when history is replicated along several repos.
  • Modifying the working directory is an expected result; of debatable usefulness, namely in the face of the behaviour of hg/monotone/darcs/other_dvcs_predating_git, but again not intrinsically bad.
  • Pausing to review others' work is needed for a merge, and is again an expected behaviour on git pull. If you do not want to merge, you should use git fetch. Again, this is an idiosyncrasy of git in comparison with previous popular dvcs, but it is expected behaviour and not intrinsically bad.
  • Making it hard to rebase against a remote branch is good. Don't rewrite history unless you absolutely need to. I can't for the life of me understand this pursuit of a (fake) linear history
  • Not cleaning up branches is good. Each repo knows what it wants to hold. Git has no notion of master-slave relationships.
  • 非线性本质上并不坏。如果它们代表了实际的历史,它们就可以了。
  • 提交的意外放归重建基础上游的错误改写历史上游结果。当历史沿多个存储库复制时,您无法重写历史记录。
  • 修改工作目录是预期的结果;有争议的有用性,即面对 hg/monotone/darcs/other_dvcs_predating_git 的行为,但本质上也不是坏事。
  • 合并需要暂停以查看其他人的工作,这也是 git pull 上的预期行为。如果您不想合并,则应使用 git fetch。同样,与以前流行的 dvc 相比,这是 git 的一个特性,但它是预期的行为,本质上并不坏。
  • 很难针对远程分支进行 rebase 是件好事。除非绝对需要,否则不要重写历史。我一生都无法理解这种对(虚假)线性历史的追求
  • 不清理树枝是好的。每个回购都知道它想要持有什么。Git 没有主从关系的概念。

回答by Hunt Burdick

It's not considered harmful if you are using Git correctly. I see how it affects you negatively given your use case, but you can avoid problems simply by not modifying shared history.

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

回答by Marc Liyanage

The accepted answer claims

接受的答案声称

The rebase-pull operation can't be configured to preserve merges

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

but as of Git 1.8.5, which postdates that answer, you can do

但是从Git 1.8.5 开始,它发布了答案,你可以做

git pull --rebase=preserve

or

或者

git config --global pull.rebase preserve

or

或者

git config branch.<name>.rebase preserve

The docssay

文件说,

When preserve,also pass --preserve-mergesalong to 'git rebase' so that locally committed merge commits will not be flattened by running 'git pull'.

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

This previous discussion has more detailed information and diagrams: git pull --rebase --preserve-merges. It also explains why git pull --rebase=preserveis not the same as git pull --rebase --preserve-merges, which doesn't do the right thing.

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

This other previous discussion explains what the preserve-merges variant of rebase actually does, and how it is a lot more complex than a regular rebase: What exactly does git's "rebase --preserve-merges" do (and why?)

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

回答by Nathan Redblur

If you go to the old git repository git upthe alias they suggest is different. https://github.com/aanand/git-up

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

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

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

This works perfect for me.

这对我来说很完美。