最终追溯 .gitignore(如何让 Git 完全/追溯忘记现在 .gitignore 中的文件)

goo*_*ogy 8 git gitignore

前言

这个问题试图消除关于追溯应用 .gitignore 的混淆,而不仅仅是现在/未来。1

基本原理

我一直在寻找一种方法来追溯强制执行我当前的 .gitignore ,就像我在第一次提交中创建了 .gitignore 一样

我正在寻求的解决方案:

  • 不会需要手动指定文件
  • 要求提交
  • 将追溯适用于所有分支的所有提交
  • 忽略工作目录中指定的 .gitignore 文件,而不是删除它们(就像最初提交的根提交的 .gitignore 文件一样)
  • 将使用 git,而不是 BFG
  • 将适用于 .gitignore 异常,例如:
 *.ext
 !*special.ext
Run Code Online (Sandbox Code Playgroud)

不是解决方案

git rm --cached *.ext
git commit
Run Code Online (Sandbox Code Playgroud)

这需要 1. 手动指定文件和 2. 附加提交,这将导致在其他开发人员拉取时删除新忽略的文件。(它实际上只是一个git rm- 这是从 git 跟踪中删除- 但它会将文件单独留在本地(您的)工作目录中。 之后其他git pull将收到文件删除提交)

git filter-branch --index-filter 'git rm --cached *.ext'
Run Code Online (Sandbox Code Playgroud)

虽然这确实可以追溯清除文件,但它 1. 需要手动指定文件和 2.本地工作目录中删除指定的文件,就像普通的一样git rm(对于其他人也是如此git pull)!


脚注

1这里有很多关于 SO 的类似帖子,其中包含不那么明确定义的问题,甚至更不准确的答案。请参阅带有 23 个答案的此问题,其中根据“忘记”的标准定义(如一个基本正确的答案所述),其中约 4k 票接受答案正确的,并且只有2 个答案包含所需的 git filter-branch命令。

这21分问题的答案 标记为前一个的副本,但问题是有不同的定义(忽略VS忘了),因此,尽管答案可能是适当的,它是重复的。

这个问题是我发现的最接近我正在寻找的问题,但答案并不适用于所有情况(带空格的路径......),并且在创建外部到-repository .gitignore 文件并将其复制到每个提交中。

goo*_*ogy 10

编辑:我最近发现了git-filter-repo。可能是更好的选择。也许是个好主意,调查理由和过滤分支陷阱的自己,但他们也不会影响到我下面的用例。


这种方法使混帐完全忘记或忽略的文件(过去/现在/未来),但并没有从工作目录(远程即使再拉)删除任何东西。

此方法需要在所有具有要忽略/忘记的文件的提交中使用/.git/info/exclude(preferred) OR a pre-existing1.gitignore

这种方法避免了在接下来的2 个开发者机器上删除新忽略的文件git pull

所有强制执行 Git 的方法都会忽略事后行为,有效地重写历史记录,因此对在此过程之后可能被拉出的任何公共/共享/协作存储库产生重大影响3

一般建议:从一个干净的 repo 开始- 提交的所有内容,工作目录或索引中没有任何未决的内容,并进行备份

此外,评论/修订历史这个答案和修订历史这个问题)可能是有用/启发。

#commit up-to-date .gitignore (if not already existing)
#these commands must be run on each branch
#these commands are not strictly necessary if you don't want/need a .gitignore file.  .git/info/exclude can be used instead

git add .gitignore
git commit -m "Create .gitignore"

#apply standard git ignore behavior only to current index, not working directory (--cached)
#if this command returns nothing, ensure /.git/info/exclude AND/OR .gitignore exist
#this command must be run on each branch
#if using .git/info/exclude, it will need to be modified per branch run, if the branches have differing (per-branch) .gitignore requirements.

git ls-files -z --ignored --exclude-standard | xargs -r0 git rm --cached

#Commit to prevent working directory data loss!
#this commit will be automatically deleted by the --prune-empty flag in the following command
#this command must be run on each branch
#optionally use the --amend flag to merge this commit with the previous one instead of creating 2 commits.

git commit -m "ignored index"

#Apply standard git ignore behavior RETROACTIVELY to all commits from all branches (--all)
#This step WILL delete ignored files from working directory UNLESS they have been dereferenced from the index by the commit above
#This step will also delete any "empty" commits.  If deliberate "empty" commits should be kept, remove --prune-empty and instead run git reset HEAD^ immediately after this command

git filter-branch --tree-filter 'git ls-files -z --ignored --exclude-standard | xargs -r0 git rm -f --ignore-unmatch' --prune-empty --tag-name-filter cat -- --all

#List all still-existing files that are now ignored properly
#if this command returns nothing, it's time to restore from backup and start over
#this command must be run on each branch

git ls-files --other --ignored --exclude-standard
Run Code Online (Sandbox Code Playgroud)

最后,遵循本 GitHub 指南的其余部分(从第 6 步开始),其中包括有关以下命令的重要警告/信息

git push origin --force --all
git push origin --force --tags
git for-each-ref --format="delete %(refname)" refs/original | git update-ref --stdin
git reflog expire --expire=now --all
git gc --prune=now
Run Code Online (Sandbox Code Playgroud)

从现在修改的远程存储库中提取的其他开发人员应该进行备份,然后:

#fetch modified remote

git fetch --all

#"Pull" changes WITHOUT deleting newly-ignored files from working directory
#This will overwrite local tracked files with remote - ensure any local modifications are backed-up/stashed

git reset FETCH_HEAD
Run Code Online (Sandbox Code Playgroud)

脚注

1因为/.git/info/exclude可以使用上述说明应用于所有历史提交,所以有关将.gitignore文件放入需要它的历史提交的详细信息可能超出了本答案的范围。我希望.gitignore在根提交中有一个适当的,好像这是我做的第一件事。其他人可能不在乎,因为/.git/info/exclude无论.gitignore提交历史记录存在于何处都可以完成相同的事情,并且显然重写历史记录是一个非常敏感的主题,即使知道后果也是如此

FWIW,潜在的方法可能包括git rebasegit filter-branch外部 复制.gitignore到每个提交中,就像这个问题的答案

2通过提交独立git rm --cached命令的结果在事后强制执行 git ignore 行为可能会导致在未来从强制推送的远程拉取中删除新忽略的文件。命令(之后)中的--prune-empty标志通过自动删除先前的“删除所有忽略的文件”索引仅提交来避免此问题。git filter-branch git reset HEAD^

3重写 git 历史也会改变提交哈希,这将对未来从公共/共享/协作存储库中拉取造成严重破坏。在对这样的 repo 执行此操作之前,请完全了解后果本 GitHub 指南指定了以下内容:

告诉你的合作者底垫中合并,他们创造了旧的(污点)仓库的历史掉任何分支机构。一次合并提交可能会重新引入您刚刚遇到清除问题的部分或全部受污染历史记录。

影响远程存储库的替代解决方案是git update-index --assume-unchanged </path/file>git update-index --skip-worktree <file>,其示例可在此处找到。


小智 6

这可能只是部分答案,但以下是我如何根据当前的 .gitignore 文件追溯删除以前的 git 提交中的文件:

  1. 备份您正在处理的存储库文件夹。我刚刚制作了整个文件夹的 .7z 存档。
  2. 安装git-filter-repo
  3. 暂时将 .gitignore 文件复制到其他位置。由于我在 Windows 上并使用命令提示符,因此我运行copy .gitignore ..\并仅将临时副本复制到目录级别
  4. 如果您的 .gitignore 文件具有通配符过滤器(例如nbproject/Makefile-*),您需要编辑临时复制的 .gitignore 文件,以便读取这些行glob:nbproject/Makefile-*
  5. 跑步git filter-repo --invert-paths --paths-from-file ..\.gitignore。我的理解是,这使用临时副本作为要删除的文件/目录列表。注意:如果您收到有关您的存储库不是干净克隆的错误,请在 git-filter-repo 帮助中搜索“FRESH CLONE SAFETY CHECK AND --FORCE”。当心。

有关更多信息,请参阅: git-filter-repo 帮助(搜索“基于多个路径的过滤”)

免责声明:我不知道我在做什么,但这对我有用。