我想立即强调两个背景点:
如果您拥有存储库,您设置规则。您为适应他人所做的任何事情都只是一般的友善。
动词 ignore 是......最棘手的。我稍后会描述我的意思。重要的是,在 .gitignore 中列出文件并不会完全忽略它,除非您对“忽略”这个词有一个奇怪的个人定义。
也就是说,友好的方式是让你的存储库只忽略你的项目将产生的文件。然后,让您的个人忽略文件忽略您的系统将产生的文件。
让我们用一个具体的例子。假设您有一个使用 Python 的项目,其中运行 python foo.py 创建 foo.pyc、foo.pyo 和/或 __pycache__/* 文件,这些文件都不应该提交。因此,您应该从:
*.pyc
*.pyo
__pycache__/
在您的.gitignore 中,因为使用您项目的任何人——您、您的同事或其他任何人——最终都会得到这些 Python“目标代码”文件,这些文件特定于特定的 Python 版本,因此应该不包括在内。
但假设您个人使用的是 MacOS 及其 Finder。 Finder 程序创建名为.DS_Store 的文件。所以你很可能会想补充:
.DS_Store
到您的.gitignore。这没有错,但它对任何使用 Windows 的人都没有任何好处。 Windows 人员需要忽略哪些文件?我不确定,我不使用 Windows。但是,Linux 人员可能希望忽略 .*.swp 编辑器创建的 vim 文件。
如果您将.DS_Store 放入您自己的$HOME/.gitignore,而Linux 人员将.*.swp 放入他们的 $HOME/.gitignore,那么你们所有人都会在您的项目中获得愉快的体验。此外,您将在他们的项目中获得愉快的体验,他们没有列出 .DS_Store,因为他们是在 Linux 上开始的。
这就是一般的想法:您的项目(存储库).gitignore 应该列出在处理您的项目时将在工作树中找到的文件的名称或名称模式,但是不应该致力于该项目。换句话说,它不是特定于操作系统的,而是特定于项目的。 其他文件名模式——特定于操作系统、特定于编辑器、特定于 IDE 等等——可以放在其他忽略文件中,因此不需要在项目的.gitignore 文件中列出。将他们列在项目文件中不一定伤害,但如果每个人都对事情敏感,那也无济于事。
不属于实际答案的不太重要的背景(您可以在此处停止阅读!)
人们发现 Git 的 .gitignore 文件令人困惑。 (我做到了,从 StackOverflow 上的数百个问题来看,几乎每个人都做到了。)我认为其中很大一部分来自对 Git 的存储模型的误解。
要了解 Git 的第一件事——可能是 最重要的事情——是 Git 与 文件 无关,也与 无关>分支机构。 Git 真的是关于commits。 Git 存储库的核心是由两个数据库组成。大型数据库包含 commits 和支持提交所需的其他内部 Git 对象。
这个包含 Git 提交和其他 Git 对象的大型数据库是 git clone 复制的内容。还有第二个较小的names: 分支名称、标签名称等数据库。这个数据库对其他 Git 是可见的,所以它可以被git clone 复制,但通常它不仅仅是复制的。相反,git clone 读取较小的数据库并修改它,完全丢弃一些名称并更改其他名称。因此,当您使用git clone 时,您将获得一个大数据库的副本(所有提交)和一个修改后的小数据库副本。 (这里我们不会仔细研究较小的那个,因为它不会影响.gitignore 文件。)
提交本身都有唯一的哈希 ID。这些是大而难看的字母和数字字符串,例如b994622632154fc3b17fb40a38819ad954a5fb88。 Git 存储库可以快速判断它是否具有与其他 Git 存储库相同的提交:发送 Git 只是列出哈希 ID。接收 Git 只是检查:我是否有一个具有该哈希 ID 的提交? 如果是,接收 Git 有 那个 提交。它不需要再次得到它。如果没有,接收 Git 需要获得该提交。
这意味着您的第一个git clone 可能会很慢:您可能需要获取许多兆字节的对象。然而,在那之后,更新一个克隆只是获取任何新的提交他们有你仍然需要的问题。你的 Git 调用他们的 Git,他们列出一些哈希 ID,你的 Git 知道要获取什么,他们的 Git 知道你拥有什么。或者,如果您向他们提交了新的提交,您的 Git 会调用他们的 Git,为他们提供一些哈希 ID,他们可以说 我已经有了那个或我没有'没有那个,给我!
当然,还有比这更多的东西。接下来要知道的是,每个提交都存储了 每个 文件的完整且完整的快照。这些文件以一种特殊的、只读的、仅限 Git 的冻结格式存储,其中文件被删除了重复数据。提交存储文件的事实是 Git,它实际上只关心提交本身,最终为我们存储文件。冻结和去重的 格式 是存储库不会变得非常庞大的原因,即使 每个 提交都有 每个 文件的完整副本:大多数提交只是重新使用上一次提交中的文件,这意味着 Git 不必存储新副本。
但如果提交中的文件是冻结的、仅限 Git 的格式,而您的计算机上没有其他程序可以使用,那么您将如何真正使用这些文件?答案是:你不会。也就是说,您不会使用 这些 文件。 Git 会做的是在某处提取这些文件。那个“某处”是你的工作树或工作树。
这里值得一提的是,虽然我们不会深入讨论,但每个提交不仅存储一个冻结的快照,还存储一些额外的元数据。这主要是您在git log 输出中看到的内容:例如,提交的人、时间和原因。 why 部分取决于提交的人:它是日志消息。一条好的日志消息非常有价值。 Git 可以告诉你发生了什么:Git 会将前一个提交或父提交的快照与当前提交或子提交的快照进行比较,并且对于每个文件不同,Git 将向您显示一个将父副本更改为子副本的配方。但是 Git 无法告诉您为什么添加或删除了某些行。只有这样做的人才能说出为什么他们这样做。
这意味着你看到和使用的文件根本不在 Git 中
如果你跑过:
git clone https://github.com/git/git
如果有 Git 的副本,您可以查看 Git 的源代码:有 Makefile、README.md,等等。但这些是您计算机上的普通文件。它们不是提交中的文件。 它们是 Git 通过从快照中提取提交的文件而制作的副本。 这些副本位于您的工作树或工作树中。您可以使用文件查看器查看它们,在编辑器中打开它们等等。但它们不在 Git 中。它们位于您的工作树中,供您随意使用。
Git 会在您要求时提取任何给定的提交到您的工作树:
git checkout v2.21.0
例如,将使用 tag v2.21.0 来查找特定的提交哈希 ID(确切地说是8104ec994ea3849a968b4667d072fedd1e688642)并提取 that 提交到您的工作 -树。 (如果你有一个 2.23 或更高版本的 Git,你可以使用 git switch 而不是 git checkout:它们在这里做的完全一样。)这个提取过程包括删除 你的 从您的工作树中创建文件,并根据您切换到的提交创建新文件。但所有这些文件都是你的文件,而不是 Git 的。
幸运的是,git checkout / git switch 进行了一些安全检查,以避免在您尚未保存所做的某些更改时删除您的文件。您可以将其关闭(例如git checkout --force)或故意使用其他破坏性命令(git reset --hard)来擦除未保存的工作。在所有情况下,您基本上只是告诉 Git 删除 you 对 your 文件所做的内容并取回其他版本,例如保存在其他提交中的版本,来自 Git 的 文件。
Git 的 index 或 暂存区
如果 Git 只使用两件事——它的提交,其中一个是 当前 提交,以及你的工作树——那么 git commit 本身就很简单。不幸的是,Git 隐藏了 第三个 位置来保存每个文件。当您通过git checkout 或git switch 选择某个提交作为当前 提交时,Git 不会只是 将该提交的快照提取到您的工作树中。相反,它首先将该提交的快照提取到 Git 的 index。
索引很复杂并且有多种用途,但它的主要用途实际上很容易描述,并且是您应该记住的开始:索引是您构建下一个 提交您计划进行的。 这就是为什么它有名称暂存区。索引保存每个文件的副本1,最初取自提交。您的工作树也包含一个副本。所以有三个活动副本:
-
git show HEAD:README.md 可以看到的那个被冻结在提交中。
- 用
git show :README.md 可以看到的那个在Git 的索引中。它是冻结的格式,但它是可替换的,与提交中的不同。 (这些文件是 Git 的一半:准备好提交,但尚未真正提交。)
- 您实际上可以使用的文件(位于普通文件中)就是普通的
README.md。这是你的,它根本不在 Git 中。
当您运行 git commit 时,Git 会收集适当的元数据,并立即冻结其索引中的所有文件,并将这些文件用作新提交的新快照。
如果:README.md 与HEAD:README.md 匹配,则这两个文件是重复的,因此新提交只是重新使用该文件。如果不是,它可能会匹配其他一些提交并以这种方式进行重复数据删除,或者它可能是全新的,并且实际上是真实存储的。无论如何,一旦你提交它,它就会被冻结并且现在完全在 Git 中。但是,如果您更改了README.md 的您的 工作树 副本,您可能希望Git 冻结更新的README.md。 这就是git add 的用武之地。
git add 命令告诉 Git:使索引副本与我的工作树副本匹配。 也就是说,Git 将从您的工作树,并将副本放入其索引中的:README.md。所以这就是为什么你经常被要求git add 文件的原因:每次你改变你的副本,如果你想让Git改变它提议的下一次提交副本,你必须再次git add。
当您稍后运行git commit 时,Git 将获取所有 index 文件并将它们冻结到一个新的提交中。因为索引副本都是冻结的格式,所以这个过程可以而且通常确实非常很快。
1从技术上讲,索引包含的不是数据的实际副本,而是文件的名称、模式和 blob 哈希 ID。除非并且直到您开始使用git ls-files --stage 或git update-index 直接挖掘索引,否则您无法真正分辨出区别。因此,可以将索引视为拥有文件的完整副本:Git 将 blob-object 技巧隐藏得如此之好,以至于您无需关心。
这就是.gitignore 的用武之地
Git 从它的索引而不是你的工作树中进行新的提交。你的工作树是你的,你可以随意处理。当你告诉 Git 覆盖它时,你只需要小心一点,因为你的工作树中的所有文件都不是 in Git(它们最多 next to或 在 Git 旁边)。但这也意味着你可以在你的工作树中创建你不希望 Git 存储到它的任何提交中的文件。由于这些文件不在提交中,并且只有 commits 被git clone 复制,因此这些文件不会出现在任何克隆中。
对于像*.pyc 这样的编译器输出文件,或者来自cc 或c++ 的*.o,或者来自Java 编译器的输出,或者其他什么,这是一件好事:你通常不会想要 这些文件将出现在任何克隆中。
但如果这些文件只是在你的工作树中,有两件事可能会出错:
-
git status 会唠叨你。
- 如果您使用集体
git add <em>everything</em> 操作,git add 会将这些文件复制到 Git 的索引中作为新文件,现在如果您使用git commit,它们将被提交。
在.gitignore 中列出文件名是防止这两种情况的一种方法。但是这里有一个技巧:如果文件已经在 Git 的索引中,则在 .gitignore 中列出它是无效的。
Git 索引中的文件称为已跟踪。 跟踪的文件是在 Git 的索引中的文件现在。 untracked 文件是存在于您的工作树中但不在 Git 的索引中的文件现在。
请记住,您现在可以使用 git add 将全新的(对 Git 的)文件放入 Git 的索引中。您现在还可以使用 git rm 将文件完全从 Git 的索引中取出。所以索引的内容不是固定的。 git checkout 填充索引,然后,您可以并且将会修改它:您将替换您想要在下一次提交中更新的任何文件。
当您运行git status 时,status 命令会进行两个单独的比较。首先它会告诉你其他有用的东西,但我们会跳过它并进行两个比较:
两个比较中的第一个将当前提交或HEAD与索引中的内容进行比较。对于每个完全匹配的文件,git status 什么都不说。如果有一些文件不匹配——或者是新的或丢失的——git status 会说为提交暂存的更改并列出这些文件的名称。
-
第二个比较将索引与您的工作树进行比较。对于每个完全匹配的文件,git status 什么都不说。如果有一些文件不匹配或丢失,git status 会说更改未暂存并列出这些文件的名称。
这里的一种特殊情况是未跟踪文件:对于每个未跟踪文件,git status 列出文件名,2调用这些未跟踪文件时间>。但如果你在.gitignore 中列出这些名字,git status 闭嘴 对他们说。
请注意,已跟踪 文件不会发生任何特殊情况。这些已经在 Git 的索引中。第一次比较涵盖了它们,Git 会将索引副本与工作树副本进行比较,无论该文件是否列在 .gitignore 中。
所以从这个意义上说,这些.gitignore 条目并不意味着忽略该文件。他们的意思是在文件未被跟踪时闭嘴。当它被跟踪时,它们没有任何效果。
同时,git add 具有 . 和 *(以及其他)用于对许多或所有文件进行整体添加操作。如果所有文件包含未跟踪的文件,这些操作将非常不方便。因此,在.gitignore 中列出文件名或模式会抑制整体添加操作。它甚至压制了故意的git add:
$ touch foo.pyo
$ git add foo.pyo
The following paths are ignored by one of your .gitignore files:
foo.pyo
Use -f if you really want to add them.
所以也许.gitignore 应该被称为.git-do-not-complain-about-these-untracked-files-and-do-not-automatically-add-them-when-using-en-masse-add-operations-or-even-explicit-requests,或者类似的名称。但是谁想输入那种名字呢?所以.gitignore 是。
2从技术上讲,每次您需要 git status -uall 或 git status -u 时都能获得此信息。否则,它有时会合并一堆物理存储在单个文件夹中的文件,并且只需提及文件夹名称。