我遇到过一些提交不属于Git仓库中的任何分支。例如,以下提交被标记为Apache Commons CSV的release,但它不属于任何分支:
https://github.com/apache/commons-csv/commit/0fbd1af5e3bd70454d5e398493a5c983aead2b67
其父提交属于master。
https://github.com/apache/commons-csv/commit/7688fbc3f9f5acf73d3c5018dd83310f7580d02e
你能帮我理解一下吗?
2条答案
按热度按时间pdkcd3nj1#
这种情况在Git中很正常,它使用分支的方式与大多数传统的版本控制系统(VCS)非常不同。实际上,这里隐藏着一个相当深刻的哲学问题:请参阅What exactly do we mean by "branch"?
分支名称标识提示提交
在大多数VC中,分支的 name 是很重要的,甚至可能是关于分支的最重要的东西。分支 names 在Git中几乎没有价值(对Git本身来说)。对Git来说,重要的是 commits。Commits是永久的--好吧,大部分是永久的--并且不可改变:一旦提交,任何提交都不能被改变。但是每个提交的真实名称都是一串可怕的、笨拙的、不发音的、不可能记住的数字和字母,比如
fe0a9eaf31dd0c349ae4308498c33a5c3794b293
。这些对人类来说是不好的,所以Git让我们用名字来代替这些原始的哈希ID。然而,关于每个提交的另一个重要的事情是,任何一个提交都存储了另一个提交的真实名称--哈希ID,我们称之为提交的 * 父 * 或 * 前趋 *。我们说这个 * 子 * 提交 * 指向 * 它的父。1如果我们取一串无法发音的哈希ID,并将它们按照“most grandparent-y”到“most child-y”的顺序排列,我们会得到如下结果:
最像子提交的提交得到分支名称,然后这个名称指向 last 提交:
Git使用最后一个(或 tip)提交以查找其父项,然后使用父项查找祖父项,依此类推,但是因为哈希ID看起来是随机的--并且故意地几乎不可能预测--甚至Git自己也想有一个 name,通过它可以找到链中的 * 最后一个 * 提交。哈希ID特别重要,因为Git会使用这个提交来查找其他提交。这给我们一个类似于下面的画面:
(我已经停止绘制箭头的内部反向,并将每个提交的哈希ID替换为圆点)。
不过,中间一行的提交有点令人费解:它们在哪个分支上?Git的答案是它们在 * 两个 * 分支上。Git提交不是属于第一次提交的分支,而是属于 * 每一个 * 分支--嗯,每一个分支 name--指向它。
要在某个分支上添加新的提交,你可以对该分支执行
git checkout
,照常操作,根据需要执行git add
,然后运行git commit
,这样就写出了一个新的提交,它指向当前提交作为父提交:然后,无论 * 新 * 提交的哈希值是什么,Git都会将哈希值 * 写入分支名称 *。为了知道 * 哪个 * 名称需要更新,Git会将你的
HEAD
附加到其中一个分支名称上。一旦新提交的哈希值被安全存储,我们就可以将更新后的图片画成:这是树枝生长的正常方式之一。
1子进程会记住父进程,而不是父进程记住子进程。由于提交是不可变的,因此这是必要的。就像人类的父进程和子进程一样,父进程在子进程创建时就已经存在,但子进程在父进程创建时还不存在。由于提交只能记住过去,因此父进程不能回忆其子进程。
标记还标识提交
标记名就像分支名一样,直接指向一个提交。但与分支名不同的是,Git不会自动 * 更改 * 标记名以使其指向任何其他提交。事实上,一般来说,你也不应该这么做--这并不是说它会破坏你自己的Git,而是它会破坏其他人 * 对 * 你的Git仓库的 * 期望。一旦他们有了标记名到哈希ID的Map,他们可能会认为从那时起他们就有了正确的哈希ID,因为标记并不像分支名称那样移动。因此,如果我们标记一些提交:
然后添加另一个提交:
标签保持在原位。
可以随时删除名称
如果我们认为
branch2
是一个坏主意,我们可以git checkout branch1
,然后删除 namebranch2
。没有了名称branch2
,我们刚刚添加的最后一个提交将不再是可查找的:然而,tag 名称
v1.2
仍然存在,它使得标记提交是可找到的。该标记提交位于 no 分支上(在图中,它的父代和祖父代都不是,但它的曾祖父代仍然位于branch1
上)。名称保护提交
我在上面提到过,提交 * 大多数 * 是永久性的。最后一个提交,它已经没有名字了,现在是 unprotected。Git有一个叫做 garbage collector 的设备,它就像一个死神一样清除剩余的、不需要的东西。这个死神收集器,
git gc
,会在整个Git数据库中搜索所有的提交,同时也使用all names 来查找所有提交。可以通过某个名称(任何名称,包括标记名)找到的提交将被标记为保留。* 不可 * 通过这种方式找到的提交(和其他Git对象),以及从命名提交中 * 不可访问 * 的提交,将被收集并销毁。这个过程让Git可以自由地生成对象,并且只在最后一刻才决定真实的使用它们。它还允许你随时移动分支名称。只要提交有名称保护,它们就会一直存在。一旦它们 * 没有 * 名称,它们将可用于垃圾回收。这就是您如何像
git stash
这样的命令的工作原理是创建不在分支上的提交,但这些提交受refs/stash
名称的保护(或者它的 reflog,我在这里就不详细介绍了)。最终git gc
真实的地将其移除。标签保护带标签的提交,以及任何更早的(父)提交,就像分支名称一样。如果你删除标签,现在未命名的提交将容易受到
git gc
的攻击。但在此之前,即使它根本不在分支上,它也可以很高兴地继续存在。请注意,由于GitHub特有的和内部的原因,GitHub 在默认情况下 * 从不 * 对提交进行垃圾收集,即使Git现在已经丢弃了它。因此,如果你知道提交的哈希ID,并且它 * 曾经 * 存在于GitHub上的某个仓库中,你仍然可以通过它的哈希ID在GitHub上的那个仓库中访问它。你可以要求GitHub操作人员手动清除它(尽管当他们收到邮件时,数据可能已经逃逸了--有“scraperbot”在寻找这些东西!--因此建议在发现这类问题时立即更改密码)。
of1yzvn42#
因为这个commit也是一个标记,正如你在这里看到的:
可以是几个选项:
1.它位于给定的分支上,但该分支已被删除
1.在分支上提交,并在分支上执行
reset
1是最有可能发生的。
内容是在功能分支中开发的,最后一次提交被赋予一个标记,分支被删除
下面是一个示例场景:
第一次