summaryrefslogtreecommitdiffstats
path: root/src/commands/who-pushed
diff options
context:
space:
mode:
Diffstat (limited to '')
-rwxr-xr-xsrc/commands/who-pushed172
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
+