5 use Getopt::Long qw/:config no_ignore_case no_auto_abbrev gnu_compat/;
10 'outstanding' => "\e[1;33m",
11 'unmerge' => "\e[1;31m",
14 'previous' => "\e[34m",
19 'outstanding' => "OUTSTANDING",
20 'unmerge' => "UNMERGED",
23 'previous' => "PREVIOUS",
28 my ($msg, $data) = @_;
29 return $data if defined $data;
49 my $width = ($ENV{COLUMNS} || backtick 'tput', 'cols' || 80);
50 chomp(my $branch = backtick 'git', 'symbolic-ref', 'HEAD');
51 $branch =~ s/^refs\/heads\///
52 or die "Not in a branch";
53 chomp(my $master = (backtick 'git', 'config', '--get', "branch-manager.$branch.master" or 'master'));
54 chomp(my $datefilter = (backtick 'git', 'config', '--get', "branch-manager.$branch.startdate" or ''));
57 if($datefilter eq 'mergebase')
59 chomp($revprefix = check_defined "git-merge-base: $!", backtick 'git', 'merge-base', $master, "HEAD");
62 elsif($datefilter ne '')
64 @datefilter = "--since=$datefilter";
69 sub reset_to_commit($)
72 #run 'git', 'merge', '-s', 'ours', '--no-commit', $r
73 # or die "git-merge: $!";
74 run 'git', 'checkout', $r, '--', '.'
75 or die "git-checkout: $!";
79 run 'git', 'update-ref', 'MERGE_HEAD', $r
80 or die "git-update-ref: $!";
81 run 'git', 'commit', '--allow-empty', '-m', "::stable-branch::reset=$r"
82 or die "git-commit: $!";
96 my $msg = backtick 'git', 'log', '-1', '--pretty=fuller', $r
100 if(/^Author:\s*(.*) <(.*)>/)
105 elsif(/^AuthorDate:\s*(.*)/)
114 open my $fh, '>', '.commitmsg'
115 or die ">.commitmsg: $!";
116 print $fh "$cmsg" . "::stable-branch::merge=$r\n"
117 or die ">.commitmsg: $!";
119 or die ">.commitmsg: $!";
121 local $ENV{GIT_AUTHOR_NAME} = $author;
122 local $ENV{GIT_AUTHOR_EMAIL} = $email;
123 local $ENV{GIT_AUTHOR_DATE} = $date;
124 run 'git', 'cherry-pick', '-n', $r
125 or run 'git', 'mergetool'
126 or die "git-mergetool: $!";
129 run 'git', 'commit', '-F', '.commitmsg'
130 or die "git-commit: $!";
134 sub unmerge_commit($)
144 my $msg = backtick 'git', 'log', '-1', '--pretty=fuller', $r
145 or die "git-log: $!";
146 for(split /\n/, $msg)
148 if(/^Author:\s*(.*)/)
152 elsif(/^AuthorDate:\s*(.*)/)
161 open my $fh, '>', '.commitmsg'
162 or die ">.commitmsg: $!";
163 print $fh "UNMERGE\n$cmsg" . "::stable-branch::merge=$r\n"
164 or die ">.commitmsg: $!";
166 or die ">.commitmsg: $!";
168 local $ENV{GIT_AUTHOR_NAME} = $author;
169 local $ENV{GIT_AUTHOR_EMAIL} = $email;
170 local $ENV{GIT_AUTHOR_DATE} = $date;
171 run 'git', 'revert', '-n', $r
172 or run 'git', 'mergetool'
173 or die "git-mergetool: $!";
176 run 'git', 'commit', '-F', '.commitmsg'
177 or die "git-commit: $!";
185 my @applied = (0) x @{$log->{order_a}};
186 my $newbase_id = $log->{order_h}{$r};
189 my @outstanding = ();
193 if(!$log->{bitmap}[$_])
195 unshift @rlog, ['unmerge', $log->{order_a}[$_]];
199 for($newbase_id+1 .. @{$log->{order_a}}-1)
201 if($log->{bitmap}[$_])
203 push @rlog, ['merge', $log->{order_a}[$_]];
207 push @outstanding, ['outstanding', $log->{order_a}[$_]];
224 return $logcache if defined $logcache;
236 my $cur_commit = undef;
238 for((split /\n/, check_defined "git-log: $!", backtick 'git', 'log', '--topo-order', '--reverse', '--pretty=fuller', @datefilter, "$revprefix$master"), undef)
240 if(defined $cur_commit and (not defined $_ or /^commit (\S+)/))
242 $cur_msg =~ s/\s+$//s;
243 $history{$cur_commit} = scalar @history;
244 $logmsg{$cur_commit} = $cur_msg;
245 push @history, $cur_commit;
246 $cur_commit = $cur_msg = undef;
248 last if not defined $_;
258 $cur_commit = $cur_msg = undef;
260 for((split /\n/, check_defined "git-log: $!", backtick 'git', 'log', '--topo-order', '--reverse', '--pretty=fuller', @datefilter, "$revprefix"."HEAD"), undef)
262 if(defined $cur_commit and (not defined $_ or /^commit (\S+)/))
264 $cur_msg =~ s/\s+$//s;
265 $logmsg{$cur_commit} = $cur_msg;
266 push @commits, $cur_commit;
267 $cur_commit = $cur_msg = undef;
269 last if not defined $_;
279 my $lastrebase = undef;
282 my $data = $logmsg{$_};
283 if($data =~ /::stable-branch::unmerge=(\S+)/)
285 push @logdata, ['unmerge', $1];
287 elsif($data =~ /::stable-branch::merge=(\S+)/)
289 push @logdata, ['merge', $1];
291 elsif($data =~ /::stable-branch::reset=(\S+)/)
296 elsif($data =~ /::stable-branch::rebase=(\S+)/)
298 $lastrebase->[0] = 'ignore'
299 if defined $lastrebase;
300 push @logdata, ($lastrebase = ['rebase', $1]);
304 if(not defined $base)
306 warn 'This branch is not yet managed by git-branch-manager';
310 order_a => \@history,
311 order_h => \%history,
316 my $baseid = $history{$base};
325 my ($cmd, $data) = @{$logdata[$i]};
328 $bitmap[$history{$data}] = 1;
330 elsif($cmd eq 'unmerge')
332 $bitmap[$history{$data}] = 0;
334 elsif($cmd eq 'rebase')
336 # the bitmap is fine, but generate a new log from the bitmap
339 order_a => \@history,
340 order_h => \%history,
343 my $rebasedlog = rebase_log $data, $pseudolog;
344 my @l = grep { $_->[0] ne 'outstanding' } @{$rebasedlog->{log}};
345 splice @logdata, 0, $i+1, @l;
348 $baseid = $history{$base};
353 my @outstanding = ();
354 for($baseid+1 .. @history-1)
356 push @outstanding, ['outstanding', $history[$_]]
363 order_a => \@history,
364 order_h => \%history,
390 $r = backtick 'git', 'rev-parse', $r
391 or die "git-rev-parse: $!"
396 print "Executing: $cmd $r\n";
402 die "PEBKAC: invalid revision number, cannot reset"
403 unless defined $l->{order_h}{$r};
407 elsif($cmd eq 'hardreset')
412 die "PEBKAC: invalid revision number, cannot reset"
413 unless defined $l->{order_h}{$r};
415 run 'git', 'reset', '--hard', $r
416 or die "git-reset: $!";
419 elsif($cmd eq 'merge')
424 die "PEBKAC: invalid revision number, cannot reset"
425 unless defined $l->{order_h}{$r} and not $l->{bitmap}[$l->{order_h}{$r}];
426 die "PEBKAC: not initialized"
427 unless defined $l->{base};
431 elsif($cmd eq 'unmerge')
436 die "PEBKAC: invalid revision number, cannot reset"
437 unless defined $l->{order_h}{$r} and $l->{bitmap}[$l->{order_h}{$r}];
438 die "PEBKAC: not initialized"
439 unless defined $l->{base};
443 elsif($cmd eq 'outstanding')
448 die "Invalid command: $cmd $r";
459 $r = backtick 'git', 'rev-parse', $r
460 or die "git-rev-parse: $!"
465 die "PEBKAC: invalid revision number, cannot reset"
466 unless defined $l->{order_h}{$r};
467 die "PEBKAC: not initialized"
468 unless defined $l->{base};
470 my $msg = backtick 'git', 'log', '-1', '--pretty=fuller', @datefilter, 'HEAD'
471 or die "git-log: $!";
472 $msg =~ /^commit (\S+)/s
473 or die "Invalid git log output";
475 my $l = rebase_log $r, parse_log();
477 local $do_commit = 0;
481 run_script @{$l->{log}};
482 run 'git', 'commit', '--allow-empty', '-m', "::stable-branch::rebase=$r"
483 or die "git-commit: $!";
489 run 'git', 'reset', '--hard', $commit_id
490 or die "$err, and then git-reset failed: $!";
500 $r = undef if $r eq '';
503 ($r = backtick 'git', 'rev-parse', $r
504 or die "git-rev-parse: $!")
509 die "PEBKAC: invalid revision number, cannot reset"
510 unless !defined $r or defined $l->{order_h}{$r};
511 die "PEBKAC: not initialized"
512 unless defined $l->{base};
515 $l = rebase_log $r, $l
517 my $last = $l->{order_h}{$l->{base}};
518 my $first = $last - $histsize;
527 (map { $seen{$l->{order_a}[$_]} ? () : ['previous', $l->{order_a}[$_]] } $first..($last-1)),
528 ['base', $l->{base}],
531 if($cmd eq 'chronology')
533 @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);
535 elsif($cmd eq 'outstanding')
538 @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);
542 my ($action, $r) = @$_;
543 my $m = $l->{logmsg}->{$r};
544 my $m_short = join ' ', map { s/^ (?!git-svn-id)(.)/$1/ ? $_ : () } split /\n/, $m;
545 $m_short = substr $m_short, 0, $width - 11 - 1 - 40 - 1;
546 printf "%s%-11s%s %s %s\n", $color{$action}, $name{$action}, $color{''}, $r, $m_short;
552 my ($cmd, $one) = @_;
555 $0 [{--histsize|-s} n] {--chronology|-c}
556 $0 [{--histsize|-s} n] {--chronology|-c} revision-hash
557 $0 [{--histsize|-s} n] {--log|-l}
558 $0 [{--histsize|-s} n] {--log|-l} revision-hash
559 $0 {--merge|-m} revision-hash
560 $0 {--unmerge|-u} revision-hash
561 $0 {--reset|-R} revision-hash
562 $0 {--hardreset|-H} revision-hash
563 $0 {--rebase|-b} revision-hash
589 my $result = GetOptions(
590 "chronology|c:s", handler \&opt_list,
591 "log|l:s", handler \&opt_list,
592 "outstanding|o:s", handler \&opt_list,
593 "rebase|b=s", handler \&opt_rebase,
594 "merge|m=s{,}", handler sub { run_script ['merge', $_[1]]; },
595 "unmerge|u=s{,}", handler sub { run_script ['unmerge', $_[1]]; },
596 "reset|R=s", handler sub { run_script ['reset', $_[1]]; },
597 "hardreset|H=s", handler sub { run_script ['hardreset', $_[1]]; },
598 "help|h", handler \&opt_help,
599 "histsize|s=i", \$histsize
603 opt_list("outstanding", "");