我想立即强调两个背景点:
If you own存储库,you制定规则。你所做的任何迁就他人的行为都只是一般的友善。
动词ignore充其量是……棘手。稍后我将描述我的意思。重要的是列出一个文件.gitignore
不完全是ignore除非你对“忽略”这个词有一个奇怪的个人定义。
也就是说,保持友好的方法就是让你的存储库仅忽略以下文件你的项目会产生。然后,有你的个人忽略文件忽略您的文件system会产生。
让我们举一个具体的例子。假设您有一个使用 Python 的项目,其中运行python foo.py
创造foo.pyc
, foo.pyo
,和/或__pycache__/*
文件,其中任何一个都不应该被提交。因此,您可以从以下开始:
*.pyc
*.pyo
__pycache__/
in your .gitignore
,因为使用您的项目的任何人(您、您的同事或其他任何人)最终都会得到这些 Python“目标代码”文件,这些文件特定于特定的 Python 版本,因此不应包含在内。
但假设您个人正在使用 MacOS 及其 Finder。 Finder 程序创建名为.DS_Store
。所以你很可能会想添加:
.DS_Store
to your .gitignore
。那不是wrong,但这对使用 Windows 的人没有任何好处。 Windows 用户需要忽略哪些文件?我不确定,我不使用Windows。然而,Linux 开发人员可能会忽略.*.swp
文件,其中vim
编辑器创建。
如果你把.DS_Store
在你自己的$HOME/.gitignore
,Linux 的人们把.*.swp
in their $HOME/.gitignore
,你们所有人都会对您的项目有愉快的体验。此外,您还将获得愉快的体验their项目,其中他们没有列出.DS_Store
因为他们是从 Linux 开始的。
这就是总体思路:你的project(存储库).gitignore
应列出在处理项目时将在工作树中找到的文件的名称或名称模式,但不应将其提交给项目。换句话说,它不是特定于操作系统的,而是特定于项目的。其他文件名模式(特定于操作系统、特定于编辑器、特定于 IDE 等)可以放入其他忽略文件中,因此不需要在项目的.gitignore
文件。这并不一定hurt将它们列在项目的文件中,但如果每个person对事情理智一点,也无济于事。
不太重要的背景,不属于实际答案的一部分(您可以在这里停止阅读!)
人们发现 Git 的.gitignore
文件混乱。 (我确实这么做了,而且从 StackOverflow 上的数百个问题来看,几乎每个人都这么做了。)我认为这很大程度上来自于对 Git 存储模型的误解。
关于 Git,首先要了解的可能是the最重要的是要知道——Git 不是关于files,并不是真正关于branches任何一个。 Git 的真正意义在于commits。 Git 存储库的核心由两个数据库组成。大数据库保存commits以及支持提交所需的其他内部 Git 对象。
这个包含 Git 提交和其他 Git 对象的大型数据库就是git clone
副本。还有第二个较小的数据库names:分支名称、标签名称等。该数据库对其他 Git 可见,因此could被复制git clone
,但通常不仅仅是复制。反而,git clone
读取较小的数据库并且modifies它完全抛弃了一些名字并改变了其他名字。所以当你使用git clone
,您将获得大数据库的副本(所有提交)以及小数据库的修改和精简副本。 (这里我们不会太仔细地观察较小的一个,因为它不会影响.gitignore
files.)
这些提交本身都有独特的hash IDs。这些是由字母和数字组成的丑陋的大字符串,例如b994622632154fc3b17fb40a38819ad954a5fb88 https://github.com/git/git/commit/b994622632154fc3b17fb40a38819ad954a5fb88。 Git 存储库可以快速判断它是否与其他 Git 存储库具有相同的提交:发送的 Git 仅列出哈希 ID。接收 Git 仅检查:我是否有使用该哈希 ID 的提交?如果是这样,接收 Git 有that犯罪。它不需要再次获得它。如果没有,接收的 Git 需要获取该提交。
这意味着你的第一个git clone
可能会很慢:您可能必须获取许多兆字节的对象。不过在那之后,updating克隆只是获取任何新提交的问题they有那个you还是需要。你的 Git 调用他们的 Git,他们列出一些哈希 ID,你的 Git 知道要获取什么,他们的 Git 也知道你拥有什么。或者,如果您向他们做出了新的提交,您的 Git 会调用他们的 Git,为他们提供一些哈希 ID,他们可以说我已经有那个了 or 我没有那个,给我!
当然,还有更多的事情要做。接下来要知道的是,每次提交都会存储完整且完整的快照every文件。这些文件以特殊的、只读的、仅限 Git 的冻结格式存储,其中的文件已进行重复数据删除。事实上,提交存储文件就是 Git 为我们存储文件的方式,它实际上只关心提交本身。冻结和重复数据删除format这就是为什么存储库不会变得非常庞大,尽管every提交有完整的副本every文件:大多数提交只是重复使用之前提交的文件,这意味着 Git 不必存储新的副本。
但是,如果提交中的文件采用冻结的、仅限 Git 的格式,计算机上的其他程序都无法使用,那么您实际上如何use这些文件?答案是:你不会。也就是说,你不会使用these文件。 Git 会做的是extract这些文件在某处。那个“某个地方”就是你的工作树 or 工作树.
这里值得一提的是,尽管我们不会进一步讨论,但每次提交不仅存储冻结快照,还存储一些额外的快照metadata。这主要是您在其中看到的内容git log
输出:例如,谁进行了提交、何时以及为什么。这why部分取决于提交的人:这是日志消息。一条好的日志消息非常有价值。吉特可以告诉你发生了什么:Git 会比较之前的,或者parent,针对当前或提交快照child提交的快照,对于每个不同的文件,Git 都会向您显示一个配方,将父级副本更改为子级副本。但 Git 不能告诉你why添加或删除了一些行。只有做过这件事的人才能说why他们这样做了。
这意味着您看到和使用的文件根本不在 Git 中
如果你运行过:
git clone https://github.com/git/git
并拥有 Git 的副本,您可以查看 Git 的源代码:有一个Makefile
, a README.md
, 等等。但这些是您计算机上的普通文件。它们不是文件in一次提交。它们是 Git 通过从快照中提取已提交文件而制作的副本。这些副本位于您的工作树或工作树中。您可以使用文件查看器查看它们,在编辑器中打开它们,等等。但他们不在Git。他们在your工作树,供您随心所欲地做。
每当您要求时,Git 都会将任何给定的提交提取到您的工作树:
git checkout v2.21.0
例如将使用tag v2.21.0
查找特定的提交哈希 ID (8104ec994ea3849a968b4667d072fedd1e688642
,准确地说)并提取that致力于你的工作树。 (如果您有 2.23 或更高版本的 Git,则可以使用git switch
代替git checkout
:它们在这里做完全相同的事情。)这个提取过程包括removing your从工作树中删除文件并根据您要切换到的提交创建新文件。但所有这些文件都是your文件,而不是 Git 的。
幸运的是,git checkout
/ git switch
有一些安全检查以避免删除your当您尚未保存所做的某些更改时,会保存文件。您可以将其关闭(git checkout --force
,例如)或故意使用其他破坏性命令(git reset --hard
) 删除未保存的工作。在所有情况下,你基本上只是告诉 Git 删除这些内容you did to your文件并获取其他版本,例如保存在其他提交中的版本Git's files.
Git's index or 暂存区
如果 Git 只使用两件事——它的提交,其中之一是current提交,以及你的工作树——然后git commit
本身就很简单。不幸的是,Git 隐藏了一个third保存每个文件的地方。当你选择一些提交时——通过git checkout
or git switch
——成为current提交,Git 不提交just将该提交的快照提取到您的工作树中。相反,它首先将该提交的快照提取到 Git 的index.
The index is complicated and has multiple purposes, but its main one is actually pretty easy to describe, and is what you should remember to start with: The index is where you build the next commit you plan to make. This is why it has the name staging area. The index holds a copy1 of each file, initially taken from the commit. Your work-tree holds a copy as well. So there are three active copies:
- 你可以看到的那个
git show HEAD:README.md
被冻结到提交中。
- 你可以看到的那个
git show :README.md
在 Git 的索引中。它处于冷冻状态format,但它是可替换的,与提交中的不同。 (这些文件有点像 Git 中的一半:准备好提交,但尚未实际提交。)
- 你实际上可以的use——这是一个普通的文件——就是普通的
README.md
。这是yours而且它根本不在 Git 中。
当你跑步时git commit
, Git 收集适当的元数据,冻结其索引中的所有文件就在那时,并使用它们作为新提交的新快照。
If the :README.md
匹配HEAD:README.md
,这两个文件是重复的,因此新的提交只是重新使用该文件。如果不是,也许它与其他提交匹配并以这种方式去重复,或者它可能是全新的,并且实际上被真实存储。无论如何,一旦你提交了它,它就会被冻结,并且现在完全在 Git 中。但如果你改变了your 工作树备份README.md
,您可能希望 Git 冻结更新的README.md
. 这是哪里git add
进来。
The git add
命令告诉 Git:使索引副本与我的工作树副本匹配。也就是说,Git 将复制(并压缩为冻结格式)您更新的内容README.md
从工作树中提取文件,并将副本放入:README.md
在其索引中。所以这就是为什么你不断被要求git add
文件:每次你改变的时候your复制,如果你想让 Git 改变它的提议的下一次提交复制,你必须git add
again.
当你跑步时git commit
,稍后,Git 将采取所有index文件并将它们冻结到新的提交中。因为索引副本都处于冻结状态format,这个过程可以而且通常确实会进行very fast.
1Technically the index contains, not an actual copy of the data, but rather the file's name, mode, and a blob hash ID. You can't really tell the difference unless and until you start digging into the index directly, using git ls-files --stage
or git update-index
. So it works OK to think of the index as having a full copy of the file: Git hides the blob-object trick so well that you don't need to care.
这是哪里.gitignore
进来
Git 从它的索引而不是你的工作树中进行新的提交。你的工作树是yours,随心所欲。当你告诉 Git 覆盖它时,你只需要小心一点,因为你的工作树中没有任何文件是inGit(最多是next to or 沿着吉特)。但这也意味着您可以在工作树中创建您不希望 Git 存储到其任何提交中的文件。由于这些文件不在提交中,并且它只是commits被复制的git clone
,这些文件不会出现在任何克隆中。
对于编译器输出文件,例如*.pyc
, or *.o
from cc
or c++
,或者 Java 编译器的输出,或者其他什么,这是一件好事:你通常不会want这些文件出现在任何克隆中。
但是,如果这些文件只是位于您的工作树中,则可能会出现两件事:
-
git status
will 向你唠叨他们的事.
- 如果您使用集体
git add everything
手术,git add
will copy这些文件作为新文件进入 Git 的索引,现在如果你git commit
.
列出文件名.gitignore
是防止这两件事发生的一种方法。但这里有一个技巧:如果文件已经在 Git 的索引中,则将其列在.gitignore
没有影响。
Git 索引中的文件称为tracked. A 跟踪文件是 Git 索引中的一个现在. An 未追踪的文件存在于你的工作树中,但不在 Git 的索引中现在.
请记住,您现在可以将全新的(Git)文件放入 Git 的索引中:git add
。您现在还可以使用以下命令将文件完全从 Git 索引中取出git rm
。所以索引的内容不是固定的。 Agit checkout
fills索引,然后之后,您可以并且将会修改它:您将replace您希望在下次提交时更新的任何文件。
当你跑步时git status
, the status
命令进行两次单独的比较。首先,它告诉您其他有用的信息,但我们将直接跳过它并进行两个比较:
两个比较中的第一个比较当前提交, or HEAD
,到索引中的内容。对于每个完全匹配的文件,git status
什么也没说。如果有一些文件don't匹配——或者是新的或者缺失——git status
says 为提交而暂存的更改并列出这些文件的名称。
第二个比较将索引与工作树进行比较。对于每个完全匹配的文件,git status
什么也没说。如果有一些文件don't匹配或缺失,git status
says 未暂存提交的更改并列出这些文件的名称。
The one special case here is for untracked files: for each untracked file, git status
lists the file's name,2 calling these untracked files. But if you list these names in .gitignore
, git status
shuts up about them.
请注意,没有什么特别的事情发生tracked文件。这些已经在 Git 的索引中了。它们被第一次比较所覆盖,Git 会将索引副本与工作树副本进行比较,无论文件是否列在.gitignore
.
所以从这个意义上来说,这些.gitignore
条目并不意味着ignore文件。他们的意思当文件未被追踪时,闭嘴。当它被跟踪时,它们没有任何作用。
同时,git add
has .
and *
(其中)因为集体行动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
it is.
2Technically, to get this every time you need git status -uall
or git status -u
. Otherwise it will sometimes combine a bunch of files that are physically stored within a single folder and only bother mentioning the folder name.