在什么情况下,“拉扯”会有害? [英] In what cases could `git pull` be harmful?
问题描述
我有一位同事声称 git pull
是有害的,只要有人使用它就会感到不安。
git pull
命令似乎是更新本地存储库的标准方式。是否使用 git pull
创建问题?它创造了什么问题?有没有更好的方式来更新一个git仓库?
总结
默认情况下, git pull
会创建合并提交,这会增加代码历史记录的噪音和复杂性。另外, pull
可以让您不用考虑您的更改如何受到传入更改的影响。
只要它只执行快进合并, git pull
命令是安全的。如果 git pull
被配置为仅执行快进合并,并且无法进行快进合并,则Git将退出且出现错误。这将使您有机会研究传入的提交,考虑它们可能会如何影响您的本地提交,并决定最佳的操作过程(合并,重定位,重置等)。
使用Git 2.0及更新版本,您可以运行:
git config --global pull.ff only
可将默认行为更改为仅快进。 Git版本在1.6.6和1.9.x之间,你必须养成打字的习惯:
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版本,
- 它可以获取所有上游分支(而不仅仅是您当前正在使用的分支),以及
- 清除旧的
h3>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
配置为只进行快速合并。 / h3>
假设有人重组了一个分支并强制推送它。通常不会发生这种情况,但有时也是必需的(例如,删除意外排列和推送的50GiB日志文件)。由
有些人可能会争辩说,真正的问题是强制更新。是的,通常建议尽可能避免推力,但它们有时是不可避免的。开发人员必须准备好处理强制更新,因为它们有时会发生。这意味着通过一个普通的git pull
完成的合并将会将新版本的上游分支合并到仍然存在于本地存储库中的旧版本中。如果你推动结果,高音叉和火把将开始以你的方式。git pull
。
惊喜工作目录修改
在完成
git pull
之前,没有办法预测工作目录或索引的外观。有可能会有合并冲突,你必须解决之前,你可以做任何事情,它可能会在工作目录中引入50GiB日志文件,因为有人不小心推动它,它可能会重命名你正在工作的目录等。
$ bgit远程更新-p
(或git fetch --all -p
)允许您在决定合并或重新绑定之前查看其他人的提交,从而允许您在采取行动之前制定计划。
难度审查其他人的提交
假设您正在进行一些更改,而其他人希望您查看他们刚刚推送的某些提交。
git pull
的merge(或rebase)操作会修改工作目录和索引,这意味着您的工作目录和索引必须是干净的。
您可以使用
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
来执行rebase而不是合并(参见<$ c $)来将这两个步骤简化为单个步骤c> branch。< branch> .rebase ,branch.autosetuprebase
和pull.rebase $ c $不幸的是,如果你有一个你想保留的未压缩的合并提交(例如,一个提交合并推送的特性分支到
设为master
),既没有分叉拉(git pull
分支。.rebase true
),也不是合并拉(默认git pull
行为)将工作。这是因为git rebase
不使用- preserve-merges
选项就消除了合并(它使DAG线性化)。无法将rebase-pull操作配置为保留合并,并且紧跟着git rebase -p @ {u}
的合并拉不会消除引起的合并通过合并拉。 更新: Git v1.8.5添加了git pull --rebase = preserve
和git config pull.rebase preserve
。这些会导致git pull
在获取上游提交之后执行git rebase --preserve-merges
。 (感谢 funkaster 的提醒!)
清理已删除的分支
git pull
不修剪远程跟踪分支,对应于从远程删除的分支库。例如,如果有人从远程仓库删除分支foo
,您仍然会看到origin / foo
。 p>
这会导致用户意外复活死亡分支,因为他们认为他们仍然活跃。
更好的选择:使用
git up
而不是git pull
code> git pull ,我建议创建并使用下面的
git up
别名:git config --global alias.up'!git remote update -p; git merge --ff-only @ {u}'
这个别名下载所有最新的提交所有上游分支(修剪死亡分支)并尝试将本地分支快速转发到上游分支上的最新提交。如果成功,那么没有本地提交,所以不存在合并冲突的风险。快速前进将失败,如果有本地(unpushed)提交,让您有机会在采取行动之前检查上游提交。
这仍然会修改您的工作目录不可预知的方式,但前提是您没有任何本地更改。不同于
git pull
,git up
永远不会让您提示您期望修复合并冲突。
另一个选项:
git pull - 只限 - 全部-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
来仅执行defa的快速合并ult:
git config --global pull.ff only
这会导致
git pull 的行为像
git pull --ff-only
,但它仍然不会获取所有上游提交或清除旧的origin / *
分支,所以我仍然更喜欢git up code>。
I have a colleague who claims that
git pull
is harmful, and gets upset whenever someone uses it.The
git pull
command seems to be the canonical way to update your local repository. Does usinggit pull
create problems? What problems does it create? Is there a better way to update a git repository?解决方案Summary
By default,
git pull
creates merge commits which add noise and complexity to the code history. In addition,pull
makes it easy to not think about how your changes might be affected by incoming changes.The
git pull
command is safe so long as it only performs fast-forward merges. Ifgit pull
is configured to only do fast-forward merges and 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.).With Git 2.0 and newer, you can run:
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:
git pull --ff-only
However, with all versions of Git, I recommend configuring a
git up
alias like this:git config --global alias.up '!git remote update -p; git merge --ff-only @{u}'
and using
git up
instead ofgit pull
. I prefer this alias overgit pull --ff-only
because:- 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.
Problems with
git pull
git pull
isn't bad if it is used properly. Several recent changes to Git have made it easier to usegit pull
properly, but unfortunately the default behavior of a plaingit pull
has several problems:- 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
These problems are described in greater detail below.
Nonlinear History
By default, the
git pull
command is equivalent to runninggit fetch
followed bygit merge @{u}
. If there are unpushed commits in the local repository, the merge part ofgit pull
creates a merge commit.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
rebase
command and why it was created.) The merge commits created bygit 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. 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). Instead,git pull
should be configured to only do fast-forward merges.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 pull
will 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.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
.Surprise Working Directory Modifications
There's no way to predict what the working directory or index will look like until
git pull
is 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 remote update -p
(orgit 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.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.You could use
git stash
and thengit 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 bygit pull
and apply the stash.git remote update -p
(orgit 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 pull
doesn't give you that flexibility.Rebasing onto a Remote Branch
A common Git usage pattern is to do a
git pull
to bring in the latest changes followed by agit rebase @{u}
to eliminate the merge commit thatgit pull
introduced. It's common enough that Git has some configuration options to reduce these two steps to a single step by tellinggit pull
to perform a rebase instead of a merge (see thebranch.<branch>.rebase
,branch.autosetuprebase
, andpull.rebase
options).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 pull
withbranch.<branch>.rebase
set totrue
) nor a merge-pull (the defaultgit pull
behavior) followed by a rebase will work. This is becausegit rebase
eliminates merges (it linearizes the DAG) without the--preserve-merges
option. The rebase-pull operation can't be configured to preserve merges, and a merge-pull followed by agit rebase -p @{u}
won't eliminate the merge caused by the merge-pull. Update: Git v1.8.5 addedgit pull --rebase=preserve
andgit config pull.rebase preserve
. These causegit pull
to dogit rebase --preserve-merges
after fetching the upstream commits. (Thanks to funkaster for the heads-up!)Cleaning Up Deleted Branches
git pull
doesn't prune remote tracking branches corresponding to branches that were deleted from the remote repository. For example, if someone deletes branchfoo
from the remote repo, you'll still seeorigin/foo
.This leads to users accidentally resurrecting killed branches because they think they're still active.
A Better Alternative: Use
git up
instead ofgit pull
Instead of
git pull
, I recommend creating and using the followinggit up
alias: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.Another Option:
git pull --ff-only --all -p
The following is an alternative to the above
git up
alias:git config --global alias.up 'pull --ff-only --all -p'
This version of
git up
has the same behavior as the previousgit up
alias, except:- 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 tofetch
) that may change in future versions of Git
If you are running Git 2.0 or newer
With Git 2.0 and newer you can configure
git pull
to only do fast-forward merges by default:git config --global pull.ff only
This causes
git pull
to act likegit pull --ff-only
, but it still doesn't fetch all upstream commits or clean out oldorigin/*
branches so I still prefergit up
.这篇关于在什么情况下,“拉扯”会有害?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!
- 它可以获取所有上游分支(而不仅仅是您当前正在使用的分支),以及