【问题标题】:Remove all except certain folders from git history从 git 历史记录中删除除某些文件夹之外的所有文件夹
【发布时间】:2017-03-16 12:59:06
【问题描述】:

我有一个复杂的 git 存储库,我想从中删除 所有 文件和历史记录,但两个文件夹除外,比如说:

foo/a
bar/x/y

虽然git filter-branch --subdirectory-filter 会让我选择一个文件夹并将其设为新的根目录,但它似乎没有给我任何选项来选择两个目录并保留它们的位置。

git filter-branch --tree-filter--index-filter 似乎可以让我遍历历史中的每个提交,我可以在不需要的文件夹上使用 git rm

我似乎找不到任何有效的方法来获取这些命令以仅保留我想要的两个文件夹,同时清除 所有内容

谢谢!

【问题讨论】:

  • 解决方案是:git filter-branch --prune-empty --index-filter 'git ls-files | grep -vE "foo/a/|bar/x/y/" | xargs git rm -rf --cached --ignore-unmatch'

标签: git


【解决方案1】:

您是对的:树过滤器或索引过滤器将是使用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。这仍然假定路径名中没有换行符。 )

【讨论】:

  • 谢谢@torek!不熟悉find 我不得不稍微解构它,直到我让它工作但大约需要一个小时。我找到了一个很好的解决方案:git filter-branch --prune-empty --index-filter 'git ls-files | grep -vE "foo/a/|bar/x/y/" | xargs git rm -rf --cached --ignore-unmatch',尽管一次迭代超过了我可以通过管道传递给xargs 的参数数量。我通过第一次使用git filter-branch --prune-empty --index-filter 'git rm -rf --cached --ignore-unmatch c/ d/ e/' HEAD首先删除整个大文件夹来解决这个问题
【解决方案2】:

对于文件,我使用git fast-export 完成了这项工作。但我不确定这会在目录上递归地工作。所以我建议使用git fast-exportfind 的组合。

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

然后创建一个新的 repo,并导入流。

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

【讨论】:

    【解决方案3】:

    实现此目的的一种更新更好的方法是使用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,请仔细阅读 “安全”部分(和“性能”部分)学习 关于过滤器分支的地雷,然后警惕 尽可能避免其中列出的许多危害。

    【讨论】:

      猜你喜欢
      • 2020-06-06
      • 1970-01-01
      • 2018-01-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-04-21
      • 2013-03-31
      • 1970-01-01
      相关资源
      最近更新 更多