#!/usr/bin/perl -w use strict; use warnings; die "ENV GL_RC not set\n" unless $ENV{GL_RC}; die "ENV GL_BINDIR not set\n" unless $ENV{GL_BINDIR}; # - show fake "reflog" from gitolite server # - recover deleted branches # - recover from bad force pushes # -------------------- # WARNING # - heavily dependent on the gitolite log file format (duh!) # - cannot recover if some other commits were made after the force push sub usage { print STDERR <<'EOF'; USAGE ssh git@server gl-reflog show r1 refs/heads/b1 # shows last 10 updates to branch b1 in repo r1 ssh git@server gl-reflog show r1 refs/heads/b1 20 # shows last 20 entries... ssh git@server gl-reflog recover r1 refs/heads/b1 # recovers the last update to b1 in r1 if it was a "+" EOF exit 1; } usage unless (@ARGV >= 3); # NOTES # - the verb "recover" is used because this is expected to be used most often # to recover deleted branches. Plus there's enough confusion in git land # caused by "reset" and "revert" I thought I should add my bit to it ;-) # - git's internal reflog is NOT recovered, even if you recover the branch. # I'm good but not *that* good ;-) # - since this program produces a log entry that satisfies it's own criteria, # it acts as a "toggle" for its own action for rewinds (but not for deletes) my($cmd, $repo, $ref, $limit) = @ARGV; $limit ||= 10; unshift @INC, $ENV{GL_BINDIR}; require gitolite or die "parse gitolite.pm failed\n"; gitolite->import; my ($perm, $creator) = check_access($repo); die "you don't have read access to $repo\n" unless $perm =~ /R/; my @logfiles = sort glob("$ENV{GL_ADMINDIR}/logs/*"); # TODO figure out how to avoid reading *all* the log files when you really # only need the last few our @loglines; { my @f; local(@ARGV) = @logfiles; while (<>) { chomp; @f = split /\t/; # field 2 is the userid, 5 is W or +, 6/7 are old/new SHAs # 8 is reponame, 9 is refname (but all those are 1-based) next unless $f[3] =~ /^(git-receive-pack|gl-reflog recover) /; next unless $f[8]; next unless $f[7] eq $repo; next unless $f[8] eq $ref; push @loglines, $_; } } if ( $cmd eq 'show' ) { my $start = @loglines - $limit; $start = 0 if $start < 0; map { print "$loglines[$_]\n" } $start .. $#loglines; exit 0; } if ( $cmd eq 'recover' ) { my @f = split /\t/, $loglines[$#loglines]; die "the last push was not yours\n" unless $f[1] eq $ENV{GL_USER}; die "the last push was not a rewind or delete\n" unless $f[4] eq '+'; my($oldsha, $newsha) = @f[5,6]; if ($newsha =~ /^0+$/) { print "recovering $repo $ref at $oldsha (was deleted)\n"; } else { print "recovering $repo $ref at $oldsha (was forced to $newsha)\n"; } chdir("$ENV{GL_REPO_BASE_ABS}/$repo.git"); my $newsha2 = $newsha; $newsha2 = '' if $newsha =~ /^0+$/; system("git", "update-ref", $ref, $oldsha, $newsha2) and die "repo $repo, update-ref $ref $oldsha $newsha failed...\n"; log_it("", "+\t$newsha\t$oldsha\t$repo\t$ref"); }