在 git 仓库中,我可以将“nothing”选中吗?

4

假设我有一个git仓库,正在使用一些分支,并且在某个时刻我想要没有任何分支被检出。我能做到吗?

换句话说,我能否在现有的仓库中获得git clone path-to-repo --no-checkout的效果?

编辑:实际上,如果主分支文件不会显示为删除,那将会更好。所以,理想的答案不应该完全像带有--no-checkoutgit clone

注:如果您的建议需要一个较新版本的git,请说明。

2个回答

6
这只是Schwern's answer的扩展,它是正确的 - 但您可能需要添加git rm -r来删除所有文件,也可能需要git clean。还有一种特殊情况,您可以什么都不做,也许您真的不想删除文件。

有几个可能的答案,取决于您真正想要的是什么。让我们从一些背景和定义开始:
一个全新的空仓库没有任何提交。只有当分支名称标识着一个提交时,该分支名称才能存在,由于没有提交可以标识,所以这个仓库没有分支。奇怪的是,你仍然在一个分支上,只不过你所在的分支不存在。

在这种类型的仓库中,你只有一种选择,那就是使用“孤儿分支”。我们马上会回到这个话题。

否则,在有提交时,你可以拥有任意数量的有效、已存在的分支名称,每个分支名称必须指向某个已存在的有效提交。你可以在其中一个这些分支名称上,或者处于断头状态,或者使用“孤儿分支”模式。
孤儿分支模式是三种模式中最奇特的一种,但让我们先描述它,然后再介绍另外两种:
  • 孤儿模式: HEAD 包含一个分支名称。这是当前的分支,所以你在一个分支上。奇怪的是,你所在的分支实际上是不存在的。执行 git branch 命令会列出存在的分支名称,但不会列出你所在的分支名称,因为它不存在。

  • 正常模式: HEAD 包含一个分支名称。那就是当前的分支,存储在该分支名称中的提交哈希值是当前提交。

  • 分离 HEAD 模式: HEAD 包含一个提交哈希值。那就是当前的提交;没有当前的分支。

git clone 命令通常会进入正常模式。成为当前分支的分支名称是您在 git clone 时选择的,使用您的 -b 选项。但是,有一些例外情况:如果您的 -b 指定了一个标签(tag),Git 将检出该标签,将您置于分离头状态。如果您没有指定 -b 选项,则您的 Git 会询问其他 Git 推荐的分支名称,并使用该名称或备用名称;如果该名称或备用名称未能命名分支,则您将进入孤立模式,当前分支为不存在的分支。如果您指定了 -b 选项,则名称必须命名另一个 Git 存储库中的现有分支或标签,否则整个克隆命令将失败。

-n选项对新克隆的模式没有影响,您将像没有使用该选项一样处于普通、分离的HEAD或孤立的模式。 -n选项的唯一影响是它防止了初始的git checkout。在使用正常模式时,分支名称仍然会在本地创建,因此,如果您克隆的存储库中有分支,您将在其中一个分支上,并且该分支名称在本地存在并指向与远程跟踪名称相同的提交。这是一个奇怪的特殊情况,我认为这是一个小错误,因为如果您运行了git initgit remote addgit fetch而没有执行git checkout,则会被留在孤立模式中。(除了git clone本身之外的命令,创建分支的步骤是git checkout,因此跳过它应该会使您留在孤立模式中。但事实并非如此。)

Git的索引和工作树

上述内容涉及 HEAD——存储在其中的内容,以及当前所处的分支和/或提交,如果有的话——以及分支名称。但是当我们在非裸库中查看时,我们会看到一堆文件,它们被存储为普通的日常文件。这些文件不是 Git 中的内容。Git 中的内容是分支、标签和其他名称——作为一种次要数据库——以及一系列提交和支持对象,存储在主(通常更大)的数据库中。
这些提交既充当档案又充当历史记录。每个提交都存储了每个文件的完整快照,就像在 tar 或 rar 或 zip 存档中一样。每个提交还存储了一些元数据,包括制作提交的人的姓名等等。这些存档中的所有内容都是严格的只读的:本质上如此,因为它们都是由加密强哈希函数产生的数字进行寻址的。任何尝试更改任何存储的数据的尝试都会导致不正确的哈希值1,Git 会检测到并报告为损坏的仓库。
但是如果没有能力更改这些文件——甚至在大多数程序中也无法查看它们——这些存档将毫无用处。因此,Git将会把一个归档文件提取到一个“工作树”中:这个区域可以让您查看和使用文件。这就是您的工作树中最初具有的内容:提取某个提交的结果。被提取的提交是“当前提交”。

从技术上讲,这就是我们所需要的:提交作为存档和历史记录,以及工作副本。当我们将它们视为“当前提交”和“工作树副本”时,就有两个副本。在某些版本控制系统中,这就是所有的副本。但出于某种原因,Git插入了每个文件的第三个副本,在永久冻结的当前提交副本和可读、可写、有用的工作树版本之间。这使得“工作树副本”成为了“第三个”副本,可以说每个文件的第二个副本存在于Git的“索引”中。
Git索引中文件的格式与提交中的文件格式相同:它们是预压缩和预去重的。提交归档中的文件都是去重的,这通常可以节省大量空间。Git用于快速处理的一个技巧是索引副本是预先去重的,因此在提交时不需要进行任何操作。这意味着此处的文件索引副本几乎不占用空间。例如,Git项目的扩展文件在我的机器上占用约57 MiB,但持有相同文件的索引仅为368790字节。(注意:这两个数字都不包括.git目录。)但是原则上,提交有三个副本:HEAD-提交本身-加上索引和工作树副本。

1但前提是,你必须花足够的计算时间来产生哈希冲突。请参见 新发现的SHA-1碰撞如何影响Git?。请注意,这不是偶然发生的,对于大多数团体而言,即使是有意为之,也是不切实际的(虽然像谷歌这样的大公司以及各个国家机构已经具备了这种能力)。

这一切与git clone -n有何关系?

当使用git clone -n时,会得到HEAD的三种模式之一:孤立分支、分离头指针或正常模式。但Git不会运行git checkout,而是git checkout填充Git的索引和您的工作树。因此,您将拥有一个名义上为空的索引和工作树。2

因此,如果您希望精确地再现这个条件,则需要:

  1. 确定要使用哪种HEAD设置;以及
  2. 清空索引和工作树。
在第一部分中,为了简化起见,您可以假设正常模式并且不做任何操作。在第二部分中,您可以使用git read-tree --empty,它会清除索引,然后使用各种选项的git clean。您可以使用git read-tree --empty -u来删除所有已索引的文件,在工作树中仅保留未跟踪的文件。或者您可以选择保持工作树不变。
如果您希望复制一个分离的HEAD(使第一部分有点复杂),则有两个选择:
  • 运行git checkout --detachgit checkout,但不是分支名称,或者
  • 使用Git 2.23或更高版本,使用任何提交说明符运行git switch --detach
指定的提交(或使用git checkout --detach而没有参数时的HEAD提交)成为当前提交,您现在处于分离头状态。您在此处检出的提交(或使用git switch切换到的提交)将填充Git的索引并更新工作树中的文件,但除了在当前分支有未提交的更改时检出另一个分支中概述的特殊情况外。
要进入孤儿模式,请使用git checkout --orphangit switch --orphan。请注意这种隐蔽的不兼容性:旧的检出方法保留Git的索引和您的工作树不受干扰。git switch命令会清空索引并像使用git read-tree --empty -u一样清理您的工作树。
(在所有情况下,无论这些未跟踪的文件是否被忽略,它们都不会受到干扰。)

2一个空的索引是一个非零长度的文件,因为索引具有头部和尾部。它们包含加密哈希,以便检测磁盘上的损坏,就像存储库对象数据库一样。为了方便起见,Git将不存在的索引视为“空”,并在内存中创建空索引,并在适当时候用正确的校验和写入。

工作树的顶层通常包含.git目录,这是存储库本身,因此“空”工作树永远不会完全为空。但是,您可以使用各种选项将存储库和工作树目录分开。


最终注意事项和结论

无论你选择哪种模式,注意运行git commit将会像往常一样尝试创建新的提交:

  • 在孤立模式下,这将创建一个没有父级(即一个新的根提交)的新提交,并创建其名称在HEAD中的分支。该分支现在存在,并持有一个根提交,不通过提交图连接到任何其他提交。

    (在合并结束时执行此操作可能是个坏主意。我不知道如果你尝试这样做会发生什么。)

  • 在脱离HEAD模式下,这将创建一个带有通常父级(当前提交作为父级,加上正在进行的合并的任何其他提交)的提交。Git将在HEAD中存储新提交的哈希ID,它仍然是脱离的,但现在指向一个只能通过HEAD找到的提交。

  • 在正常模式下,这将像通常一样创建新的提交(包括像脱离HEAD模式一样结束合并),然后将新提交的哈希ID存储在HEAD中存储的分支名称中。

新提交将存储Git索引中的所有文件作为其快照。如果您清空了索引,那么这是一个空树。如果您在索引中留下或放入文件,则这些文件是快照中的文件。
可能没有真正的理由去做任何这些事情,但这些各种模式中最安全的可能是使用空索引和空工作树的新孤立分支模式。这样,没有人会意外地在某个现有分支上git commit一个新的空树。其中最简单的可能是分离的HEAD模式;这样,您可以不太担心地清除索引和工作树,也可以不清除。

  1. 请查看我的澄清编辑。
  2. 根据您的描述,我认为我想要的是一个孤立的提交,其中没有任何文件,这样git status就不会抱怨任何东西被删除了;并且没有分支是当前的。
  3. 当使用git checkout --orphan时,至少在我的git版本(2.20.1)中需要为孤立分支命名。
- einpoklum
是的,git switch --orphan <name>(或者使用 git checkout 但需要使用 git rmgit read-tree --empty 技巧)。请注意,git switch 是在2.23版本中新增的。 - torek
谢谢,git checkout --detach ... 对我有用。 - Meir Gabay

2

是的,删除这些文件。

git clone --no-checkout 仍然在一个分支上,只是没有文件。


好的,我明白你的意思,但实际上,那不是我想要的。 当我说我想要达到 git clone --no-checkout 的确切效果时,我说错了。请看澄清的编辑。 - einpoklum
1
@einpoklum 你做不到。除非存储库是裸的或空的,据我所知,总是有一个已检出的提交。你可以做一些愚蠢的事情,比如创建一个没有文件的孤立提交并将其检出。也许你可以解释一下你想要实现什么? - Schwern
@Schwern他想要实现的目标非常简单:返回到初始的空目录状态,因为文件还不需要或者需要空间。无论出于什么原因,回到克隆后的空工作副本是一个理想的需求。 - Laurent Giroud
@LaurentGiroud 这需要什么进程? - Schwern
@Schwern 如果你想了十分钟,我相信你一定能找到一些解决方案。 ;)假设我正在使用分离的 HEAD 来检查多个存储库的内容,并且我选择一个空的工作副本作为“完成”的约定。或者我只是需要磁盘空间。或者我不想麻烦地检查 git 状态以知道我当前是否在处理存储库,如果工作副本为空,则无需运行任何命令。 - Laurent Giroud
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接