代码之家  ›  专栏  ›  技术社区  ›  Richard Hansen Bi Rico

在什么情况下“git pull”会有害?

  •  434
  • Richard Hansen Bi Rico  · 技术社区  · 11 年前

    我有一位同事声称 git pull 是有害的,每当有人使用它时就会感到不安。

    这个 数字拉力 命令似乎是更新本地存储库的规范方式。是否使用 数字拉力 制造问题?它会产生什么问题?有没有更好的方法来更新git存储库?

    5 回复  |  直到 10 年前
        1
  •  573
  •   mloskot    6 年前

    总结

    默认情况下, git pull 创建合并提交,这会给代码历史增加噪音和复杂性。此外 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 pull --ff-only 因为:

    • 它适用于Git的所有(非古代)版本,
    • 它获取所有上游分支(而不仅仅是您当前正在处理的分支),并且
    • 它能清除旧的 origin/* 上游不再存在的分支。

    存在的问题 数字拉力

    数字拉力 如果使用得当,还不错。Git最近的几项更改使其更易于使用 数字拉力 正确,但不幸的是 数字拉力 存在以下几个问题:

    • 它在历史中引入了不必要的非线性
    • 它很容易意外地重新引入有意在上游重新建立基础的提交
    • 它以不可预测的方式修改您的工作目录
    • 暂停你正在做的事情来回顾别人的工作是令人讨厌的 数字拉力
    • 这使得很难正确地重新建立到远程分支上
    • 它不会清理在远程回购中删除的分支

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

    非线性历史

    默认情况下 数字拉力 命令相当于运行 git fetch 然后 git merge @{u} 。如果本地存储库中有未推送的提交,则的合并部分 数字拉力 创建合并提交。

    合并提交本身并没有什么不好的地方,但它们可能很危险,应该受到尊重:

    • 合并提交本身就很难检查。为了理解合并的作用,你必须理解所有父母之间的差异。传统的差异不能很好地传达这种多维信息。相比之下,一系列正常的提交很容易审查。
    • 合并冲突的解决很棘手,而且由于合并提交很难审查,错误往往会在很长一段时间内未被发现。
    • 合并可以悄悄地取代常规提交的效果。代码不再是增量提交的总和,从而导致对实际更改内容的误解。
    • 合并提交可能会破坏一些连续集成方案(例如,在假设第二个父路径指向正在进行的未完成工作的约定下,仅自动构建第一个父路径)。

    当然,合并是有时间和地点的,但了解什么时候应该使用合并,什么时候不应该使用合并可以提高存储库的实用性。

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

    可以配置 数字拉力 重新建立基础而不是合并,但这也有问题(稍后讨论)。相反 数字拉力 应该配置为只进行快进合并。

    重新引入回扣佣金

    假设有人重新设置分支的基础并强制推送它。这通常不应该发生,但有时是必要的(例如,删除一个意外调试和推送的50GiB日志文件)。由完成的合并 数字拉力 将上游分支的新版本合并到本地存储库中仍然存在的旧版本中。如果你推动结果,投球叉和火炬就会开始朝你走来。

    有些人可能会认为,真正的问题是部队更新。是的,通常建议尽可能避免用力推动,但有时是不可避免的。开发人员必须做好处理强制更新的准备,因为它们有时会发生。这意味着不要通过普通的 数字拉力 .

    意外的工作目录修改

    直到 数字拉力 完成。可能存在合并冲突,您必须先解决这些冲突,然后才能执行其他操作,它可能会在您的工作目录中引入50GiB的日志文件,因为有人不小心推送了它,它可能重命名您正在工作的目录,等等。

    git remote update -p (或 git fetch --all -p )允许您在决定合并或重新建立基础之前查看其他人的提交,允许您在采取行动之前制定计划。

    审查他人委员会的困难

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

    你可以使用 git stash 然后 数字拉力 ,但当你复习完后会做什么?要回到原来的位置,必须撤消由创建的合并 数字拉力 并涂抹藏匿物。

    git远程更新-p (或 git获取--all-p )不会修改工作目录或索引,因此即使您有暂存和/或未暂存的更改,也可以随时安全运行。你可以暂停你正在做的事情,回顾别人的承诺,而不用担心隐藏或完成你正在进行的承诺。 数字拉力 不会给你那么大的灵活性。

    重新调整到远程分支机构

    一个常见的Git使用模式是 数字拉力 引入最新的更改,然后 git rebase @{u} 要消除合并提交 数字拉力 介绍。Git有一些配置选项,通过告诉 数字拉力 要执行重新基准而不是合并(请参阅 branch.<branch>.rebase , branch.autosetuprebase pull.rebase 选项)。

    不幸的是,如果您有一个要保留的未推送合并提交(例如,一个将推送的功能分支合并到的提交 master ),既不是重新基准拉动( 数字拉力 具有 分支机构&书信电报;分支>。再基底 设置为 true )也没有合并拉取(默认 数字拉力 行为),然后重新设置基准将起作用。这是因为 git rebase 在没有 --preserve-merges 选项不能将rebase pull操作配置为保留合并,合并pull后面跟着 git rebase -p @{u} 不会消除由合并拉动引起的合并。 更新: 已添加Git v1.8.5 git pull --rebase=preserve git config pull.rebase preserve 。这些原因 数字拉力 要做的事 git rebase --preserve-merges 在获取上游提交之后。(感谢 funkaster 抬头看!)

    清理已删除的分支

    数字拉力 不修剪与从远程存储库中删除的分支相对应的远程跟踪分支。例如,如果有人删除了分支 foo 从远程回购中,您仍然可以看到 origin/foo .

    这会导致用户意外地复活被杀死的分支,因为他们认为它们仍然处于活动状态。

    更好的选择:使用 加大力度 而不是 数字拉力

    而不是 数字拉力 ,我建议创建并使用以下内容 加大力度 别名:

    git-config--全局别名.up'!git远程更新-p;git merge--仅限ff@{u}'
    

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

    这仍然会以不可预测的方式修改您的工作目录,但前提是您没有任何本地更改。不像 数字拉力 , 加大力度 永远不会让您出现提示,期望您修复合并冲突。

    另一种选择: git pull --ff-only --all -p

    以下是上述方案的替代方案 加大力度 别名:

    git config --global alias.up 'pull --ff-only --all -p'
    

    此版本的 加大力度 具有与上一个相同的行为 加大力度 别名,除了:

    • 如果您的本地分支没有配置上游分支,则错误消息会更加神秘
    • 它依赖于一个未记录的功能( -p 参数,传递给 fetch )在Git的未来版本中可能会发生变化

    如果您正在运行Git 2.0或更新版本

    使用Git 2.0及更新版本,您可以配置 数字拉力 默认情况下只进行快进合并:

    git-config—仅全局pull.ff
    

    这导致 数字拉力 表现得像 git pull-仅ff ,但它仍然无法获取所有上游提交或清除旧提交 起源/* 树枝,所以我还是更喜欢 加大力度 .

        2
  •  200
  •   Ward Muylaert stochastic_zeitgeist    10 年前

    我的答案来自于讨论 arose 关于黑客新闻:

    我很想用头条新闻的Betteridge定律来回答这个问题:为什么 git pull 被认为有害?事实并非如此。

    • 非线性本质上并不坏。如果它们代表了实际的历史,那么它们是可以的。
    • 意外重新引入提交 重新定基 上游是错误地改写上游历史的结果。当历史记录沿多个转发进行复制时,您无法重写历史记录。
    • 修改工作目录是预期的结果;有争议的有用性,即面对hg/notone/darcs/other_dvcs_predating_git的行为,但本质上并不坏。
    • 合并需要暂停以查看其他人的工作,这也是git pull的预期行为。如果您不想合并,您应该使用git fetch。同样,与以前流行的dvd相比,这是git的一种特质,但这是意料之中的行为,本质上并不坏。
    • 让远程分支很难重新建立基础是件好事。除非你绝对需要,否则不要重写历史。我一辈子都无法理解这种对(伪)线性历史的追求
    • 不清理树枝是件好事。每个回购都知道自己想持有什么。Git没有主从关系的概念。
        3
  •  26
  •   awhie29urh2    10 年前

    如果你正确使用Git,它不会被认为是有害的。考虑到您的用例,我看到了它对您的负面影响,但您可以通过不修改共享历史来避免问题。

        4
  •  18
  •   Community datashaman    7 年前

    已接受的答案声称

    无法将rebase pull操作配置为保留合并

    但截至 Git 1.8.5 ,你可以把答案放在哪个后面

    git pull --rebase=preserve
    

    git config --global pull.rebase preserve
    

    git config branch.<name>.rebase preserve
    

    这个 docs

    什么时候 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复杂得多的: What exactly does git's "rebase --preserve-merges" do (and why?)

        5
  •  -1
  •   Nathan Redblur    5 年前

    如果您转到旧的git存储库 加大力度 他们建议的别名不同。 https://github.com/aanand/git-up

    git config --global alias.up 'pull --rebase --autostash'

    这非常适合我。