5 use Getopt::Long qw/:config no_ignore_case no_auto_abbrev gnu_compat/;
10 'outstanding' => "\e[1;33m",
11 'unmerge' => "\e[1;31m",
13 'unreject' => "\e[31m",
16 'previous' => "\e[34m",
21 '' => "color: black; background-color: black",
22 'outstanding' => "color: black; background-color: yellow",
23 'unmerge' => "color: black; background-color: lightred",
24 'reject' => "color: black; background-color: red",
25 'unreject' => "color: black; background-color: red",
26 'merge' => "color: black; background-color: green",
27 'base' => "color: black; background-color: lightblue",
28 'previous' => "color: black; background-color: blue",
33 'outstanding' => "OUTSTANDING",
34 'unmerge' => "UNMERGED",
35 'reject' => "REJECTED",
36 'unreject' => "UNREJECTED",
39 'previous' => "PREVIOUS",
44 my ($msg, $data) = @_;
45 return $data if defined $data;
65 my $width = ($ENV{COLUMNS} || backtick 'tput', 'cols' || 80);
66 my $branch = $ENV{GIT_BRANCH};
69 chomp($branch = backtick 'git', 'symbolic-ref', 'HEAD');
70 $branch =~ s/^refs\/heads\///
71 or die "Not in a branch";
73 chomp(my $master = (backtick 'git', 'config', '--get', "branch-manager.$branch.master" or 'master'));
74 chomp(my $datefilter = (backtick 'git', 'config', '--get', "branch-manager.$branch.startdate" or ''));
77 if($datefilter eq 'mergebase')
79 chomp($revprefix = check_defined "git-merge-base: $!", backtick 'git', 'merge-base', $master, $branch);
82 elsif($datefilter ne '')
84 @datefilter = "--since=$datefilter";
87 # if set, don't actually merge/revert changes, just mark as such
92 sub reset_to_commit($)
95 #run 'git', 'merge', '-s', 'ours', '--no-commit', $r
96 # or die "git-merge: $!";
97 run 'git', 'checkout', $r, '--', '.'
98 or die "git-checkout: $!";
102 run 'git', 'update-ref', 'MERGE_HEAD', $r
103 or die "git-update-ref: $!";
104 run 'git', 'commit', '--allow-empty', '-m', "::stable-branch::reset=$r"
105 or die "git-commit: $!";
111 # reject == merge but skip
120 my $msg = backtick 'git', 'log', '-1', '--pretty=fuller', $r
121 or die "git-log: $!";
122 for(split /\n/, $msg)
124 if(/^Author:\s*(.*) <(.*)>/)
129 elsif(/^AuthorDate:\s*(.*)/)
138 open my $fh, '>', '.commitmsg'
139 or die ">.commitmsg: $!";
140 print $fh "REJECT! $cmsg" . "::stable-branch::reject=$r\n"
141 or die ">.commitmsg: $!";
143 or die ">.commitmsg: $!";
145 local $ENV{GIT_AUTHOR_NAME} = $author;
146 local $ENV{GIT_AUTHOR_EMAIL} = $email;
147 local $ENV{GIT_AUTHOR_DATE} = $date;
150 run 'git', 'commit', '--allow-empty', '-F', '.commitmsg'
151 or die "git-commit: $!";
155 sub unreject_commit($)
157 # reject == merge but skip
166 my $msg = backtick 'git', 'log', '-1', '--pretty=fuller', $r
167 or die "git-log: $!";
168 for(split /\n/, $msg)
170 if(/^Author:\s*(.*) <(.*)>/)
175 elsif(/^AuthorDate:\s*(.*)/)
184 open my $fh, '>', '.commitmsg'
185 or die ">.commitmsg: $!";
186 print $fh "UNREJECT! $cmsg" . "::stable-branch::unreject=$r\n"
187 or die ">.commitmsg: $!";
189 or die ">.commitmsg: $!";
191 local $ENV{GIT_AUTHOR_NAME} = $author;
192 local $ENV{GIT_AUTHOR_EMAIL} = $email;
193 local $ENV{GIT_AUTHOR_DATE} = $date;
196 run 'git', 'commit', '--allow-empty', '-F', '.commitmsg'
197 or die "git-commit: $!";
211 my $msg = backtick 'git', 'log', '-1', '--pretty=fuller', $r
212 or die "git-log: $!";
213 for(split /\n/, $msg)
215 if(/^Author:\s*(.*) <(.*)>/)
220 elsif(/^AuthorDate:\s*(.*)/)
229 open my $fh, '>', '.commitmsg'
230 or die ">.commitmsg: $!";
231 print $fh "$cmsg" . "::stable-branch::merge=$r\n"
232 or die ">.commitmsg: $!";
234 or die ">.commitmsg: $!";
236 local $ENV{GIT_AUTHOR_NAME} = $author;
237 local $ENV{GIT_AUTHOR_EMAIL} = $email;
238 local $ENV{GIT_AUTHOR_DATE} = $date;
241 run 'git', 'cherry-pick', '-n', $r
242 or run 'git', 'mergetool'
243 or die "git-mergetool: $!";
247 run 'git', 'commit', '--allow-empty', '-F', '.commitmsg'
248 or (run 'git', 'mergetool'
249 and run 'git', 'commit', '--allow-empty', '-F', '.commitmsg')
250 or die "git-commit: $!";
254 sub unmerge_commit($)
264 my $msg = backtick 'git', 'log', '-1', '--pretty=fuller', $r
265 or die "git-log: $!";
266 for(split /\n/, $msg)
268 if(/^Author:\s*(.*) <(.*)>/)
273 elsif(/^AuthorDate:\s*(.*)/)
282 open my $fh, '>', '.commitmsg'
283 or die ">.commitmsg: $!";
284 print $fh "UNMERGE! $cmsg" . "::stable-branch::unmerge=$r\n"
285 or die ">.commitmsg: $!";
287 or die ">.commitmsg: $!";
289 local $ENV{GIT_AUTHOR_NAME} = $author;
290 local $ENV{GIT_AUTHOR_EMAIL} = $email;
291 local $ENV{GIT_AUTHOR_DATE} = $date;
294 run 'git', 'revert', '-n', $r
295 or run 'git', 'mergetool'
296 or die "git-mergetool: $!";
300 run 'git', 'commit', '--allow-empty', '-F', '.commitmsg'
301 or (run 'git', 'mergetool'
302 and run 'git', 'commit', '--allow-empty', '-F', '.commitmsg')
303 or die "git-commit: $!";
311 my @applied = (0) x @{$log->{order_a}};
312 my $newbase_id = $log->{order_h}{$r};
315 my @outstanding = ();
319 if($log->{bitmap}[$_] < 0)
321 unshift @rlog, ['reject', $log->{order_a}[$_]];
323 elsif($log->{bitmap}[$_] == 0)
325 unshift @rlog, ['unmerge', $log->{order_a}[$_]];
329 for($newbase_id+1 .. @{$log->{order_a}}-1)
331 if($log->{bitmap}[$_] > 0)
333 push @rlog, ['merge', $log->{order_a}[$_]];
335 elsif($log->{bitmap}[$_] < 0)
337 push @rlog, ['reject', $log->{order_a}[$_]];
341 push @outstanding, ['outstanding', $log->{order_a}[$_]];
358 return $logcache if defined $logcache;
370 my $cur_commit = undef;
372 for((split /\n/, check_defined "git-log: $!", backtick 'git', 'log', '--topo-order', '--reverse', '--pretty=fuller', @datefilter, "$revprefix$master"), undef)
374 if(defined $cur_commit and (not defined $_ or /^commit (\S+)/))
376 $cur_msg =~ s/\s+$//s;
377 $history{$cur_commit} = scalar @history;
378 $logmsg{$cur_commit} = $cur_msg;
379 push @history, $cur_commit;
380 $cur_commit = $cur_msg = undef;
382 last if not defined $_;
392 $cur_commit = $cur_msg = undef;
394 for((split /\n/, check_defined "git-log: $!", backtick 'git', 'log', '--topo-order', '--reverse', '--pretty=fuller', @datefilter, "$revprefix$branch"), undef)
396 if(defined $cur_commit and (not defined $_ or /^commit (\S+)/))
398 $cur_msg =~ s/\s+$//s;
399 $logmsg{$cur_commit} = $cur_msg;
400 push @commits, $cur_commit;
401 $cur_commit = $cur_msg = undef;
403 last if not defined $_;
413 my $lastrebase = undef;
416 my $data = $logmsg{$_};
417 if($data =~ /::stable-branch::unmerge=(\S+)/)
419 next if not defined $history{$1};
420 push @logdata, ['unmerge', $1];
422 elsif($data =~ /::stable-branch::merge=(\S+)/)
424 next if not defined $history{$1};
425 push @logdata, ['merge', $1];
427 elsif($data =~ /::stable-branch::reject=(\S+)/)
429 next if not defined $history{$1};
430 push @logdata, ['reject', $1];
432 elsif($data =~ /::stable-branch::unreject=(\S+)/)
434 next if not defined $history{$1};
435 push @logdata, ['unreject', $1];
437 elsif($data =~ /::stable-branch::reset=(\S+)/)
439 next if not defined $history{$1};
443 elsif($data =~ /::stable-branch::rebase=(\S+)/)
445 next if not defined $history{$1};
446 $lastrebase->[0] = 'ignore'
447 if defined $lastrebase;
448 push @logdata, ($lastrebase = ['rebase', $1]);
452 if(not defined $base)
454 warn 'This branch is not yet managed by git-branch-manager';
458 order_a => \@history,
459 order_h => \%history,
464 my $baseid = $history{$base};
473 my ($cmd, $data) = @{$logdata[$i]};
476 $bitmap[$history{$data}] = 1;
478 elsif($cmd eq 'unmerge')
480 $bitmap[$history{$data}] = 0;
482 elsif($cmd eq 'reject')
484 $bitmap[$history{$data}] = -1;
486 elsif($cmd eq 'unreject')
488 $bitmap[$history{$data}] = 0;
490 elsif($cmd eq 'rebase')
492 # the bitmap is fine, but generate a new log from the bitmap
495 order_a => \@history,
496 order_h => \%history,
499 my $rebasedlog = rebase_log $data, $pseudolog;
500 my @l = grep { $_->[0] ne 'outstanding' } @{$rebasedlog->{log}};
501 splice @logdata, 0, $i+1, @l;
504 $baseid = $history{$base};
509 my @outstanding = ();
510 for($baseid+1 .. @history-1)
512 push @outstanding, ['outstanding', $history[$_]]
519 order_a => \@history,
520 order_h => \%history,
546 $r = backtick 'git', 'rev-parse', $r
547 or die "git-rev-parse: $!"
552 print "Executing: $cmd $r\n";
558 die "PEBKAC: invalid revision number, cannot reset"
559 unless defined $l->{order_h}{$r};
563 elsif($cmd eq 'hardreset')
568 die "PEBKAC: invalid revision number, cannot reset"
569 unless defined $l->{order_h}{$r};
571 run 'git', 'reset', '--hard', $r
572 or die "git-reset: $!";
575 elsif($cmd eq 'merge')
580 die "PEBKAC: invalid revision number, cannot reset"
581 unless defined $l->{order_h}{$r} and $l->{bitmap}[$l->{order_h}{$r}] == 0;
582 die "PEBKAC: not initialized"
583 unless defined $l->{base};
587 elsif($cmd eq 'unmerge')
592 die "PEBKAC: invalid revision number, cannot reset"
593 unless defined $l->{order_h}{$r} and $l->{bitmap}[$l->{order_h}{$r}] > 0;
594 die "PEBKAC: not initialized"
595 unless defined $l->{base};
599 elsif($cmd eq 'reject')
604 die "PEBKAC: invalid revision number, cannot reset"
605 unless defined $l->{order_h}{$r} and $l->{bitmap}[$l->{order_h}{$r}] == 0;
606 die "PEBKAC: not initialized"
607 unless defined $l->{base};
611 elsif($cmd eq 'unreject')
616 die "PEBKAC: invalid revision number, cannot reset"
617 unless defined $l->{order_h}{$r} and $l->{bitmap}[$l->{order_h}{$r}] < 0;
618 die "PEBKAC: not initialized"
619 unless defined $l->{base};
623 elsif($cmd eq 'outstanding')
628 die "Invalid command: $cmd $r";
639 $r = backtick 'git', 'rev-parse', $r
640 or die "git-rev-parse: $!"
645 die "PEBKAC: invalid revision number, cannot reset"
646 unless defined $l->{order_h}{$r};
647 die "PEBKAC: not initialized"
648 unless defined $l->{base};
650 my $msg = backtick 'git', 'log', '-1', '--pretty=fuller', @datefilter, $branch
651 or die "git-log: $!";
652 $msg =~ /^commit (\S+)/s
653 or die "Invalid git log output";
655 my $l = rebase_log $r, parse_log();
657 local $do_commit = 0;
661 run_script @{$l->{log}};
662 run 'git', 'commit', '--allow-empty', '-m', "::stable-branch::rebase=$r"
663 or die "git-commit: $!";
669 run 'git', 'reset', '--hard', $commit_id
670 or die "$err, and then git-reset failed: $!";
676 my ($toencode,$newlinestoo) = @_;
677 return undef unless defined($toencode);
678 $toencode =~ s{&}{&}gso;
679 $toencode =~ s{<}{<}gso;
680 $toencode =~ s{>}{>}gso;
681 $toencode =~ s{"}{"}gso;
692 $r = undef if $r eq '';
695 ($r = backtick 'git', 'rev-parse', $r
696 or die "git-rev-parse: $!")
701 die "PEBKAC: invalid revision number, cannot reset"
702 unless !defined $r or defined $l->{order_h}{$r};
703 die "PEBKAC: not initialized"
704 unless defined $l->{base};
707 $l = rebase_log $r, $l
709 my $last = $l->{order_h}{$l->{base}};
710 my $first = $last - $histsize;
719 (map { $seen{$l->{order_a}[$_]} ? () : ['previous', $l->{order_a}[$_]] } $first..($last-1)),
720 ['base', $l->{base}],
723 if($cmd eq 'chronology')
725 @l = map { [$_->[1], $_->[2]] } sort { $l->{order_h}{$a->[2]} <=> $l->{order_h}{$b->[2]} or $a->[0] <=> $b->[0] } map { [$_, $l[$_]->[0], $l[$_]->[1]] } 0..(@l-1);
727 elsif($cmd eq 'outstanding')
730 @l = reverse grep { !$seen{$_->[1]}++ && !$l->{bitmap}->[$l->{order_h}->{$_->[1]}] } reverse map { [$_->[1], $_->[2]] } sort { $l->{order_h}{$a->[2]} <=> $l->{order_h}{$b->[2]} or $a->[0] <=> $b->[0] } map { [$_, $l[$_]->[0], $l[$_]->[1]] } 0..(@l-1);
734 print "Content-Type: text/html\n\n<table border>\n";
737 my ($action, $r) = @$_;
738 my $m = $l->{logmsg}->{$r};
739 my $m_short = join ' ', map { s/^ (?!git-svn-id)(.)/$1/ ? $_ : () } split /\n/, $m;
740 printf "<tr style=\"%s\"><td>%s</td><td><a href=\"%s%s\">%s</a></td><td style=\"white-space: pre\">%s</td></tr>\n", $html_style{$action}, $name{$action}, escapeHTML($cgi_url), escapeHTML($r), escapeHTML($r), escapeHTML($m_short);
748 my ($action, $r) = @$_;
749 my $m = $l->{logmsg}->{$r};
750 my $m_short = join ' ', map { s/^ (?!git-svn-id)(.)/$1/ ? $_ : () } split /\n/, $m;
751 $m_short = substr $m_short, 0, $width - 11 - 1 - 40 - 1;
752 printf "%s%-11s%s %s %s\n", $color{$action}, $name{$action}, $color{''}, $r, $m_short;
759 my ($cmd, $one) = @_;
762 $0 [{--histsize|-s} n] {--chronology|-c}
763 $0 [{--histsize|-s} n] {--chronology|-c} revision-hash
764 $0 [{--histsize|-s} n] {--log|-l}
765 $0 [{--histsize|-s} n] {--log|-l} revision-hash
766 $0 {--merge|-m} revision-hash
767 $0 {--unmerge|-u} revision-hash
768 $0 {--reset|-R} revision-hash
769 $0 {--hardreset|-H} revision-hash
770 $0 {--rebase|-b} revision-hash
796 my $result = GetOptions(
797 "chronology|c:s", handler \&opt_list,
798 "log|l:s", handler \&opt_list,
799 "outstanding|o:s", handler \&opt_list,
800 "rebase|b=s", handler \&opt_rebase,
801 "skip", handler \$skip,
802 "merge|m=s{,}", handler sub { run_script ['merge', $_[1]]; },
803 "unmerge|u=s{,}", handler sub { run_script ['unmerge', $_[1]]; },
804 "reject|r=s{,}", handler sub { run_script ['reject', $_[1]]; },
805 "unreject|U=s{,}", handler sub { run_script ['unreject', $_[1]]; },
806 "reset|R=s", handler sub { run_script ['reset', $_[1]]; },
807 "hardreset|H=s", handler sub { run_script ['hardreset', $_[1]]; },
808 "help|h", handler \&opt_help,
809 "histsize|s=i", \$histsize,
814 opt_list("outstanding", "");