我有一位同事声称这git pull
是有害的,并且每当有人使用它时都会感到不安。
该git pull
命令似乎是更新本地存储库的规范方式。使用会git pull
产生问题吗?它会产生什么问题?有没有更好的方法来更新 git 存储库?
默认情况下,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
因为:
origin/*
上游不再存在的旧分支。git pull
git pull
如果使用得当,还不错。最近对 Git 的一些更改使其更易于git pull
正确使用,但不幸的是,普通的默认行为git pull
有几个问题:
git pull
下面更详细地描述这些问题。
默认情况下,该git pull
命令相当于运行git fetch
后跟git merge @{u}
. 如果本地存储库中有未推送的提交,则合并部分git pull
会创建合并提交。
合并提交本身并没有什么坏处,但它们可能很危险,应该受到尊重:
当然,合并有时间和地点,但是了解何时应该使用和不应该使用合并可以提高存储库的实用性。
请注意,Git 的目的是让共享和使用代码库的演变变得容易,而不是准确地记录历史的展开。(如果您不同意,请考虑该rebase
命令及其创建原因。) by 创建的合并提交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
and then git pull
,但是当你完成复习时你会做什么?要回到原来的位置,您必须撤消由创建的合并git pull
并应用存储。
git remote update -p
(or git fetch --all -p
) 不会修改工作目录或索引,因此可以随时安全地运行——即使您已暂存和/或未暂存更改。您可以暂停正在执行的操作并查看其他人的提交,而无需担心存储或完成您正在处理的提交。git pull
没有给你那种灵活性。
一个常见的 Git 使用模式是执行 agit pull
以引入最新的更改,然后执行 agit rebase @{u}
以消除git pull
引入的合并提交。git pull
Git 有一些配置选项可以通过告诉执行变基而不是合并来将这两个步骤减少到一个步骤,这很常见(请参阅branch.<branch>.rebase
、branch.autosetuprebase
和pull.rebase
选项)。
不幸的是,如果您想要保留一个未推送的合并提交(例如,将推送的功能分支合并到的提交master
),则既不是 rebase-pull(git pull
设置branch.<branch>.rebase
为true
)也不是 merge-pull(默认git pull
行为),然后是变基将起作用。这是因为在没有选项git rebase
的情况下消除了合并(它使 DAG 线性化) 。--preserve-merges
rebase-pull 操作不能配置为保留合并,并且后跟 a 的合并拉取git rebase -p @{u}
不会消除由合并拉取引起的合并。 更新: 添加了 Git v1.8.5git 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 pull
为默认仅进行快进合并:
git config --global pull.ff only
这会导致git pull
行为类似git pull --ff-only
,但它仍然不会获取所有上游提交或清除旧origin/*
分支,所以我仍然更喜欢git up
。
我的回答来自 HackerNews 上的讨论:
我很想用 Betteridge Law of Headlines 来回答这个问题:为什么被git pull
认为是有害的?它不是。
如果你正确使用 Git,它不会被认为是有害的。鉴于您的用例,我看到它如何对您产生负面影响,但是您可以通过不修改共享历史来避免问题。
接受的答案声称
无法将 rebase-pull 操作配置为保留合并
但从Git 1.8.5开始,你可以这样做
git pull --rebase=preserve
或者
git config --global pull.rebase preserve
或者
git config branch.<name>.rebase preserve
文档说_
当
preserve,
也传递--preserve-merges
给'git rebase',以便本地提交的合并提交不会通过运行'git pull'而变平。
之前的讨论有更详细的信息和图表:git pull --rebase --preserve-merges。它还解释了为什么git pull --rebase=preserve
不一样git pull --rebase --preserve-merges
,这不正确。
之前的另一个讨论解释了rebase的preserve-merges变体实际上做了什么,以及它如何比常规rebase复杂得多:git的“rebase --preserve-merges”到底做了什么(为什么?)
如果你去旧的 git 存储库git up他们建议的别名是不同的。 https://github.com/aanand/git-up
git config --global alias.up 'pull --rebase --autostash'
这对我来说很完美。