简体   繁体   English

为什么使用通配符 (*) 和 git 状态从 gitignore 中排除时未列出 .config 目录?

[英]Why .config directory is not listed when excluded from gitignore using wildcard (*) and git status?

I know the question is a bit cryptic, I couldn't word it exactly in a single sentence ( might need some help on that ).我知道这个问题有点神秘,我不能用一个句子准确地说出来(可能需要一些帮助)。

I initialized git in my home directory (ie, ~/ , on Arch Linux ) to backup my dot-files (mainly configs).我在我的主目录(即~/ ,在Arch Linux上)初始化了 git 以备份我的点文件(主要是配置)。 I want to include every file and folder in it except the ones starting with a .我想在其中包含每个文件和文件夹,除了以. (like .config/ and .bashrc ). (如.config/.bashrc )。

So I made a .gitignore file whose contents are:所以我制作了一个.gitignore文件,其内容是:

# Ignore everything
*

# Except these files and folders
!.*

But the problem is when I list all the untracked files ( git status ), it doesn't list the .config/ directory for some reason.但问题是当我列出所有未跟踪的文件( git status )时,由于某种原因它没有列出.config/目录。 I tried playing around with .gitignore and adding我尝试使用.gitignore并添加

!*/

shows all directories including .config/ and also Documents , Downloads etc, which I don't want to include.显示所有目录,包括.config/以及DocumentsDownloads等,我不想包括在内。

And instead adding而是添加

!.*/

shows every other directory that starts with a .显示以 . 开头的所有其他目录. like .cache/ , .vim/ etc. But for some reason the .config/ doesn't show up..cache/.vim/等。但由于某种原因.config/没有出现。

I even tried我什至试过

!.config/

and

!.config

it doesn't work.它不起作用。 The only thing that works is !*/ (all directories, which is not what I want)唯一有效的是!*/ (所有目录,这不是我想要的)

Any way to solve this.有什么办法可以解决这个问题。 Its really annoying.它真的很烦人。

[Kinda Solved]: edit: it was a bug [有点解决]:编辑: 这是一个错误

After some playing around I figured a way around it.玩了一圈之后,我想出了一个解决办法。 I don't know if I can call it exactly a solution but it works and this is how my .gitignore looks like:我不知道我是否可以将其称为完全解决方案,但它可以工作,这就是我的.gitignore的样子:

# Ignore everything
*

# Except these files and folders
!.*
!.*/*

And it works, when I run git status it includes all files and directories starting with a .它有效,当我运行git status时,它包括所有以. including .config/ and some others which I didn't notice wasn't shown earlier along with .config/ .包括.config/和其他一些我没有注意到的,之前没有与.config/一起显示。 Check my comment below if you wanna try to figure out (or help me to figure out) why this works.如果您想弄清楚(或帮助我弄清楚)为什么会这样,请查看下面的评论

TL;DR TL;博士

After the bug fix, you'll want what you put in your "kinda solved" section, or something similar.修复错误后,您将需要在“有点解决”部分中放入的内容或类似内容。 I think you'll want what I put in my "bottom line" section, really:我想你会想要我放在“底线”部分的内容,真的:

/*
!/.*

Long

As jthill noted in a comment , there is a bug in the .gitignore wildcard handling in Git 2.34.0, which will be fixed in 2.34.1.正如jthill 在评论中指出的那样,Git 2.34.0 中的.gitignore通配符处理存在一个错误,该错误将在 2.34.1 中修复。 In this case I think the bug is making your wildcarding work better than it would otherwise, though.在这种情况下,我认为该错误使您的通配符工作比其他情况更好

The first lines:第一行:

# Ignore everything
*

do just what they claim: ignore everything .做他们声称的事:忽略一切 All files and folders (directories) are ignored.所有文件和文件夹(目录)都被忽略。 Subsequent lines insert exceptions.后续行插入异常。 But hang on a moment, what does ignored really mean?但是等一下,忽略的真正含义是什么? To get there, we must note what Git's index (or staging area ) is and how Git makes new commits from the index / staging-area.要到达那里,我们必须注意 Git 的索引(或暂存区域)是什么以及 Git 如何从索引/暂存区域进行新提交。

The index, or staging area, in Git, is a central and crucial concept. Git 中的索引或暂存区是一个中心且至关重要的概念。 Trying to use Git without understanding what the index is doing is a bit like trying to pilot an airplane without understanding what the wings and engine are for.在不了解索引在做什么的情况下尝试使用 Git 有点像在不了解机翼和发动机用途的情况下尝试驾驶飞机。 1 So: the index is all about the next commit you plan to make . 1所以:索引是关于你计划进行的下一次提交 If you never make any new commits, you don't really need to know about it, but if you do want to make new commits, you need to know this.如果您从未进行任何新的提交,您实际上不需要知道它,但如果您确实想进行新的提交,您需要知道这一点。 2 2

When you first extract some commit, in order to use and work on it, Git fills in its index from that commit, so that the index contains all the files from that commit.当您第一次提取某个提交时,为了使用和处理它,Git该提交中填写其索引,以便该索引包含该提交中的所有文件。 From this point onward, everything you are doing in your working tree, in the pursuit of making a new commit, is irrelevant to Git .从现在开始,你在工作树中所做的一切,为了追求新的提交,都与 Git 无关 That is, it's irrelevant up until you tell Git that you'd like Git to copy updated and/or new files into Git's index.也就是说,在您告诉 Git 您希望 Git 将更新和/或新文件复制到 Git 索引之前,它是无关紧要的。

The git add command is about updating Git's index. git add命令是关于更新 Git 的索引。 The files you name to git add , with git add file1 file2 for instance, are to be copied into Git's index.您命名为git add的文件,例如git add file1 file2 ,将被复制到 Git 的索引中。 If there's already a copy of those two files, those copies get booted out of the index, replaced with the updated ones.如果已经有这两个文件的副本,则这些副本会从索引中启动,并替换为更新的副本。 If not, those files are newly added to the index.如果没有,这些文件将新添加索引中。

Once a file is in the index , you can replace it at any time: any .gitignore entry is irrelevant at this point.一旦文件在 index中,您可以随时替换它:此时任何.gitignore条目都无关紧要。 You can also remove it from the index , with git rm , or by using git add after removing the working tree copy: either one will remove the index copy.您也可以使用git rm或使用git add在删除工作树副本后将其索引中删除:任何一个都将删除索引副本。 Now it's no longer in the index and the .gitignore entries are back in play.现在它不再在索引中并且.gitignore条目重新发挥作用。

You can use an en-masse git add , as in git add.您可以使用git add ,如git add. or git add * , 3 to have Git scan directories and files and add them for you.git add * , 3让 Git扫描目录和文件并为您添加它们。 When you do this, Git will skip certain directories and/or files if it can, and this is an area where .gitignore really comes into play.当你这样做时,Git 将跳过某些目录和/或文件,如果可以的话,这是.gitignore真正发挥作用的领域。


1 "Why should I care about those? I only care about getting my passengers and cargo from point A to point B, and those are inside the plane, not out on the wings." 1 “我为什么要关心这些?我只关心将我的乘客和货物从 A 点运送到 B 点,这些都在飞机内,而不是机翼外。”

2 To extend the plane analogy a bit more: if you're just planning to use the fuselage as a house, then indeed, you don't need to care about the engines and wings. 2再把飞机类比扩展一点:如果你只是打算把机身当房子用,那么实际上,你不需要关心发动机和机翼。

3 Note that in Unix-like shells, git add * is quite different from git add. 3请注意,在类 Unix shell 中, git add *git add. because the shell will expand * for Git: Git never sees the literal asterisk.因为shell将扩展*对于 Git:Git 永远不会看到文字星号。 When the shell expands * , it does so with dot-files excluded, by default at least (bash in particular has a control knob to change this behavior).当 shell 扩展*时,它会排除点文件,至少默认情况下(特别是 bash 有一个控制旋钮来更改此行为)。 In some CLIs, the literal asterisk * gets through to Git, and then Git will expand * , and now it can act like git add.在某些 CLI 中,文字星号*会传递到 Git,然后Git将扩展* ,现在它可以像git add. if Git wants it to.如果 Git 想要它。 But it's easier to type in git add.但是输入git add. (no SHIFT key required) so that's what I always do anyway, which removes the difference in the first place. (不需要SHIFT键)所以无论如何我总是这样做,这首先消除了差异。


How Git scans the working tree Git如何扫描工作树

If you run git add.如果你运行git add. or equivalent (see footnote 3 again), Git will:或等效(再次参见脚注 3),Git 将:

  1. Open the directory .打开目录. . .
  2. Open and read any .gitignore file at this level, adding (appending) these rules to the ignore rules.在此级别打开并读取任何.gitignore文件,将这些规则添加(附加)到忽略规则中。 (These rules then get dropped when we finish this directory.) (当我们完成这个目录时,这些规则就会被删除。)
  3. Read this directory: it contains the names of files and sub-directories ("folders", if you prefer that term).阅读此目录:它包含文件和子目录的名称(“文件夹”,如果您更喜欢该术语)。
  4. Check each file and folder name as we read them, against all the ignore rules that are in effect right now.在我们阅读每个文件和文件夹名称时检查它们,对照目前有效的所有忽略规则。 Note that some rules apply only to directories / folders , and others apply to both folders and files .请注意,某些规则仅适用于目录/文件夹,而其他规则同时适用于文件夹和文件 The folder-only rules are those that end with a slash.仅文件夹规则是以斜杠结尾的规则。 Also, some rules are "positive" (do ignore) and some are "negative" (do not ignore).此外,有些规则是“积极的”(不要忽略),有些是“消极的”(不要忽略)。 The negative rules are the ones starting with !否定规则是以!开头的规则。 . .

Git finds the last applicable rule , whatever that is, in the current set of rules, and then obeys that rule. Git 在当前规则集中找到最后适用的规则,无论是什么,然后遵守该规则。 So first, let's define which rules apply to which directory-scan results, and then what the various rules do.所以首先,让我们定义哪些规则适用于哪些目录扫描结果,然后是各种规则的作用。

A rule in a .gitignore can be: .gitignore中的规则可以是:

  • a simple text string with no slashes, such as generated.file ;一个没有斜杠的简单文本字符串,例如generated.file
  • a text string with a trailing slash, but no other slashes: somedir/ ;带有斜杠的文本字符串,但没有其他斜杠: somedir/
  • a text string with a leading or embedded slash, with or without a trailing slash: /foo , a/b , /foo/ , a/b/ , and so on;带有前导或嵌入斜杠的文本字符串,带有或不带有斜杠: /fooa/b/foo/a/b/等; or或者
  • any of the above with various glob -style wildcard characters.上面的任何一个都带有各种glob样式的通配符。

These can all be negated: if a rule starts with !这些都可以否定:如果规则以! it's negated, and we strip off the !它被否定了,我们去掉了! and then use the remaining tests.然后使用剩余的测试。 The two keys tests are these:两个关键测试是:

  • Does the entry end with a literal / ?条目是否以文字/结尾 If so, it applies only to directories / folders.如果是这样,它仅适用于目录/文件夹。 Ignore that slash while answering the remaining question.在回答剩下的问题时忽略那个斜线。
  • Does the entry begin with or contain a slash / character?条目是否以斜杠/字符开头包含斜杠/字符? (The one at the end does not count here.) If so, this entry is anchored or rooted (I like the term anchored myself, but I've seen both terms used). (最后一个在这里不算。)如果是这样,这个条目是锚定的或扎根的(我喜欢锚定自己的术语,但我已经看到使用这两个术语)。

An anchored entry matches only a file or folder name found at this level .锚定条目仅匹配在此级别找到的文件或文件夹名称。 That is, /foo or foo/bar won't match sub/foo or sub/foo/bar , only ./foo and ./foo/bar , where .也就是说, /foofoo/bar不会匹配sub/foosub/foo/bar ,只有./foo./foo/bar ,其中. is the directory (folder) that Git is scanning right now .是 Git现在正在扫描的目录(文件夹)。 This means that if the entry has several levels— foo/bar or one/two/three for instance—Git will have to remember to apply this entry when it gets around to scanning bar in foo , or two in one and three in one/two .这意味着如果条目有多个级别——例如foo/barone/two/three ——Git 必须记住在扫描foo中的bar时应用此条目,或者two one three one/two So we do have to consider "higher level" rules.所以我们必须考虑“更高级别”的规则。 But since lower level rules get appended , a lower level .gitignore can cancel out the higher level one if it wants to.但是由于附加了较低级别的规则,因此如果需要,较低级别的.gitignore可以取消较高级别的规则。

An un-anchored entry applies here and—unless overridden—in every sub-directory as well .未锚定的条目适用于此处,并且(除非被覆盖)也适用于每个子目录 That is, if we do have ./one/two/three , Git will presumably open and read one to find two , and then open and read two to find three , all while still working on the current directory .也就是说,如果我们确实有./one/two/three ,Git 可能会打开并读取one找到two ,然后打开并读取two找到three同时仍在当前目录上工作 Meanwhile any un-anchored entry from this .gitignore will apply within the one and one/two directories, and within one/two/three if that's a directory, and so on.同时,此.gitignore中的任何未锚定条目都将适用oneone/two目录,如果是目录,则适用于one/two/three ,依此类推。

So, there's already a lot to think about.所以,已经有很多事情要考虑了。 Now we throw in glob matches.现在我们加入全局匹配。

The usual glob is * : people write foo*bar or *.pyc or whatever.通常的 glob 是* :人们写foo*bar*.pyc或其他什么。 Git allows ** as well, with meaning similar to that in bash: zero or more directories. Git 也允许** ,含义类似于 bash:零个或多个目录。 (I've found ** in Git to be weird and in my opinion slightly buggy, where it sometimes seems to mean "one or more" instead of "zero or more", so I recommend avoiding ** if possible. It's hard to reason about, so it's generally not a great idea in the first place, and Git's ignore rules mostly eliminate any need for ** . So if you are going to use it, test it carefully and be prepared to have it shift on you in some future Git, in case the one-or-more?bug? gets fixed, or affects your use case, or whatever.) (我发现 Git 中的**很奇怪,而且在我看来有点错误,有时它似乎意味着“一个或多个”而不是“零个或多个”,所以我建议尽可能避免使用** 。很难原因,所以这通常不是一个好主意,而且 Git 的忽略规则基本上消除了对**的任何需要。所以如果你使用它,请仔细测试它并准备好让它在某些时候转移到你身上未来的 Git,以防一个或多个?错误?得到修复,或影响您的用例,或其他。)

Let's suppose, then, that we have these two entries:那么,假设我们有这两个条目:

*
!.*

Git opens and reads . Git 打开并读取. and finds the following names:并找到以下名称:

dir
file
.dir
.file

where dir and .dir are directories (folders) and file and .file are non-directories (files).其中dir.dir是目录(文件夹),而file.file是非目录(文件)。

The * rule matches all four names. *规则匹配所有四个名称。 The ..* rule matches the last two names. ..*规则匹配最后两个名称。 The ..* rule is later in the .gitignore file, so it overrides the * rule. ..*规则稍后在.gitignore文件中,因此它覆盖了*规则。 Git therefore "sees" .dir and .file . Git 因此“看到” .dir.file

Since .file is a file, this means that git add.由于.file一个文件,这意味着git add. "sees" it. “看到它。 It will check whether .file needs to be git add -ed to displace the existing .file file, or added to the index.它将检查.file是否需要被git add -ed 以替换现有的.file文件,或添加到索引中。

Since dir and file are excluded, this scanning pass doesn't see them, and does not try to git add either one.由于dirfile被排除在外,因此此扫描过程不到它们,并且不会尝试将git add任何一个。 Since dir itself is a directory (not a file), it's never in the index itself.由于dir本身是一个目录(不是文件),因此它永远不会在索引本身中。 There may be a file in the index named dir/thing , and Git will check to see if that should be updated by this git add.索引中可能有一个名为dir/thing的文件,Git 将检查该文件是否应由此git add. , but Git won't scan dir to see if there are other files in dir . , 但 Git 不会扫描dir以查看dir是否还有其他文件。

Since file is an excluded file, the scanning pass does not see it.由于file是排除文件,因此扫描过程看不到它。 But if file already exists in the index, Git will check to see if it should be updated by this git add.但是如果file已经存在于索引中,Git 将检查它是否应该被这个git add. , even though it didn't get scanned here. ,即使它没有在这里被扫描 In other words, these "existing files already in the index" checks happen outside (either before or after) the "scan the directories" pass.换句话说,这些“已存在于索引中的文件”检查发生在“扫描目录”通道之外(之前或之后)。

Meanwhile, since .dir isn't excluded, Git now opens and reads .dir , recursively:同时,由于排除.dir ,因此 Git 现在打开并递归读取.dir

  • Git checks for a .dir/.gitignore (the .gitignore that applies to entries found in .dir ). Git 检查.dir/.gitignore (适用于.dir中的条目的.gitignore )。 If that exists, Git appends those rules.如果存在,Git 会附加这些规则。
  • Git scans .dir recursively, using all the same methods. Git 使用所有相同的方法递归地扫描.dir Then it's done scanning .dir so Git removes the appended rules.然后它完成扫描.dir所以 Git 删除附加的规则。

Let's look now at the rules Git has in effect as it scans .dir .现在让我们看看 Git 在扫描.dir时生效的规则。

The appended-to rules附加规则

If there is a .dir/.gitignore , Git opens and reads it and appends to the existing rules.如果有.dir/.gitignore , Git 打开并读取它并附加到现有规则。 If not, we still have the same set of rules in effect:如果没有,我们仍然有相同的规则集:

*     (positive wildcard: ignore every name)
!.*   (negative wildcard: don't ignore dot-names)

What's in .dir ? .dir中有什么? Let's say we have:假设我们有:

file1
dir1
.file2
.dir2

The name file1 matches * so it gets ignored .名称file1*匹配,因此被忽略 Git won't git add it to the index if it's not already there. Git 不会git add到索引中。 Similarly, dir1 matches * , so it gets ignored .同样, dir1匹配* ,因此它被忽略 Git won't even scan it to see if there are any files there. Git 甚至不会扫描它以查看那里是否有任何文件。

The name .file2 matches * , but also matches .* , so the override negative entry is the rule that applies: Git will git add.dir/.file2 .名称.file2匹配* ,但也匹配.* ,因此覆盖否定条目是适用的规则: Git 将git add.dir/.file2 The name .dir2 has the same features, so the override applies and Git will open and read .dir/.dir2 .名称.dir2具有相同的功能,因此应用覆盖并且 Git 将打开并读取.dir/.dir2 This goes through the same recursion as before: Git looks for .dir/.dir2/.gitignore to append rules, and will use the appended-to rules while scanning .dir/.dir2 , and then drop back to our own .dir/.gitignore -appended rule set while continuing to scan .dir , and then return from this recursion level and drop the .dir/.gitignore rules.这经历了与以前相同的递归: Git 查找.dir/.dir2/.gitignoreappend规则,并在扫描.dir/.dir2时使用附加到规则,然后返回到我们自己的.dir/.gitignore附加规则集,同时继续扫描.dir ,然后从该递归级别返回并删除.dir/.gitignore规则。

The bottom line底线

In the end, the trick here is that we want the * rule to apply only at the top level .最后,这里的诀窍是我们希望*规则仅适用于顶层 Once we get into, say, .foo/ , we don't want to ignore .foo/main_config and .foo/secondary_config .一旦我们进入.foo/ ,我们不想忽略.foo/main_config.foo/secondary_config So we want * to apply only at the top level .所以我们希望*只在顶层应用。

Using:使用:

# Ignore everything
*

# Except these files and folders
!.*
!.*/*

gets us closer: we ignore everything, but then—via the negative rules ..* and ..*/* —we carefully don't ignore .foo and the like.让我们更接近:我们忽略所有内容,但随后——通过否定规则..*..*/* ——我们小心地不要忽略.foo等。 Once we get into .foo , we carefully don't ignore .foo/main_config .一旦我们进入.foo ,我们小心不要忽略.foo/main_config

The bug, or possible bug, depending on what you really do want, here is... well, suppose we have .foo/thing1/config and .foo/thing2/config .错误或可能的错误,取决于您真正想要的,这里是......好吧,假设我们有.foo/thing1/config.foo/thing2/config The .*/* pattern contains an embedded slash , which means it is anchored . .*/*模式包含一个嵌入的斜线,这意味着它是锚定的。 It matches .foo/thing1 , so that directory gets scanned.它匹配.foo/thing1 ,因此该目录被扫描。 But it doesn't match .foo/thing1/config .但它匹配.foo/thing1/config

We could try something like:我们可以尝试类似:

!.*/*
!.*/**/

I particularly hate this one because ** is so tough to reason about.我特别讨厌这个,因为**很难推理。 We could also write:我们也可以这样写:

!.*/*
!.*/*/
!.*/**/

in case the ** "one or more" bug bites us (I don't think it will, but it's a consideration).以防** “一个或多个”错误咬我们(我认为不会,但这是一个考虑因素)。 But it's simplest to anchor the original globs , by writing:但是最简单的锚定原始 glob的方法是:

/*
!/.*

This makes the top level .gitignore rules apply only to top-level work-tree entries .这使得顶级.gitignore规则适用于顶级工作树条目 Sub-level .gitignore files, if they exist, can establish sub-level rules and do not need to override any top-level rules, because the top-level rules already don't apply at any sub-level , thanks to anchoring.子级.gitignore文件,如果存在,可以建立子级规则,不需要覆盖任何顶级规则,因为顶级规则已经不适用于任何子级,这要归功于锚定。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 删除Linux上.git目录和.gitignore的所有内容 - remove everything exept for .git directory and .gitignore on linux rsync link_stat使用通配符时没有这样的文件或目录 - rsync link_stat No such file or directory when using wildcard 向.gitignore添加了文件(不要忽略),但是git status看不到它 - Added file to .gitignore (to not ignore) but git status doesn't see it 文件未与 git status 一起列出并在提交后丢失 - Files were not listed with git status and got lost after a commit 使用Linux在目录中使用通配符查找文件 - Find files using wildcard in directory using Linux 从目录中的对象构建.ko时在Makefile中使用通配符的问题 - Issues with using wildcard in Makefile while build .ko from objects across directory 当我的 shell 使用该目录时,为什么可以删除该目录? - Why can a directory be removed when my shell is using that directory? 如何使用 pip -t 从 git 将 package 安装到特定目录中 - How to install a package from git into a specific directory using pip -t 任何人都可以解释为什么“git status”在linux下的共享下运行时显示文件是否已修改? - Can anyone explaine why “git status” shows files as modfied when running under a share on linux? Git pull 不会拉取 .gitignore - Git pull does not pull .gitignore
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM