diff options
Diffstat (limited to 'src/commands/who-pushed')
-rwxr-xr-x | src/commands/who-pushed | 172 |
1 files changed, 172 insertions, 0 deletions
diff --git a/src/commands/who-pushed b/src/commands/who-pushed new file mode 100755 index 0000000..e59a750 --- /dev/null +++ b/src/commands/who-pushed @@ -0,0 +1,172 @@ +#!/usr/bin/perl +use strict; +use warnings; + +use lib $ENV{GL_LIBDIR}; +use Gitolite::Easy; + +usage() if not @ARGV; +usage($ARGV[1]) if $ARGV[1] and $ARGV[1] =~ /^[\w-]+$/ and $ARGV[0] eq '-h'; + +( my $logdir = $ENV{GL_LOGFILE} ) =~ s(/[^/]+$)(); + +# deal with migrate +my %gl_log_lines_buffer; +my $countr = 0; +my $countl = 0; +migrate(@ARGV) if $ARGV[0] eq '--migrate'; # won't return; exits right there + +# tip search? +my $tip_search = 0; +if ($ARGV[0] eq '--tip') { + shift; + $tip_search = 1; +} + +# the normal who-pushed +usage() if @ARGV < 2 or $ARGV[0] eq '-h'; +usage() if $ARGV[1] !~ /^[0-9a-f]+$/i; + +my $repo = shift; +my $sha = shift; $sha =~ tr/A-F/a-f/; + +$ENV{GL_USER} and ( can_read($repo) or die "no read permissions on '$repo'" ); + +# ---------------------------------------------------------------------- + +my $repodir = "$ENV{GL_REPO_BASE}/$repo.git"; +chdir $repodir or die "repo '$repo' missing"; + +my @logfiles = reverse glob("$logdir/*"); +@logfiles = ( "$repodir/gl-log" ) if -f "$repodir/gl-log"; + +for my $logfile ( @logfiles ) { + @ARGV = ($logfile); + for my $line ( reverse grep { m(\tupdate\t($repo|$repodir)\t) } <> ) { + chomp($line); + my @fields = split /\t/, $line; + my ( $ts, $pid, $who, $ref, $d_old, $new ) = @fields[ 0, 1, 4, 6, 7, 8 ]; + + # d_old is what you display + my $old = $d_old; + $old = "" if $d_old eq ( "0" x 40 ); + $old = "$old.." if $old; + + if ($tip_search) { + print "$ts $pid $who $ref $d_old $new\n" if $new =~ /^$sha/; + } else { + system("git rev-list $old$new 2>/dev/null | grep ^$sha >/dev/null && echo '$ts $pid $who $ref $d_old $new'"); + } + } +} + +# ---------------------------------------------------------------------- +# migration + +sub migrate { + chdir $ENV{GL_REPO_BASE}; + my @repos = `gitolite list-phy-repos`; chomp @repos; + + my $count = scalar( grep { -f "$_.git/gl-log" } @repos ); + if ( $count and ( $_[1] || '' ) ne '--force' ) { + say2 "$count repo(s) already have gl-log files. To confirm overwriting, please re-run as:"; + say2 "\tgitolite who-pushed --migrate --force"; + say2 "see help ('-h', '-h logfiles', or '-h migrate') for details."; + exit 1; + } + + foreach my $r (@repos) { + _print("$r.git/gl-log", ''); + } + + my %repo_exists = map { $_ => 1 } @repos; + @ARGV = sort ( glob("$logdir/*") ); + while (<>) { + say2 "processed '$ARGV'" if eof(ARGV); + next unless /\tupdate\t/; + my @f = split /\t/; + my $repo = $f[3]; + if ($repo =~ m(^/)) { + $repo =~ s/^$ENV{GL_REPO_BASE}\///; + $repo =~ s/\.git$//; + } + + gen_gl_log($repo, $_) if $repo_exists{$repo}; + } + flush_gl_log(); + + exit 0; +} +sub gen_gl_log { + my ($repo, $l) = @_; + + $countr++ unless $gl_log_lines_buffer{$repo}; # new repo, not yet seen + $countl++; + $gl_log_lines_buffer{$repo} .= $l; + + # once we have buffered log lines for about 100 repos, or about 10,000 log + # lines, we flush them + flush_gl_log() if $countr >= 100 or $countl >= 10_000; +} +sub flush_gl_log { + while (my ($r, $l) = each %gl_log_lines_buffer) { + open my $fh, ">>", "$r.git/gl-log" or _die "open flush_gl_log failed: $!"; + print $fh $l; + close $fh; + } + %gl_log_lines_buffer = (); + say2 "flushed $countl lines to $countr repos..."; + $countr = $countl = 0; +} + +__END__ + +=for usage +usage: ssh git@host who-pushed [--tip] <repo> <SHA> + +Determine who pushed the given commit. The first few hex digits of the SHA +should suffice. If the '--tip' option is supplied, it'll only look for the +SHA among "tip" commits (i.e., search the "new SHA"s, without running the +expensive 'git rev-parse' for each push). + +Each line of the output contains the following fields: timestamp, a +transaction ID, username, refname, and the old and new SHAs for the ref. + +Note on the "transaction ID" field: if looking at the log file doesn't help +you figure out what its purpose is, please just ignore it. + +TO SEE ADDITIONAL HELP, run with options "-h logfiles" or "-h migrate". +=cut + +=for logfiles +There are 2 places that gitolite logs to, based on the value give to the +LOG_DEST rc variable. By default, log files go to ~/.gitolite/logs, but you +can choose to send them to syslog instead (in which case 'who-pushed' will not +work), or to both syslog and the normal log files. + +In addition, gitolite can also be told to log just the "update" records to a +special "gl-log" file in the bare repo directory. This makes 'who-pushed' +**much** faster (thanks to milki for the problem *and* the simple solution). + +'who-pushed' will look for that special file first and use only that if it is +found. Otherwise it will look in the normal gitolite log files, which will of +course be much slower. +=cut + +=for migrate +If you installed gitolite before v3.6.4, and you wish to use the new, more +efficient logging that helps who-pushed run faster, you should first update +the rc file (see http://gitolite.com/gitolite/rc.html for notes on that) to +specify a suitable value for LOG_DEST. + +After that you should probably do a one-time generation of the repo-specific +'gl-log' files from the normal log files. This can only be done from the +server command line, even if the 'who-pushed' command has been enabled for +remote access. + +To do this, just run 'gitolite who-pushed --migrate'. If some of your repos +already had gl-log files, it will warn you, and tell you how to override. +You're only supposed to to use this *once* after upgrading to v3.6.4 and +setting LOG_DEST in the rc file anyway. +=cut + |