【问题标题】:Why "git fetch origin branch:branch" works only on a non-current branch?为什么“git fetch origin branch:branch”仅适用于非当前分支?
【发布时间】:2015-05-15 17:25:57
【问题描述】:

在处理功能分支时,我使用此 Git 命令将我的“开发”分支更新为最新状态,然后将我的功能分支与“开发”分支合并:

git fetch origin develop:develop

这可行,即本地“develop”指向与“origin/develop”相同的提交,并且处于带有 origin 的最新状态。

但是,当“develop”分支被签出时,这个命令会失败:

fatal: Refusing to fetch into current branch refs/heads/develop of non-bare repository
fatal: The remote end hung up unexpectedly

如果我知道为什么会这样,这将有助于我更好地理解 Git。

【问题讨论】:

    标签: git


    【解决方案1】:

    错误信息来自builtin/fetch.c#check_not_current_branch()
    该函数一直追溯到commit 8ee5d73, Oct. 2008, git 1.6.0.4

    评论很有启发性:

    一些令人困惑的教程建议获取 进入当前分支,如下所示:

    git fetch origin master:master
    

    (或者更糟糕的是:使用“pull”而不是“fetch”的同一命令行)。
    虽然存储您想要提取的内容可能是有意义的,但通常是 当前分支为“master”时,完全错误。
    仅当(不正确的)“git pull origin master:master”试图通过将--update-head-ok 提供给底层“git fetch”来解决此问题时才应允许这样做,否则我们应该拒绝它,但在某些地方我们失去了这种行为。

    当前分支的检查现在在非裸机中执行 存储库,这是对原始行为的改进。

    考虑到函数check_not_current_branch()called with

    if (!update_head_ok)
            check_not_current_branch(ref_map);
    

    这意味着git fetch -u origin develop:develop 应该可以工作。

    -u
    --update-head-ok
    

    默认情况下 git fetch 拒绝更新当前分支对应的头部。此标志禁用检查。
    这纯粹是供git pullgit fetch 通信的内部使用,除非您正在实现自己的Porcelain,否则您不应该使用它。

    即使您不应该使用该选项,但它确实满足了您的初始要求,使“git fetch origin branch:branch”在当前分支上工作。


    关于这个补丁的来源,follow the discussion there

    虽然存储您想要提取的内容可能很有意义

    这是fetch 部分:它存储来自更新的origin/master 的远程历史记录。
    但是,当当前本地分支也是 master 时,这一点尤其被打破。
    如前所述in this answer

    master 是当前分支时,我认为“git fetch url side:master” 并且我们省略了--update-head-ok 已损坏。
    测试在当前master 上失败。

    它也将无法更新工作目录并离开 索引就像你要删除所有东西一样。


    以“git pull with refspec”为例。
    torek 显示了一个示例,其中:

    假设我运行 git fetch 并引入了两个新提交,我将标记为 CD
    C 的父级是 AD 的父级是B 之前的节点:

                    C
                   /
       ...--o--o--A   <-- master
                   \
                    o--B   <-- develop
                     \
                      D
    

    此 git fetch 的输出将其列为:

      aaaaaaa..ccccccc  master     -> origin/master
    + bbbbbbb...ddddddd develop    -> origin/develop  (forced update)
    

    如果您当前的分支不是develop,那么强制更新可能就是您想要的。
    但是如果您在输入git fetch origin develop:develop开启 develop,并且如果允许获取更新HEAD,...那么您当前的索引将反映D,而不再是@987654377 @.
    因此,在您的工作树中完成 git diff 会显示您的文件和 D 之间的差异,而不是您之前的 HEAD B

    这很糟糕,因为您最初的 git checkout develop 创建了一个与 B HEAD 文件相同的工作树。
    即使您的git status 是干净的(没有任何形式的修改),如果git fetch origin develop:develop 更新了 HEAD(强制从 B 更新到 D),git status 现在会报告在获取之前没有差异的地方。

    这就是为什么默认情况下git fetch拒绝更新当前分支对应的头部


    注意:Git 2.29 中的一个错误也会触发类似的错误消息。

    当“git commit-graph(man) 在合并图层时检测到多次记录的同一提交时,它过去会死掉。
    代码现在忽略除其中一个之外的所有代码并继续,在 Git 2.30(2021 年第一季度)中修复。

    参见Derrick Stolee (derrickstolee)commit 85102accommit 150f115(2020 年 10 月 9 日)。
    (由 Junio C Hamano -- gitster -- 合并到 commit 307a53d,2020 年 11 月 2 日)

    commit-graph: 合并图层时忽略重复

    报告人:Thomas Braun
    帮助人:Taylor Blau
    合着人:Jeff King
    签字人:Derrick Stolee

    Thomas reported 表示“git fetch(man) 命令失败并显示“意外的重复提交 ID”错误。

    $ git fetch origin +refs/head/abcd:refs/remotes/origin/abcd 致命:意外的重复提交 ID 31a13139875bc5f49ddcbd42b4b4d3dc18c16576

    根本原因是他们启用了fetch.writeCommitGraph,这会生成commit-graph链,而这个实例正在合并两个包含相同提交ID的层。

    最初的假设是,如果提交 ID 已经存在于较低的 commit-graph 层中,Git 不会将提交 ID 写入到 commit-graph 层中。
    不知何故,这个特定案例确实陷入了这种情况,导致了这个错误。

    虽然出乎意料,但这实际上并不是无效的(只要两层就提交的元数据达成一致)。当我们解析在commit_graph_data_slab, 中没有graph_pos 的提交时,我们在commit-graph 层中使用二进制搜索来查找提交并设置graph_pos
    在这种情况下,该位置不再使用。但是,当我们从 commit-graph 文件解析提交时,我们会从 commit-graph 加载其父项并在此时分配 graph_pos
    如果这些父母已经从commit-graph 中解析出来,那么什么都不需要做。否则,这个graph_poscommit-graph 中的有效位置,因此我们可以在必要时解析父母。

    因此,这个die() 太激进了。最简单的做法是忽略重复项。

    如果我们只忽略重复项,那么我们将生成一个在相邻位置列出的具有相同提交 ID 的提交图。这些多余的数据永远不会从commit-graph 中删除,这可能会级联成显着膨胀的文件大小。

    谢天谢地,我们可以折叠列表以删除重复的提交指针。这使我们能够获得我们想要的最终结果,而无需额外的内存成本和最少的 CPU 时间。

    根本原因是由于禁用了core.commitGraph,这会阻止在“git commit-graph write --split(man)”命令期间解析来自较低层的提交。
    由于我们使用 'graph_pos' 值来确定提交是否在较低层,我们永远不会发现这些提交已经在 commit-graph 链中并将它们添加到顶层。然后将该层向下合并,创建副本。

    t5324-split-commit-graph.sh 中添加的测试在没有此更改的情况下失败。但是,我们还没有完全消除这种重复检查的需要。这将在后续更改中出现。

    还有:

    commit-graph: 禁用时不要写提交图

    报告者:Thomas Braun
    帮助者:Jeff King
    帮助者:Taylor Blau
    签字人:Derrick Stolee

    core.commitGraph 配置设置可以设置为“false”以防止解析来自commit-graph 文件的提交。这会在尝试使用“--split”进行写入时导致问题,需要区分现有commit-graph 层中的提交和不存在的提交。
    现有机制使用parse_commit(),然后检查是否有“graph_pos”表明提交是从commit-graph 文件中解析的。

    core.commitGraph=false 时,我们不解析来自commit-graph 的提交,而“graph_pos”表示现有文件中没有提交。
    --split 逻辑向前移动,在顶部创建一个新层来保存所有可访问的提交,然后可能向下合并到这些层中,从而导致重复提交。之前的更改使合并过程对这种情况更加稳健,以防它发生在写入的commit-graph 数据中。

    这里的简单答案是如果禁用读取提交图,则避免写入commit-graph。因为生成的commit-graph 将不会被后续的 Git 进程读取。这比在“write”进程中强制core.commitGraph 成为true 更自然。

    git commit-graph 现在包含在其man page 中:

    根据 packfiles 中的提交写一个commit-graph 文件。如果 配置选项core.commitGraph 被禁用,则此命令将 输出警告,然后返回成功而不写入commit-graph 文件。

    【讨论】:

    • 你能否更清楚地说明 why git 默认阻止git fetch origin master:master?我觉得理解这一点的关键可能在这句话中,但我不清楚其含义:“虽然存储您想要提取的内容可能很有意义,但当当前分支为 ' 时通常是完全错误的master'。” 作者所说的“存储你想拉的东西”是什么意思?
    • @sleeparrow 我不得不回到这个补丁的原始线程:spinics.net/lists/git/msg82242.html。我已经更新了答案。
    • @VonC ,我知道他们将git fetch 的默认行为设置为不更新HEAD。但为什么 ?看到this讨论但不明白为什么?
    • @BreakingBenjamin 我认为(3 年后重新阅读我的答案)最后一句话是关键:“它也将无法更新工作目录,并且会留下索引,就好像你正在删除一切。”在非裸仓库中,一个索引,这不是你想要的!
    • @VonC,对不起。我还是没有得到你。如果 index 是 clean ,那为什么不允许 git fetch 更新当前分支呢?
    【解决方案2】:

    git fetch 只从远程仓库获取数据

    1. 它不会更新您的本地分支,即使设置为跟踪远程分支
    2. (因为 1)它无法将远程分支提取到本地分支。这只是不是git fetch做的

    根据man

    git fetch [][[…]]

    你可以像git fetch origin develop一样运行它,它只会更新你的远程分支引用origin/develop

    为了更新您的本地分支,您可以通过以下一种方式执行此操作:

    1. 明确指定应将哪个远程分支拉到哪个本地分支:git pull origin develop:develop
    2. 如果develop 分支现在已检出并设置为跟踪origin/develop,您只需运行git pull,它就会知道要拉出什么

    更新

    更多来自男人:

    当 git fetch 使用明确的分支和/或标签运行时 命令行,例如git fetch origin master, 给定的 s 在命令行上确定要获取的内容(例如 master in 示例,它是 master: 的简写:,这反过来意味着 “获取主分支,但我没有明确说明什么 远程跟踪分支以从命令行进行更新”),以及 示例命令将仅获取主分支。这 remote..fetch 值确定哪个远程跟踪 分支(如果有)已更新。当以这种方式使用时, remote..fetch 值对决定没有任何影响 获取了什么(即,当 命令行列出 refspecs);它们仅用于决定 获取的 refs 通过充当映射来存储。

    可以更新本地分支,但我仍然不明白为什么当您在要更新的分支上时无法执行。

    【讨论】:

    • “它无法将远程分支提取到本地分支” - 但它可以!请参阅我更新的问题。它可以工作,条件是本地“开发”未被签出。
    • @MaDa,是的,你是对的。我错过了这个......现在我也很困惑:)
    猜你喜欢
    • 2019-10-30
    • 2015-10-06
    • 2014-01-18
    • 2011-09-28
    • 2011-02-10
    • 2018-04-11
    • 2012-11-28
    • 2013-01-14
    • 2014-03-28
    相关资源
    最近更新 更多