这只是Schwern's answer 的扩展,这是正确的,但您可能需要在所有文件中添加一个git rm -r,可能还需要添加一个git clean。还有一种极端情况,您根本无法执行任何操作,而且您可能并不想删除文件。
长
对此有多种可能的答案,具体取决于您真正要寻找的内容。让我们从一些背景和定义开始:
-
一个新的、完全空的存储库没有提交。只有当它标识了一个 commit 并且没有提交时,分支名称才能存在。所以这个存储库没有分支。奇怪的是,你仍然是 on 一个分支。只是你所在的分支不存在。
在这种存储库中,您只有一个选择,即在“孤儿分支”上。我们稍后再讨论这个问题。
-
否则——当有 个提交时——你可以拥有任意数量的有效的现有分支名称,每个分支名称都必须指向某个现有的有效提交。您可以恰好在这些分支名称之一上,或者您可以处于 分离 HEAD 模式,或者您可以使用“孤立分支”模式。
孤儿分支模式是这三种模式中最奇怪的一种,但我们先描述它,然后再描述其他两种:
-
孤儿模式:HEAD 包含一个分支名称。这是当前分支,所以你在一个分支上。奇怪的是你所在的分支不存在。 git branch 命令将列出确实存在的分支名称,但不会列出您所在的分支名称,因为它不存在。
-
普通模式:HEAD 包含一个分支名称。那是当前分支,并且存储在in中的提交哈希ID是当前提交。
-
Detached-HEAD 模式:HEAD 包含提交哈希 ID。那是当前的提交;没有当前分支。
git clone 通常会导致正常模式。成为当前分支的分支名称是您在git clone 时间选择的名称,带有-b 选项。不过,这条规则也有一些例外:如果你的 -b 命名了一个 标签,Git 会检查该标签,让你进入 detached-HEAD 模式。如果您没有指定 -b 选项,您的 Git 会询问其他 Git 推荐的分支名称,并使用该名称或备用名称;如果该名称或备用名称未能命名分支,您将进入孤立模式,将不存在的分支作为当前分支。如果您确实指定了 -b 选项,则该名称必须命名另一个 Git 存储库中的现有分支或标记,否则整个克隆命令将失败。
-n 选项对新克隆的模式没有影响。您处于正常、分离 HEAD 或孤儿模式,就像没有它一样。 -n 选项的唯一效果是它阻止了初始 git checkout。使用普通模式时,分支名称仍然在本地创建,因此如果您克隆的存储库中有分支,您将位于其中一个分支上,该分支名称存在于本地并指向与远程相同的提交- 跟踪名称。这是一个奇怪的特殊情况,我认为这是一个小错误,因为如果你运行git init、git remote add 和git fetch 而不执行git checkout,你将被留在孤儿模式。 (当使用 git clone 本身以外的命令时,创建分支的是 git checkout 步骤,因此跳过它应该让您处于孤立模式。但事实并非如此。)
Git 的索引和你的工作树
以上都是关于HEAD——其中存储了什么,哪个分支和/或提交是当前的,如果有的话——以及分支名称。但是当我们在一个非裸存储库中查看时,我们会看到一堆文件,存储为普通的日常文件。这些文件不是 Git 中的文件。 Git 中的 is 是分支和标签以及其他名称(作为一种辅助数据库)以及一系列 commits 和支持对象,存储在主数据库中(以及通常更大)数据库。
提交功能作为档案和作为历史。每个提交都存储每个文件的完整快照,就像在 tar 或 rar 或 zip 存档中一样。每个提交还存储一些元数据,包括提交人的姓名等。 在这些档案中的所有内容都是严格的,完全只读的:本质上是这样,因为它们都是通过加密强大的哈希函数产生的数字来寻址的。任何更改任何存储数据的尝试都会导致不正确的哈希值,1 Git 检测到,并报告为损坏的存储库。
但如果无法更改这些文件——甚至在大多数程序中查看它们——这些档案将毫无用处。因此,Git 会将档案提取到工作树:您可以在其中查看和使用文件的区域。这是——至少最初是——你在你的工作树中拥有的东西:提取一些提交的结果。提取的提交是当前提交。
从技术上讲,这就是我们所需要的:作为档案和历史的提交,以及一个工作副本。当我们将它们视为“当前提交”和“工作树副本”时,这是两个副本。这些都是一些版本控制系统中的全部内容。但无论出于何种原因,Git 都会插入每个文件的 第三个 副本,在您的工作树中永久冻结的当前提交副本和可读、可写、有用的版本之间。这使得 工作树副本 成为 第三个 副本,就像是:每个文件的第二个副本都存在于 Git 的 index 中。
Git 索引中文件的格式 与提交中的文件相同:它们是预压缩和预去重的。提交档案中的文件都经过重复数据删除,这通常可以节省大量空间。 Git 在这里快速运行的一个技巧是 index 副本是 pre-de-duplicated,因此在提交时不需要工作。这意味着文件的索引副本几乎不占用空间。例如,Git 项目的扩展文件在我的一台机器上占用了大约 57 MiB,但保存这些相同文件所需的索引只有 368790 字节。 (注意:这些数字都不计入.git 目录。)但是原则上有三个提交副本:HEAD——提交本身——加上索引加上工作树副本。
1除非,也就是说,您可以花费足够的计算时间来产生哈希冲突。请参阅 How does the newly found SHA-1 collision affect Git? 请注意,这不是偶然发生的,即使在今天,大多数团体也不可能故意这样做(尽管它不再超出 Google 等大公司的能力,也不是各个民族国家的能力范围)。
这一切与git clone -n 有何关系
当您使用git clone -n 时,您将获得HEAD 的三种模式之一:孤立分支、分离的HEAD 或正常。但是 Git 不运行 git checkout,它是 git checkout 填充 Git 的索引和你的工作树。所以你有一个名义上为空的索引和工作树。2
因此,如果您希望准确地重现此条件,您需要:
- 确定使用哪种
HEAD 设置;和
- 清空索引和工作树。
在第 1 部分中,为了简单起见,您可以简单地假设正常模式并且什么都不做。在第 2 部分中为简单起见,您可以使用 git read-tree --empty,它会擦除索引,然后是带有各种选项的 git clean。您可以使用git read-tree --empty -u 删除所有索引文件,在工作树中只留下未跟踪 文件。或者您可以选择不理会工作树。
如果您希望重现分离的 HEAD(使第 1 部分复杂一点),您有两个选择:
- 运行
git checkout --detach 或git checkout 的任何不是分支名称,或
- 使用 Git 2.23 或更高版本,使用任何提交说明符运行
git switch --detach。
指定的提交(或HEAD 提交,当使用不带参数的git checkout --detach 时)成为当前提交,您现在处于分离头模式。您在此处签出的提交(或使用 git switch 切换到)将填充 Git 的索引并更新您工作树中的文件,Checkout another branch when there are uncommitted changes on the current branch 中列出的特殊情况除外。
要进入孤儿模式,请使用git checkout --orphan 或git switch --orphan。请注意这种偷偷摸摸的不兼容性:旧的 checkout 方法使 Git 的索引和您的工作树不受干扰。 git switch 命令清空索引并清理工作树,就像使用 git read-tree --empty -u 一样。
(在所有情况下,未跟踪文件都不受干扰,无论这些未跟踪文件是否也被忽略。)
2空索引是一个非零长度的文件,因为索引有标题和尾。这些包含加密哈希,以便检测磁盘损坏,就像存储库对象数据库一样。为了方便这项工作,Git 将 不存在的 索引视为“空”,并在内存中创建空索引,并在合适时使用正确的校验和写入它。 p>
工作树的顶层通常包含.git 目录,它是适当的存储库,因此“空”工作树永远不会相当为空。但是,您可以使用各种选项将存储库和工作树目录分开。
最后的说明和结论
无论您进入何种模式,请注意运行 git commit 现在将像往常一样尝试创建新提交:
-
在孤儿模式下,这将创建一个没有父级的新提交(一个新的根提交)并创建名称在HEAD 中的分支。分支现在存在,并持有一个根提交,未通过提交图连接到任何其他提交。
(在结束合并时执行此操作可能是个坏主意。我不知道如果您尝试此操作会发生什么。)
-
在 detached-HEAD 模式下,这将创建一个具有通常父级的提交(当前提交作为父级,加上正在进行的合并中的任何其他提交)。然后,Git 会将新提交的哈希 ID 存储在 HEAD 中,该 ID 继续分离,但现在指向只能通过 HEAD 找到的提交。
-
在普通模式下,这将像往常一样创建一个新的提交(包括像往常一样结束合并,就像使用 detached-HEAD 模式一样),然后将新提交的哈希 ID 存储在存储在 HEAD 中的分支名称中.
新提交将存储 Git 索引中的任何文件作为其快照。如果你清空了索引,那就是empty tree。如果您将文件留在或放入索引中,这些文件就是快照中的文件。
可能没有真正的理由这样做,但这些不同模式中最安全的可能是具有空索引和空工作树的新孤儿分支模式。这样,没有人会意外地git commit 某个现有分支上的新空树。其中最简单的可能是 detached-HEAD 模式;这样一来,您就可以清理或不清理索引和工作树。