it-swarm.com.de

Welches Commit hat dieser Blob?

Gibt es angesichts des Hashs eines Blobs eine Möglichkeit, eine Liste der Commits abzurufen, die diesen Blob in ihrem Baum haben?

131
Readonly

In beiden folgenden Skripten wird das SHA1 des Blobs als erstes Argument und optional danach alle Argumente verwendet, die git log wird verstehen. Z.B. --all, um in allen Filialen zu suchen, anstatt nur in der aktuellen, oder -g um im Reflog zu suchen, oder was auch immer du möchtest.

Hier ist es als Shell-Skript - kurz und süß, aber langsam:

#!/bin/sh
obj_name="$1"
shift
git log "[email protected]" --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

Und eine optimierte Version in Perl, immer noch ziemlich kurz, aber viel schneller:

#!/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 );
}
92

Leider waren die Skripte für mich etwas langsam, so dass ich sie etwas optimieren musste. Zum Glück hatte ich nicht nur den Hash, sondern auch den Pfad einer Datei.

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

Ich dachte, das wäre im Allgemeinen eine nützliche Sache, also schrieb ich ein kleines Perl-Skript, um es zu tun:

#!/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 ([email protected]) {
    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);
}

Ich werde das auf Github setzen, wenn ich heute Abend nach Hause komme.

Update: Es sieht aus wie jemand hat dies bereits getan . Dieser verwendet die gleiche allgemeine Idee, aber die Details sind unterschiedlich und die Implementierung ist viel kürzer. Ich weiß nicht, welche schneller wäre, aber die Leistung spielt hier wahrscheinlich keine Rolle!

Update 2: Für das, was es wert ist, ist meine Implementierung um Größenordnungen schneller, insbesondere für ein großes Repository. Das git ls-tree -r tut wirklich weh.

Update 3: Ich sollte beachten, dass meine obigen Leistungskommentare für die Implementierung gelten, die ich oben im ersten Update verlinkt habe. Aristoteles 'Implementierung arbeitet vergleichbar mit meiner. Weitere Details in den Kommentaren für diejenigen, die neugierig sind.

7
Greg Hewgill

Gibt es angesichts des Hashs eines Blobs eine Möglichkeit, eine Liste der Commits abzurufen, die diesen Blob in ihrem Baum haben?

Mit Git 2.16 (Q1 2018) wäre git describe eine gute Lösung, da es gelehrt wurde, Bäume tiefer zu graben, um einen <commit-ish>:<path> Zu finden, der sich auf einen bestimmten Blob bezieht Objekt.

Siehe Festschreiben 644eb6 , Festschreiben 4dbc59a , Festschreiben cdaed0c , Festschreiben c87b65 , Festschreiben ce5b6f9 (16. November 2017) und Festschreiben 91904f5 , Festschreiben 2deda (2. November 2017) von Stefan Beller (stefanbeller .
(Zusammengeführt von Junio ​​C Hamano - gitster - in Festschreiben 556de1a , 28. Dezember 2017)

builtin/describe.c : Beschreibe einen Blob

Manchmal wird Benutzern ein Hash eines Objekts gegeben, und sie möchten es weiter identifizieren (Beispiel: Verwenden Sie verify-pack, Um die größten Blobs zu finden, aber was sind diese? Oder genau diese SO = Frage " Welches Commit hat diesen Blob? ")

Bei der Beschreibung von Commits versuchen wir, sie an Tags oder Refs zu verankern, da diese konzeptionell auf einer höheren Ebene als das Commit liegen. Und wenn es keinen Hinweis oder Tag gibt, der genau passt, haben wir kein Glück.
Also verwenden wir eine Heuristik, um einen Namen für das Commit zu finden. Diese Namen sind nicht eindeutig, es kann verschiedene Tags oder Verweise geben, an denen verankert werden muss, und es kann einen anderen Pfad in der DAG geben, über den der Commit präzise ermittelt werden kann.

Bei der Beschreibung eines Blobs wollen wir den Blob auch von einer höheren Ebene aus beschreiben, was ein Tupel von (commit, deep/path) Ist, da die beteiligten Baumobjekte eher uninteressant sind.
Derselbe Blob kann von mehreren Commits referenziert werden. Wie entscheiden wir also, welches Commit verwendet werden soll?

In diesem Patch wird ein eher naiver Ansatz implementiert: Da es keine Rückverweise von Blobs auf Commits gibt, in denen der Blob auftritt, gehen wir von den verfügbaren Tipps aus und listen die Blobs auf. Reihenfolge des Commits und sobald wir den Blob gefunden haben, nehmen wir den ersten Commit, der den Blob auflistet .

Beispielsweise:

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

sagt uns, dass Makefile wie es in v0.99 war, in commit 7672db2 eingeführt wurde.

Das Gehen wird in umgekehrter Reihenfolge ausgeführt, um die Einführung eines Klecks anstelle seines letzten Auftretens zu zeigen.

Das bedeutet, dass die Manpage git describe die Zwecke dieses Befehls ergänzt:

git describe Beschreibt ein Commit nicht einfach mit dem zuletzt erreichbaren Tag, sondern gibt einem Objekt bei Verwendung als git describe <blob> Einen vom Menschen lesbaren Namen, der auf einer verfügbaren Referenz basiert.

Wenn sich das angegebene Objekt auf einen Blob bezieht, wird es als <commit-ish>:<path> Beschrieben, sodass der Blob unter <path> Im <commit-ish> Zu finden ist, der selbst das erste Commit beschreibt in dem dieser Blob in einem Reverse-Revision-Walk von HEAD auftritt.

Aber:

BUGS

Baumobjekte sowie Tag-Objekte, die nicht auf Commits zeigen, können nicht beschrieben werden .
Bei der Beschreibung von Blobs werden die Lightweight-Tags, die auf Blobs zeigen, ignoriert, der Blob wird jedoch weiterhin als <committ-ish>:<path> Beschrieben, obwohl das Lightweight-Tag günstig ist.

6
VonC

Während die ursprüngliche Frage nicht danach fragt, halte ich es für nützlich, auch den Staging-Bereich zu überprüfen, um festzustellen, ob auf einen Blob verwiesen wird. Ich habe das ursprüngliche Bash-Skript dahingehend geändert und in meinem Repository festgestellt, was auf einen beschädigten Blob verweist:

#!/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 "[email protected]" --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
6
Mario

Also ... ich musste alle Dateien über ein bestimmtes Limit in einem Repo mit einer Größe von über 8 GB und über 108.000 Revisionen finden. Ich habe das Perl-Skript von Aristoteles zusammen mit einem Ruby) -Skript angepasst, um zu dieser vollständigen Lösung zu gelangen.

Zuerst, git gc - Tun Sie dies, um sicherzustellen, dass sich alle Objekte in Packdateien befinden. Wir scannen keine Objekte, die nicht in Packdateien enthalten sind.

Weiter Führen Sie dieses Skript aus, um alle Blobs über CUTOFF_SIZE-Bytes zu suchen. Ausgabe in eine Datei wie "large-blobs.log" aufnehmen

#!/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

Bearbeiten Sie als Nächstes die Datei, um alle nicht wartenden Blobs und die INPUT_THREAD-Bits oben zu entfernen. Führen Sie das folgende Skript folgendermaßen aus, wenn Sie nur Zeilen für die gewünschten sha1s haben:

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

Bei dem die git-find-blob Skript ist unten.

#!/usr/bin/Perl

# taken from: http://stackoverflow.com/questions/223678/which-commit-has-this-blob
# and modified by Carl Myers <[email protected]> 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;
}

Die Ausgabe sieht folgendermaßen aus:

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

Und so weiter. Jedes Commit, das eine große Datei in seinem Baum enthält, wird aufgelistet. Wenn Sie grep die Zeilen, die mit einem Tabulator beginnen, und uniq die Zeilen herausnehmen, erhalten Sie eine Liste aller Pfade, die Sie durch Filtern und Verzweigen entfernen können, oder Sie können etwas Komplizierteres tun.

Lassen Sie mich noch einmal wiederholen: Dieser Prozess lief erfolgreich auf einem 10-GB-Repo mit 108.000 Commits. Es hat viel länger gedauert, als ich vorhergesagt hatte, als ich mit einer großen Anzahl von Blobs lief, aber über 10 Stunden muss ich sehen, ob das Merkbit funktioniert ...

3
cmyers

Zusätzlich zu git describe, Das ich in meiner vorherigen Antwort erwähnt habe , git log Und git diff Profitieren jetzt auch von "--find-object=<object-id> ", um die Ergebnisse auf Änderungen zu beschränken, die das angegebene Objekt betreffen.
Das ist in Git 2.16.x/2.17 (Q1 2018)

Siehe Festschreiben 4d8c51a , Festschreiben 5e50525 , Festschreiben 15af58c , Festschreiben cf63051 , Festschreiben c1ddc46 , commit 929ed7 (04. Januar 2018) von Stefan Beller (stefanbeller) .
(Zusammengeführt von Junio ​​C Hamano - gitster - in Festschreiben von c0d75f , 23. Januar 2018)

diffcore: Füge eine Spitzhacke hinzu, um einen bestimmten Blob zu finden

Manchmal wird Benutzern ein Hash eines Objekts gegeben, und sie möchten es weiter identifizieren (Beispiel: Verwenden Sie verify-pack, um die größten Blobs zu finden, aber was sind diese? Oder diese Stapelüberlauf-Frage " Welches Commit hat diesen Blob? ? ")

Man könnte versucht sein, git-describe Zu erweitern, um auch mit Blobs zu arbeiten, so dass git describe <blob-id> Eine Beschreibung als ':' liefert.
Dies war hier implementiert ; Wie die schiere Anzahl der Antworten (> 110) zeigt, ist es schwierig, die richtigen Antworten zu finden.
Der schwierigste Teil, den Fehler zu beheben, besteht darin, das richtige "Commit-ish" auszuwählen, da dies das Commit sein kann, das den Blob (erneut) eingeführt hat oder das den Blob entfernt hat. Der Blob kann in verschiedenen Zweigen existieren.

Junio ​​deutete auf einen anderen Lösungsansatz für dieses Problem hin, den dieser Patch implementiert.
Bringen Sie der diff Maschine ein anderes Flag bei, um die Informationen auf das Gezeigte zu beschränken.
Beispielsweise:

$ ./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:"

wir stellen fest, dass der Makefile, wie er mit 2.0 ausgeliefert wurde, in v1.9.2-471-g47fbfded53 und in v2.0.0-rc1-5-gb2feb6430b vorkommt.
Der Grund, warum diese beiden Commits vor v2.0.0 auftreten, sind böse Zusammenführungen, die mit diesem neuen Mechanismus nicht gefunden werden.

2
VonC