忽略Git仓库中所有文件但保留文件夹结构。

34

我有如下文件夹结构:

/foo/
/foo/test.txt
/foo/.gitkeep
/foo/bar/test.txt
/foo/bar/.gitkeep
/foo/bar/baz/test.txt
/foo/bar/baz/.gitkeep

现在我想要排除“foo”文件夹中的所有文件以及其子文件夹(包括子文件夹的子文件夹),但保留所有的“.gitkeep”文件(这样可以保持文件夹结构)。这也适用于超过3个级别的子文件夹。

以下是可行的.gitignore规则:

/foo/**
!/foo/**/
!/foo/**/.gitkeep

有人能解释一下为什么这个有效吗?有没有更简洁的方法?


1
我认为这样会更清晰:/foo// !/foo/*/.gitkeep - Kalamarico
不行,不起作用。/foo/test.txt 没有被忽略,/foo/bar/.gitkeep 被忽略了,/foo/bar/baz/.gitkeep 也被忽略了。 - David Vielhuber
4个回答

49

规则很简单:

如果父目录已经被排除,那么无法重新包含该文件。

这就是为什么你需要递归地忽略文件和文件夹:

  • /foo/**
    

(如果只忽略/foo/,即该文件夹,则无论有多少个'!'排除规则都不起作用,因为Git会停在foo/文件夹上)

  • 然后从忽略规则中排除文件夹:

!/foo/**/
  • 将像.gitkeep这样的文件列入白名单之前

  • !/foo/**/.gitkeep
    

    这是因为.gitkeep父文件夹被排除在忽略规则外。

    与Bernardo最初的建议(在他编辑之前)相反:

    /foo/** 
    !**/.gitkeep
    

    如果不在/**忽略规则中排除文件夹,则文件的排除规则将无效。

    您可以使用以下命令进行检查:

    git check-ignore -v -- /path/to/.gitkeep
    

    如在scm .gitignore中所述:

    • 使用 '*' 与 '**' 相同
    • 使用!.gitkeep(不带 / 锚点)将递归地排除该文件。

    在这两种情况下,“递归”是一个关键术语,它解释了为什么排除规则可以应用:如果忽略一个文件夹(例如 . /foo/),那么您将无法排除该文件夹内的任何内容。
    但是,如果您递归地忽略元素(文件和文件夹)(***),并且从 gitignore 规则中排除文件夹(再次通过递归规则 !*),则可以排除(白名单)文件。


    3
    谢谢您!我已经寻找了很久,但所有的.gitkeep解决方案都没有提到递归忽略和异常背后的逻辑。 - mattador

    6
    我已经完成了。看起来需要在所需文件夹内创建一个.gitignore文件。
    看看这个存储库,它正是你想要的。
    我试图在foo/foo/bar/foo/bar/baz/中添加任何文件名,但它只接受.gitkeep。
    诀窍是在文件夹内创建一个包含以下内容的.gitignore文件:
    *
    !*/
    !.gitignore
    !.gitkeep
    

    以下是需要翻译的内容:

    [编辑:根据评论添加]

    你应该能够将其简化为:

    *
    !*/
    !.git*
    

    虽然未来可能不太清楚意图,但这个解决方案并没有缩短原始的.gitignore所需的行数,但它具有其他几个优点-主要的一个是这将是一个文件夹内的.gitignore文件,这意味着它可以是一个干净的文件,只有这些行,并且仅特定于此文件夹。
    这意味着该.gitignore文件可以轻松地移动到需要此过滤器的任何特定文件夹中。此外,整个文件夹可以轻松地在存储库内移动,甚至可以移动到另一个存储库,而无需修改.gitignore。这更加清洁和可维护,并且不会被埋在存储库根文件夹中通常很大的.gitignore文件中。

    这个不起作用。根据这个规则,只有下面的文件被忽略: /foo/.gitkeep我也希望这些文件不被忽略: /foo/bar/.gitkeep /foo/bar/baz/.gitkeep - David Vielhuber
    似乎无法简化该语句,现在我明白了。我想知道为什么我找不到那个特定的答案。 - David Vielhuber
    @DavidVielhuber 我已经完成了,请查看我的编辑解决方案,其中包括一个示例存储库进行测试。尝试在这些文件夹中添加任何文件名,您会发现它不接受任何 ;D - Bernardo Duarte
    是的,那也可以,但有一些注意事项(4行而不是3行,以及一个通用的忽略规则)。 - David Vielhuber
    事实上,使用这个方法,您只需将所需文件夹放入其中,而无需将它们的名称放入忽略列表中,使其具有不可知性,因此更加灵活和可扩展。您只需要复制并移动它以在另一个文件夹甚至另一个项目中使用即可。对于这4行规则,我认为如果您添加一个通用的!.git*异常,它将同时捕获!.gitignore和!.gitkeep。 - Bernardo Duarte
    显示剩余2条评论

    4

    我的声望不足以发表评论,但我想提出一个重要问题:

    • 使用挂载的数据文件夹会导致像 git status 这样的 git 命令运行速度非常慢,因为这些命令仍然会扫描所有文件(尽管没有提交它们),这将通过网络缓慢进行。

    • 更加难以预测的是,如果安装了 Oh My Zsh,每次执行命令后都会暂时挂起shell,因为它会静默地检查 git 分支(我在这里提出了这个问题:here)。

    解决这个问题的一种方法是执行以下操作:

    1. 将以下内容添加到您的.gitignore 中:
    data/**
    !data/**/
    !data/**/.gitkeep
    
    1. 使用git add data命令将文件添加到Git暂存区。

    2. 从你的.gitignore文件中删除第二行:

    data/**
    # !data/**/
    !data/**/.gitkeep
    
    1. git add .gitignore

    2. git commit

    此命令将数据目录结构提交到 git,并确保任何未来的 git 命令都会完全忽略数据子目录的内容。


    1

    空目录无法提交到Git。所以创建一个README.md文件或任何空文件,忽略所有其他文件(使用模式匹配)。这样目录将被提交带有占位符文件。


    这个很有帮助。与问题帖子中使用.gitkeep不同,我可以在子目录中使用.gitignore作为文件,以便子目录不为空。 - undefined

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接