如何删除在.gitignore中列出但仍存在于代码库中的文件?

649

我在代码仓库中有一些不需要的文件,我把它们加入到了 .gitignore 中,但是它们并没有从我的代码仓库中删除。

所以我的问题是,是否有一个神奇的命令或脚本可以使用 filter-branch 重写我的历史记录并轻松地删除所有这些文件?或者只需要一个命令就能创建一个提交来删除它们?


3
重复的 .gitignore 文件没有生效 - user456814
1
类似于https://dev59.com/pXM_5IYBdhLWcg3wrFMt的问题,让Git忘记一个曾经被跟踪但现在在.gitignore中的文件。 - testing
4
可能是重复的问题,与如何使 Git “忘记”已被跟踪但现在在 .gitignore 中的文件有关。原文链接为:How to make Git "forget" about a file that was tracked but is now in .gitignore? - Stevoisiak
警告:尽管这不会从您的本地删除物理文件,但它将在下一次git pull时从其他开发人员的机器上删除文件。[如何使Git“忘记”已跟踪但现在在.gitignore中的文件?] (https://dev59.com/pXM_5IYBdhLWcg3wrFMt) - LF00
@Stevoisiak 这不是那个问题的重复,因为这个问题问的是所有被忽略的文件,而且它也有比任何相似问题更好的答案。 - Omn
你也需要添加.gitignore文件。 - S.S. Anne
12个回答

767
您可以手动从存储库中删除它们:

您可以手动从仓库中删除它们:

git rm --cached file1 file2 dir/file3

或者,如果你有很多文件:

git rm --cached `git ls-files -i -c --exclude-from=.gitignore`

但是这似乎在Windows上的Git Bash中无法正常工作,它会产生一个错误信息。以下方法效果更好:

git ls-files -i -c --exclude-from=.gitignore | xargs git rm --cached  

在 Windows 上的 PowerShell 中,这可以工作得更好(处理路径和文件名中的空格):

git ls-files -i -c --exclude-from=.gitignore | %{git rm --cached $_}

关于没有这些文件重写整个历史,我非常怀疑是否有一种自动的方式可以做到。
而我们都知道,重写历史是不好的,对吧? :)


4
很遗憾,Windows上的Git Bash命令无法处理包含空格的路径。 - Nate Bundy
1
@NateBundy 如果你指的是xargs无法处理空格的问题,大多数命令行工具都会通过使用特殊标志来解决这个问题,使空格不再成为问题。我记不清git ls-filesxargs的标志是什么(我想xargs可能是-0),但你可以查一下。 - user456814
1
"git ls-files -i --exclude-from=.gitignore" 非常有用,它告诉我哪些文件被 .ignore 排除了。 - Owen Cao
1
我很高兴找到了这些命令,但它并没有真正从存储库中删除文件。当我删除了2300 kB的图像时,存储库大小仅减少了10 kB。因此,它不能用于使存储库更小、传输更快。 - Jan Potužník
1
请注意,无论您当前的目录是什么,--exclude-from=都相对于git根目录。因此,如果您想在子目录中使用.gitignore,请使用--exclude-from=[subdirectory]/.gitignore - marcvangend
显示剩余13条评论

639
一个无论操作系统如何都适用的更简单的方法是执行:
git rm -r --cached .
git add .
git commit -m "Drop files from .gitignore"

基本上,您需要删除并重新添加所有文件,但是git add会忽略.gitignore中的文件。

使用--cached选项将保留文件在您的文件系统中,因此您不会从磁盘中删除文件。

注意: 评论中有些人指出您将丢失所有文件的历史记录。我在MacOS上测试了git 2.27.0,并不是这种情况。如果要检查发生了什么,请在提交之前检查您的git diff HEAD~1


4
“fake commit message”是什么意思?这可是真正的“提交信息”(commit message)哦!:P 当然,根据你的需要,你可以更改该信息。 - gtatr
24
不要这样做,它会删除所有文件的历史记录。 - agrath
3
@agrath,你在哪里测试的?使用的是哪个版本的git?操作系统是什么? 在我的MacOS和git 2.27.0上并不是这种情况。 - gtatr
3
仅为澄清一下,因为执行了git rm -r --cached .git add .之后的git status可能看起来有点吓人:@gtatr提供的这三个命令实际上是从Git中删除了以前被跟踪过的文件,但是现在已经添加到你的.gitignore文件中。当我第一次运行它时,我看到了一堆文件,有点慌了,但仔细检查后发现它们都是列在我的.gitignore文件中的文件。 - Logan Besecker
4
这绝对应该是被接受的答案,而且你不会删除文件的历史记录(无论如何,在提交之前总是可以检查)。我刚在GitHub上对我的存储库进行了操作,完美地删除了所有不需要的文件,历史记录仍然存在!谢谢! - bastio84
显示剩余11条评论

213

由于在.gitignore文件中的文件未被跟踪,您可以使用git clean命令递归删除不在版本控制下的文件。

使用git clean -xdn进行干扰运行以查看将要被删除的内容。
然后使用git clean -xdf来执行它。

基本上,git clean -hman git-clean(在Unix上)会给您帮助。

请注意,此命令还将删除不在暂存区中的新文件


45
这个回答不适用——问题提出者说.gitignore中的文件正在被跟踪。 - Ken Williams
47
注意!这将永久删除所有未被追踪的文件,而不仅仅是从分支中删除。 - Emmanuel
12
git clean -xdn 是一个干运行命令,不会删除任何文件。下一个命令才会真正执行删除操作。 - JohnZaj
48
这个回答极具误导性 - 原帖作者想要从代码库中删除,而不是完全删除文件。我差点删除了我的IDE所需但不需要放在代码库中的大量动态文件。 - Auspice
对我来说确实有帮助,但是有点误导。可能更适合作为对被接受答案的评论。 - J Agustin Barrachina

8

我通过使用sed操作.gitignore语句的输出来实现了一个非常简单的解决方案:

cat .gitignore | sed '/^#.*/ d' | sed '/^\s*$/ d' | sed 's/^/git rm -r /' | bash

说明:

  1. 打印.gitignore文件
  2. 从打印结果中删除所有注释
  3. 删除所有空行
  4. 在每一行的开始添加“git rm -r ”
  5. 执行每一行。

25
sed 直接明了? - IgorGanapolsky
另一个类似的问题示例:https://dev59.com/4Ggu5IYBdhLWcg3w3qt7#63534476 - Rafael Valero
+1 对于编写脚本并解释它。没有经过审核就直接在bash中运行的危险可能是一个疏忽。 - Rawheiser
这不够健壮。一些.gitignore文件使用白名单模式,它们会忽略所有内容,但通过使用"!"来排除需要保留的内容。 - FarisHijazi
这不够健壮。有些.gitignore文件使用白名单模式,在这种模式下,它们会忽略所有内容,只排除需要保留的部分,使用"!"来实现。 - undefined

5

git rm --cached -r . 是删除所有缓存的命令,可以递归执行。

git add . 是将所有未被.gitignore忽略的文件添加到版本控制中。

你需要提交一些已经在文件系统中删除但没有真正删除的文件。

使用下面这个命令可以一次性完成以上两个操作:git rm --cached -r . && git add .


3

"git clean"(man)git ls-files -i(man) 在处理或显示被忽略目录中的被忽略路径时存在困惑,这在 Git 2.32 (Q2 2021) 中得到了纠正。

这意味着 接受的答案 的 2021 版本将是:

git ls-files -i -c --exclude-from=.gitignore | xargs git rm --cached  
                ^^

请查看提交 b548f0f, 提交 dd55fc0, 提交 aa6e1b2, 提交 a97c7a8, 提交 2e4e43a, 提交 b338e9f, 提交 7fe1ffd, 提交 7f9dd87 (2021年5月12日),作者是Elijah Newren (newren)
请查看提交 4e689d8 (2021年5月12日),作者是Derrick Stolee (derrickstolee)
(由Junio C Hamano -- gitster --提交 33be431中合并)

ls-files:未指定 -o 或 -c 时出现 -i 错误

签署者:Elijah Newren

ls-files --ignored(man)可以与--others--cached一起使用。

在一段时间的困惑和代码挖掘后,我认为ls-files -i只是坏掉了,没有输出任何东西,当我最终意识到-i可以与--cached一起使用来查找已跟踪的忽略文件时,我已经准备好提交一个很棒的补丁了。

虽然这是我失误,仔细阅读文档可能会更清晰明了,但我怀疑其他人也可能犯同样的错误。
实际上,在我们的测试套件中有两个用途,我相信其中之一也犯了这个错误。
在t1306.13中,没有跟踪的文件,所有在该测试中和以前测试中构建和使用的排除都必须与未跟踪的文件相关。
但是,由于他们寻找空结果,所以他们的错误命令也恰巧给出了空答案,这个错误就没有被发现。

-i大部分时间将与-o一起使用,这表明我们可以在缺少-o-c的情况下使-i暗示-o,但这将是一种向后不兼容的破坏。
相反,让我们只标记没有-o-c-i为错误,并更新两个相关的测试用例以指定它们的意图。

这意味着如果没有 -c,你将在 Git 2.32 (2021年第二季度)之后得到以下结果:
fatal: ls-files -i must be used with either -o or -c

注意:这仍然是一个正在进行中的工作,因为它在 Git 2.32-rc2 中被还原,但在 2021 年 5 月 27 日由 Junio C Hamano (gitster)修复,使用了提交 2c9f1bf提交 1df046b
请参见 提交 906fc55(2021 年 5 月 27 日)由Elijah Newren (newren) 提交。
请参见 提交 eef8148(2021 年 5 月 27 日)由Derrick Stolee (derrickstolee) 提交。
(由 Junio C Hamano -- gitster -- 合并于 提交 329d63e,2021 年 5 月 28 日)

dir: 介绍 readdir_skip_dot_and_dotdot() 辅助函数

署名:Elijah Newren


2

这个解决方案增加了回车(我是WSL用户,所以这很重要),以及括号转义(有时对LaTeX用户很重要,例如*.synctex(busy))。


灵感来自Scott的解决方案

cat .gitignore | sed "s/\r//" | sed -r "/^(#.*|\s*)$/d" | sed -r "s/([()])/\\\\\1/g" | sed "s/^/git rm -r /" | bash
  1. 删除:回车符(s/\r//)。
  2. 删除包含:注释(/^#.*$/)、空行组(/^\s*$/,匹配空格或空行)。注意管道符|字符,这是标准正则表达式,并需要-r(尽管我相信-E也可以)。
  3. 替换:括号/([()])/为其转义版本\\\1\1匹配该组,在本例中表示[()](),无论匹配到哪个。注意g标志,这是为了匹配(并替换)所有的括号。如果你喜欢,可以将其重写为"s/(\(|\))/\\\\\1/g"
  4. 在前面添加git rm -r

替换看起来像s/$old/$new/$flags。删除看起来像/$old/d。在前面添加是替换/^/。你可以通过替换/$/来进行追加。当然,由于我所知道的,在bash中不能创建原始字符串,因此需要转义一些字符。最后,这行可以被压缩,但是出于可读性的考虑,我选择保留它的扩展形式。


我看到有人在Scott的解决方案中质疑sed是直截了当的。我认为这种方法是最基本和最朴素的方法,这很好,因为如果你需要这个变体的话,可以立即制作它。如果有什么问题,这是练习正则表达式的好借口。


1

如果您真的想要修剪您的历史记录中被 .gitignore 忽略的文件,首先将 .gitignore 保存在仓库外面,例如作为 /tmp/.gitignore,然后运行以下命令:

git filter-branch --force --index-filter \
    "git ls-files -i -X /tmp/.gitignore -c | xargs -r git rm --cached --ignore-unmatch -rf" \
    --prune-empty --tag-name-filter cat -- --all

注意:

  • https://git-scm.com/docs/git-filter-branch#_warning 不是为了好玩而存在的。
  • git filter-branch --index-filter 运行在 .git 目录中,如果你想使用相对路径,需要先添加一个更多的 ../。显然,你不能使用 ../.gitignore,实际上的 .gitignore 文件,因为某些原因会导致 "fatal: cannot use ../.gitignore as an exclude file"(也许在执行 git filter-branch --index-filter 时,工作目录为空)。
  • 我希望使用类似 git ls-files -iX <(git show $(git hash-object -w .gitignore)) 的东西来避免将 .gitignore 复制到其他地方,但仅此而已就返回空字符串(而 cat <(git show $(git hash-object -w .gitignore)) 确实按预期打印了 .gitignore 的内容),所以我无法在 git filter-branch 中使用 <(git show $GITIGNORE_HASH)...
  • 如果你只想对特定分支进行 .gitignore 清理,那么请在最后一行中用它的名称替换 --all。然而,--tag-name-filter cat 可能无法正常工作,也就是说,你可能无法直接正确地转移单个分支的标签。

不建议这样做 https://git-scm.com/docs/git-filter-branch#_warning - rkedge

0
对于GitHub来说,最简单的方法是通过按下.键来打开在线的VS Code编辑器来查看存储库。然后,您只需右键单击并删除左侧面板中的文件/文件夹,然后提交更改。

VS Code Source Control


-1
在Linux中,您可以使用以下命令:
例如,如果我想要删除*.py~文件,那么我的命令应该是 ==> find . -name "*.py~" -exec rm -f {} \;

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