摘要
默认情况下,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
为变基而不是合并,但这也有问题(稍后讨论)。而是git pull
应配置为仅进行快速合并。
重新引入重新定基的提交
假设有人重新设置了分支的基础并用力推动了它。通常不应发生这种情况,但有时是有必要的(例如,删除意外提交和推送的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>.rebase
,branch.autosetuprebase
和pull.rebase
选项)。
不幸的是,如果你有一个unpushed合并提交要保存(如提交合并推送的特性分支成master
),既不是底垫式(git pull
带branch.<branch>.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
。
这导致用户意外恢复被杀死的分支,因为他们认为它们仍然处于活动状态。
更好的选择:git up
代替git pull
git pull
我建议创建并使用以下git up
别名代替:
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
。