164 votes

Lesquels a cet objet blob ?

Étant donné le hachage d’un blob, est-il un moyen pour obtenir une liste de validations ayant cet objet blob dans leur arbre ?

112voto

Aristotle Pagaltzis Points 43253

Ici, c’est comme un script shell :

Vous passez l’objet blob SHA1 comme premier paramètre et ensuite n’importe quel nombre d’arguments à `` .


Mise à jour : et voici une version optimisée en Perl, encore assez courte :

7voto

Greg Hewgill Points 356191

J'ai pensé que ce serait utile d'avoir ce truc, j'ai donc écrit un petit script perl pour le faire:

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

Je vais mettre cela en place sur github quand je rentre à la maison ce soir.

Mise à jour: Il semble que quelqu'un a déjà fait cette. Que l'on utilise la même idée générale, mais les détails sont différents et la mise en œuvre est beaucoup plus courte. Je ne sais pas ce qui serait plus rapide, mais la performance n'est probablement pas une préoccupation ici!

Mise à jour 2: Pour ce que ça vaut, mon œuvre est un ordre de grandeur plus rapide, surtout pour un grand dépôt. Qu' git ls-tree -r fait vraiment mal.

Mise à jour 3: je note que ma performance des commentaires ci-dessus s'appliquent à la mise en œuvre j'ai lié ci-dessus dans la première mise à Jour. Aristote, de la mise en œuvre effectue comparable à la mienne. Plus de détails dans les commentaires pour ceux qui sont curieux.

6voto

Mario Points 951

Alors que la question initiale ne demande pas pour elle, je pense qu’il est utile de vérifier également la zone de transit pour voir si un blob est référencé. J’ai modifié le script bash original pour ce faire et trouver ce qui a été référençant un blob corrompu dans mon dépôt :

4voto

cmyers Points 588

Alors... j'ai besoin de trouver tous les fichiers sur une limite donnée dans une pension de plus de 8 go en taille, avec plus de 108 000 révisions. J'ai adapté Aristote script perl avec un script ruby que j'ai écrit pour parvenir à cette solution complète.

Tout d'abord, git gc - faire pour s'assurer que tous les objets sont dans packfiles - nous n'avons pas l'analyse des objets pas en pack de fichiers.

Suivant l'Exécution de ce script pour localiser tous les objets blob sur CUTOFF_SIZE octets. La Capture de la sortie vers un fichier comme "grand-gouttes.journal"

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

Ensuite, éditez le fichier à supprimer tous les objets blob, vous n'attendez pas et le INPUT_THREAD bits à la partie supérieure. une fois que vous avez seulement les lignes de la sha1s vous voulez trouver, exécutez le script suivant comme ceci:

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

Où l' git-find-blob script ci-dessous.

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

La sortie ressemble à ceci:

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

Et ainsi de suite. Chaque commit qui contient un fichier volumineux dans son arbre sera répertorié. si vous grep les lignes qui commencent par un onglet, et uniq ,, vous aurez une liste de tous les chemins vous pouvez filter-branch pour supprimer, ou vous pouvez faire quelque chose de plus compliqué.

Permettez-moi de le répéter, ce processus a été exécuté avec succès, sur un de 10 go repo avec de 108 000 commits. Il a fallu beaucoup plus de temps que je l'avais prédit lors de l'exécution sur un grand nombre de blobs bien que, plus de 10 heures, je vais voir si les mémoriser des bits fonctionne...

3voto

Adam Spiers Points 4193

Voici les détails d’un script que j’ai poli vers le haut comme la réponse à une question similaire, et ici vous pouvez le voir en action :

screenshot of git-ls-dir runs

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X