git - 初始化“空”目录
问题描述
文件/目录结构:
main/home/script.py
main/home/a/.init
main/home/b/.init
我想设置我gitignore
排除主目录中的所有内容,但要包含特定的文件类型。
我尝试了什么:
home/* #exclude everything in the home directory and subdirectories
!home/*.py #include python files immediately in the home directory
!**.init #include .init files in all directories and subdirectories.
问题,我似乎无法确保.init
包含文件。该文件的目的是确保 git 将创建我所有的目录,即使它们还没有文件。因此,我想在每个目录中放置一个空的 0 字节 .init 文件,以确保“空”目录由 git 提交。
谢谢。
解决方案
例如,如果你想创建home/foo/.init
这个文件并将其放入 Git 的索引中(有关索引的更多信息,请参见下文),你需要告诉 Git不要切断对home/*/
目录的搜索:
!home/*/
然后,正如Fady Adal 指出的(但我稍作调整),您可能还想要:
!**/.init
这样当 Git 搜索时home/*/
,它会找到并取消忽略名为.init
. 请注意,这将忽略所有 .init
文件;也许你想要:
!home/**/.init
在这里,您可以忽略一个名为的文件,例如nothome/foo/.init
. (您甚至可以home/**/*
在不忽略home/**/*/
and时忽略home/**/.init
。)
龙:这是怎么回事?
我喜欢说 Git 只存储文件,而不存储目录,这是真的——但它是真的原因与 Git 构建新提交的方式有关,即从 Git 的index。
提交和索引
每个提交都存储了 Git 知道的每个文件的完整副本。然而,这个完整的副本以一种特殊的、只读的、仅限 Git 的、永久冻结的格式存储,其中重复的文件会自动进行重复数据删除。这样,您的第一次提交(例如)一个README.md
几乎不会更改的文件这一事实意味着每个提交都只是共享该README.md
文件。如果它确实发生了变化,新的提交将开始共享新文件。如果它变回,之后的新提交将返回共享原始文件。因此,如果只有 3 个版本,README.md
尽管有 300 万次提交,这 300 万次提交都共享文件的三个版本。
但请注意,这些文件实际上是只读的。你不能改变它们。甚至Git也无法更改它们(出于与哈希 ID 有关的技术原因;所有现有提交也是如此)。它们也不是大多数计算机程序都可以使用的格式。这意味着要处理文件,甚至只是查看它,Git 必须将冻结和压缩的、仅 Git 提交的文件扩展为普通的日常形式。
这意味着当您选择某个提交来处理它时,Git 必须从该提交中提取所有文件。因此,每个文件已经有两个副本:冻结的一个,以仅 Git 压缩和去重的形式,以及有用的一个,在您的工作树中。
大多数版本控制系统 (VCS-es) 具有相同的模式:每个文件都有一个提交的副本,以某种 VCS 特定的形式保存在 VCS 中,并且有一个纯文本/普通格式的版本供您使用。许多 VCS 到此为止,只有两个活动文件(其中一个可能存储在某个中央存储库中,而不是在您的计算机上;Git 将 VCS 副本存储在您的计算机上)。
那么,要进行新的提交,VCS 显然必须打包您的所有工作树(普通格式)文件。一些版本控制系统确实做到了这一点。大多数情况下,至少在这里放置一个缓存以使其更快,因为以这种方式做事非常缓慢。然而,Git 使用了一个鬼鬼祟祟的把戏。
在 Git 中,每个活动文件都有第三个副本。这第三个副本位于 Git 所称的不同的地方,即index、staging area,或者——现在很少见—— cache。从技术上讲,这通常不是副本,因为 Git 以内部、压缩和去重的形式存储它,所以它实际上只是对 blob-hash-ID 的引用。这也意味着它已准备好进入下一次提交。
这意味着索引(或暂存区,如果您更喜欢该术语)可以被描述为持有您打算进行的下一次提交。 索引在冲突合并期间发挥了扩展的作用,因此这不是一个完整的描述,但足以考虑它。当你git commit
用来进行新的提交时,Git 只是从索引中打包所有准备好的、冻结格式、预先去重的文件。但是索引只包含文件——例如,具有长名称的文件home/a/.init
,但包含文件,而不是目录。
签出一些提交,进行处理,意味着从该提交中提取文件。Git 将它们(以它们的冻结格式,但现在可以更改)放入索引中,以便它们准备好进行新的提交,并将它们解压缩为您的工作树中的普通格式,以便您可以查看和工作在他们。然后,当您使用 时git add
,您是在告诉 Git:使某个文件的索引副本与该文件的工作树副本匹配。
如果已经有一个索引副本,则该索引副本将被启动(尽管它可能在某些提交中是安全的)并且 Git 将工作树副本去重复为适当的压缩、冻结格式副本并将其放入索引中。
如果没有索引副本,现在有。(它仍然是重复数据删除:如果你创建一个包含一些旧文件内容的新文件,旧提交中的旧内容会被重新使用。)
无论哪种方式,它现在都准备好进行新的提交了。
这是.gitignore
进来的地方
这些.gitignore
文件的名称有些错误。它们并没有真正让 Git忽略文件。文件是否存在于您所做的新提交中,严格取决于您运行时该文件是否在索引中git commit
。
相反,.gitignore
它是双重的。首先,当您使用 时git status
,Git 会抱怨存在于您的工作树中但不在 Git 索引中的文件。此投诉的形式是告诉您某些文件未被跟踪。这就是 untracked 的字面意思:在你的工作树中有一个文件,你可以在其中查看和编辑它等等,但它现在不在 Git 的索引中。仅此而已,因为您可以随时将文件放入 Git 的索引 ( git add
) 或取出文件 (git rm
或git rm --cached
)。但是因为索引是每个新提交的来源,所以知道某个文件是否在索引中很重要——这就是为什么 Git 会抱怨它不在。
不过,有时这种抱怨只是烦人:是的,我知道这个编译的目标代码文件不在索引中。别告诉我!我已经知道了,这并不重要! 因此,为了防止 Git 抱怨,您将文件列在另一个可能应该被调用的文件中.git-do-not-complain-about-these-untracked-files
。
但这不是您通过在.gitgnore
. 它不仅会关闭git status
,而且git add
不会实际添加文件。所以git add *
或者git add .
不会添加目标代码文件,或者其他什么。因此,为了防止 Git 添加,您将文件列在一个可能应该被称为.git-do-not-auto-add-these-files
.
因此.gitignore
可能被称为.git-do-not-complain-about-these-untracked-files-and-do-not-automatically-add-them-either
. 但是一旦这些文件在索引中,一个.gitignore
条目就没有效果,所以也许它应该是.git-do-not-complain-about-these-untracked-files-and-do-not-automatically-add-them-either-but-if-they-are-in-the-index-go-ahead-and-commit-them
. 但这太荒谬了,.gitignore
事实就是如此。
扫描目录很慢
当你有一个庞大的 Git 存储库,其中包含数百万个文件时,Git 通常会很快完成的一些事情开始真正陷入困境。即使只有几十万个文件,有些事情也可能很慢。最慢的方法之一是扫描目录(或文件夹)以查找未跟踪的文件。2
通过在文件中列出一个目录,例如,您授予 Git 使用快捷方式的权限。通常,Git 会对自己说:啊,这是一个目录。我必须打开它并读出其中的每个文件,并查看这些文件是否在索引中,以确定这些文件是否未被跟踪和/或需要添加。 但如果要忽略整个目录,Git 可以稍作停顿:等等!我看是可以忽略的!我可以完全跳过它! 所以它继续而不是往里看。home/a/
.gitignore
home/a
home/a
home/b/
home/a/
要确保 Git不会跳过目录,您必须确保它不会被忽略。这是.gitignore
条目中尾随斜杠的来源。
1大多数甚至都没有这么大,但微软正在努力让 Git 在这种大小的存储库中运行。
2解决这类速度问题的常用技巧是插入缓存。这里的问题是,根据定义,未跟踪的文件不在索引中。Git 的索引确实有一个扩展来做一些未跟踪的缓存,但这永远无法捕获所有内容。
行.gitignore
格式
行的格式.gitignore
为:
- 空白行和注释行被忽略;
!
以否定开头的行;- 以引用目录结尾的行;
/
和 - 该行的其余部分命名文件,并带有前导和/或嵌入的斜杠。
否定只对撤消前一行的影响才有意义。一般来说,后面的行会覆盖前面的行,但是有一个很大的例外与跳过整个目录有关。
在任何!
标记否定之后-<em>以斜杠开头的行提供了根或锚定路径。3 因此/home
,例如,这意味着——<code>/home——而不是类似a/home
. 包含嵌入斜线的行也有根,因此home/a
两者/home/a
的含义相同。
最后的斜线(如果存在)将从“已扎根/锚定”测试中删除。也就是说,home/
并且/home/
是不同的,因为home
它是无根/无锚定的,但/home
它是有根/锚定的。
当 Git 扫描目录(文件夹)和子目录(子文件夹)时,它会尝试将在每个级别找到的每个文件或目录名称与所有非根 / 非锚定名称匹配。但是,只有特定级别的那些.gitignore
才能与根/锚定名称匹配。
模式中的尾部斜杠表示仅当这是一个目录时才匹配。所以 ifhome/a
是一个目录,它同时匹配home/*
and home/*/
; ifhome/xyz
是一个文件,它只匹配home/*
,不匹配home/*/
。
因此,如果我们想忽略下面的所有文件home
,我们使用:
home/*
忽略它们。这有一个嵌入的斜线,所以它是根/锚定的。不幸的是,它允许 Git跳过所有子目录,所以我们必须通过以下方式来应对:
!home/*/
它有一个尾部斜杠,因此它仅适用于目录。它也被锚定了。
3我在这里借用了正则表达式描述 中的锚定术语。Rooted是指 Git 存储库工作树的顶层。这两个术语都应该传达正确的想法;使用您喜欢的更好。
推荐阅读
- odoo - 如何隐藏树视图上的操作按钮?
- xml - VB.NET 无法将字符串写入根节点
- c - 将所有 C 必要的头文件放在一个头文件中是否很好?
- docker - 无法在 OpenShift 在线平台上加载 git-lfs 文件
- c# - Azure 搜索:如何使用 C# 以编程方式创建和删除帐户
- python - 从列表中获取正确的信息
- python - 尝试在 Flask 中加载 html 时,“加载资源失败:服务器响应状态为 404(未找到)”
- amazon-web-services - 适用于多个社交提供商的 AWS Cognito 联合身份:更好地合并身份还是将它们分开?
- ruby-on-rails - 未找到资源类(omniauth)
- python - 如何更新 cupy/CUDA 以使其再次工作并修复我的 conda 环境?