我有一个非常混乱的分支,在master后面有很多版本,我拉进去,只想删除它。
我一直在处理此分支的分离头,如果删除该分支,我会失去分离的头吗?
答案 0 :(得分:3)
Git会重载“ head”一词,这可能会使您误入歧途。不过,公平地说,Git也严重地重载了“ branch”一词。为了使它们保持直线,我们在这里使用短语分支名称。
HEAD
一词(用大写形式完全像这样拼写, 1 )表示当前提交和当前分支名称。好吧,更准确地说,它总是表示当前提交,并且附加时,它也表示当前分支名称。
为此,Git通常(通常是 )将分支的名称存储在其HEAD
中。然后分支名称标识提交!因此,HEAD
拥有分支的名称,而分支名称则拥有提交哈希ID。
1 在Windows和MacOS上,您也可以使用小写字母,这让人感到困惑,但这是当您要求Windows或MacOS打开文件{{ 1}},并且存在一个名为readme
的文件,它们将打开大写命名的文件。 Git将您对README
的想法存储在名为HEAD
的文件中。如果愿意,请查看文件:例如HEAD
。它总是只包含一行,通常一行是cat .git/HEAD
。
实际上,提交哈希ID对于使用分支本身非常重要!我认为这有助于绘制。假设您从一个只有三个提交的微小存储库开始。它们都有一些大的丑陋的哈希ID,对于每个提交都是唯一的,但是为了使其易于管理,我们就称它们为ref: refs/heads/<name>
,A
和B
,其中C
是您进行的第一次提交,A
是第二次提交,B
是第三次提交。
现在,每个提交都在其只读的,永久保存的数据中记住在之前的提交的哈希ID。由于C
是 first 提交,因此它之前没有任何内容,因此它是独立存在的。由于A
在B
之后,因此A
会记住B
的ID:我们说A
指向 {{1} }。同样,B
记住,即指向A
:
C
Git现在需要知道的是提交B
的哈希ID,即提交链的 end 。这就是分支名称的来源。让我们使用名称A <-B <-C
指向提交C
,方法是将master
的实际哈希ID存储在名称{ {1}}。并且由于一瞬间就会变得显而易见的原因,让我们这样绘制提交:
C
现在,在不更改存储库中的 commits 集合的情况下,让我们附加另一个分支名称C
来提交master
而不是 C <-- master
/
B
/
A
:
dev
Git如何知道哪个提交是当前提交,哪个分支名称是当前分支 ?这就是B
的来源。让我们在其中绘制名称C
,并将其附加到分支 C <-- master
/
B <-- dev
/
A
:
HEAD
现在让我们看看分支在Git中实际如何工作的秘密。使用相同的提交顺序,并在HEAD
上附加dev
,我们进行一次 new 提交。与所有提交一样,Git将保存所有文件的快照,并为该新快照提供新的,唯一的,大的丑陋哈希ID,但我们将其称为 C <-- master
/
B <-- dev (HEAD)
/
A
。
新提交HEAD
必须记住当前提交。我们如何找到当前提交?是的,没错:我们看看dev
!它附加到D
,并且D
指向HEAD
,因此dev
是当前提交。这意味着我们的新提交dev
必须指向B
:
B
而且,现在我们有了新的提交D
作为开发分支的 tip ,Git只需将B
的哈希ID写入 name < / em> C
/
B--D
/
A
。但是Git如何知道使用名称D
?如果您说: Git看着D
,恭喜,您已经了解了附件dev
的工作原理!让我们把标签放回去:
dev
请注意,只要我们记得,我们就可以按照自己喜欢的方式绘制此图形:
Git可以向后工作。它从附加的HEAD
开始以获取分支名称,然后从分支名称中,Git查找分支的 tip 提交。
每个提交点向后指向其父级。 Git跟随向后链接到链中的下一个(上一个?)提交。
当我们厌倦了后续操作,或者达到了无法再进一步执行的提交时,动作将停止:commit HEAD
, root 提交。
所以:
C <-- master
/
B--D <-- dev (HEAD)
/
A
或:
HEAD
是绘制此图的所有有效方法。 分支名称,A
和A--B--C <-- master
\
D <-- dev (HEAD)
标识 tip 提交,然后Git在其余提交中向后工作。>
请注意,提交 C <-- master
/
A--B
\
D <-- dev (HEAD)
和master
在两个分支上。最后一张图纸使这一点非常清晰,因此有时是要走的路。同样清楚的是,如果我们添加一个新名称(如dev
,该名称指向提交B
,就像A
:
m2
现在,提交C
位于两个分支,master
和 C <-- master, m2
/
A--B
\
D <-- dev (HEAD)
上。我们可以安全地删除两个名称之一,因为我们仍然可以使用另一个名称来查找提交A-B-C
。
因此,如果您的HEAD通常是附加到分支名称-文件master
包含分支的名称-分离的HEAD到底是什么?结果也很简单。当文件m2
包含原始的 commit哈希ID 而不是分支名称时,将发生分离的HEAD。让我们再次使用最后一个图形绘制,但是现在从C
中分离.git/HEAD
并使.git/HEAD
本身指向直接提交HEAD
:
dev
现在,从这里开始,通过修改某些文件以及HEAD
和B
的常用方法,添加一个 new 提交。 Git将使用新的大的丑陋哈希ID进行新的提交,但我们将其称为 C <-- master
/
A--B <-- HEAD
\
D <-- dev
。提交git add
将照常指向git commit
...然后Git将E
的哈希ID写入E
而不是某个分支名称中,从而得到:
B
假设您已完成上述操作,分离的E
指向HEAD
。现在,假设您删除分支名称 C <-- master
/
A--B--E <-- HEAD
\
D <-- dev
:
HEAD
提交E
会发生什么?从某种意义上说,它仍然存在,但是现在没有明显的方法找到。让我们放回dev
,这次,让我们重新附加 C <-- master
/
A--B--E <-- HEAD
\
D ???
:
D
提交dev
会发生什么?同样,它仍然是那里,但是没有明显的方法来找到。因此,在之前我们将HEAD
重新附加到 C <-- master
/
A--B--E ???
\
D <-- dev (HEAD)
或E
,让我们指定一些分支名称来提交{{1} }。也就是说,让我们回到:
HEAD
并运行dev
或master
。这些操作是创建一个新名称 E
,指向当前提交:
C <-- master
/
A--B--E <-- HEAD
\
D <-- dev
或:
git branch temp
这两者之间的区别在于git checkout -b temp
是否已附加。使用temp
可以创建新名称,而无需附加 C <-- master
/
A--B--E <-- temp, HEAD
\
D <-- dev
。使用 C <-- master
/
A--B--E <-- temp (HEAD)
\
D <-- dev
来命名新名称,并在和上附加HEAD
。
就是这样-真的很简单! 分支名称指向提示提交,git branch
记住分支名称之一。或者,如果您有一个分离的HEAD,HEAD
会直接记住其中一个提交。
提交 无关紧要,但是Git使用名称作为起点(或终点?)来找到它们。自动添加 new 提交 会更新当前分支 name ,以指向该分支的新最终提交。或更确切地说,如果将git checkout -b
附加到分支,就可以做到!您可以并且应该期望分支名称以这种方式前进。
这里没有介绍,但是很重要:例如某些提交没有 no 名称-例如,因为我们是在独立的目录上找不到提交HEAD
,在将HEAD重新连接到其他地方之前,HEAD从来没有为其指定一个名称-提交很容易受到Git的垃圾收集器 HEAD
的攻击。自从您上次能够通过HEAD
看到提交以来,提交至少通常在30天内不受此类收集的保护,但是,如果没有明显的名称,它们将变得很难找到。但是,这个严峻的 Reaper 收集器HEAD
通常会在一个月左右后删除不必要的提交。例如,这就是E
的工作方式:git gc
复制提交,然后通过移动分支名称 la HEAD
—放弃原始提交,转而使用更新,更明亮的副本。
答案 1 :(得分:1)
否,HEAD不能“丢失”。只需签出其他任何内容,即可获得HEAD所在的指针的全新目标。
关于最近的提交:
就像Tim在评论中提到的那样,您可以从reflog中找回它们。
但是,如果不确定丢失这些提交并希望以某种方式保留它们,请创建一个备份分支,分离的HEAD当前指向该分支。
# let's create a memory of your currently detached HEAD
git checkout -b sleepyHollow
然后继续在您的master上继续工作,如果您需要上一个凌乱的分支进行特定提交,只需将其樱桃拾取回master中,以免合并/减轻困难。备份分支的存在将保留垃圾收集器中的“丢失”提交。