首页 > 解决方案 > 公共存储库时应该包含什么 .gitignore 文件?

问题描述

我一直在学习所有关于 .gitignore 文件的知识,但有一个问题我想解决。.gitignore 应该包含您要忽略的所有文件。因此,您应该忽略操作系统生成的文件,您正在使用的 IDE...当存储库在 Github 上时,我的问题就会出现,人们可以克隆它并推送更改。这些人可以使用其他操作系统,也可以使用其他 IDE。因此,gitignore 应该忽略这些其他操作系统和 IDE 生成的文件。

你该怎么办?所有操作系统生成的所有IDE生成的所有文件都必须写在gitignore中吗?

标签: gitgithubrepositorygitignore

解决方案


我想立即强调两个背景点:

  1. 如果您拥有存储库,则可以设置规则。您为适应他人所做的任何事情都只是一般的友善。

  2. 动词忽略是......棘手,充其量。我稍后会描述我的意思。重要的是,列出一个文件.gitignore并不会完全忽略它,除非你对“忽略”这个词有一个奇怪的个人定义。

也就是说,友好的方式是让您的存储库仅忽略您的项目将生成的文件。然后,让您的个人忽略文件忽略系统将生成的文件。

让我们用一个具体的例子。假设您有一个使用 Python 的项目,其中运行python foo.py创建foo.pycfoo.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 真的是关于提交。Git 存储库的核心是由两个数据库组成。大数据库保存提交和支持提交所需的其他内部 Git 对象。

这个包含 Git 提交和其他 Git 对象的大型数据库就是git clone副本。还有第二个较小的名称数据库:分支名称、标签名称等。该数据库对其他 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:例如,提交的人、时间和原因。为什么部分取决于做出提交的人:它是日志消息。一条好的日志消息非常有价值。Git 可以告诉你发生了什么: Git 会将前一个或parent提交的快照与当前或提交的快照进行比较,对于每个不同的文件,Git 会显示一个将父副本更改为子副本的配方。但是 Git 不能告诉你为什么添加或删除了某些行。只有这样做的人才能说出他们这样做的原因

这意味着您看到和使用的文件根本不在 Git 中

如果你跑过:

git clone https://github.com/git/git

并拥有 Git 的副本,您可以查看 Git 的源代码:有 a Makefile、 aREADME.md等。但这些是您计算机上的普通文件。它们不是提交的文件。 它们是 Git 通过从快照中提取提交的文件而制作的副本。 这些副本在您的工作树或工作树中。您可以使用文件查看器查看它们,在编辑器中打开它们等等。但它们不在Git中。它们在您的工作树中,供您随意使用。

Git 将在您要求时提取任何给定的提交到您的工作树:

git checkout v2.21.0

例如,将使用标签 v2.21.0来查找特定的提交哈希 ID(8104ec994ea3849a968b4667d072fedd1e688642准确地说是 )并将提交提取到您的工作树。(如果你有一个 2.23 或更高版本的 Git,你可以使用git switch:git checkout这些在这里做完全相同的事情。)这个提取过程包括从你的工作树中删除 你的文件并根据你的提交创建新的文件'重新切换到。但是所有这些文件都是你的文件,而不是 Git 的。

幸运的是,git checkout/git switch有一些安全检查,以避免在您尚未保存所做的某些更改时删除您的文件。您可以将其关闭(git checkout --force例如)或故意使用其他破坏性命令(git reset --hard)来删除未保存的工作。在所有情况下,您基本上只是告诉 Git 删除对文件所做的内容并从Git 的文件中获取其他版本,例如保存在其他提交中的版本。

Git 的索引暂存区

如果 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 会收集适当的元数据,立即冻结其索引git commit的所有文件,并将这些文件用作新提交的新快照。

如果:README.md匹配HEAD:README.md,这两个文件是重复的,所以新的提交只是重新使用该文件。如果不是,也许它会匹配其他一些提交并以这种方式去重,或者它可能是全新的,并且实际上是真实存储的。无论如何,一旦你提交它,它就会被冻结并且现在完全在 Git 中。但是,如果您更改 工作树副本README.md,您可能希望 Git 冻结更新的README.md. 这就是git add进来的地方。

git add命令告诉 Git:使索引副本与我的工作树副本匹配。 也就是说,GitREADME.md将从您的工作树中复制(并压缩为冻结格式)您更新的文件,并将副本放入:README.md其索引中。所以这就是为什么你经常需要git add文件的原因:每次你改变了你的副本,如果你想让 Git 改变它提议的下一个提交副本,你必须git add再次。

稍后,当您运行git commit时,Git 将获取所有索引文件并将它们冻结到新的提交中。因为索引副本都是冻结格式,所以这个过程可以而且通常会非常快。


1从技术上讲,索引包含的不是数据的实际副本,而是文件的名称、模式和blob 哈希 IDgit ls-files --stage除非并且直到您开始使用or直接挖掘索引,否则您无法真正分辨出区别git update-index。因此,可以将索引视为拥有文件的完整副本:Git 将 blob-object 技巧隐藏得如此之好,以至于您无需关心。


这是.gitignore进来的地方

Git 从它的索引中做出新的提交,而不是从你的工作树中。你的工作树是的,随你的便。当你告诉 Git 覆盖它时,你只需要小心一点,因为你的工作树中的文件都不Git 中(它们最多在 Git旁边旁边)。但这也意味着你可以在你的工作树中创建你不希望 Git 存储到它的任何提交中的文件。由于这些文件不在提交中,并且只有被复制的提交git clone,所以这些文件不会出现在任何克隆中。

*.pyc对于像, 或*.ofrom ccor之类的编译器输出文件,或来自c++Java 编译器的输出,或其他什么,这是一件好事:您通常不希望这些文件出现在任何克隆中。

但是如果这些文件只是在你的工作树中,有两件事可能会出错:

  1. git status唠叨你
  2. 如果您使用 en-masse操作,会将这些文件作为新文件复制到 Git 的索引中,现在如果您使用.git add everythinggit addgit commit

列出文件名.gitignore是防止这两种情况的一种方法。但是这里有个技巧:如果一个文件已经在 Git 的索引中,那么在 a 中列出它是.gitignore没有效果的。

Git 索引中的文件称为tracked被跟踪的文件是当前在 Git 索引中文件。未跟踪的文件是存在于您的工作树中但现在不在 Git 的索引中的文件

请记住,您现在可以使用git add. 您现在还可以使用git rm. 所以索引的内容不是固定的。Agit checkout 填充索引,然后在此之后,您可以并且将会修改它:您将替换您想要在下一次提交中更新的任何文件。

当您运行时git status,该status命令会进行两次单独的比较。首先它会告诉你其他有用的东西,但我们将跳过它并进行两个比较:

  1. 两个比较中的第一个将当前提交HEAD与索引中的内容进行比较。对于每个完全匹配的文件,git status什么都不说。如果有一些文件匹配——或者是新的或丢失的——<code>git status 表示更改暂存等待提交并列出这些文件的名称。

  2. 第二个比较将索引与您的工作树进行比较。对于每个完全匹配的文件,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 -uallgit status -u. 否则,它有时会合并一堆物理存储在单个文件夹中的文件,而只需提及文件夹名称。


推荐阅读