我有一个同事,他声称git pull是有害的,每当有人使用它时,他就会感到不安。
git pull命令似乎是更新本地存储库的规范方法。使用git pull会产生问题吗?它会产生什么问题?有没有更好的方法来更新git存储库?
我有一个同事,他声称git pull是有害的,每当有人使用它时,他就会感到不安。
git pull命令似乎是更新本地存储库的规范方法。使用git pull会产生问题吗?它会产生什么问题?有没有更好的方法来更新git存储库?
当前回答
如果正确地使用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, 它获取所有上游分支(不仅仅是您当前正在处理的分支),并且 它清除上游不再存在的旧的原点/*分支。
git pull的问题
如果使用得当,Git pull还不错。最近对Git的一些更改使得正确使用Git pull变得更容易,但不幸的是,普通Git pull的默认行为有几个问题:
它在历史中引入了不必要的非线性 它很容易意外地重新引入有意在上游重新基于的提交 它以不可预知的方式修改您的工作目录 暂停你正在做的事情来审查别人的工作是讨厌的git拉 这使得正确地重新基于远程分支变得很困难 它不会清理在远程回购中删除的分支
下面将更详细地描述这些问题。
非线性的历史
默认情况下,git pull命令相当于运行git fetch,然后运行git merge @{u}。如果本地存储库中有未推送的提交,git pull的合并部分将创建一个合并提交。
合并提交本身没有什么不好的,但它们可能是危险的,应该尊重地对待:
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).
当然,合并是有时间和地点的,但是理解什么时候应该使用合并,什么时候不应该使用合并可以提高存储库的有用性。
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 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. Why have those merge commits if they aren't meaningful to others and could be dangerous?
可以将git pull配置为rebase而不是merge,但这也有问题(稍后讨论)。相反,git pull应该配置为只做快进合并。
重新引入改基提交
假设有人改变了树枝的底座,用力推动它。这通常不应该发生,但有时是必要的(例如,删除意外提交和推送的50GiB日志文件)。git pull完成的合并将把上游分支的新版本合并到本地存储库中仍然存在的旧版本中。如果你推动结果,推销叉和火炬就会开始向你走来。
有些人可能认为真正的问题是军力更新。是的,通常情况下,尽可能避免武力推动是明智的,但有时这是不可避免的。开发人员必须准备好处理强制更新,因为它们有时会发生。这意味着不要盲目地通过普通的git pull来合并旧的提交。
意外修改工作目录
在git pull完成之前,没有办法预测工作目录或索引将会是什么样子。可能会有合并冲突,您必须在执行其他操作之前解决,它可能会在您的工作目录中引入一个50GiB日志文件,因为有人意外地推送了它,它可能会重命名您正在工作的目录,等等。
Git remote update -p(或Git fetch——all -p)允许你在决定合并或重基之前查看其他人的提交,允许你在采取行动之前制定计划。
难以审查其他人的提交
假设您正在进行一些更改,而其他人希望您检查他们刚刚提交的一些提交。Git pull的merge(或rebase)操作修改了工作目录和索引,这意味着你的工作目录和索引必须是干净的。
你可以使用git stash和git pull,但是当你完成审查时你要做什么?要回到你在哪里,你必须撤消合并创建的git拉和应用stash。
Git remote update -p(或Git fetch——all -p)不会修改工作目录或索引,所以在任何时候运行它都是安全的——即使你有阶段性和/或非阶段性的变化。您可以暂停正在做的事情并查看其他人的提交,而不必担心存储或完成您正在处理的提交。Git拉不给你这样的灵活性。
重新基于一个远程分支
一个常见的Git使用模式是执行Git pull来引入最新的更改,然后执行Git rebase @{u}来消除Git pull引入的合并提交。Git有一些配置选项,通过告诉Git pull执行重基而不是合并,将这两个步骤简化为一个步骤,这是很常见的(参见branch.<branch>。变基,分支。Autosetuprebase,然后拉。变基选项)。
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 with branch.<branch>.rebase set to true) nor a merge-pull (the default git pull behavior) followed by a rebase will work. This is because git 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 a git rebase -p @{u} won't eliminate the merge caused by the merge-pull. Update: Git v1.8.5 added git pull --rebase=preserve and git config pull.rebase preserve. These cause git pull to do git rebase --preserve-merges after fetching the upstream commits. (Thanks to funkaster for the heads-up!)
清理已删除的分支
Git拉不修剪远程跟踪分支对应于从远程存储库删除的分支。例如,如果有人从远程repo中删除分支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。
如果正确地使用Git,那么它不会被认为是有害的。鉴于您的用例,我知道它是如何对您产生负面影响的,但是您可以通过不修改共享历史来避免问题。
已接受的答案声明
重构基拉操作不能配置为保存合并
但是从Git 1.8.5开始,你就可以这样做了
git pull --rebase=preserve
or
git config --global pull.rebase preserve
or
git config branch.<name>.rebase preserve
医生说
当保存时,还要传递——preserve-merge到'git rebase',这样本地提交的合并提交就不会通过运行'git pull'而被压平。
前面的讨论有更详细的信息和图表:git pull -rebase - save -merges。这也解释了为什么git pull -rebase=preserve和git pull -rebase -preserve-merges不一样,它做的事情不对。
前面的另一个讨论解释了rebase的save -merges变体实际上做了什么,以及它如何比常规的rebase复杂得多:git的“rebase - save -merges”究竟做了什么(为什么?)
我的回答摘自HackerNews上的讨论:
我很想用贝特里奇标题定律来回答这个问题:为什么git pull被认为是有害的?它不是。
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. 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.
如果你去旧的git仓库git的别名,他们建议是不同的。 https://github.com/aanand/git-up
Git配置——全局别名。Up 'pull -rebase -autostash'
这对我来说非常合适。