git - 在什么情况下 `git pull` 可能有害?

标签 git git-pull

我有一个同事声称 git pull是有害的,并且每当有人使用它时都会感到不安。
git pull命令似乎是更新本地存储库的规范方式。是否使用 git pull制造问题?它会产生什么问题?有没有更好的方法来更新 git 存储库?

最佳答案

概括

默认情况下,git pull创建 merge 提交,这会增加代码历史的噪音和复杂性。另外,pull让您很容易不去考虑您的更改可能会如何受到传入更改的影响。
git pull命令是安全的,只要它只执行快进 merge 。如 git pull配置为仅进行快进 merge ,当无法进行快进 merge 时,Git 将退出并显示错误。这将使您有机会研究传入的提交,思考它们可能如何影响您的本地提交,并决定最佳的行动方案( merge 、 rebase 、重置等)。

使用 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 暂停您正在做的事情以查看其他人的工作很烦人
  • 这使得很难正确地 rebase 到远程分支
  • 它不会清理在远程仓库中删除的分支

  • 下面更详细地描述这些问题。

    非线性历史

    默认情况下,git pull命令相当于运行 git fetch其次是 git merge @{u} .如果本地仓库有未推送的提交,git pull的 merge 部分创建 merge 提交。

    merge 提交本身没有什么坏处,但它们可能是危险的,应该受到尊重:
  • merge 提交本质上很难检查。要了解 merge 正在做什么,您必须了解与所有父级的差异。传统的差异不能很好地传达这种多维信息。相比之下,一系列正常的提交很容易审查。
  • merge 冲突的解决很棘手,而且由于 merge 提交难以审查,错误常常在很长一段时间内未被发现。
  • merge 可以悄悄地取代常规提交的影响。代码不再是增量提交的总和,导致对实际更改内容的误解。
  • merge 提交可能会破坏一些持续集成方案(例如,在第二个父级指向正在进行的未完成工作的假定约定下,仅自动构建第一个父级路径)。

  • 当然, merge 是有时间和地点的,但是了解何时应该和不应该使用 merge 可以提高存储库的实用性。

    请注意,Git 的目的是使共享和使用代码库的演变变得容易,而不是在历史展开时准确地记录历史。 (如果您不同意,请考虑 rebase 命令及其创建原因。) git pull 创建的 merge 提交不要向其他人传达有用的语义——他们只是说在你完成更改之前有人碰巧推送到存储库。如果这些 merge 提交对其他人没有意义并且可能很危险,为什么要进行这些 merge 提交?

    可以配置 git pull rebase 而不是 merge ,但这也有问题(稍后讨论)。相反,git pull应配置为仅进行快进 merge 。

    重新引入重新定位的提交

    假设有人重新设置分支并强制推送它。这通常不应该发生,但有时是必要的(例如,删除意外提交和推送的 50GiB 日志文件)。 merge 由 git pull 完成将上游分支的新版本 merge 到本地存储库中仍然存在的旧版本中。如果你 push 结果, fork 和手电筒将开始向你走来。

    有些人可能会争辩说,真正的问题是强制更新。是的,通常建议尽可能避免强制 push ,但有时它们是不可避免的。开发人员必须准备好处理强制更新,因为它们有时会发生。这意味着不要通过普通的 git pull 盲目地 merge 旧的提交。 .

    惊喜工作目录修改

    git pull 之前,无法预测工作目录或索引的样子。已经完成了。在执行其他任何操作之前,您可能必须解决 merge 冲突,它可能会在您的工作目录中引入一个 50GiB 的日志文件,因为有人不小心推送了它,它可能会重命名您正在工作的目录等。
    git remote update -p (或 git fetch --all -p )允许您在决定 merge 或 rebase 之前查看其他人的提交,从而允许您在采取行动之前制定计划。

    难以审查其他人的提交

    假设您正在进行一些更改,而其他人希望您查看他们刚刚推送的一些提交。 git pull的 merge (或 rebase )操作会修改工作目录和索引,这意味着您的工作目录和索引必须是干净的。

    您可以使用 git stash然后 git pull ,但是当你完成审查后你会做什么?要回到原来的位置,您必须撤消 git pull 创建的 merge 。并应用 stash 。
    git remote update -p (或 git fetch --all -p )不会修改工作目录或索引,因此随时运行都是安全的——即使您已经暂存和/或未暂存的更改。您可以暂停正在执行的操作并查看其他人的提交,而无需担心存储或完成您正在处理的提交。 git pull没有给你那种灵活性。

    重新定位到远程分支

    一个常见的 Git 使用模式是做一个 git pull引入最新的变化,然后是 git rebase @{u}消除 git pull 的 merge 提交介绍。 Git 有一些配置选项可以通过告诉 git pull 将这两个步骤减少为一个步骤,这很常见。执行 rebase 而不是 merge (参见 branch.<branch>.rebasebranch.autosetuprebasepull.rebase 选项)。

    不幸的是,如果您想要保留未推送的 merge 提交(例如,将推送的功能分支 merge 到 master 的提交),则既不是 rebase pull ( git pullbranch.<branch>.rebase 设置为 true )也不是 merge pull (默认 git pull 行为)后跟 rebase 将起作用。这是因为 git rebase在没有 --preserve-merges 的情况下消除 merge (它使 DAG 线性化)选项。不能将 rebase-pull 操作配置为保留 merge ,并且 merge pull 后跟 git rebase -p @{u}不会消除由 merge pull 引起的 merge 。 更新:添加 Git v1.8.5 git pull --rebase=preservegit 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}'
    

    此别名从所有上游分支下载所有最新提交(修剪死分支)并尝试将本地分支快进到上游分支上的最新提交。如果成功,则没有本地提交,因此不存在 merge 冲突的风险。如果有本地(未推送)提交,快进将失败,让您有机会在采取行动之前查看上游提交。

    这仍然会以不可预测的方式修改您的工作目录,但前提是您没有任何本地更改。不像 git pull , git up永远不会让您看到提示,希望您修复 merge 冲突。

    另一种选择: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
  • 的 future 版本中可能会改变。

    如果您运行的是 Git 2.0 或更新版本

    使用 Git 2.0 及更新版本,您可以配置 git pull默认情况下只进行快进 merge :
    git config --global pull.ff only
    

    这会导致 git pull表现得像 git pull --ff-only ,但它仍然没有获取所有上游提交或清除旧的 origin/*分支,所以我还是更喜欢 git up .

    关于git - 在什么情况下 `git pull` 可能有害?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15316601/

    相关文章:

    git - VSCode : How to remove git decorator information from file tabs?

    azure - 无法读取 'Repository URL' : terminal prompts disabled 的密码

    merge - 如何接受 Remote 上的所有更改?

    Git pull 中止

    git - 如何从原始存储库更新 github 分支?

    git - 如何 pull 下远程分支?

    git - Jenkins 设置 github 提交状态不起作用

    git rebase origin/develop 与 git rebase develop

    将未完成的本地分支推送到远程以进行备份的 Git 工作流思路

    java - 项目构建错误: Non-resolvable parent POM cannot find repository