我可以在 git 存储库中检出 "nothing" 吗?

Can I have "nothing" checked out in a git repository?

假设我有一个 git 存储库,在一些分支上工作,并且在某个时候我想要 no 分支签出。我可以这样做吗?

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

编辑: 实际上,如果主分支文件不显示为已删除会更好。所以,理想的答案不会像 git clone--no-checkout.

注意:如果您的建议需要 git 的最新版本,请说明。

是,删除文件。

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

这只是 Schwern's answer 的扩展,这是正确的 — 但您可能需要添加所有文件的 git rm -r,也许还需要添加 git clean。还有一种极端情况,您根本无能为力,也许您真的不想删除文件。

对此有多种可能的答案,具体取决于您真正要查找的内容。让我们tar了解一些背景和定义:

  • 一个新的、完全空的存储库没有提交。一个 b运行ch 名称只有在它标识一个 commit 并且没有提交时才能存在 none 来标识。所以这个存储库没有 b运行ches。奇怪的是,您仍然 on a b运行ch。只是你所在的b运行频道不存在.

    在这种存储库中,您只有一个选择,即在“孤儿 b运行ch”上。我们稍后会回到这个问题。

  • 否则——当有 提交时——你可以有任意数量的有效的、现有的 b运行ch 名称,每个名称都必须指向一些现有的,有效的承诺。您可以恰好位于这些 b运行ch 名称之一,或者您可以处于 detached HEAD 模式,或者您可以使用“orphan b运行ch " 模式。

orphan b运行ch 模式是三个模式中最奇怪的,但让我们先描述它,然后再描述其他两个:

  • 孤立模式:HEAD 包含一个 b运行ch 名称。这是当前的 b运行ch,所以你在 b运行ch 上。奇怪的是你在 上的 b运行ch 不存在 git branch 命令将列出确实存在的 b运行ch 名称,但不会列出您所在的 b运行ch 名称,因为它不存在。

  • 普通模式:HEAD 包含一个 b运行ch 名称。也就是当前的b运行ch,commit hash ID存储在in b运行ch name就是当前的commit.

  • Detached-HEAD 模式:HEAD 包含提交哈希 ID。那是当前的提交;当前没有b运行ch.

A git clone 通常会导致正常模式。成为当前 b运行ch 的 b运行ch 名称是您在 git clone 时 select 使用您的 -b 选项。但是,此规则有一些例外情况:如果您的 -b 命名为 标签 ,Git 将签出该标签,将您放入 detached-HEAD模式。如果您没有指定 -b 选项,您的 Git 会询问其他 Git 什么 b运行ch 名称 他们 推荐,并使用该名称或后备名称;如果该名称或后备名称未能命名为 b运行ch,您将进入孤儿模式,nonexistent b运行ch 作为当前 b运行ch .如果您确实指定了 -b 选项,该名称必须命名另一个 Git 存储库中现有的 b运行ch 或标记,否则整个克隆命令将失败。

-n选项对新克隆的模式没有影响。您处于正常、detached-HEAD 或孤儿模式,与没有它时完全一样。 -n 选项的唯一影响是它阻止了初始的 git checkout。 b运行ch 名称仍然在本地创建,当使用正常模式时,因此如果在您克隆的存储库中有 b运行ches,您将在其中之一,与 b 运行ch 名称存在于本地并指向与 remote-tracking 名称相同的提交。这是一个奇怪的特殊情况,我认为这是一个小错误,因为如果您 运行 git initgit remote addgit fetch 没有执行 git checkout相反,您将处于孤儿模式。 (这是创建 b运行ch 的 git checkout 步骤,当使用 git clone 本身以外的命令时,因此跳过它 应该 让你成为孤儿模式。但它没有。)

Git 的索引和您的工作树

以上是关于 HEAD 的所有内容——它存储了什么,b运行ch and/or 提交是当前的,如果有的话——和 b运行ch名字。但是当我们在 non-bare 存储库中环顾四周时,我们看到一堆 文件 ,存储为普通的日常文件。这些文件不是 Git 中的文件。 在Git中的是b运行ch和tag等名称——作为一种二级数据库——也是一系列提交 和支持 objects,存储在主要(通常更大)数据库中。

提交功能作为存档并且作为历史。每个提交都会存储每个文件的完整快照,就像在 tar 或 rar 或 zip 存档中一样。每个提交还存储一些元数据,包括提交人的姓名等。 这些档案中的所有东西都是严格的,完全read-only:本质上是这样,因为它们都是通过cryptographically-strong散列函数产生的数字来寻址的。任何 更改 任何存储数据的尝试都会导致不正确的散列值,1 Git 检测到,并报告为 c存储库损坏。

但是由于无法更改 这些文件——甚至在大多数程序中无法查看它们——这些档案将毫无用处。所以 Git 将 提取 一个存档到 工作树: 一个你可以看到和使用你的文件的区域。这是——至少最初——你在工作树中拥有的:提取一些提交的结果。提取的提交是 当前提交.

从技术上讲,这就是我们所需要的:作为 archives-and-history 的提交和一个工作副本。当我们将它们视为“当前提交”和“工作树副本”时,这是两个副本。这些就是某些版本控制系统中的全部内容。但无论出于何种原因,Git 都会在 frozen-for-all-time current-commit 副本和可读、可写、有用的版本之间插入每个文件的 第三个 副本在你的工作树中。这使得 working tree copy 成为 third 副本,可以说:每个文件的第二个副本存在于 Git's 索引.

Git 索引中文件的 格式 是提交文件的格式:它们是 pre-compressed 和 pre-de-duplicated .提交存档中的文件都是 de-duplicated,通常可以节省大量 space。 Git 用来快速移动的一个技巧是 index 副本是 pre-de-duplicated,所以有提交时不需要任何工作。这意味着文件的索引副本几乎不需要 space。例如,Git 的 Git 项目的 expanded-out 文件在我这里的一台机器上占用了大约 57 MiB,但保存这些相同文件所需的索引仅为 368790 字节。 (注意:这些数字都不算 .git 目录。)但是 原则上 有三个提交副本:HEAD——提交本身——加上索引加上工作树副本。


1除非,也就是说,你可以花费足够的计算时间来产生散列冲突。请参阅 How does the newly found SHA-1 collision affect Git? 请注意,这不是偶然发生的,对于大多数团体来说,即使今天是故意这样做也是不切实际的(尽管它不再超出像 Google 这样的大公司的能力,也不是各种 nation-states).

这一切与 git clone -n

有何关系

当您使用 git clone -n 时,您会获得 HEAD 的三种模式之一:孤儿 b运行ch、分离的 HEAD 或正常。但是Git没有运行git checkout填写的是git checkoutGit的索引和你的工作树。所以你有一个 nominally-empty 索引和工作树。2

因此,如果您想完全重现这种情况,您需要:

  1. 确定要使用哪种 HEAD 设置;和
  2. 清空索引和工作树。

为了第 1 部分的简单性,您可以简单地假设正常模式,什么都不做。为简单起见,在第 2 部分中,您可以使用 git read-tree --empty,它会删除索引,然后使用带有各种选项的 git clean。您可以使用 git read-tree --empty -u 删除所有 索引文件 ,仅在工作树中留下 未跟踪 文件。或者您可以选择单独保留工作树。

如果你想复制一个分离的 HEAD(使第 1 部分稍微复杂一点),你有两个选择:

  • 运行 git checkout --detachgit checkout 任何非 b运行ch 名称
  • 使用 Git 2.23 或更高版本,运行 git switch --detach 使用任何提交说明符。

指定的提交(或 HEAD 提交,当使用不带参数的 git checkout --detach 时)成为当前提交,您现在处于 detached-HEAD 模式。您在此处签出的提交(或使用 git switch 切换到)将填充 Git 的索引并更新您工作树中的文件,Checkout another branch when there are uncommitted changes on the current branch 中列出的特殊情况除外。

要进入 orphan 模式,请使用 git checkout --orphangit switch --orphan。请注意这种偷偷摸摸的不兼容性:旧的结帐方法使 Git 的索引和您的工作树不受干扰。 git switch 命令清空索引并清理您的工作树,就好像使用 git read-tree --empty -u.

(在所有情况下,未跟踪的 文件未受干扰,无论那些未跟踪的文件是否也被忽略。)


2空索引是一个 non-zero-length 文件,因为索引有 headers 和尾部。这些包含加密哈希,以便检测 on-disk 损坏,就像存储库 object 数据库所做的那样。为了方便这项工作,Git 将 non-existent 索引视为“空”,并在内存中创建空索引,并用正确的校验和写入一次这样做是合适的。

工作树的顶层通常包含 .git 目录,它是适当的存储库,因此“空”工作树永远不会 相当 空。但是,您可以直接拆分存储库和工作树有多种选择。


最后的说明和结论

无论您进入何种模式,请注意 运行ning git commit 现在将像往常一样尝试创建新提交:

  • 在孤立模式下,这将创建一个没有 parent 的新提交(一个新的 root 提交)并创建 b运行ch 其名称在 HEAD 中。 b运行ch 现在存在,并持有一个根提交,未通过提交图连接到任何其他提交。

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

  • 在 detached-HEAD 模式下,这将创建一个具有通常 parent 的提交(当前提交为 parent,加上来自 in-progress合并)。 Git 然后将新提交的哈希 ID 存储在 HEAD 中,它继续分离,但现在指向一个可以 只能 通过 [=] 找到的提交12=].

  • 在正常模式下,这将像往常一样创建一个新提交(包括像往常一样结束合并,就像 detached-HEAD 模式一样),然后存储新提交的哈希 ID在 b运行ch 名称中存储在 HEAD.

新提交将存储 Git 索引中的任何文件作为其快照。如果清空索引,则为 empty tree。如果您在索引中留下或放置了文件,这些文件就是快照中的文件。

可能没有真正的理由这样做,但这些不同模式中最安全的可能是具有空索引和空工作树的new-orphan-branch模式.那样的话,没有人会不小心 git commit 在现有的 b运行ch 上出现一棵新的空树。其中最简单的大概就是detached-HEAD模式;这样,您可以不用太担心清理索引和工作树。