有什么方法可以让我从跟踪中删除包文件夹,同时在提交提交时不会导致包从远程删除?
没有。
幸运的是,您可能不需要。
不幸的是,你在这里做的任何事情都会有些难看和使用起来很痛苦。
我已经阅读了有关假设不变和跳过工作树的信息...但我不确定使用哪个命令以及这些命令中的任何一个会对遥控器产生什么影响(如果有的话)?
两者都可以,但--skip-worktree 是您应该在这里使用的。两者都不会对任何其他 Git 存储库产生任何影响。
要理解所有这些,您需要一个 Git 实际工作的正确模型。
首先请记住,Git 中的基本存储单元是 commit。每个提交都有一个唯一的、又大又丑的哈希 ID,例如 083378cc35c4dbcc607e4cdd24a5fca440163d17。该哈希 ID 是提交的“真实名称”。各地的每个 Git 存储库都同意 that 哈希 ID 是为 that 提交保留的,即使所讨论的 Git 存储库还没有 那个提交。 (这就是 Git 中所有真正魔力的来源:这些看似随机但实际上完全非随机的哈希 ID 的唯一性。)
提交存储的内容分为两部分:数据,由所有文件的快照组成;加上 元数据,Git 存储诸如谁提交、何时(日期和时间戳)以及为什么(日志消息)等信息。作为元数据的关键部分,每个提交还存储一些 previous 提交哈希 ID,作为文本中的原始哈希 ID。这让 Git 从任何给定的提交 backwards 转到某个先前的提交。
任何 Git 提交的实际哈希 ID 只是其所有数据的校验和。 (从技术上讲,它只是元数据的校验和,因为快照本身存储为一个单独的 Git 对象,其哈希 ID 进入提交对象。但是,这个单独的对象的哈希 ID 也是校验和,所以通过 @987654321 的数学@,一切都解决了。)这就是为什么提交中的所有内容都是完全只读的,并且一直被冻结。如果您尝试更改提交中的任何内容,您实际上并没有更改该提交。相反,您会得到一个 new 提交,它带有一个新的和不同的哈希 ID。旧的提交仍然存在,其哈希 ID 未更改。
所以:Git 是关于提交的,Git 通过它们的哈希 ID 查找提交。但是我们人类无法处理哈希 ID(快速,是 08337-something 还是 03887-something?)。我们想要名字,比如master。同时,Git 想要一种快速的方法来找到在某个点结束的某个提交链中的 last 提交。所以 Git 通过让我们创建分支名称来为我们提供名称。
分支名称只是保存某个链中last 提交的哈希 ID。该提交作为它的 parent 持有链中 previous 提交的哈希 ID。父提交作为它的父提交——我们最后一次提交的祖父提交——持有更远一步提交的哈希 ID,依此类推:
... <-F <-G <-H <-- master
如果提交哈希 ID 是像 H 这样的单个字母,这可能是一个准确的绘图:名称 master 将持有哈希 ID H,提交 H 将持有哈希 ID G 作为其父级, commit G 将持有哈希 ID F 作为其父级,依此类推。
new 提交的行为包括:
- 写出所有文件的快照;和
- 添加适当的元数据:您作为作者和提交者,“现在”作为日期和时间戳,等等。这个新提交的 parent 应该是 current 提交的任何内容,如当前分支名称中记录的那样。如果
master 指向H,那么新提交的父级(我们称之为I)将是H,因此I points back toH`。
在实际进行了这个提交(并在过程中找到了它的哈希 ID)之后,Git 只需将新的哈希 ID I 写入分支名称 master:
... <-F <-G <-H <-I <-- master
我们有一个新的提交。
要查看在诸如I 这样的提交中发生了什么,Git 将提交(其所有文件)提取到一个临时区域,然后将先前提交 H 的文件提取到一个临时区域,并进行比较。对于那些相同的,Git 什么也没说。对于那些不同的,Git 显示了差异。对于那些新的,Git 说它们被“添加”了,而对于那些在之前的提交中但没有在这次提交中的,git 说它们被“删除”了。
现在,对某个特定提交进行git checkout 意味着将该提交的内容(即数据)以您可以使用的形式输出。提交中文件的永久冻结副本采用仅 Git 格式,这对于存档来说很好,但对于完成新工作却无用。因此,Git 必须将提交提取到一个工作区,您可以在其中查看和处理您的文件。 Git 将此工作区称为您的 work-tree 或 working tree(或这些名称的一些变体)。除了在您询问时将文件写入其中之外,Git 基本上不干涉这个工作区域:那是 您的 游乐场,而不是 Git。
但是新提交中的新快照从何而来?在某些版本控制系统中,新快照来自工作树中的文件。这在 Git 中不是。相反,Git 从 Git 的 index 中的任何内容进行新的提交。您无法看到这些文件——至少,不容易——但是当 Git 第一次提取某个提交时,它会有效地将所有该提交的已保存、冻结的文件复制到 Git 的索引中。只有当它们在索引中时,Git 才会将它们复制(并解冻/再水化)到您的工作树中,以便您可以使用它们。
提交中的冻结副本与索引中的“软冻结”副本之间的关键区别在于您可以覆盖索引副本。1您无法覆盖 已提交 副本,但这没关系:提交无法更改,但您可以进行新的和更好的提交,这就是版本控制的意义所在。
每当您运行 git commit 时,Git 在第一步(制作快照)中所做的就是将每个文件的所有预冻结 index 副本打包。因此我们可以将索引视为提议的下一次提交。这也是为什么你必须一直 git add 文件的原因,即使它们已经在之前的提交中。 git add 所做的是将工作树文件复制到该文件索引中的任何内容之上(尽管技术细节再次参见脚注 1)。
这意味着每个文件始终存在三个“实时”副本。一个在当前提交中被冻结。一个是半冻结的,在 index 中,Git 也将其称为 staging area。最后一个是你的副本,在你的工作树中,你可以做任何你想做的事情:它是一个普通的文件,不是一个特殊的 Git-only 格式。
当您运行 git status 时,Git 会运行两个单独的比较:
首先,git status 将当前 (HEAD) 提交中的所有文件与索引中的所有文件进行比较。对于每个相同的文件,Git 什么也没说。对于每个不同的文件,Git 表示该文件已为提交暂存。如果索引中的文件是新文件——不在HEAD 中——Git 称它为新文件;如果某个文件从索引中消失,Git 会说它已删除。
然后,git status 将索引中的所有文件与工作树中的所有文件进行比较。对于每个相同的文件,Git 什么也没说。对于每个不同的文件,Git 表示该文件不会为提交暂存。如果工作树中的文件是新文件(不在索引中),Git 会抱怨该文件未跟踪。如果文件从工作树中消失,Git 会说它已被删除。
最后一种情况是 未跟踪 文件的来源。它还为我们提供了未跟踪的定义:如果工作树中存在的文件也不存在于索引中,则它是未跟踪的。由于我们无法看到索引,我们只看到git status 抱怨这些未跟踪的文件时的情况。
在 .gitignore 文件中列出未跟踪的文件会使 Git 闭嘴:git status 不会再抱怨了。如果文件尚不存在,它也会使git add 不添加 文件到索引中,但它对索引中 的文件没有影响。如果文件在索引中,根据定义,它会被跟踪,git add 会很乐意添加它。
这终于是--assume-unchanged 和--skip-worktree 的用武之地了。这些是您可以在索引中的文件上设置的标志。设置任何一个标志都会告诉 Git:嘿,当你要考虑这个文件的工作树副本时......你现在可以跳过它。也就是说,git add 会查看索引和工作树,并检查.gitignore 文件,以查看跟踪的内容、未跟踪的内容、工作树中更新的内容以及在建议的下一次提交中需要更新的内容,等等。如果某些文件未跟踪并在.gitignore 中列出,git add 将跳过它。如果它被跟踪,如果工作树副本不同,Git 将添加它...除非设置了跳过标志。如果设置了--assume-unchanged 标志,Git 将假定它没有改变,也不会添加它。如果设置了--skip-worktree 标志,Git 知道它绝对不应该添加它,即使文件实际上已更改。
所以--skip-worktree 意味着我们在这里想要的:不要git add 这个文件,即使它已经改变了。--assume-unchanged 标志也可以工作,因为 Git 假设它没有改变并且因此git add 也不是。今天实际操作并没有什么不同,但是“skip worktree”表达了正确的intent。
请注意,由于这些标志设置在文件的 index(也称为暂存区域)副本上,因此它们仅适用于 已跟踪 文件。跟踪的文件是索引/暂存区域中的文件。该文件必须在索引中,然后才能设置标志。而且,如果该文件在索引中,则该文件的 该副本(即现在在索引中的那个)就是将在 next 提交中的那个制作。
但是这个文件的副本是从哪里来的呢?答案在我们之前的git checkout 中:git checkout 将我们选择的提交中的所有文件复制到索引中。它通过我们的第一个git checkout 进入索引,然后进入我们的工作树。如果从那时起我们一直对工作树副本大惊小怪,那么我们设置的 flag 意味着git add 从未将工作树副本复制回索引副本,所以它仍然是相同的作为旧的提交。我们一直在使用保存在索引中的文件的旧副本进行新的提交,可能是几天或几个月或任何时间。
让这件事头疼的是,如果我们 git checkout 一些 other 提交,而另一个提交中包含文件的 不同 副本, Git 将希望用我们尝试切换到的提交中的副本替换我们的索引副本。将其复制到索引不会删除我们设置的标志,但它 覆盖 工作树副本。如果我们更改了工作树副本,Git 会在不询问的情况下覆盖它(这可能很糟糕)或者说:我无法检查那个提交,它会覆盖你的(假设/跳过,但我不会提及)该文件的工作树副本。实际上,Git 采用后一种方法。
要解决这个问题,每次您git checkout 提交将覆盖您的标记文件时,您都必须移动或复制您的工作树副本,let git checkout 覆盖索引和工作树副本,然后将您的工作树副本移动或复制回原位。显然最好一开始就不要陷入这种情况。
但是,如果您 git rm 这些文件,那么从拥有文件的提交转移到没有文件的提交的人else会发生什么?例如,也许您正在推送的遥控器现在已签出该文件,然后他们将前往 git checkout 一个 new 提交,而您所做的 没有 有这些文件。当然,他们的 Git 会尽职尽责地从 他们的 Git 的索引以及 他们的 Git 用户的工作树中删除这些文件。那是你不想要的,所以现在你只能在你的 Git 的索引中保留他们的该文件的副本,以便它进入你的新提交。
这就是这个复杂的舞蹈的全部意义所在。 每个提交都是一个快照,在您的新提交中,您希望您的快照拥有它们的某些特定文件的副本。因此,您必须将 他们的 副本复制到 您的 Git 的索引中。你从一些提交中得到它,将它复制到你的索引中。然后将其保留在 您的 Git 的索引/暂存区域中,即使您没有在自己的工作树中使用它。在处理这三个副本时,您将 正确 副本(不是您的工作树副本)保存在您自己的 Git 索引中。
1从技术上讲,索引中的内容是对冻结副本的引用。更新索引副本包括制作一个 new 冻结副本,准备好提交,并将新引用写入索引。如果您开始直接使用git update-index 来放入新文件,或者使用git ls-files --stage 查看索引,这些细节很重要:您将在此处看到Git 的内部blob 对象哈希ID。但是您可以将索引视为以内部冻结格式保存每个文件的完整副本:该心智模型对于您通常使用 Git 的级别来说已经足够好了。