summaryrefslogtreecommitdiffstats
path: root/src/commands/who-pushed
blob: e59a750ace2d67381e2488bbd15def2b1f6f765d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
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