IT story

이 얼룩이있는 커밋은 무엇입니까?

hot-time 2020. 6. 28. 18:07
반응형

이 얼룩이있는 커밋은 무엇입니까?


Blob의 해시가 주어지면 트리 에이 Blob이있는 커밋 목록을 얻는 방법이 있습니까?


다음 스크립트는 BLOB의 SHA1을 첫 번째 인수로 사용하고 선택적으로 git log이해 하는 모든 인수를 사용합니다 . --all들어 , 현재의 지점이 아닌 모든 지점 -g에서 검색 하거나 , reflog에서 검색하거나 다른 멋진 항목을 검색하십시오.

짧고 달콤하지만 느리게 쉘 스크립트로 작성되었습니다.

#!/bin/sh
obj_name="$1"
shift
git log "$@" --pretty=format:'%T %h %s' \
| while read tree commit subject ; do
    if git ls-tree -r $tree | grep -q "$obj_name" ; then
        echo $commit "$subject"
    fi
done

그리고 Perl의 최적화 된 버전은 여전히 ​​짧지 만 훨씬 빠릅니다.

#!/usr/bin/perl
use 5.008;
use strict;
use Memoize;

my $obj_name;

sub check_tree {
    my ( $tree ) = @_;
    my @subtree;

    {
        open my $ls_tree, '-|', git => 'ls-tree' => $tree
            or die "Couldn't open pipe to git-ls-tree: $!\n";

        while ( <$ls_tree> ) {
            /\A[0-7]{6} (\S+) (\S+)/
                or die "unexpected git-ls-tree output";
            return 1 if $2 eq $obj_name;
            push @subtree, $2 if $1 eq 'tree';
        }
    }

    check_tree( $_ ) && return 1 for @subtree;

    return;
}

memoize 'check_tree';

die "usage: git-find-blob <blob> [<git-log arguments ...>]\n"
    if not @ARGV;

my $obj_short = shift @ARGV;
$obj_name = do {
    local $ENV{'OBJ_NAME'} = $obj_short;
     `git rev-parse --verify \$OBJ_NAME`;
} or die "Couldn't parse $obj_short: $!\n";
chomp $obj_name;

open my $log, '-|', git => log => @ARGV, '--pretty=format:%T %h %s'
    or die "Couldn't open pipe to git-log: $!\n";

while ( <$log> ) {
    chomp;
    my ( $tree, $commit, $subject ) = split " ", $_, 3;
    print "$commit $subject\n" if check_tree( $tree );
}

불행히도 스크립트는 약간 느리기 때문에 조금 최적화해야했습니다. 운 좋게도 해시뿐만 아니라 파일 경로도 가지고있었습니다.

git log --all --pretty=format:%H -- <path> | xargs -n1 -I% sh -c "git ls-tree % -- <path> | grep -q <hash> && echo %"

나는 이것이 일반적으로 유용한 것이라고 생각했기 때문에 작은 펄 스크립트를 작성했다.

#!/usr/bin/perl -w

use strict;

my @commits;
my %trees;
my $blob;

sub blob_in_tree {
    my $tree = $_[0];
    if (defined $trees{$tree}) {
        return $trees{$tree};
    }
    my $r = 0;
    open(my $f, "git cat-file -p $tree|") or die $!;
    while (<$f>) {
        if (/^\d+ blob (\w+)/ && $1 eq $blob) {
            $r = 1;
        } elsif (/^\d+ tree (\w+)/) {
            $r = blob_in_tree($1);
        }
        last if $r;
    }
    close($f);
    $trees{$tree} = $r;
    return $r;
}

sub handle_commit {
    my $commit = $_[0];
    open(my $f, "git cat-file commit $commit|") or die $!;
    my $tree = <$f>;
    die unless $tree =~ /^tree (\w+)$/;
    if (blob_in_tree($1)) {
        print "$commit\n";
    }
    while (1) {
        my $parent = <$f>;
        last unless $parent =~ /^parent (\w+)$/;
        push @commits, $1;
    }
    close($f);
}

if (!@ARGV) {
    print STDERR "Usage: git-find-blob blob [head ...]\n";
    exit 1;
}

$blob = $ARGV[0];
if (@ARGV > 1) {
    foreach (@ARGV) {
        handle_commit($_);
    }
} else {
    handle_commit("HEAD");
}
while (@commits) {
    handle_commit(pop @commits);
}

오늘 저녁에 집에 도착하면 github에 올려 놓겠습니다.

업데이트 : 누군가 이미 이것을 한 것처럼 보입니다 . 그것은 동일한 일반적인 아이디어를 사용하지만 세부 사항은 다르며 구현은 훨씬 짧습니다. 어느 쪽이 더 빠를 지 모르겠지만 성능은 여기에 문제가되지 않습니다!

업데이트 2 : 가치가있는 것, 특히 대규모 저장소의 경우 구현 속도가 훨씬 빠릅니다. 그건 git ls-tree -r정말 아프다.

업데이트 3 : 위의 성능 의견은 첫 번째 업데이트에서 위에 링크 된 구현에 적용됩니다. 아리스토텔레스의 구현 은 광산과 비슷하게 수행됩니다. 궁금한 사람들의 의견에 대한 자세한 내용.


원래 질문에 대해서는 묻지 않지만 준비 영역을 검사하여 블롭이 참조되는지 확인하는 것이 유용하다고 생각합니다. 이 작업을 수행하기 위해 원래 bash 스크립트를 수정하고 내 저장소에서 손상된 얼룩을 참조하는 것을 발견했습니다.

#!/bin/sh
obj_name="$1"
shift
git ls-files --stage \
| if grep -q "$obj_name"; then
    echo Found in staging area. Run git ls-files --stage to see.
fi

git log "$@" --pretty=format:'%T %h %s' \
| while read tree commit subject ; do
    if git ls-tree -r $tree | grep -q "$obj_name" ; then
        echo $commit "$subject"
    fi
done

Blob의 해시가 주어지면 트리 에이 Blob이있는 커밋 목록을 얻는 방법이 있습니까?

Git 2.16 (2018 년 1 분기) 을 사용하면 지정된 얼룩 개체를 나타내는 git describe나무를 더 깊게 파는 방법을 배웠으므로 좋은 솔루션이 될 것 <commit-ish>:<path>입니다.

참조 644eb60 커밋 , 4dbc59a 커밋 , cdaed0c 커밋 , c87b653 커밋 , ce5b6f9 커밋 (2017년 11월 16일을), 및 91904f5 커밋 , 2deda00 투입 하여 (02 년 11 월 2017 년) 스테판 벨러를 ( stefanbeller) .
( Junio ​​C gitsterHamano의해 합병 -- 커밋 556de1a , 2017 년 12 월 28 일)

builtin/describe.c: 얼룩을 설명

때때로 사용자는 객체의 해시를 부여하고 추가를 식별 할 (예 : 사용 verify-pack최대 규모의 모양을 찾을 수 있지만,이? 또는이 매우 SO 질문 무엇 " 이 방울을 가지고 커밋? ")

커밋을 설명 할 때 개념적으로는 커밋보다 상위 수준이므로 태그 또는 참조에 고정하려고합니다. 정확하게 일치하는 심판이나 태그가 없으면 운이 좋지 않습니다.
그래서 우리는 커밋의 이름을 구성하기 위해 휴리스틱을 사용합니다. 이러한 이름은 모호하며 다른 태그 또는 참조가 고정 될 수 있으며 DAG에 커밋에 정확하게 도달하기 위해 이동하는 경로가 다를 수 있습니다.

블롭을 기술 할 때, 우리는 상위 계층의 블랍도 기술하려고합니다. 이것은 (commit, deep/path)관련된 트리 객체가 다소 흥미롭지 않기 때문에 튜플입니다 .
동일한 블롭을 여러 커밋으로 참조 할 수 있으므로 사용할 커밋을 어떻게 결정합니까?

이 패치는 이것에 대해 다소 순진한 접근 방식을 구현합니다 . blob에서 blob이 발생하는 커밋에 대한 백 포인터가 없기 때문에 사용 가능한 모든 팁에서 걷기 시작하여 커밋 순서대로 blob을 나열합니다. blob, 우리는 blob을 나열한 첫 번째 커밋을 가져옵니다 .

예를 들면 다음과 같습니다.

git describe --tags v0.99:Makefile
conversion-901-g7672db20c2:Makefile

미국 이야기 Makefile가 있다는 사실 v0.99에 도입 된 7672db2 커밋 .

걷기는 마지막 발생이 아닌 얼룩의 도입을 보여주기 위해 역순으로 수행됩니다.

이는 git describe매뉴얼 페이지 가이 명령의 목적에 추가됨을 의미합니다 .

가장 최근에 접근 할 수있는 가장 최근의 태그를 사용하여 커밋을 설명하는 대신 git describe실제로로 사용될 때 사용 가능한 참조를 기반으로 사람이 읽을 수있는 이름을 객체에 제공합니다 git describe <blob>.

지정된 객체의 Blob를 참조하는 경우,이를 설명한다 <commit-ish>:<path>블롭이에서 발견 될 수 있도록, <path><commit-ish>자체는 먼저이 Blob 헤드로부터 역방향 개정 거리에서 발생하는 커밋 설명한다.

그러나:

버그

커밋을 가리 키지 않는 태그 객체뿐만 아니라 트리 객체도 설명 할 수 없습니다 .
블롭을 기술 할 때, 블롭을 가리키는 경량 태그는 무시되지만 <committ-ish>:<path>, 경량 태그가 유리 함에도 불구하고 블롭은 여전히 ​​설명된다 .


그래서 ... 10GB가 넘는 수정 사항이있는 8GB 이상의 저장소에서 주어진 제한을 초과하는 모든 파일을 찾아야했습니다. 필자는이 완벽한 솔루션에 도달하기 위해 작성한 루비 스크립트와 함께 Aristotle의 펄 스크립트를 수정했습니다.

먼저, git gc모든 객체가 팩 파일에 있는지 확인하려면 팩 파일에없는 객체는 검사하지 않습니다.

다음이 스크립트를 실행하여 CUTOFF_SIZE 바이트 이상의 모든 얼룩을 찾습니다. "large-blobs.log"와 같은 파일로 출력 캡처

#!/usr/bin/env ruby

require 'log4r'

# The output of git verify-pack -v is:
# SHA1 type size size-in-packfile offset-in-packfile depth base-SHA1
#
#
GIT_PACKS_RELATIVE_PATH=File.join('.git', 'objects', 'pack', '*.pack')

# 10MB cutoff
CUTOFF_SIZE=1024*1024*10
#CUTOFF_SIZE=1024

begin

  include Log4r
  log = Logger.new 'git-find-large-objects'
  log.level = INFO
  log.outputters = Outputter.stdout

  git_dir = %x[ git rev-parse --show-toplevel ].chomp

  if git_dir.empty?
    log.fatal "ERROR: must be run in a git repository"
    exit 1
  end

  log.debug "Git Dir: '#{git_dir}'"

  pack_files = Dir[File.join(git_dir, GIT_PACKS_RELATIVE_PATH)]
  log.debug "Git Packs: #{pack_files.to_s}"

  # For details on this IO, see http://stackoverflow.com/questions/1154846/continuously-read-from-stdout-of-external-process-in-ruby
  #
  # Short version is, git verify-pack flushes buffers only on line endings, so
  # this works, if it didn't, then we could get partial lines and be sad.

  types = {
    :blob => 1,
    :tree => 1,
    :commit => 1,
  }


  total_count = 0
  counted_objects = 0
  large_objects = []

  IO.popen("git verify-pack -v -- #{pack_files.join(" ")}") do |pipe|
    pipe.each do |line|
      # The output of git verify-pack -v is:
      # SHA1 type size size-in-packfile offset-in-packfile depth base-SHA1
      data = line.chomp.split(' ')
      # types are blob, tree, or commit
      # we ignore other lines by looking for that
      next unless types[data[1].to_sym] == 1
      log.info "INPUT_THREAD: Processing object #{data[0]} type #{data[1]} size #{data[2]}"
      hash = {
        :sha1 => data[0],
        :type => data[1],
        :size => data[2].to_i,
      }
      total_count += hash[:size]
      counted_objects += 1
      if hash[:size] > CUTOFF_SIZE
        large_objects.push hash
      end
    end
  end

  log.info "Input complete"

  log.info "Counted #{counted_objects} totalling #{total_count} bytes."

  log.info "Sorting"

  large_objects.sort! { |a,b| b[:size] <=> a[:size] }

  log.info "Sorting complete"

  large_objects.each do |obj|
    log.info "#{obj[:sha1]} #{obj[:type]} #{obj[:size]}"
  end

  exit 0
end

그런 다음 파일을 편집하여 기다리지 않는 얼룩과 맨 위에있는 INPUT_THREAD 비트를 제거하십시오. 찾고 싶은 sha1에 대한 행만 있으면 다음과 같이 다음 스크립트를 실행하십시오.

cat edited-large-files.log | cut -d' ' -f4 | xargs git-find-blob | tee large-file-paths.log

어디 git-find-blob스크립트는 다음과 같습니다.

#!/usr/bin/perl

# taken from: http://stackoverflow.com/questions/223678/which-commit-has-this-blob
# and modified by Carl Myers <cmyers@cmyers.org> to scan multiple blobs at once
# Also, modified to keep the discovered filenames
# vi: ft=perl

use 5.008;
use strict;
use Memoize;
use Data::Dumper;


my $BLOBS = {};

MAIN: {

    memoize 'check_tree';

    die "usage: git-find-blob <blob1> <blob2> ... -- [<git-log arguments ...>]\n"
        if not @ARGV;


    while ( @ARGV && $ARGV[0] ne '--' ) {
        my $arg = $ARGV[0];
        #print "Processing argument $arg\n";
        open my $rev_parse, '-|', git => 'rev-parse' => '--verify', $arg or die "Couldn't open pipe to git-rev-parse: $!\n";
        my $obj_name = <$rev_parse>;
        close $rev_parse or die "Couldn't expand passed blob.\n";
        chomp $obj_name;
        #$obj_name eq $ARGV[0] or print "($ARGV[0] expands to $obj_name)\n";
        print "($arg expands to $obj_name)\n";
        $BLOBS->{$obj_name} = $arg;
        shift @ARGV;
    }
    shift @ARGV; # drop the -- if present

    #print "BLOBS: " . Dumper($BLOBS) . "\n";

    foreach my $blob ( keys %{$BLOBS} ) {
        #print "Printing results for blob $blob:\n";

        open my $log, '-|', git => log => @ARGV, '--pretty=format:%T %h %s'
            or die "Couldn't open pipe to git-log: $!\n";

        while ( <$log> ) {
            chomp;
            my ( $tree, $commit, $subject ) = split " ", $_, 3;
            #print "Checking tree $tree\n";
            my $results = check_tree( $tree );

            #print "RESULTS: " . Dumper($results);
            if (%{$results}) {
                print "$commit $subject\n";
                foreach my $blob ( keys %{$results} ) {
                    print "\t" . (join ", ", @{$results->{$blob}}) . "\n";
                }
            }
        }
    }

}


sub check_tree {
    my ( $tree ) = @_;
    #print "Calculating hits for tree $tree\n";

    my @subtree;

    # results = { BLOB => [ FILENAME1 ] }
    my $results = {};
    {
        open my $ls_tree, '-|', git => 'ls-tree' => $tree
            or die "Couldn't open pipe to git-ls-tree: $!\n";

        # example git ls-tree output:
        # 100644 blob 15d408e386400ee58e8695417fbe0f858f3ed424    filaname.txt
        while ( <$ls_tree> ) {
            /\A[0-7]{6} (\S+) (\S+)\s+(.*)/
                or die "unexpected git-ls-tree output";
            #print "Scanning line '$_' tree $2 file $3\n";
            foreach my $blob ( keys %{$BLOBS} ) {
                if ( $2 eq $blob ) {
                    print "Found $blob in $tree:$3\n";
                    push @{$results->{$blob}}, $3;
                }
            }
            push @subtree, [$2, $3] if $1 eq 'tree';
        }
    }

    foreach my $st ( @subtree ) {
        # $st->[0] is tree, $st->[1] is dirname
        my $st_result = check_tree( $st->[0] );
        foreach my $blob ( keys %{$st_result} ) {
            foreach my $filename ( @{$st_result->{$blob}} ) {
                my $path = $st->[1] . '/' . $filename;
                #print "Generating subdir path $path\n";
                push @{$results->{$blob}}, $path;
            }
        }
    }

    #print "Returning results for tree $tree: " . Dumper($results) . "\n\n";
    return $results;
}

결과는 다음과 같습니다.

<hash prefix> <oneline log message>
    path/to/file.txt
    path/to/file2.txt
    ...
<hash prefix2> <oneline log msg...>

And so on. Every commit which contains a large file in its tree will be listed. if you grep out the lines that start with a tab, and uniq that, you will have a list of all paths you can filter-branch to remove, or you can do something more complicated.

Let me reiterate: this process ran successfully, on a 10GB repo with 108,000 commits. It took much longer than I predicted when running on a large number of blobs though, over 10 hours, I will have to see if the memorize bit is working...


In addition of git describe, that I mention in my previous answer, git log and git diff now benefits as well from the "--find-object=<object-id>" option to limit the findings to changes that involve the named object.
That is in Git 2.16.x/2.17 (Q1 2018)

See commit 4d8c51a, commit 5e50525, commit 15af58c, commit cf63051, commit c1ddc46, commit 929ed70 (04 Jan 2018) by Stefan Beller (stefanbeller).
(Merged by Junio C Hamano -- gitster -- in commit c0d75f0, 23 Jan 2018)

diffcore: add a pickaxe option to find a specific blob

Sometimes users are given a hash of an object and they want to identify it further (ex.: Use verify-pack to find the largest blobs, but what are these? or this Stack Overflow question "Which commit has this blob?")

One might be tempted to extend git-describe to also work with blobs, such that git describe <blob-id> gives a description as ':'.
This was implemented here; as seen by the sheer number of responses (>110), it turns out this is tricky to get right.
The hard part to get right is picking the correct 'commit-ish' as that could be the commit that (re-)introduced the blob or the blob that removed the blob; the blob could exist in different branches.

Junio hinted at a different approach of solving this problem, which this patch implements.
Teach the diff machinery another flag for restricting the information to what is shown.
For example:

$ ./git log --oneline --find-object=v2.0.0:Makefile
  b2feb64 Revert the whole "ask curl-config" topic for now
  47fbfde i18n: only extract comments marked with "TRANSLATORS:"

we observe that the Makefile as shipped with 2.0 was appeared in v1.9.2-471-g47fbfded53 and in v2.0.0-rc1-5-gb2feb6430b.
The reason why these commits both occur prior to v2.0.0 are evil merges that are not found using this new mechanism.

참고URL : https://stackoverflow.com/questions/223678/which-commit-has-this-blob

반응형