如果我将分支A合并到分支B然后删除A,那么分支A(现已删除)的提交属于哪个分支?当我获得这些提交的链接时,我发现“此提交不属于此存储库上的任何分支,并且可能属于存储库外部的分支。”
我尝试了这个问题的所有答案,但没有解决 mu 问题 列出和删除没有分支下的 Git 提交(悬空?)
解决办法是什么?
您无法“删除”此提交。 您一开始就没有此提交,即使您有,您仍然无法真正删除它。
\n\n\n如果我将分支A合并到分支B然后删除A,那么分支A(现已删除)的提交属于哪个分支?
\n
你可能想要的答案在这里\xe2\x80\x94,它没有错,但也不正确\xe2\x80\x94是“分支B”。不幸的是,这个问题存在根本性错误。我相信这个错误本身来自 GitHub 相当误导性的声明,即提交不“属于此存储库上的任何分支”:
\n\n\n\xe2\x9a\xa0\xef\xb8\x8f 此提交不属于此存储库上的任何分支,并且可能属于存储库外部的分支。
\n
问题本身的错误\xe2\x80\x94 以及上面的文本具有误导性的原因\xe2\x80\x94 是提交并不归因于 Git 中分支名称的存在。在 Git 中,您可以有任意数量的提交,并且根本不需要分支名称。承诺从不“属于”任何人提交从一开始
\n相反,我们在 Git 中使用的一个关键概念是可达性。如果某个提交C i可以从Git 存储库R中的其他提交C j \xe2\x80\x89访问,这意味着C i是C j的祖先(或者等效地,C i \xe2\x89\xba C i,其中“\xe2\x89\xba”\xe2\x80\x94a 一种弯曲的小于号\xe2\x80\x94 读作“前面”):这定义了提交图上的部分顺序,这是一个有向非循环图,或 DAG。1
\n然后,我们将分支\xe2\x80\x94 或至少Git 中的分支名称\xe2\x80\x94 定义为引用(或 ref),其名称以 开头refs/heads/
,其散列 ID 被限制为提交的散列 ID,其中ref本身定义为包含哈希 ID 的名称。2 因此,像这样的名称refs/heads/branch
是一个分支名称,并且存储在该分支名称中的哈希 ID 必须是某个提交的哈希 ID 。
一次提交会到达它的所有祖先。每个提交都会存储一个列表\xe2\x80\x94,通常只有一个条目长\xe2\x80\x94,其中包含先前提交的哈希 ID。这些形式提交成链,带有向后指向的箭头。简单的情况下,每次提交只有一个向后箭头,指向其前一个:
\nA <-B <-C ... <-F <-G <-H\n
Run Code Online (Sandbox Code Playgroud)\n在这里,在我们的简单存储库R中,我们正好有八次提交。我们没有使用 Git 的实际提交哈希 ID,而是给它们单个大写字母。(这个方案在真实的存储库中是不切实际的:如果提交超过 26 次,我们会怎么做?但它对于思考这里的问题很有用。)我们所做的最后一次提交,H
在其内部存储了哈希 ID倒数第二次提交G
。我们说H
指向 G
. G
存储F
\的哈希ID,所以我们说它G
指向F
. 这将继续向后,沿着整个提交链,直到我们点击 commit A
。因为这是第一次提交,所以它不能向后指向,而且也确实如此:它的父哈希 ID 列表为空。
1这个特定的定义有点倒退,因为 Git 本身是向后工作的。在正常的 DAG 中,可达性意味着后继,而不是前驱。但在 Git 中,所有箭头都指向向后,而不是向前。
\n2大多数参考文献都是拼写的refs/*
,但也存在伪参考文献,例如HEAD
和CHERRY_PICK_HEAD
。伪引用是一种特殊情况,对于那些致力于为 Git 建立正确的引用数据库的人来说,这会带来麻烦。请注意,伪引用是每个工作树的,但其他一些引用(例如二分引用)也是每个工作树的。
我们从简单的八提交存储库开始,结尾为:
\n...--G--H <-- main (HEAD)\n
Run Code Online (Sandbox Code Playgroud)\n我们添加了分支名称 main
并存储在main
commit 的真实哈希 ID中H
。所以我们说“main
指向” H
,就像“H
指向”一样G
。(对于 Stack Overflow 上的文本/ASCII 艺术目的,我未能将从 到 的箭头绘制H
为G
箭头:我们只需要记住,仅提交向后链接。没有从G
到 的链接H
链接,反之亦然。 )
此设置意味着该名称main
允许我们访问存储库中的八个提交中的任何一个。现在让我们再添加两个分支名称br1
和br2
,它们都指向提交H
:
...--G--H <-- br1, br2, main (HEAD)\n
Run Code Online (Sandbox Code Playgroud)\n所有三个名称都指向 commit H
。因此所有八个提交都可以从所有名称访问。这意味着所有提交都在所有分支上。
这里附加HEAD
的意味着我们正在使用的main
分支名称是,因此当前提交是 commit 。让我们现在运行,更改附加的名称:HEAD
H
git checkout
git switch
HEAD
git switch br1\n
Run Code Online (Sandbox Code Playgroud)\n这导致:
\n...--G--H <-- br1 (HEAD), br2, main\n
Run Code Online (Sandbox Code Playgroud)\n此时唯一改变HEAD
的是现在附加到br1
. 所有八次提交仍然存在;我们仍在使用commit H
;H
但现在我们通过名称使用br1
。
现在我们以通常的方式进行新的提交。这个新的提交获得了一个新的、唯一的哈希 ID,但我们只是将其称为“提交I
”以保持理智。为了绘制它,我们需要绘制一个I
指向 的箭头H
,并使名称br1
指向I
,因为这就是 Git 在内部实际处理此问题的方式:
I <-- br1 (HEAD)\n /\n...--G--H <-- br2, main\n
Run Code Online (Sandbox Code Playgroud)\n我们现在I
通过 name使用 commit br1
。
如果我们添加另一个新的提交J
,我们会得到:
I--J <-- br1 (HEAD)\n /\n...--G--H <-- br2, main\n
Run Code Online (Sandbox Code Playgroud)\n我们现在使用的是J
通过 name提交br1
。现在我们切换到br2
:
git switch br2\n\n I--J <-- br1\n /\n...--G--H <-- br2 (HEAD), main\n
Run Code Online (Sandbox Code Playgroud)\n我们现在H
再次通过 name使用提交br2
。如果我们再进行两次提交,我们会得到:
I--J <-- br1\n /\n...--G--H <-- main\n \\\n K--L <-- br2 (HEAD)\n
Run Code Online (Sandbox Code Playgroud)\n瞧,我们有“分支机构”了!提交I-J
可以说是“属于”branch br1
,提交K-L
也可以说是“属于”branch br2
,但是提交之前的情况又如何呢H
?有些人会说这些“属于” main
,但 Git 没有这样的区别:它们“位于”所有三个分支上。当我们第一次创建两个br*
分支名称时,所有提交都在所有三个分支上,并且这些提交仍然在所有三个分支上。只是目前新提交I-J
仅在,上br1
,并且新提交K-L
仅在,上。br2
当我们使用 时git merge
,我们并不是真正合并分支。我们确实正在合并提交。让我们现在就这样做:
git switch br1\ngit merge br2\n
Run Code Online (Sandbox Code Playgroud)\n通过附加到使提交git switch
成为当前J
提交。Git定位的不是一个、不是两个,而是三个提交:HEAD
br1
git merge
HEAD
提交,J
;br2
指向L
; 和合并基础是通过最低公共祖先算法定义的,其输入是 commitJ
和L
,该算法会计算出 commit 的哈希 ID H
。
合并本身通过比较、和中存储的快照来工作。这使得 Git 能够弄清楚链上“我们做了什么”,以及链上“他们做了什么”。(请注意,提交和在此仅用于它们的链接,而不用于它们的快照:两者都链接回 commit ,这导致 commit成为合并基础。)H
J
L
H-I-J
H-K-L
I
K
H
H
如果一切顺利,Git 会自行进行新的合并提交。这个新的合并提交M
不是一个而是两个父项\xe2\x80\x94两个向后指向的箭头\xe2\x80\x94链接到*两个提交J
和L
,如下所示:
I--J\n / \\\n...--G--H M\n \\ /\n K--L\n
Run Code Online (Sandbox Code Playgroud)\n我暂时从绘图中删除了所有分支名称,因为我们不需要它们:提交独立于任何分支名称而存在。但是在 Git 中进行新的提交总是会做同样的事情:
\n当我们进行提交时I
,Git 将新提交的哈希 ID 写入当时的分支名称br1
;
当我们进行提交时J
,Git 将新提交的哈希 ID 写入当时的分支名称br1
;和
当我们进行提交K
时L
,Git 将新提交的哈希 ID 写入当时的分支名称br2
;
现在我们已经完成了M
,Git 将M
其哈希 ID 写入当前的分支名称中br1
:
I--J\n / \\\n ...--G--H M <-- br1 (HEAD)\n \\ /\n K--L\n
Run Code Online (Sandbox Code Playgroud)\n名称main
和br2
仍然存在,并且仍然指向H
和L
。在这个 ASCII 艺术中没有绘画的空间,而且现在main
也没有必要绘画。br2
我们可以问:可以从名称访问哪些提交br1
? 答案是:全部!
以前只进行K-L
提交,br2
但现在,由于合并提交M
,提交K-L
位于两个分支上。因此,只要我们稍微改一下措辞,这就能为您提供原始问题的答案:在真正的合并之后,删除分支名称是“安全的”,因为仍然可以通过合并提交找到提交。他们现在在两个分支上,并拿走了一个名字\xe2\x80\x94我们现在不使用的名字,br2
在这种情况下\xe2\x80\x94仍然留下至少一个他们\的其他名称\是“开”的。
虽然该git merge
命令有时会进行合并提交M
:
I--J <-- br1\n /\n...--G--H\n \\\n K--L <-- br2\n
Run Code Online (Sandbox Code Playgroud)\n我们可以想出其他不适用的情况:
\n...--P--Q <-- br3 (HEAD)\n \\\n R <-- br4\n
Run Code Online (Sandbox Code Playgroud)\n在这里,git merge br4
将进行快进操作而不是合并,产生:
...--P--Q--R <-- br3 (HEAD), br4\n
Run Code Online (Sandbox Code Playgroud)\n在快进的情况下,删除br4
仍然是安全的:以前只是“on”的提交br4
,commit R
,现在也“on”了br3
。
但我们也可以运行git merge --squash
,并且该特定选项指示git merge
进行非合并“挤压”提交:
I--J <-- br1 (HEAD)\n /\n...--G--H\n \\\n K--L <-- br2\n\n[we now run:\n git merge --squash br2\nand a second Git command that we\'re forced to run, to get:]\n\n I--J--S <-- br1 (HEAD)\n /\n...--G--H\n \\\n K--L <-- br2\n
Run Code Online (Sandbox Code Playgroud)\nS
在 后,这里的新提交具有与我们进行真正合并时得到的快照git merge --squash
相同的快照。也就是说,Git 仍然执行了真正合并所需执行的所有正常“查找合并基础、运行两个差异、合并工作”步骤。但然后停止并让我们运行3,当我们运行时,进行一个普通的非合并提交,我在上面将其绘制为。git merge
git merge
git commit
git commit
S
3这没有充分的理由。如果我们想要这个动作,我们可以运行git merge --squash --no-commit
。这种组合是允许的!它所做的事情与今天完全相同git merge --squash
。但在遥远的过去,该--squash
选项被作为 的特殊情况处理--no-commit
,因此它完成了这两件事,这意味着它现在必须以向后兼容性的名义继续完成这两件事。
一般来说,在 Git 中,我们\xe2\x80\x94 甚至 Git 本身\xe2\x80\x94使用名称查找提交。 它们不一定是分支名称,但它们通常是分支名称,或者在克隆中,远程跟踪名称(origin/*
例如)。无论名称\xe2\x80\x94 分支名称、标记名称、远程跟踪名称、内部二分引用或任何名称\xe2\x80\x94 是什么类型,该名称都包含一个哈希 ID。如果这是提交哈希 ID,则足以通过图可达性算法找到所有前驱提交。
但有时我们可能会有只能由一个引用找到的提交,例如一个分支名称br2
:
I--J--S <-- br1 (HEAD)\n /\n...--G--H\n \\\n K--L <-- br2\n
Run Code Online (Sandbox Code Playgroud)\n如果我们删除这一引用br2
,我们如何找到提交K-L
?
一个答案是:我们不这样做。(另一个\xe2\x80\x94但只是临时的\xe2\x80\x94答案是使用Git\的reflogs,它半秘密地保留提交哈希ID一段时间。不过,最终reflog条目会过期,然后我们回到“我们不”的答案。)
\n如果我们和 Git找不到某个提交,那么该提交就有资格进行git gc
. 4git gc
Git 将在 Git 确定的不规则时间自动为您 运行。这git gc
会\xe2\x80\x94缓慢而痛苦地通过爬行整个存储库R \xe2\x80\x94 找到任何无法访问的提交和其他 Git 对象,如果满足其他几个条件,5实际上会从存储库中删除对象对象数据库。
这个GC系统非常聪明。它允许 Git 程序在有用时自由生成内部对象,然后在不再使用时将其丢弃。垃圾收集器/清洁服务稍后会过来进行清理。
\n4git gc
是一般 Git 维护和管理的一部分,现在正在研究一个git maintenance
命令,该命令将以更通用、可预测和可用的方式处理服务器设置。这可能git maintenance
最终对普通用户和 Git 管理员都有用,但首先还有很多工作要做。
5最重要的一点是该物体本身足够旧。由于可以随时git gc
“在后台”运行,因此重要的是不要删除存在的对象,因为某些命令\xe2\x80\x94说,\xe2\x80\x94刚刚创建了该对象,刚才,但还没有抽出时间将其连接起来以使其可见。如果垃圾收集之前的新提交可以将其哈希 ID 写入分支名称,那将很糟糕。因此,默认情况下,所有事情都会有两周的时间来完成它正在做的事情。两周可能足以完成新的提交。git commit
git gc
git commit
git commit
(开个玩笑,Git 的操作比我们以前使用的版本控制系统快得多。我最好就此打住,免得这变成Monty Python 四约克郡人草图。)
\n当 GitHub 说:
\n\n\n\xe2\x9a\xa0\xef\xb8\x8f 此提交不属于此存储库上的任何分支,并且可能属于存储库外部的分支。
\n
他们的意思是什么?
\n直接含义是无法从此存储库中的分支名称访问此提交。我刚才引用的短语和我的更精确的替换短语都有两个形容词,它们都充当限定词:一个特定的提交\xe2\x80\x94(大概是您在浏览器中显示的一个\xe2\x80\x94)和一个特定的存储库,通过该存储库找到Gitthis
提交。
我们刚才说过,我们通常使用名称来查找提交。但事实上,我们使用其哈希 ID 在存储库的对象数据库中找到了底层提交对象。哈希 ID是提交的“真实名称”。我们使用名称发现的是哈希 ID,而不是提交对象本身。如果我们手头有哈希 ID,这就是我们所需要的\xe2\x80\x94,当我们使用浏览器查看 GitHub 存储库提交时,我们提供提交哈希 ID。例如,URL https://github.com/git/git/commit/5a73c6bdc717127c2da99f57bc630c4efd8aed02以5a73c6bdc7...
. 那是提交哈希 ID。因此 GitHub 可以在不使用分支名称的情况下访问提交。
现在,这个特定的提交 \xe2\x80\x94 \xe2\x80\x94是我写这篇文章时5a73c6bdc7...
最近的提交,所以如果 GitHub 查看此存储库中的分支名称,他们会立即看到这是。如果在您阅读本文时,GitHub名称找到了其他提交,则 GitHub 软件很容易查看是否是then 的尖端提交的祖先,如果是,则仍然可以从 访问,因此仍然“在”分支上。master
5a73c6bdc7...
master
refs/heads/master
5a73c6bdc7...
master
5a73c6bdc7...
master
master
但是,如果我们在其他存储库中选择其他提交,也许该提交不是从任何分支名称访问如果是这样,则满足引用中条款的第一部分:
\n\n\n\xe2\x9a\xa0\xef\xb8\x8f 此提交不属于此存储库上的任何分支
\n
我们可以停在那里,或者推测也许git gc
最终会删除这个提交。(如果其他人可以找到该提交,则 A不会删除该git gc
提交名称(例如标记名称)删除该提交。您可以拥有只能通过标记名称找到的提交,而不能通过任何分支名称找到。GitHub 是否会生成对于此类提交的此类警告由 GitHub 决定。)
但他们接着补充道:
\n\n\n并且可能属于存储库之外的分支。
\n
这是 GitHub 特定的。Fork 不是 Git 的一部分:它们是 GitHub 的附加组件。(这个特殊的附加组件也在其他托管网站上找到,但据我所知,GitHub 是最先出现的。Bitbucket 和 GitLab 似乎在 GitHub 上模拟了他们的分支。)
\nGitHub 上的分叉是具有附加功能的服务器端克隆。这些附加功能包括提出 Pull 请求的能力(这是 GitHub 的另一个附加功能)。为了使这些 Pull 请求发挥作用,GitHub 在内部使用了 Git 已经实施了数十年的一些技巧(至少自 2005 年左右的 Git v1.0.0 以来)。这些技巧之一是 Git 可以在其他存储库的对象数据库中查找以检索 Git 对象。这意味着,如果您在 GitHub 上有一些存储库R you,其他人可以拥有他们从您的R you分叉的不同存储库R se(se
代表其他人)。他们可以自己提交并将它们发送到R se ...然后,在以后可能适用的任何条件下,您可以使用将其提交哈希 ID 嵌入到存储库名称下的URL,并且由于这种排序替代技巧,查看他们的提交,即使它在他们的 fork中。7
所有这一切的结果是,您可以查看他们的存储库中的提交,他们作为拉取请求向您提出,就像它在您的存储库中一样。当你这样做时,你肯定会触发相同的“不属于此存储库上的任何分支”条件。这将产生您在此处看到的警告:
\n\n\n\xe2\x9a\xa0\xef\xb8\x8f 此提交不属于此存储库上的任何分支,并且可能属于存储库外部的分支。
\n
在这种特殊情况下,提交确实不在您的存储库中,而是在GitHub 上。所以没有办法从 R you中删除它。它不在R you中 ,而是在R se中。你可以从R you看到它。
\n您无法从警告中判断出哪个潜在条件触发了警告。您所知道的是,您现在正在查看的提交无法从R you中的任何分支名称访问。这可能是因为它是可访问的,但不能通过分支名称访问;可能是因为它无法访问,并且正在等待 GC 处理;或者可能是因为它在其他人的存储库中。
\n在所有三种情况下,您都无法直接删除提交本身。在一种情况下,git gc
可能会自行删除它,但您无法使 GitHub 运行git gc
。8 在一种情况下\xe2\x80\x94,如果您有提交的标签,例如\xe2\x80\x94,您可能可以采取一些措施来自行删除 git gc
它。在最后的情况下,即使你可以获得git gc
删除的。
7相同类型的规则也可能适用于您的提交:如果他们知道哈希 ID,他们可能能够在其 fork中看到这些提交。这有明显的安全隐患,我不知道 GitHub 可能对此做了什么。GitHub 有很多非常有能力的程序员,他们可能已经使这一切变得非常安全,因此只有他们向您提出 PR 时您才能看到他们的提交,并且只有在公开的情况下他们才能看到您的提交。我只是指出,在低级别上,不小心使用“替代品”会引入各种安全问题,所以如果您使用它,请小心。
\n8 GitHub 支持可以git gc
为您运行,但您必须联系他们才能开始该过程。从这个意义上说,您可以让它们运行git gc
,但这是一种间接的。
归档时间: |
|
查看次数: |
3242 次 |
最近记录: |