从git历史记录中删除除特定文件夹以外的所有文件夹

acruukt9  于 2022-12-25  发布在  Git
关注(0)|答案(4)|浏览(162)

我有一个复杂的git repo,我想删除其中的所有文件和历史记录,除了两个文件夹,比方说:

foo/a
bar/x/y

虽然git filter-branch --subdirectory-filter可以让我选择一个文件夹,并将其作为新的根目录,但它似乎没有给我任何选择两个目录并保留其位置的选项。
git filter-branch --tree-filter--index-filter似乎可以让我迭代历史中的每个提交,这样我就可以在不需要的文件夹上使用git rm
我似乎找不到任何有效的方法来让这些命令只保留我想要的两个文件夹,同时清除所有其他
谢谢!

wkftcu5l

wkftcu5l1#

您是正确的:树过滤器或索引过滤器将是用git filter-branch完成此操作的方式。
树过滤器更简单,但速度慢得多(很容易慢10到100倍)。树过滤器的工作方式是,您提供的命令在一个临时目录中运行,该目录包含且仅包含原始目录中存在的所有文件(现在被复制)提交。任何你的命令留下的文件,保留在复制的提交中。任何你的命令在临时目录中创建的文件,(你可以在临时目录中创建或删除目录,但这两种方式都不起作用,因为Git只存储文件。)因此,要删除除A和B之外的所有文件,请编写一个命令,删除除A或B之外的所有文件:

find . -name A -prune -o -name B -prune -o -print0 | xargs -0 rm

例如。
索引过滤器更难,但速度更快,因为Git不需要复制所有的文件到文件树,然后重新扫描文件树来建立一个新的索引,以复制原始提交。相反,它只提供一个索引,然后你可以使用git rm -rf --cached --ignore-unmatchgit update-index等命令来操作它。现在你所拥有的工具只有Git中那些操作索引的工具,没有花哨的Unix find命令。
当然,你有git ls-files,它读取索引的当前内容,因此你可以用任何你喜欢的语言编写一个程序(这里我可能会首先使用Python,其他人可能会从Perl开始),它本质上是:

for (all files in the index)
    if (file name starts with 'A/' or 'B/')
        do nothing
    else
        add to removal list
invoke "git rm --cached" on paths in removal list

如果你愿意相信没有文件名有一个嵌入式换行符,以上可以在常规shell中完成:

git ls-files | IFS=$'\n' while read path; do
    case "$path" in A/*|B/*) continue;; esac
    git rm --cached "$path"
done

这并不是非常高效(每个路径一个git rm --cached!),但应该作为--index-filter "开箱即用"。
(未经测试,但可能有效,而且效率应该更高:将git ls-files输出通过grep -v以移除所需文件,并将grep输出通过管道传输到git update-index --force-remove --stdin。这仍然假设路径名中没有换行符。)

xmakbtuz

xmakbtuz2#

对于 files,我已经用git fast-export做了这个,但是我不确定它是否能递归地工作在目录上,所以我建议使用git fast-exportfind的组合。

git fast-export HEAD -- `find foo/a bar/x/y -type f` >../myfiles.fi

然后创建一个新的存储库,并导入流。

git init
 git fast-import <../myfiles.fi
oaxa6hgo

oaxa6hgo3#

一种更新且更好的实现方法是使用filter-repo

git filter-repo --path foo/a --path bar/x/y

filter-branch现在在其文档中建议不使用(此处为版本2.30.0):
警告
git filter-branch有太多的缺陷,可能会对预期的历史重写产生不明显的破坏(而且可能会让您几乎没有时间调查此类问题,因为它的性能如此糟糕)。这些安全和性能问题无法向后兼容修复,因此,不建议使用它。请使用其他历史记录筛选工具,如git filter-repo[1]。如果仍需要使用git filter-branch,请仔细阅读“安全”一节(以及“性能”一节),了解过滤器分支的地雷,然后警惕地尽可能避免其中列出的危险。

wqnecbli

wqnecbli4#

经过多次失败的尝试后,我终于用git filter-repo解决了这个问题,这要归功于@Ken的回答和方法3:https://www.baeldung.com/git-remove-file-commit-history#using-git-filter-repo
其他的答案对我来说不合适,或者只是简单的混乱。特别是,没有一个filter-branch选项对我有效。

git clone <remoteA-URL>
cd <repo>

# If you want to push results to a new repo, uncomment these two lines:
#git remote rm origin
#git remote add origin <remoteB-URL>

# not sure this is necessary, but the resource above recommends it
git filter-repo --analyze

# Rewrite the commit history of the repo,
# recalculating hashes etc, removing all files, commits,
# and even partial commit-data not associated w/ the listed dirs
git filter-repo --force --path keepDir0 --path keepDir1
git push origin <branch>

请注意,根据官方git文档,git filter-repogit filter-branch更可取:https://git-scm.com/docs/git-filter-branch

相关问题