【问题标题】:How to extract one file with commit history from a Git repo with index-filter & co?如何使用 index-filter & co 从 Git 存储库中提取具有提交历史记录的文件?
【发布时间】:2011-11-14 14:06:41
【问题描述】:

我有一个从 SVN 转换为 Mercurial 再到 Git 的 Git 存储库,我只想提取一个源文件。我也有奇怪的字符,比如(编码不匹配损坏了 Unicode ä)和文件名中的空格。

如何从存储库中提取一个文件并将其放在新存储库的根目录下?

【问题讨论】:

  • 这就是我所需要的。顺便说一句,stackoverflow.com/questions/5998987/… 不是任何子目录过滤问题的克隆。提取文件需要 --subdirectory-filter 步骤和 --index-filter 或 --tree-filter。
  • 或者更确切地说是我想要的,因为我将制作一个提供 trie 的单个文件的包。我也想在其他项目中使用它,并在 Github 上发布,我在 repo 中有一些我不想开源的代码(至少现在)。
  • 截至 2.24 (03/07/2019) git-filter-repo 是 git-filter-branch 的推荐替代品

标签: git extract git-filter-branch


【解决方案1】:

似乎这不是特别容易,这就是我将回答我自己的问题的原因,尽管关于 git [index-filter|subdirectory-filter|filter-tree] 有许多类似的问题,因为我需要使用所有以前的来实现这一点!

首先快速说明一下,即使是在Splitting a set of files within a git repo into their own repository, preserving relevant history 上的评论中的咒语

SPELL='git ls-tree -r --name-only --full-tree "$GIT_COMMIT" | grep -v "trie.lisp" | tr "\n" "\0" | xargs -0 git rm --cached -r --ignore-unmatch'
git filter-branch --prune-empty --index-filter "$SPELL" -- --all

对于名为 imaging/DrinkkejaI<0300>$'\302\210'.txt_74x2032.gif 的文件没有帮助。 aI<0300>$'\302\210' 部分曾经是一个字母:ä

所以为了提取单个文件,除了 filter-branch 我还需要做:

git filter-branch -f --subdirectory-filter lisp/source/model HEAD

或者,您可以使用 --tree-filter: (需要进行测试,因为该文件之前位于另一个目录中,请参阅: How can I move a directory in a Git repo for all commits?)

MV_FILTER='test -f source/model/trie.lisp && mv ./source/model/trie.lisp . || echo "Nothing to do."'
git filter-branch --tree-filter $MV_FILTER HEAD --all

要查看文件的所有名称,请使用:

git log --pretty=oneline --follow --name-only git-path/to/file | grep -v ' ' | sort -u

http://whileimautomaton.net/2010/04/03012432所述

之后也按照以下步骤操作:

$ git reset --hard
$ git gc --aggressive
$ git prune
$ git remote rm origin # Otherwise changes will be pushed to where the repo was cloned from

【讨论】:

【解决方案2】:

请注意,如果您将此与将所需文件移动到新目录的附加步骤相结合,事情会变得容易得多。

这可能是一个非常常见的用例(例如,将所需的单个文件移动到根目录)。
我这样做(使用 git 1.9)是这样的(首先移动文件,然后删除旧树):

git filter-branch -f --tree-filter 'mkdir -p new_path && git mv -k -f old_path/to/file new_path/'
git filter-branch -f --prune-empty --index-filter 'git rm -r --cached --ignore-unmatch old_path'

您甚至可以轻松地为所需文件使用通配符(无需乱用 grep -v )。

我认为这('mv' 和 'rm')也可以在一个过滤器分支中完成,但它对我不起作用。

我没有尝试使用奇怪的字符,但我希望这对你有所帮助。让事情变得更容易对我来说似乎总是一个好主意。

提示:
这是对大型回购的耗时操作。因此,如果您想执行多个操作(例如获取一堆文件,然后在“new_path/subdirs”中重新排列它们),最好尽快执行“rm”部分以获得更小更快的树。

【讨论】:

  • 我也在 ubuntu 12.04 和 git 1.7.x 上尝试过,结果如下: * ubuntu 上也出现了权限被拒绝的问题 * git 1.7.x 对我提到的命令表现不佳上面(因为只有 1 个文件匹配它总是被重命名为它应该移动的目录。所以我推荐 git 1.9.x 我在我的 Windows 机器上运行
  • 修改了我的帖子,因为我的大部分问题似乎是由我不存在的 bash 技能引起的 -> 使用 '&&' 而不是 '|'现在组合命令
  • 第一步在 git 2.2.1 中对我不起作用。回购没有变化。
  • 我有时也遇到这个问题,第一步没有改变。事实证明 mv 命令没有移动任何文件,因为路径不匹配任何文件(注意 git 不保留任何关于空目录的信息)
  • 第二步为我删除了所有文件(旧路径为.)为什么不:git filter-branch -f --subdirectory-filter new_path -- --all
【解决方案3】:

完成相同任务的更快、更易于理解的过滤器:

git filter-branch --index-filter '
                        git read-tree --empty
                        git reset $GIT_COMMIT -- $your $files $here
                ' \
        -- --all -- $your $files $here

【讨论】:

  • 这对我来说非常有效。我添加了一个 --prune-empty 参数来删除任何空提交。
  • @AaronJensen 最后一行的--all -- $your $files $here 被传递给filter-branch 运行的git rev-list,因此过滤器分支看到的提交已经被修剪。这比让 filter-branch 毫无意义地加载索引并运行过滤器并创建新树和提交要快得多,然后再将其全部扔掉以进行未触及这些文件的提交。不过,添加它并没有什么坏处。
  • 如何将它应用到单个分支?将-- --all 替换为-- branchname ?
  • 对我来说,这保留了触及相关文件的提交,但它们都是空的,并且文件本身在首次创建的提交中以 present 状态添加文件(即不在当时的实际状态)。
  • 我不确定你为什么认为我使用了 cmd。这实际上是在 Linux 上的鱼。
【解决方案4】:

以下内容将重写历史记录并仅保留涉及您提供的文件列表的提交。您可能希望在您的存储库的克隆中执行此操作以避免丢失原始历史记录。

FILES='path/to/file1 other-path/to/file2 file3'
git filter-branch --prune-empty --index-filter "
                        git read-tree --empty
                        git reset \$GIT_COMMIT -- $FILES
                " \
        -- --all -- $FILES

然后您可以根据您的用例通过普通的mergerebase 命令将该新分支合并到您的目标存储库中。

【讨论】:

    【解决方案5】:

    我在这里找到了一个使用 git log 和 git 的优雅解决方案: https://www.pixelite.co.nz/article/extracting-file-folder-from-git-repository-with-full-git-history/

    如果它消失了,你可以这样做:

    1. 在原始仓库中,

      git log --pretty=email --patch-with-stat --reverse --full-index --binary -- path/to/file_or_folder > /tmp/patch
      
    2. 如果文件在子目录中,或者如果您想重命名它

      sed -i -e 's/deep\/path\/that\/you\/want\/shorter/short\/path/g' /tmp/patch
      
    3. 在一个新的空仓库中

      git am < /tmp/patch
      

    【讨论】:

      【解决方案6】:

      现在有一个新的命令git filter-repo
      它有更多的可能性和更好的性能。
      man page

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2019-08-27
        • 2015-01-12
        • 1970-01-01
        • 1970-01-01
        • 2014-11-01
        • 2012-12-14
        • 2019-03-17
        相关资源
        最近更新 更多