2

这个问题听起来与这里提出的许多问题相似,但令人讨厌的不同。

我有一个 git 存储库,它曾经是 svn 存储库(曾经是 cvs 存储库)。这包含可追溯到 1999 年左右的数据。

是时候将这个存储库拆分为几个不同的存储库了,以保留所有这些丰富的历史。但是,存储库的结构经常发生变化。当前所有的项目都来自一个基础项目,这个基础项目增长到几个项目,然后缩小到两个项目,然后再次增长。代码已被移动,但从未重复;它现在已经在几个成熟的项目之一中找到了最后的安息之地。

如果我想保留历史记录,这使得拆分存储库变得非常困难。使用 git-filter-branch 似乎是正确的方法,但所有这些似乎都破解了存储库的一部分并用它们截断了历史记录。

编辑添加为了澄清,这是一个小例子,假装我在存储库的根目录中。假设存储库如下所示:

foo/
    bar/
        file.txt
    baz/

现在假设我编辑file.txt. 然后我将其重命名为newfile.txt. 然后我再次编辑内容。然后我将此文件移出bar/和移入baz/. 我的存储库现在看起来像这样:

foo/
    bar/
    baz/
        newfile.txt

好的,现在假设我想拆分baz/到它自己的存储库中。使用 git filter-branch 或使用 git subtree split 将丢失所有提交消息和历史记录,以便newfile.txt在它位于内部bar/和命名时返回file.txt

我知道查看历史版本可能很疯狂;它可能引用了一个叫做的东西,../bar/或者它可能引用了一个不存在的无效目录并严重失败。我不在乎,只要我可以查看任何特定修订版的文件内容即可。

结束编辑

我想做的事情似乎有两条路径:

  1. 克隆存储库 N 次,在该存储库中保留我想要的文件夹(通过 git rm-ing 其他文件夹),并以某种方式破解任何最终不会引用 HEAD 中文件的修订。我意识到这会产生一些负面影响,因为检查旧版本不会提供有意义的代码库——我不在乎。为了做到这一点,我需要找到一种方法来获取来自 HEAD 中存在的所有文件的所有路径,我可以使用丑陋的脚本来做到这一点。

  2. 为每个索引期间存储库的样子构建某种历史索引。使用树过滤器并删除在其各自版本中不匹配的文件。然后,删除 HEAD 中未出现或源自文件的文件。

是否可以找到所有未出现在 HEAD 中的文件并删除与它们相关的任何历史记录?我不关心恢复已被长期删除的文件,这似乎是我问题的症结所在。

替代解决方案也将受到赞赏。我对 git 比较陌生,所以我可能遗漏了一些明显的东西。

4

2 回答 2

1

我最终不得不在几个阶段的过程中做到这一点。

首先,我得到了在存储库中找到的所有文件路径的列表:

git log --pretty=format: --name-only --diff-filter=A | sort -u

使用它,我能够确定我想要保留的文件曾经驻留在某个位置。就我而言,它们在其整个生命周期中都驻留在存储库中的四个独立目录中。我使用这些信息手动创建了一个正则表达式,例如(?:^foo|^bar/baz|^qux/(?:moo|woof)). 这与我想保留的目录相匹配。

然后我创建了一个 perl 脚本来保存这些路径名和包含它们的任何父路径名。

use Path::Class;    
if(scalar(@ARGV) < 1) { die "no regex"; }

my $regex = qr/$ARGV[0]/;    
my @want; my @remove; my $last = undef; my $lastrm = undef;

while(<STDIN>) {
    chomp;
    my $d = $_;
    if( $d =~ $regex ) {
        if( ! defined($last) || ! dir($last)->subsumes(dir($d)) ) {
            $last = $d;
            push @want, $d;
        }
    } else {
        if( ! defined($last) || ! dir($last)->subsumes(dir($d)) ) {
           push @remove, $d;
        }
    }
}
foreach $rm (@remove) {
    my $no_rm = 0;
    if( defined($lastrm) && dir($lastrm)->subsumes($rm) ) {
        $no_rm++;
    } else {
        foreach $keep (@want) {
            if( dir($rm)->subsumes(dir($keep)) ) {
                $no_rm++;
            }
        }
    }
    if( $no_rm == 0 ) {
        print "$rm\n";
        $lastrm = $rm;
    }
}

最后,我使用 git filter-branch 将我的新过滤器与我的正则表达式一起使用来保留我想要的路径。

git filter-branch --prune-empty --index filter '
    git ls-tree -d -r -t --name-only --full-tree $GIT_COMMIT 
    | sort | /path/to/filter.pl "(?:regex|of|paths)" 
    | xargs -n 50 git rm -rf --cached --ignore-unmatch' -- --all

排序是必要的,因为它可以确保 perl 脚本以正确的层次结构获取目录。

我希望这对某人有所帮助,因为我花了很多很多时间才想出这个。:)

于 2012-04-23T21:47:36.287 回答
0

您应该考虑安装和使用 git subtree https://github.com/apenwarr/git-subtree它可以很好地处理拆分存储库和保存历史记录。

于 2012-04-21T13:25:15.670 回答