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

本文翻译自:In what cases could `git pull` be harmful?

I have a colleague who claims that git pull is harmful, and gets upset whenever someone uses it. 我有一个同事声称git pull是有害的,每当有人使用它时都会感到沮丧。

The git pull command seems to be the canonical way to update your local repository. git pull命令似乎是更新本地存储库的规范方法。 Does using git pull create problems? 使用git pull是否会产生问题? What problems does it create? 它会产生什么问题? Is there a better way to update a git repository? 有没有更好的方法来更新git存储库?




Summary 摘要

By default, git pull creates merge commits which add noise and complexity to the code history. 默认情况下, git pull创建合并提交,这会增加代码历史记录的噪音和复杂性。 In addition, pull makes it easy to not think about how your changes might be affected by incoming changes. 另外, pull使您很容易就不用考虑传入更改将如何影响您的更改。

The git pull command is safe so long as it only performs fast-forward merges. git pull命令是安全的,只要它仅执行快进合并即可。 If git pull is configured to only do fast-forward merges and when a fast-forward merge isn't possible, then Git will exit with an error. 如果将git pull配置为仅执行快速合并,而无法进行快速合并,则Git将退出并显示错误。 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.). 这将使您有机会研究传入的提交,考虑它们可能如何影响您的本地提交,并确定最佳的操作过程(合并,变基,重设等)。

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 up alias like this: 但是,对于所有版本的Git,我建议像这样配置git up别名:

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

and using git up instead of git pull . 并使用git up代替git pull I prefer this alias over git pull --ff-only because: git pull --ff-only我更喜欢使用此别名,因为:

  • it works with all (non-ancient) versions of Git, 它适用于所有(非古代)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. 它会清除上游不再存在的旧的origin/*分支。

Problems with git pull git pull问题

git pull isn't bad if it is used properly. git pull如果使用得当,还不错。 Several recent changes to Git have made it easier to use git pull properly, but unfortunately the default behavior of a plain git pull has several problems: 最近对Git进行的几处更改使正确使用git pull更容易,但是不幸的是,普通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 使用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 它不会清理在远程仓库中删除的分支

These problems are described in greater detail below. 这些问题将在下面更详细地描述。

Nonlinear History 非线性历史

By default, the git pull command is equivalent to running git fetch followed by git merge @{u} . 默认情况下, git pull命令等效于先运行git fetch再运行git merge @{u} If there are unpushed commits in the local repository, the merge part of git pull creates a merge commit. 如果本地存储库中有未推送的提交,则git pull的合并部分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 (eg, 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. 请注意,Git的目的是使共享和使用代码库的演化变得容易,而不是准确地记录其展开的历史记录。 (If you disagree, consider the rebase command and why it was created.) The merge commits created by git pull do 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. (如果您不同意,请考虑一下rebase命令及其创建原因。) git pull创建的合并提交不会向其他人传达有用的语义-他们只是说在您完成更改之前,其他人碰巧将其推送到存储库。 Why have those merge commits if they aren't meaningful to others and could be dangerous? 如果这些合并提交对其他人没有意义并且可能很危险,为什么还要合并?

It is possible to configure git pull to rebase instead of merge, but this also has problems (discussed later). 可以将git pull配置为重新设置基准,而不是合并,但这也有问题(稍后讨论)。 Instead, git pull should be configured to only do fast-forward merges. 相反,应将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 (eg, to remove a 50GiB log file that was accidentally comitted and pushed). 通常不应发生这种情况,但有时是有必要的(例如,删除意外提交和推送的50GiB日志文件)。 The merge done by git pull will merge the new version of the upstream branch into the old version that still exists in your local repository. git pull完成的合并会将上游分支的新版本合并为本地存储库中仍然存在的旧版本。 If you push the result, pitch forks and torches will start coming your way. 如果您推动结果,则音叉和火把将开始向您行驶。

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 pull is done. 在完成git pull之前,无法预测工作目录或索引的外观。 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. 在执行其他任何操作之前,可能需要解决合并冲突,它可能会在工作目录中引入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的merge(或rebase)操作会修改工作目录和索引,这意味着您的工作目录和索引必须是干净的。

You could use git stash and then git pull , but what do you do when you're done reviewing? 您可以先使用git stash然后使用git pull ,但是完成检查后会做什么? To get back to where you were you have to undo the merge created by git pull and apply the stash. 要回到原来的位置,您必须撤消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. git remote update -p (或git fetch --all -p )不会修改工作目录或索引,因此可以安全地随时运行-即使您已暂存和/或暂存更改。 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 pull doesn't give you that flexibility. git pull并没有给您带来灵活性。

Rebasing onto a Remote Branch 迁移到远程分支

A common Git usage pattern is to do a git pull to bring in the latest changes followed by a git rebase @{u} to eliminate the merge commit that git pull introduced. 常见的Git使用模式是执行git pull引入最新更改,然后执行git rebase @{u}以消除git pull引入的合并提交。 It's common enough that Git has some configuration options to reduce these two steps to a single step by telling git pull to perform a rebase instead of a merge (see the branch.<branch>.rebase , branch.autosetuprebase , and pull.rebase options). Git拥有一些配置选项,可以通过告诉git pull执行rebase而不是合并来将这两个步骤简化为一个步骤,这很常见(请参阅branch.<branch>.rebasebranch.autosetuprebasepull.rebase选项)。

Unfortunately, if you have an unpushed merge commit that you want to preserve (eg, a commit merging a pushed feature branch into master ), neither a rebase-pull ( git pull with branch.<branch>.rebase set to true ) nor a merge-pull (the default git pull behavior) followed by a rebase will work. 不幸的是,如果您要保留未推送的合并提交(例如,将推送的功能分支合并到master的提交),则既不能进行rebase-pull( git pull with branch.<branch>.rebase设置为true ),也不能进行合并-拉(默认的git pull行为),然后进行重新设置将起作用。 This is because git rebase eliminates merges (it linearizes the DAG) without the --preserve-merges option. 这是因为git rebase在没有--preserve-merges选项的情况下消除了合并(线性化了DAG)。 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. 无法将rebase-pull操作配置为保留合并,并且在合并后加上git rebase -p @{u}的合并拉不会消除由merge-pull引起的合并。 Update: Git v1.8.5 added git pull --rebase=preserve and git config pull.rebase preserve . 更新: Git v1.8.5添加了git pull --rebase=preservegit config pull.rebase preserve These cause git pull to do git rebase --preserve-merges after fetching the upstream commits. 这些导致git pull在获取上游提交后执行git rebase --preserve-merges (Thanks to funkaster for the heads-up!) (感谢funkaster为单挑!)

Cleaning Up Deleted Branches 清理已删除的分支

git pull doesn't prune remote tracking branches corresponding to branches that were deleted from the remote repository. git pull不会修剪与从远程存储库中删除的分支相对应的远程跟踪分支。 For example, if someone deletes branch foo from the remote repo, you'll still see origin/foo . 例如,如果有人从远程仓库中删除了分支foo ,您仍然会看到origin/foo

This leads to users accidentally resurrecting killed branches because they think they're still active. 这导致用户意外恢复被杀死的分支,因为他们认为它们仍然处于活动状态。

A Better Alternative: Use git up instead of git pull 更好的选择:使用git up代替git pull

Instead of git pull , I recommend creating and using the following git up alias: 我建议创建并使用以下git up别名,而不是git pull

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 up will never drop you to a prompt expecting you to fix a merge conflict. git pull不同, git up绝不会将您引导到期望您解决合并冲突的提示。

Another Option: git pull --ff-only --all -p 另一种选择: git pull --ff-only --all -p

The following is an alternative to the above git up alias: 以下是上述git up别名的替代方法:

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

This version of git up has the same behavior as the previous git up alias, 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 -p argument, 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 pull to only do fast-forward merges by default: 使用Git 2.0和更高版本,您可以将git pull配置为默认情况下仅执行快进合并:

git config --global pull.ff only

This causes git pull to 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 pullgit pull --ff-only ,但是它仍然不能获取所有上游提交或清除旧的origin/*分支,因此我仍然更喜欢git up


My answer, pulled from the discussion that arose on HackerNews: 我的答案,从讨论中拉起来的HackerNews:

I feel tempted to just answer the question using the Betteridge Law of Headlines: Why is git pull considered harmful? 我很想用贝特里奇标题新闻法回答这个问题:为什么git pull被认为是有害的? It isn't. 不是。

  • Nonlinearities aren't intrinsically bad. 非线性本质上不是坏的。 If they represent the actual history they are ok. 如果它们代表实际的历史,则可以。
  • Accidental reintroduction of commits rebased upstream 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. 值得商useful的有用性,即面对hg / monotone / darcs / other_dvcs_predating_git的行为,但同样不是本质上的坏。
  • Pausing to review others' work is needed for a merge, and is again an expected behaviour on git pull. 合并需要暂停查看他人的工作,这也是git pull上的预期行为。 If you do not want to merge, you should use git fetch. 如果您不想合并,则应使用git fetch。 Again, this is an idiosyncrasy of git in comparison with previous popular dvcs, but it is expected behaviour and not intrinsically bad. 同样,与以前流行的dvc相比,这是git的特质,但它是预期的行为,本质上不是坏的。
  • 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. Git没有主从关系的概念。


It's not considered harmful if you are using Git correctly. 如果正确使用Git,则认为这不是有害的。 I see how it affects you negatively given your use case, but you can avoid problems simply by not modifying shared history. 在给定用例的情况下,我看到了它对您的负面影响,但是您可以通过不修改共享历史记录来避免问题。


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 docs say 医生

When preserve, also pass --preserve-merges along 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 . 前面的讨论有更多详细的信息和图表: git pull --rebase --preserve-merges It also explains why git pull --rebase=preserve is not the same as git pull --rebase --preserve-merges , which doesn't do the right thing. 这也解释了为什么git pull --rebase=preservegit 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的save-merges变体实际上做了什么,以及它比常规rebase复杂得多: git的“ rebase --preserve-merges”究竟做了什么(为什么?)


If you go to the old git repository git up the alias they suggest is different. 如果您转到旧的git仓库git up别名,他们建议是不同的。 https://github.com/aanand/git-up https://github.com/aanand/git-up

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

This works perfect for me. 这对我来说很完美。

  • 1
  • 0
  • 0
  • 扫一扫,分享海报

参与评论 您还未登录,请先 登录 后发表或查看评论
©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页
钱包余额 0