在Git中提取一个已经更名的子目录的历史记录

3

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

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

不幸的是,这个子目录在历史上被重命名了,从a subdirectoryaSubdirectory。不幸的是,filter-branch在重命名处停止。

有没有办法解决这个问题?

2个回答

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 subdirectory”或“aSubdirectory”之外的所有内容。第二个命令将这些目录中的所有内容添加到存储库根目录中。最后,第三个命令通过删除这些目录中的任何文件来完成移动。
例如,从以下历史记录开始: $ 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
列出了Git日志并显示工作目录中当前分支中的更改情况,以及这些更改的文件名和状态。
其中,* da6c7ae (HEAD, myBranch) file2表示提交ID、分支名称和修改的文件名。而| A file2表示该文件是添加的。
此外,refs/original/refs/heads/myBranch是一个备份,用于验证结果后可以通过git update-ref -d refs/original/refs/heads/myBranch来丢弃。最终的输出只包括提交ID和文件名的列表。
  • 使用 --prune-empty,重命名目录的提交已经消失了。
  • 我不知道这将如何处理重命名冲突。
  • git lolgit lola 是非标准的但是非常有用的别名

感谢您提供如此详细的答案。我现在无法进行测试,但我注意到“重命名”是硬编码到脚本中的。有没有一种方法可以_检测_重命名而不必手动找出它们? - Onur
@Onur,该脚本无法手动检测重命名。您需要一个等效于“--subdirectory-filter”的操作,因此对于每个提交,它将移动任何位于“a subdirectory”或“aSubdirectory”中的内容到存储库根目录,并删除其他所有内容。 - Greg Bacon
我觉得你误解了我的意思。我不想在代码库中硬编码子目录的名称列表。我正在寻找一种方法,只需提供当前目录的名称,如“aSubdirectory”,让命令自己找出这个文件夹曾经被称为“a subdirectory”。 - Onur
@Onur 你是对的:我确实误解了。这是一个有趣的问题,需要更多的思考。 - Greg Bacon

0

我发现这个很有用,但是因为xargs和egrep缺少一些功能,所以在我的OSX上,@Greg Bacon的解决方案出现了一些问题。最终我写了一个Perl脚本,并确保它在$PATH中。

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

这个 Perl 脚本的样子是这样的(我猜你可以修改它来执行所有三个步骤,但这只执行了“第一部分”,请确保在这种情况下使用 system 而不是 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"
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接