Git - gitignore / exclude中的最大深度

问题描述 投票:0回答:1

一个简短的故事:我最近在我的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/中的文件和目录,而不监控其内容。谢谢!

linux git
1个回答
2
投票

TL;DR

你想要的是使用.gitignore专门忽略某些文件和子目录:

*/
!.config
!.config/*
.config/*/
!.local
!.local/*
.local/*/

要了解它是如何工作的,以及它为您做了什么(和不做什么),请阅读长版本。 (!.config/*几乎肯定是不必要的;当我把*作为不保存任何顶级文件的一部分时,我把它放进去。这不是你要求的。对于!.local/*也是如此。虽然没有实际测试它,但是,我'我不确定.config/afile是否符合.config规则。)

(但请注意,您可能确实希望源代码控制其他.config文件。我还建议以完全不同的方式执行此操作,使用.mloc类型文件的符号链接 - 这就是我所做的。)

Long

除了系统施加的最大值(根据您的操作系统而异)之外,没有任何最大深度。但是这里有一个很大的问题: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如何使用和维护工作树。


1Git存储树条目,could work as a flag by which to save empty directories, but other parts of Git combine in strange ways to make this whole concept fail


Git is built around the concept of commits

正如我们上面提到的,从根本上说,Git存储的是提交。提交是一组完整的,大部分是一些文件集的快照,Git称之为blob。 (这故意忽略子模块和符号链接,但它们也存储为blob,使用一种类型的树条目将它们与普通文件区分开。)我说“大多数是独立的”因为每次提交都会记录一些父提交哈希值ID,尽管最常见,只有一个。存储三个父哈希ID的提交取决于这三个父提交的存在:缺少三个父项的存储库在某种程度上是不完整的.2父链接对于这个特定的应用程序并不重要,但是知道这是如何工作的很好。

但是,生命中存在一个特别困难的事件:创造它。创建提交后,它是只读的。它具有唯一的哈希ID,仅由提交的内容(包括其所有父哈希ID)确定。但是什么文件进入提交?这是关键问题,也是.gitignore最终进入图片的地方。


2这是浅层克隆的本质。不浅的克隆(因此是完整的)从每个分支的提示提交(以及任何标记的提交或带注释的标记对象)开始。这些提交(或带注释的标记对象)指向早期的祖先,通过其父哈希ID提交。由于存储库已完成,因此也存在这些对象;它们包含父哈希ID,并且存在这些提交对象,依此类推。只有当我们达到一些没有父级的提交时,整个过程才会停止。通常这是有史以来第一次提交,显然不能有父母。这样的提交称为根提交,并且在任何非空但完整的存储库中,始终至少有一个根提交。


The files in a new commit are set up in the index

除了存储库本身 - 存储库是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 -agit commit <file>,Git实际上只是将文件复制到索引中 - 有时候是(辅助)索引 - 并从该索引构建提交。


The work-tree

存储在Git中的所有文件,无论是在存储库中还是在索引中,都是一种特殊的Git格式。如果计算机上的任何其他程序都可以使用这些文件,那么很少,因此Git会将每个文件提取到一个可用的版本中,您可以在那里工作。这是你的工作树。

通常,当前提交中的每个文件也会出现在工作树中。当然,当前提交是你运行git checkout的那个。如果您只是运行git checkout master来检查主分支,那么您在当前提交方面所做的就是检查master标识的任何提交:该分支的提示。

如上所述,此时所有文件(blob对象)都被复制到索引中。 Git还能够使用索引中的任何内容来了解​​在此之前工作树中的内容:对于索引中的任何文件(因此在工作树中)并且现在不在索引中在这个结帐时,Git应该从工作树中删除该文件。它确实如此!对于Git必须在索引中替换的任何文件,或添加到索引,Git应该将索引版本复制到工作树 - 它确实如此。

git checkout之后的索引中的内容恰好是您检出的提交中的blob(通过任何中间树对象)。这些文件的工作树版本将匹配这些文件的索引版本,但工作树版本实际上是可用的。这些文件的索引版本将匹配这些文件的提交版本 - 事实上,它们共享底层存储,因为索引只存储路径名和blob哈希ID。

现在,工作树中可能存在Git不知道的文件。根据定义,这些文件不在索引中。这些是未跟踪的文件。这就是未经跟踪的文件,在Git中:它是一个不在索引中的文件。没有更多的东西。

(好吧,你可以从索引中删除一个文件。然后它不在索引中,因此没有跟踪。这不是更多,但值得记住。)

Ignoring untracked files

未跟踪文件的问题是Git抱怨它们。 :-)它一直在抓住你,告诉你文件A,B和C没有跟踪。所以这就是.gitignore的用武之地 - 但是.gitignore是关于工作树的,而且与提交不同,工作树确实有目录。

您可以在.gitignore中列出特定文件。如果这些文件不在索引中(未跟踪),但是在工作树中,Git会抱怨它们......但是它会看到它们被列在.gitignore中并关闭。

您还可以使用git addgit 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来实际进行清洁,这很有用。但是我永远无法重现它,而且尝试......永远不够重要:-)

© www.soinside.com 2019 - 2024. All rights reserved.