在主题行中对您的问题的字面回答:
如何在不跟踪的情况下将 png 文件保存在 git 中?
只是“你不知道”。这是因为在 Git 中,tracked file 是存在于 Git 的 index 中的文件。
Git 的索引是您必须了解的关键数据结构(否则 Git 的行为是莫名其妙的)。它的名字太笼统,也太糟糕了,以至于它还有两个名字:它也被称为暂存区,或者有时——现在很少见——缓存。这些天来,您主要在某些命令的某些标志中看到术语“缓存”,例如git rm --cached 或git diff --cached。但是这三个术语实际上都指的是同一件事。
索引的功能复杂而繁多,但实际上您需要了解的只是其中的一小部分。主要的——这也是人们称它为 staging area 的原因——它始终保存将进入您的 next 提交的文件的副本。
“副本”一词可能应该在此处用引号引起来,因为索引中的内容确实是将进入下一次提交的内容。提交确实存储文件,但它们以冻结、压缩、Git 化和重复内容删除的形式存储它们。这允许 Git 将 每个 文件存储在 每个提交 中。事实上,一个新的提交主要存储与前一个提交相同的文件,并且 Git 对每个文件的内容进行重复数据删除,这意味着文件的新“副本”不占用空间。对于索引中的“副本”也是如此:它们是预先去重的,因此只要它们与 当前 提交匹配,它们就不会占用空间。1 不过,除此之外,它们确实就像每个文件的副本一样。
换句话说,Git 在任何时候都有三个您正在处理或使用的每个(已提交)文件的副本:
-
在当前提交中有一个冻结的,永久保存。您无法更改此格式,并且它不是您可以用于任何内容的格式,但 Git 可以读取它。
-
Git 的索引中有一个“副本”,准备进入 next 提交。这个副本开始匹配提交的副本(好吧,通常无论如何)。
此索引副本是冻结的格式,但实际上并未冻结。如果您修改工作树副本,则必须运行 git add 来更新 index 副本。2
-
最后,当然还有文件的普通读/写副本。此副本位于您的工作树或工作树中。这是您可以看到、使用和更改的版本,但它实际上 根本不在 Git 中。这不是提交中的副本,也不是准备进入 next 提交的副本 - 这就是为什么如果您更改此工作树副本必须git add 文件.
当您运行 git commit 时,Git 从索引中的任何内容创建 new 快照。在您运行git checkout 和运行git commit 之间,您 可以使用git add 和/或git rm 控制Git 索引中的内容。因此,Git 索引中的文件副本可以更改,甚至完全删除。一旦你真正开始运行git commit,这会影响 next 提交。在那之前,它只是“暂存”更新的文件。
(git status 命令通过运行两个比较来工作:
- 第一个比较告诉您当前提交和索引之间的不同。任何不同的地方都准备提交。
- 第二个比较告诉您索引和工作树之间的不同。任何不同之处都不会为提交上演。
由于存在三个副本,这就是文件可以同时被暂存和未暂存的方式:提交版本、索引版本和工作树版本都是不同的版本。)
无论如何,我们回到跟踪文件的定义:跟踪文件是您工作树中的任何文件和 Git的索引现在。 未跟踪的文件是一个文件,它现在在你的工作树中,但现在不在 Git 的索引中。它进入 Git 的索引是因为 (a) 它来自提交, 和/或 (b) 你在上面运行了git add。如果要在新提交中,它必须 在 Git 的索引中。如果它现在不在 Git 的索引中,例如,因为你在上面使用了 git rm,它就不会在 next 提交中。如果您不想在该提交中使用它,那很好;但如果您确实希望在该提交中使用它,则需要将其放回 Git 的索引中。
我跑了git update-index --skip-worktree filename ...
这个,以及它相关的--assume-unchanged 变体,只是在文件的索引条目中设置一个特殊位(必须是在 Git 的索引中,以便有空间放置该位放)。当该位被设置时,Git 会经常——不总是,但经常——假装文件的工作树副本与文件的索引副本匹配。然后它将不理会索引副本。因此,如果索引副本当前与当前提交副本匹配,则设置该位可确保未来的git add 不使用工作树副本(或缺少工作树副本)来更新索引副本.索引副本仅保留在索引中,仍然与当前提交匹配。
(当你使用git checkout 或git switch 切换到一个新的不同的提交时,Git 不得不更新各种文件的索引副本,Git 不会遵守假设不变或跳过工作树位,如果更改索引副本会破坏工作树中任何未提交的工作,通常会抱怨并停止提交更改。但这至少有点危险,因为在.gitignore中列出文件可以让Git有权销毁工作树副本。)
1它们需要一点空间来保存文件名称、缓存数据以及 Git 认为有助于 Git 工作和快速运行的任何其他内容。它们还占用一些字节(目前每个文件 20 个字节)来保存内容哈希 ID,这与 Git 的内部存储格式有关。这意味着列出几千个文件的索引需要几千 KiB 来存储。例如,在 Git 的 Git 存储库中,我们发现:
$ git ls-files | wc -l
表示索引中有 3865 个文件,ls -l on .git/index 的索引大小为 366964(字节)。该索引还存储标头和尾标以及其他项目,因此这里不能保证直接相关,但它通常平均起来很像这样。
2从技术上讲,这个git add 会立即压缩和去重文件的内容,然后更新索引中的blob 哈希ID。如果内容已经在其他地方的存储库中,则对新的准备提交文件进行重复数据删除。如果内容是新的,它现在是第一次在存储库中,可以提交。
请注意,如果您在将文件从工作树中删除之后git add,Git 将在此时删除索引副本。 git add 命令实际上意味着使索引副本与工作树副本匹配,如果工作树副本消失,这意味着 Git 应该删除索引副本以进行匹配。
当然,如果根本没有索引副本,Git 将创建一个新的索引条目,像往常一样压缩和删除工作树内容。索引现在保存文件名、blob 哈希 ID 和缓存数据,并且索引副本现在与工作树副本匹配。