一个简短的故事:我最近在我的PC上彻底安装了Arch Linux,因为我的旧安装非常臃肿,带有不必要的软件包和配置目录。现在我想保持我的主目录干净简单。我决定使用git监督那里的每个文件和文件夹,但我不能只排除每个日志(或任何其他不断更新的目录/文件),因为它太麻烦了。
这个想法只包括$HOME/
,$HOME/.config/
和$HOME/.local/share/
中的第一级文件和目录。例如,包括.config/foo/
并排除其内容,即.config/foo/*
所以我可以检查git日志,当我卸载一个软件包时创建的目录是什么创建并手动删除它们(当然,如果我不再使用它)
我试图通过添加到我的.git/info/exclude
来实现这一目标
*/*
*/*/*
*/*/*/*
*/*/*/*/*
.local/share/*/*
.local/share/*/*/*
.local/share/*/*/*/*
.local/share/*/*/*/*/*
.config/*/*
.config/*/*/*
.config/*/*/*/*
.config/*/*/*/*/*
因为我读到git需要一个单独的通配符用于每个目录级别。你可能已经理解了 - 它没有用。
所以,问题是 - 如何监控$HOME/
,$HOME/.config/
和$HOME/.local/share/
中的文件和目录,而不监控其内容。谢谢!
你想要的是使用.gitignore
专门忽略某些文件和子目录:
*/
!.config
!.config/*
.config/*/
!.local
!.local/*
.local/*/
要了解它是如何工作的,以及它为您做了什么(和不做什么),请阅读长版本。 (!.config/*
几乎肯定是不必要的;当我把*
作为不保存任何顶级文件的一部分时,我把它放进去。这不是你要求的。对于!.local/*
也是如此。虽然没有实际测试它,但是,我'我不确定.config/afile
是否符合.config
规则。)
(但请注意,您可能确实希望源代码控制其他.config
文件。我还建议以完全不同的方式执行此操作,使用.mloc类型文件的符号链接 - 这就是我所做的。)
除了系统施加的最大值(根据您的操作系统而异)之外,没有任何最大深度。但是这里有一个很大的问题:Git不存储目录
Git在其顶层存储项(即提交)下面存储的是文件(Git调用blob)以及相关的路径名。如果你要求Git提取提交#1234567 ...,Git会查看提交内容,找到各种blob的路径名,并在必要时创建目录(新的,空的)来保存特定的blob(即文件) )Git从该提交中提取它们存储在该提交中的名称。
这并不意味着你的想法注定要失败,只是因为你开始存在误解。例如,Git根本不会保存目录.config
。例如,它只会保存文件.config/Trolltech.conf
。如果Git已经在某个提交中保存了该文件,并且你git checkout
该特定提交,Git将根据需要创建一个新的空.config
。如果目录已经存在,Git将不会对此做任何事情。在某些情况下,例如从存在该文件的提交移动到不存在该文件的提交,Git也将删除该目录,但在某些情况下它不会,并且您将需要使用git clean -d
来制作Git真的删除它(如果可能的话,即它是否为空)。
保存该特定文件后,如果Git被指示忽略子目录.config/git
,Git可能无法保存文件.config/git/ignore
。这是事情变得复杂的地方。您需要了解Git如何提交工作,索引是什么以及它如何(在某种程度上)如何工作,以及Git如何使用和维护工作树。
正如我们上面提到的,从根本上说,Git存储的是提交。提交是一组完整的,大部分是一些文件集的快照,Git称之为blob。 (这故意忽略子模块和符号链接,但它们也存储为blob,使用一种类型的树条目将它们与普通文件区分开。)我说“大多数是独立的”因为每次提交都会记录一些父提交哈希值ID,尽管最常见,只有一个。存储三个父哈希ID的提交取决于这三个父提交的存在:缺少三个父项的存储库在某种程度上是不完整的.2父链接对于这个特定的应用程序并不重要,但是知道这是如何工作的很好。
但是,生命中存在一个特别困难的事件:创造它。创建提交后,它是只读的。它具有唯一的哈希ID,仅由提交的内容(包括其所有父哈希ID)确定。但是什么文件进入提交?这是关键问题,也是.gitignore
最终进入图片的地方。
2这是浅层克隆的本质。不浅的克隆(因此是完整的)从每个分支的提示提交(以及任何标记的提交或带注释的标记对象)开始。这些提交(或带注释的标记对象)指向早期的祖先,通过其父哈希ID提交。由于存储库已完成,因此也存在这些对象;它们包含父哈希ID,并且存在这些提交对象,依此类推。只有当我们达到一些没有父级的提交时,整个过程才会停止。通常这是有史以来第一次提交,显然不能有父母。这样的提交称为根提交,并且在任何非空但完整的存储库中,始终至少有一个根提交。
除了存储库本身 - 存储库是Git对象的数据库,即提交和blob以及Git调用树的中间事物(这些存储文件的名称,以及其他数据)-Git具有三个不同名称的关键数据结构。它被称为索引,临时区域和缓存。
索引通常几乎看不见。有一个Git命令,git ls-files
,可以直接显示索引的内容(git ls-files --stage
,甚至更详细,git ls-files --debug
),但它对最终用户并没有用。不过,对索引的一个很好的顶级描述是,它是你构建下一个提交的地方。
当您运行git commit
时,Git会以索引中当前具有的任何形式获取当前位于索引中的每个文件,并从中进行新的提交。这些是存储在新提交中的文件。新提交的作者和提交者是你;时间戳是“现在”;并且新提交的父级是您之前签出的任何提交;但是文件 - blob及其相关名称 - 完全由索引中的任何内容设置.3同样,当您使用git checkout
提取某些特定提交时,Git首先要做的是将该提交的文件复制到索引中。
请注意,当您进行新提交时,该新提交将成为当前提交。当发生这种情况时,Git会更新当前的分支名称 - 您已签出的分支,例如master
,以便它记录新的提交。实际上,每个分支名称只记录一个哈希ID。 Git将此称为分支的一角。正如我们在上面的脚注2中看到的那样,Git从分支提示开始向后工作,以查找分支中包含的所有提交。因此,进行新提交会将新提交的哈希ID推送到分支名称表中。
3即使你使用git commit -a
或git commit <file>
,Git实际上只是将文件复制到索引中 - 有时候是(辅助)索引 - 并从该索引构建提交。
存储在Git中的所有文件,无论是在存储库中还是在索引中,都是一种特殊的Git格式。如果计算机上的任何其他程序都可以使用这些文件,那么很少,因此Git会将每个文件提取到一个可用的版本中,您可以在那里工作。这是你的工作树。
通常,当前提交中的每个文件也会出现在工作树中。当然,当前提交是你运行git checkout
的那个。如果您只是运行git checkout master
来检查主分支,那么您在当前提交方面所做的就是检查master
标识的任何提交:该分支的提示。
如上所述,此时所有文件(blob对象)都被复制到索引中。 Git还能够使用索引中的任何内容来了解在此之前工作树中的内容:对于索引中的任何文件(因此在工作树中)并且现在不在索引中在这个结帐时,Git应该从工作树中删除该文件。它确实如此!对于Git必须在索引中替换的任何文件,或添加到索引,Git应该将索引版本复制到工作树 - 它确实如此。
git checkout
之后的索引中的内容恰好是您检出的提交中的blob(通过任何中间树对象)。这些文件的工作树版本将匹配这些文件的索引版本,但工作树版本实际上是可用的。这些文件的索引版本将匹配这些文件的提交版本 - 事实上,它们共享底层存储,因为索引只存储路径名和blob哈希ID。
现在,工作树中可能存在Git不知道的文件。根据定义,这些文件不在索引中。这些是未跟踪的文件。这就是未经跟踪的文件,在Git中:它是一个不在索引中的文件。没有更多的东西。
(好吧,你可以从索引中删除一个文件。然后它不在索引中,因此没有跟踪。这不是更多,但值得记住。)
未跟踪文件的问题是Git抱怨它们。 :-)它一直在抓住你,告诉你文件A,B和C没有跟踪。所以这就是.gitignore
的用武之地 - 但是.gitignore
是关于工作树的,而且与提交不同,工作树确实有目录。
您可以在.gitignore
中列出特定文件。如果这些文件不在索引中(未跟踪),但是在工作树中,Git会抱怨它们......但是它会看到它们被列在.gitignore
中并关闭。
您还可以使用git add
或git add .
进行git add --all
文件整合。这让Git扫描文件的工作树,并在找到它们之后,将每个git add
写入索引,将工作树版本复制到builds-the-next-commit索引版本中。显然,如果文件A,B和C当前都未被跟踪和忽略,但Git不应该添加它们。所以.gitignore
还告诉Git不要将现有的未跟踪和忽略的文件添加到索引中。
索引中的现有文件会被自动跟踪,因此任何可能添加这些文件的大型git add
都会添加它们,无论.gitignore
中列出了什么。换句话说,将跟踪文件添加到.gitignore
对它没有影响。在.gitignore
只影响未跟踪的文件。
但那是文件,而不是目录。这是一切都变得松鼠的地方。文件存在于目录内,普通文件系统中(即,不在Git中,而是在工作树中)。
Git拥有索引(并将其称为缓存)的一个重要原因是,查看大文件树中的每个文件往往非常慢。 Git可以使用索引来记录有关所有跟踪文件的信息,包括加速大量git add --all
样式操作的信息。对于索引中的文件来说这很好,但是对于(a)不在索引中的整个子目录来说怎么样呢?因此根据定义它们是未跟踪的并且(b)将被忽略,因此它们不会进入索引并将保持未跟踪?
Git可以完全避免扫描这些子目录。如果.config/dir/
将被忽略,而Git刚刚遇到.config/dir
这个名字并且它是一个目录,为什么然后,Git可以跳过它内部的阅读。这比读取它并检查每个文件以查看是否应该被忽略要快得多。
当Git扫描工作树时,它从顶部开始并读取树的全部内容:所有文件名和所有子目录名。它知道哪些是文件,哪些是子目录,但它还没有查看任何子目录。
现在,Git会检查所有文件:它们是否在索引中?如果是这样,他们会被跟踪:看看他们是否应该更新。如果没有,他们就没有受到攻击:看看Git是否应该抱怨他们。
接下来,Git检查所有子目录。对于每个子目录:索引中是否有任何文件?如果是这样,则必须检查子目录。但如果没有,是否忽略了子目录?如果是这样,甚至不要看里面。否则,请查看内部,就像我们在索引中存在文件时一样。
现在,对于每个文件或子目录,可以有一个或多个.gitignore
条目。以*
结尾的条目匹配文件和目录。以*/
结尾的条目与目录匹配。以!
开头的条目意味着:显然不会被忽略。
因此,假设Git正在扫描顶级并且遇到名称.a
,它是一个文件。 Git会查找匹配.a
的任何忽略条目。如果有一个条目*/
,那么,这与.a
不匹配;所以.a
被添加,除非有一个后来的条目覆盖它。没有,所以我们添加文件.a
。
接下来,Git遇到.adir
,这是一个目录。索引中没有.adir
文件,因此不强制扫描,因此Git将检查与.adir
匹配的忽略条目。由于*/
是唯一的匹配,Git会忽略该目录。现在它根本不会看到.adir
内部(除非你以某种方式将.adir/file
添加到索引中,这迫使Git读取.adir
以检查.adir/file
是否仍然存在)。
当Git遇到.config
(这是一个目录)时,有一个*/
说要忽略它,但它被!.config
覆盖,它说不要忽略它。有一个.config/*
但这只是.config
-the-directory,而不是.config/something
。所以!.config
是最后一个适用的条目,Git必须扫描.config
。
迟早,4 Git将会看到.config
内部。它可能会找到.config/afile
;这匹配!.config/*
。它匹配的最后一个条目告诉Git该文件不被忽略,因此它将被添加到索引中。然后Git遇到了.config/git
,这是一个目录。它匹配!.config/*
,然后.config/*/
;所以它被忽略了。 Git从来没有看过.config/git
。
这对.config
的其余部分重复。可能会有更多的.
文件,Git将照常处理,直到Git遇到.local
,这就像.config
一样。
一如既往,请记住,这不会影响任何现有的提交。检查任何存在违反.gitignore
规则的文件的现有提交将导致Git提取该文件,并在需要时创建其父目录。从该提交转移到缺少相同文件的提交,Git将删除该文件,如果包含它的目录为空,通常也会删除该目录。
4这是深度优先与广度优先扫描的结果.Git目前通过Git组织索引的方式进行ASCII排序,深度优先的目录遍历(因此它实际上是“现在”)。然而,从我们的“被忽视和不被忽视”的角度来看并不重要。
5每隔一段时间我就会看到一种奇怪的行为,这让我觉得必须有一些错误。偶尔的git clean -ndf
看看会被清理的东西,也许接着是git clean -df
来实际进行清洁,这很有用。但是我永远无法重现它,而且尝试......永远不够重要:-)