【问题标题】:List last commit dates for a large number of files, quickly快速列出大量文件的最后提交日期
【发布时间】:2012-02-23 17:04:36
【问题描述】:

我想列出 git 存储库中大量文件的最后提交日期

为了具体起见,让我们假设我想获取特定子目录中所有*.txt 文件的最后提交日期。存储库中总共有数万个文件,相关*.txt 文件的数量在数百个左右。存储库中已经有数千个提交。

我尝试了三种不同的方法。


解决方案 1。 This question 给出了一个答案,基于 git log。但是,如果我尝试做这样的事情,它会很慢:

find . -name '*.txt' |
    xargs -n1 git log --format=format:%ai -n1 --all -- '{}'

在我的测试用例中,它花了几分钟——对于我的目的来说太慢了。


解决方案 2。 这样的事情会快得多,不到一秒:

git log --format=format:%ai --name-only .

但是,我必须编写一个脚本来对输出进行后处理。此外,上面的命令会打印出 lots 永远不需要的信息:不相关的文件和旧的提交。


解决方案 3. 我也尝试过类似的方法,以删除不相关的文件:

git log --format=format:%ai --name-only `find . -name '*.txt'`

然而,这比解决方案 2 慢。(运行时间相差 3 倍。)此外,它仍然会打印不再需要的旧提交。 p>


问题。我错过了什么吗?有没有快捷方便的方法?当我们有更多的提交时,最好是不仅现在而且将来也可以使用的东西?

【问题讨论】:

  • 你试过用 grep 代替 find 吗? IE。 git log --format=format:%ai --name-only | grep "*\.txt"
  • @deiga:这不正是上面的“解决方案 2”,即获取所有内容的日志并进行一些后期处理吗?

标签: git


【解决方案1】:

试试这个。

在 git 中,每个提交都引用一个 tree 对象,该对象具有指向每个文件状态的指针(文件是 blob 对象)。

因此,您要做的是编写一个程序,该程序以您感兴趣的所有文件的列表开始,并从HEAD 对象(通过git rev-parse HEAD 获得的SHA1 提交)开始。它检查是否在该树中修改了任何“感兴趣的文件”(树从git cat-file commit [SHA1] 的“树”属性获取)-注意,您必须下降到每个目录的子树。如果它们被修改(意味着与“先前”修订版中的不同 SHA1 哈希),它会从兴趣集中删除每个此类并打印适当的信息。然后它继续到当前树的每个父节点。这一直持续到兴趣集为空为止。

如果您想要最大速度,您将使用 git C API。如果您不希望那么那么快,您可以使用git cat-file tree [SHA1 hash](或者更简单的git ls-tree [SHA1 hash] [files]),它将执行绝对最少的工作来读取特定的树对象(它是管道层的一部分)。

这在未来能否继续发挥作用值得怀疑,但如果向前兼容是一个更大的问题,您可以从 git cat-file 提升一个级别 - 但正如您已经发现的那样,git log 相对较慢,因为它瓷器的一部分,而不是管道。

请参阅here 以获得有关 git 对象模型如何工作的非常好的资源。

【讨论】:

    【解决方案2】:

    我也认为您的解决方案 #2 是最快的,您可以找到几个使用此方法设置访问时间的脚本。避免打印较旧访问时间的一种方法是使用例如一个哈希。

    我在 perl 中编写了一些脚本来修改访问时间,经过一些修改,这是一个应该打印你所追求的版本:

    #!/usr/bin/perl
    my $commit = $ARGV[0];
    
    $commit = 'HEAD' unless $commit;
    
    # git a list of access times and files
    my @logbook = `git whatchanged --pretty=%ai $commit`;
    
    my %seen;
    my $timestamp;
    my $filename;
    foreach (@logbook) {
        next if /^$/; # skip emtpy lines
        if (/^:/) {
            next unless /.txt$/;
            chomp ($filename = (split /\t/)[1]);
            next if $seen{$filename};
            print "$timestamp $filename\n";
            $seen{$filename} = 1;
        } else {
            chomp ($timestamp = $_);
        }
    }
    

    我使用git whatchanged 而不是git log 来获得一种方便的格式,其中包含以: 开头的非时间行,因此我可以轻松地将带有文件的行与上次修改时间分开。

    【讨论】:

      【解决方案3】:

      我在这里聚会有点晚了,但是here's a little Bash script 使用 OP 的 #2 中的调用,并在 awk 中进行后处理。 (对于我的使用,我不需要查看截至当前日期已被删除的文件,因此也有存在检查。)

      #!/bin/bash
      (
          git ls-files | sed 's/^/+ /'
          git log --format=format:"~ %aI" --name-only .
      ) | gawk '
      /^~/ {date=$2;}
      /^+/ {extant[$2] = 1;}
      /^[^~+]/ {dates[$1] = date;}
      END { for (file in dates) if(extant[file]) print(dates[file], file); }
      ' | sort
      

      【讨论】:

        【解决方案4】:

        这是一个 Powershell 函数

        function Get-GitRevisionDates($Path='.', $Ext='.md')
        {
            [array] $log = git --no-pager log --format=format:%ai --name-only $Path
        
            $date_re = "^\d{4}-\d\d-\d\d \d\d:\d\d:\d\d .\d{4}$"
            [array] $dates = $log | Select-String $date_re | select LineNumber, Line
        
            $files = $log -notmatch "^$date_re$" | ? { $_.EndsWith($Ext) } | sort -unique
        
            $res = @()
            foreach ($file in $files) {
                $iFile = $log.IndexOf($file) + 1
                $fDate = $dates | ? LineNumber -lt $iFile | select -Last 1
                $res += [PSCustomObject]@{ File = $file; Date = $fDate.Line }
            }
        
            $res | sort Date -Desc
        }
        

        【讨论】:

          猜你喜欢
          • 2014-06-13
          • 1970-01-01
          • 1970-01-01
          • 2022-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多