public 存储库应该包含什么 .gitignore 文件?

What should contain .gitignore file when is a public repository?

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

生成的文件

你应该怎么办?你必须在 gitignore 中写入所有操作系统生成的所有文件以及所有 IDEs 生成的所有文件吗?

repository is on Github and people can clone it and push the changes

这是您设置某种质量门槛的地方,例如代码审查。它们旨在进行讨论,并让其他人审视这些变化。查看差异,您会注意到其他无用的东西,例如IDE 个文件。然后,您请贡献者删除那些并重新提交。

在大多数 OSS 的情况下,我认为,维护者有一个存储库,这是由贡献者 cloned/forked 提供的,当他们想要引入更改时,他们会进行 PR。因为您通常不希望 任何人 对您的代码进行更改,所以您将写入权限限制在您信任的人身上,因此其他人无法直接推送到主仓库。

对于较小的项目,您认识所有的贡献者,仍然有可能不小心引入不需要的文件,这仍然是为什么您需要在合并到主流之前进行代码审查之类的事情。

不管怎样,这是一个流程问题,不一定是 git。视情况而定。就像任何其他重复性工作一样,当您注意到一种模式时,将其自动化。

您对必须考虑任何可以做出贡献的人的所有系统感到有点害怕,这是对的,但您不必考虑。

  1. 我认为大多数语言都有代码检查器,因此您可以强制执行编码风格(例如制表符与空格)。
  2. 此外,您通常知道一种语言可以生成哪些文件,例如.exes,.dll,因此您可以将它们添加到 .gitignore 文件中。
  3. 对于任何漏掉的内容,都有拉取请求。

我想马上强调两个背景要点:

  1. 如果您拥有存储库,则设置规则。任何你为迎合他人而做的事情都只是一般的友善。

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

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

让我们用一个具体的例子。假设您有一个带有 Python 的项目,其中 运行ning python foo.py 创建 foo.pycfoo.pyo、and/or __pycache__/* 文件,none 其中应该永远提交。因此,您将从:

*.pyc
*.pyo
__pycache__/

在您的 .gitignore 中,因为任何使用您的项目的人——您或您的 co-workers,或其他任何人——最终都会得到这些 Python "object code" 文件,特定于特定 Python 版本,因此不应包括在内。

但假设您个人正在使用 MacOS 及其 Finder。 Finder 程序创建名为 .DS_Store 的文件。所以你很可能想添加:

.DS_Store

给你的.gitignore。那不是 错误 ,但它对使用 Windows 的人没有任何好处。 Windows 人需要忽略哪些文件?我不确定,我不使用 Windows。然而,Linux 人可能想忽略 vim 编辑器创建的 .*.swp 文件。

如果你把 .DS_Store 放在你自己的 $HOME/.gitignore 中,而 Linux 人把 .*.swp 放在 他们的 $HOME/.gitignore,你们所有人都会对你的项目有愉快的体验。此外,您会对 他们的 项目有愉快的体验,他们没有列出 .DS_Store 因为他们开始于 Linux.

这就是一般的想法:您的项目(存储库).gitignore应该列出将在[=中找到的文件的名称或name-patterns 481=] 在处理您的项目时,但不应将其提交给项目。换句话说,不是OS-specific,而是project-specific。 其他文件名模式——OS-specific、editor-specific、IDE-specific, 等等——可以进入其他忽略文件,因此不需要在项目的 .gitignore 文件中列出。将它们列在项目文件中不一定伤害,但如果每个对事情都很明智,那也无济于事。

Less-important 不属于实际答案的背景(您可以在这里停止阅读!)

人们发现 Git 的 .gitignore 文件令人困惑。 (我做了,从 Whosebug 上的数百个问题来看,几乎每个人都这样做了。)我认为其中很大一部分来自误解 Git 的存储模型。

关于 Git 的第一件事——可能是 最重要的事情——是 Git 不是关于 files,也不是关于 branches。 Git 实际上就是 提交 。 Git 存储库的核心由两个数据库组成。大数据库保存 提交 和支持提交所需的其他内部 Git 对象。

这个包含 Git 提交和其他 Git 对象的大数据库是 git clone 副本。还有一个较小的数据库,包含 names: 分支名称、标签名称等。此数据库对其他 Git 可见,因此 可以 git clone 复制,但通常它不仅仅是复制。相反,git clone 读取较小的数据库并 修改 它,完全丢弃一些名称并更改其他名称。因此,当您使用 git clone 时,您将获得大数据库的副本(所有提交)和小数据库的修改后的 sawed-down 副本。 (我们不会在此处仔细查看较小的文件,因为它不会影响 .gitignore 个文件。)

提交本身都有唯一的哈希ID。这些是由字母和 digit 组成的又大又丑的字符串,例如 b994622632154fc3b17fb40a38819ad954a5fb88。 Git 存储库可以快速判断它是否与其他 Git 存储库具有相同的提交:发送 Git 仅列出哈希 ID。接收方 Git 只是检查:我是否有使用该哈希 ID 的提交? 如果是,接收方 Git 有 那个 提交。它不需要再次获得它。如果没有,接收方 Git 需要获得该提交。

这意味着您的第一个 git clone 可能会很慢:您可能必须获取许多兆字节的对象。不过,在那之后,更新 克隆只是获得任何新提交的问题 他们 还是需要的。你的 Git 打电话给他们Git,他们列出了一些哈希 ID,你的 Git 知道要得到什么,他们的 Git 知道你有什么。或者,如果您向他们提交了新的提交,您的 Git 会调用他们的 Git,为他们提供一些哈希 ID,他们可以说 我已经有了那个 [= =342=] 或 我没有那个,给我!

当然,还有更多内容。接下来要知道的是,每次提交都会存储 每个 文件的完整快照。这些文件以特殊的 read-only、Git-only 冻结格式存储,其中文件为 de-duplicated。提交存储文件的事实是 Git,它实际上只关心提交本身,最终为我们存储文件。冻结和 de-duplicated 格式 是存储库不会变得非常胖的原因,即使 每个 提交都有 [=206] 的完整副本=]每个文件:大多数提交只是re-use上一次提交的文件,这意味着Git不必存储新副本。

但是,如果提交中的文件是冻结的 Git-only 格式,您计算机上的其他程序都无法使用,您将如何实际 使用 这些文件?答案是:你不会。也就是说,您不会使用 这些 文件。 Git 将做的是提取 这些文件到某处。 "somewhere" 是您的 工作树 work-tree.

这里值得一提的是,虽然我们不会深入探讨,但每个提交不仅存储一个冻结的快照,而且还存储一些额外的 元数据 。这主要是您在 git log 输出中看到的内容:例如,谁进行了提交、何时提交以及为什么提交。 why 部分取决于提交的人:它是日志消息。一条好的日志消息很有价值。 Git 可以告诉你 发生了什么: Git 将比较以前的,或 parent,commit 的快照与当前或child 提交的快照,对于每个不同的文件,Git 将向您展示将父副本更改为子副本的配方。但是 Git 无法告诉您 为什么 添加或删除了某些行。只有这样做的人才能说出 为什么 他们那样做。

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

如果你运行:

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

并拥有 Git 的副本,您可以查看 Git 的来源:有一个 Makefile、一个 README.md,等等。但这些是您计算机上的普通文件。它们不是 in 提交的文件。 它们是 Git 通过从快照中提取提交的文件制作的副本。 这些副本在您的工作树或 work-tree 中。您可以使用文件查看器查看它们,在编辑器中打开它们,等等。但它们不在 Git 中。它们在你的work-tree,任你随心所欲。

Git 将在您要求时随时提取对您的 work-tree 的任何给定提交:

git checkout v2.21.0

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

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

Git的索引暂存区

如果 Git 只使用了两件事——它的提交,其中一个是 当前 提交,和你的 work-tree——那么 git commit 本身会很简单。不幸的是,Git 隐藏了 第三个 位置来保存每个文件。当您 select 通过 git checkoutgit switch 进行某些提交时,成为 当前 提交,Git 不会 只需 将提交的快照提取到您的work-tree。相反,它首先将该提交的快照提取到 Git 的 index.

索引很复杂并且有多种用途,但它的主要用途实际上很容易描述,并且是您应该记住的开始:索引是您构建 next 你打算做的承诺 这就是它得名 暂存区 的原因。该索引包含每个文件的副本1,最初是从提交中获取的。您的 work-tree 也有一份副本。所以有三个个活动副本:

  • 你可以看到 git show HEAD:README.md 的那个被冻结到提交中。
  • 你可以用git show :README.md看到的那个在Git的索引里。它是冻结的格式,但它是可替换的,与提交中的不同。 (这些文件有点 half-in Git:准备提交,但尚未实际提交。)
  • 您实际上可以 使用 的文件(在普通文件中)就是普通文件 README.md。这是 你的 而它根本不在 Git 中。

当您 运行 git commit、Git 收集适当的元数据时,立即冻结其索引 中的任何文件 ,并且使用这些作为新提交的新快照。

如果 :README.md 匹配 HEAD:README.md,这两个文件是重复的,所以新提交只是 re-use 文件。如果不是,它可能与其他提交和 de-duplicates 匹配,或者它可能是全新的,并且实际上是真实存储的。在任何情况下,一旦您提交它,它就会被冻结并且现在真的完全在 Git 中。但是,如果您更改了 您的 work-tree 副本 README.md,您可能希望 Git 冻结更新后的 README.md这就是 git add 的用武之地。

git add命令告诉Git:使索引副本匹配我的work-tree副本。即Git将从您的 work-tree 复制(并压缩成冻结格式)您更新后的 README.md 文件,并将副本放入其索引中的 :README.md 中。所以这就是为什么你总是需要 git add 文件:每次你改变 你的 副本,如果你想要 Git 改变它的 建议下一次提交复制,你必须再次git add

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


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


这就是 .gitignore 发挥作用的地方

Git 从其索引而不是您的 work-tree 进行新提交。你的 work-tree 是 你的 ,随心所欲。当您告诉 Git 覆盖它时,您只需要小心一点,因为 work-tree 中的 none 个文件是 Git(它们最多旁边或旁边Git)。但这也意味着您可以在 work-tree 中创建您不想 Git 存储到其任何提交中的文件。由于这些文件不在提交中,并且只有 提交 git clone 复制,这些文件不会出现在任何克隆中。

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

但是如果这些文件就在您的 work-tree 中,有两件事可能会出错:

  1. git status唠叨他们.
  2. 如果您使用 en-masse git 添加 <em>everything</em> 操作,git add将这些文件作为新文件复制到Git的索引中,现在如果你git commit.
  3. 它们就会被提交

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

Git 索引中的文件被称为 tracked跟踪的文件 是 Git 的索引 现在 中的文件。 未跟踪 文件存在于您的 work-tree,但现在 不在 Git 的索引中。

记住,您现在可以使用 git add 将 all-new(至 Git)个文件放入 Git 的索引中。您现在还可以使用 git rm 从 Git 的索引中完全取出文件。所以索引的内容是不固定的。 git checkout 填充 索引,然后您可以——并且将会——修改它:您将替换 任何文件你想在下一次提交时更新。

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

  1. 两个比较中的第一个将当前提交HEAD与索引中的内容进行比较。对于每个完全匹配的文件,git status 什么都不说。如果有一些文件 匹配——或者是新的或丢失的——git statuschanges staged for commit 并列出这些文件的名称。

  2. 第二次比较是将索引与您的 work-tree 进行比较。对于每个完全匹配的文件,git status 什么都不说。如果有一些文件 匹配或丢失,git status 会说 changes not staged for commit 并列出这些文件' 名称。

这里的一个特例是 未跟踪的 文件:对于每个未跟踪的文件,git status 列出文件名,2 调用这些 未跟踪的文件 。但是,如果您在 .gitignore 中列出这些名称,git status 闭嘴

请注意,已跟踪 文件不会发生任何特殊情况。这些已经在 Git 的索引中。它们包含在第一次比较中,Git 会将索引副本与 work-tree 副本进行比较,无论文件是否列在 .gitignore.

所以从这个意义上说,这些 .gitignore 条目并不意味着 忽略 文件。他们的意思是在文件未被跟踪时闭嘴。当它被跟踪时,它们没有任何作用。

同时,git add.*(以及其他)用于对许多或 en-masse add 操作所有的文件。如果所有文件包含未跟踪的文件,这些操作将非常不方便。因此,在 .gitignore 中列出文件名或模式会抑制 en-masse 添加操作。它甚至会抑制故意 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 时都可以得到它。否则,它有时会将物理上存储在单个文件夹中的一堆文件组合在一起,并且只麻烦提及文件夹名称。

一般来说,您在典型项目中放入 .gitignore 的内容包括您的构建系统可能生成的任何内容,以及任何特定于用户的配置文件(例如,如果您的程序需要用户生成配置文件 运行)。如果用户可能在开发中创建了其他构建产品或生成的文件(例如,来自 Markdown 或 AsciiDoc 的 HTML 文件)但通常不是构建的,那么您也应该忽略它们。

如果您的项目是每个人 必须 使用相同的 IDE 或 OS(例如,您的项目仅使用 Visual Studio 编译或者在 macOS 上,没有人会 ever 使用另一个 IDE 或 OS),那么你可以把 editor- 或 OS-特定文件也在那里。

人们可以在自己的系统上拥有自己的忽略文件(通过 core.excludesFile),因此如果用户使用 Vim,他们应该调整自己的每个用户忽略文件,以便他们忽略交换文件。同样,macOS 用户应该忽略 .DS_Store。您不负责处理某人可能使用的每个操作系统或编辑器。您可以选择使用预先创建的 gitignore 文件,其中包含其中的一些内容,但没有义务这样做。

话虽如此,通常项目会执行代码审查,因此如果用户没有在他们的系统上适当地配置 Git 并签入了不合适的文件,审查者可以要求他们修复它并调整他们的Git 配置。这通常是大多数大型项目使用的方法并且效果很好。