在重命名的git中提取子目录历史记录

时间:2013-11-12 16:28:45

标签: git git-filter-branch

我知道我可以使用如下命令为git中的指定分支提取给定文件夹的历史记录:

git filter-branch --subdirectory-filter "a sub directory" -- myBranch

不幸的是,子目录在历史记录的某个时刻从a subdirectory重命名为aSubdirectory。不幸的是,filter-branch在重命名时停止。

有没有办法实现这个目标?

2 个答案:

答案 0 :(得分:2)

git filter-branch --prune-empty --index-filter '
  git ls-files -z |
    egrep --invert-match --null-data "^(a subdirectory|aSubdirectory)/" |
      xargs -0 --no-run-if-empty git rm --cached -q

  git ls-files -s | sed -re "s-\t(a subdirectory|aSubdirectory)/-\t-" |
    git update-index --index-info

  git ls-files -z |
    egrep --null-data "^(a subdirectory|aSubdirectory)/" |
      xargs -0 git rm --cached -q
' -- myBranch

重命名文件是一个高级操作(哈!),因此我们将其分解为删除并添加组件。

脚本中的第一个命令会删除a subdirectoryaSubdirectory之外的所有内容。第二个将这些目录中的所有内容添加到存储库根目录中最后,第三个通过删除这些目录中的任何文件来完成移动。

例如,从

的历史开始
$ git lol --name-status
* 27c7275 (HEAD, myBranch) file2
| A     aSubdirectory/file2
* 39d7e75 mv
| D     a subdirectory/file1
| A     aSubdirectory/file1
* c710654 file1
  A     a subdirectory/file1

运行上面的git filter-branch命令会产生

的历史记录
$ git lola --name-status
* da6c7ae (HEAD, myBranch) file2
| A     file2
* d94110a file1
  A     file1
* 27c7275 (refs/original/refs/heads/myBranch) file2
| A     aSubdirectory/file2
* 39d7e75 mv
| D     a subdirectory/file1
| A     aSubdirectory/file1
* c710654 file1
  A     a subdirectory/file1

refs/original/refs/heads/myBranch是一个备份,在验证结果后,您可以使用

进行放弃
git update-ref -d refs/original/refs/heads/myBranch

离开

$ git lola
* da6c7ae (HEAD, myBranch) file2
* d94110a file1

备注:

  • 由于--prune-empty
  • ,重命名目录的提交消失了
  • 我不知道这将如何处理重命名冲突。
  • git lolgit lola是非标准的highly useful aliases

答案 1 :(得分:0)

我发现这很有用,但是我在OSX上并且在@Greg Bacon的解决方案上遇到了一些麻烦,因为xargs和egrep缺乏一些功能。 我最终编写了一个perl脚本,我确信它是在$ PATH

git filter-branch --prune-empty --tag-name-filter cat --index-filter 'git_prune_non_proxy_dirs.pl' -- --all

perl脚本看起来像这样(我想你可以修改它来执行所有这三个步骤"第一部分"确保在这种情况下使用系统而不是exec来调用git)

#!/usr/bin/perl 

open(LS,"git ls-files|") || die "Failed to git ls-files: $!\n";
my %to_remove=();
while ( <LS> ) {
    chomp;
    if( $_ =~ m#(^adserver-proxy(|-api)/)#) {
    }else{
        my @components = split('/',$_, 2);
        my $path_to_delete = $components[0];
        $to_remove{$path_to_delete}=1;
    }
}
if (%to_remove) { 
    my @cmd=(
        "git","rm","-rfq","--cached","--", keys %to_remove);
    exec(@cmd) || die "Failed to invoke git rm"
}