我有一个复杂的git repo,我想删除所有文件和历史记录,除了两个文件夹,让我们说:
foo/a
bar/x/y
虽然git filter-branch --subdirectory-filter
允许我选择一个文件夹,并将其设为新的根目录,但它似乎没有给我任何选择两个目录的选项,并保留它们的位置。
git filter-branch --tree-filter
或--index-filter
似乎会让我遍历历史记录中的每次提交,我可以在不需要的文件夹上使用git rm
。
我似乎找不到任何有效的方法来让这些命令保留我想要的两个文件夹,同时清除所有内容。
谢谢!
答案 0 :(得分:2)
你是对的:使用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-unmatch
之类的命令进行操作,或者对于最常见的情况使用git 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
。仍然假设路径名中没有换行符。)
答案 1 :(得分:1)
对于文件,我已使用git fast-export
完成此操作。但我不确定这会对目录进行有效的处理。因此,我建议使用git fast-export
和find
的组合。
git fast-export HEAD -- `find foo/a bar/x/y -type f` >../myfiles.fi
然后创建一个新的仓库,并导入流。
git init
git fast-import <../myfiles.fi
答案 2 :(得分:0)
实现这一目标的更新更好的方法是使用 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
,请仔细阅读
称为“安全”的部分(以及称为“性能”的部分)来学习
关于过滤器分支的地雷,然后警惕
尽可能避免此处列出的危险。