initial import of git tools...
authorRudolf Polzer <divVerent@alientrap.org>
Mon, 11 Jan 2010 13:29:46 +0000 (14:29 +0100)
committerRudolf Polzer <divVerent@alientrap.org>
Mon, 11 Jan 2010 13:29:46 +0000 (14:29 +0100)
git-branch-manager [new file with mode: 0755]
git-pullall [new file with mode: 0755]
git-svn-checkout [new file with mode: 0755]
git-svn-update [new file with mode: 0755]

diff --git a/git-branch-manager b/git-branch-manager
new file mode 100755 (executable)
index 0000000..f551155
--- /dev/null
@@ -0,0 +1,609 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use Getopt::Long qw/:config no_ignore_case no_auto_abbrev gnu_compat/;
+
+my %color =
+(
+       '' => "\e[m",
+       'outstanding' => "\e[1;33m",
+       'unmerge' => "\e[1;31m",
+       'merge' => "\e[32m",
+       'base' => "\e[1;34m",
+       'previous' => "\e[34m",
+);
+
+my %name =
+(
+       'outstanding' => "OUTSTANDING",
+       'unmerge' => "UNMERGED",
+       'merge' => "MERGED",
+       'base' => "BASE",
+       'previous' => "PREVIOUS",
+);
+
+sub check_defined($$)
+{
+       my ($msg, $data) = @_;
+       return $data if defined $data;
+       die $msg;
+}
+
+sub backtick(@)
+{
+       open my $fh, '-|', @_
+               or return undef;
+       undef local $/;
+       my $s = <$fh>;
+       close $fh
+               or return undef;
+       return $s;
+}
+
+sub run(@)
+{
+       return !system @_;
+}
+
+my $width = ($ENV{COLUMNS} || backtick 'tput', 'cols' || 80);
+chomp(my $branch = backtick 'git', 'symbolic-ref', 'HEAD');
+       $branch =~ s/^refs\/heads\///
+               or die "Not in a branch";
+chomp(my $master = (backtick 'git', 'config', '--get', "branch-manager.$branch.master" or 'master'));
+chomp(my $datefilter = (backtick 'git', 'config', '--get', "branch-manager.$branch.startdate" or ''));
+my @datefilter = ();
+my $revprefix = "";
+if($datefilter eq 'mergebase')
+{
+       chomp($revprefix = check_defined "git-merge-base: $!", backtick 'git', 'merge-base', $master, "HEAD");
+       $revprefix .= "^..";
+}
+elsif($datefilter ne '')
+{
+       @datefilter = "--since=$datefilter";
+}
+
+our $do_commit = 1;
+my $logcache = undef;
+sub reset_to_commit($)
+{
+       my ($r) = @_;
+       #run 'git', 'merge', '-s', 'ours', '--no-commit', $r
+       #       or die "git-merge: $!";
+       run 'git', 'checkout', $r, '--', '.'
+               or die "git-checkout: $!";
+       if($do_commit)
+       {
+               $logcache = undef;
+               run 'git', 'update-ref', 'MERGE_HEAD', $r
+                       or die "git-update-ref: $!";
+               run 'git', 'commit', '--allow-empty', '-m', "::stable-branch::reset=$r"
+                       or die "git-commit: $!";
+       }
+}
+
+sub merge_commit($)
+{
+       my ($r) = @_;
+       my $cmsg = "";
+       my $author = "";
+       my $email = "";
+       my $date = "";
+       if($do_commit)
+       {
+               $logcache = undef;
+               my $msg = backtick 'git', 'log', '-1', '--pretty=fuller', $r
+                       or die "git-log: $!";
+               for(split /\n/, $msg)
+               {
+                       if(/^Author:\s*(.*) <(.*)>/)
+                       {
+                               $author = $1;
+                               $email = $2;
+                       }
+                       elsif(/^AuthorDate:\s*(.*)/)
+                       {
+                               $date = $1;
+                       }
+                       elsif(/^    (.*)/)
+                       {
+                               $cmsg .= "$1\n";
+                       }
+               }
+               open my $fh, '>', '.commitmsg'
+                       or die ">.commitmsg: $!";
+               print $fh "$cmsg" . "::stable-branch::merge=$r\n"
+                       or die ">.commitmsg: $!";
+               close $fh
+                       or die ">.commitmsg: $!";
+       }
+       local $ENV{GIT_AUTHOR_NAME} = $author;
+       local $ENV{GIT_AUTHOR_EMAIL} = $email;
+       local $ENV{GIT_AUTHOR_DATE} = $date;
+       run 'git', 'cherry-pick', '-n', $r
+               or run 'git', 'mergetool'
+                       or die "git-mergetool: $!";
+       if($do_commit)
+       {
+               run 'git', 'commit', '-F', '.commitmsg'
+                       or die "git-commit: $!";
+       }
+}
+
+sub unmerge_commit($)
+{
+       my ($r) = @_;
+       my $cmsg = "";
+       my $author = "";
+       my $email = "";
+       my $date = "";
+       if($do_commit)
+       {
+               $logcache = undef;
+               my $msg = backtick 'git', 'log', '-1', '--pretty=fuller', $r
+                       or die "git-log: $!";
+               my $cmsg = "";
+               my $author = "";
+               my $email = "";
+               my $date = "";
+               for(split /\n/, $msg)
+               {
+                       if(/^Author:\s*(.*)/)
+                       {
+                               $author = $1;
+                       }
+                       elsif(/^AuthorDate:\s*(.*)/)
+                       {
+                               $date = $1;
+                       }
+                       elsif(/^    (.*)/)
+                       {
+                               $cmsg .= "$1\n";
+                       }
+               }
+               open my $fh, '>', '.commitmsg'
+                       or die ">.commitmsg: $!";
+               print $fh "UNMERGE\n$cmsg" . "::stable-branch::merge=$r\n"
+                       or die ">.commitmsg: $!";
+               close $fh
+                       or die ">.commitmsg: $!";
+       }
+       local $ENV{GIT_AUTHOR_NAME} = $author;
+       local $ENV{GIT_AUTHOR_EMAIL} = $email;
+       local $ENV{GIT_AUTHOR_DATE} = $date;
+       run 'git', 'revert', '-n', $r
+               or run 'git', 'mergetool'
+                       or die "git-mergetool: $!";
+       if($do_commit)
+       {
+               run 'git', 'commit', '-F', '.commitmsg'
+                       or die "git-commit: $!";
+       }
+}
+
+sub rebase_log($$)
+{
+       my ($r, $log) = @_;
+
+       my @applied = (0) x @{$log->{order_a}};
+       my $newbase_id = $log->{order_h}{$r};
+
+       my @rlog = ();
+       my @outstanding = ();
+
+       for(0..$newbase_id)
+       {
+               if(!$log->{bitmap}[$_])
+               {
+                       unshift @rlog, ['unmerge', $log->{order_a}[$_]];
+               }
+       }
+
+       for($newbase_id+1 .. @{$log->{order_a}}-1)
+       {
+               if($log->{bitmap}[$_])
+               {
+                       push @rlog, ['merge', $log->{order_a}[$_]];
+               }
+               else
+               {
+                       push @outstanding, ['outstanding', $log->{order_a}[$_]];
+               }
+       }
+
+       return
+       {
+               %$log,
+               base => $r,
+               log => [
+                       @rlog,
+                       @outstanding
+               ]
+       };
+}
+
+sub parse_log()
+{
+       return $logcache if defined $logcache;
+
+       my $base = undef;
+       my @logdata = ();
+
+       my %history = ();
+       my %logmsg = ();
+       my @history = ();
+
+       my %applied = ();
+       my %unapplied = ();
+
+       my $cur_commit = undef;
+       my $cur_msg = undef;
+       for((split /\n/, check_defined "git-log: $!", backtick 'git', 'log', '--topo-order', '--reverse', '--pretty=fuller', @datefilter, "$revprefix$master"), undef)
+       {
+               if(defined $cur_commit and (not defined $_ or /^commit (\S+)/))
+               {
+                       $cur_msg =~ s/\s+$//s;
+                       $history{$cur_commit} = scalar @history;
+                       $logmsg{$cur_commit} = $cur_msg;
+                       push @history, $cur_commit;
+                       $cur_commit = $cur_msg = undef;
+               }
+               last if not defined $_;
+               if(/^commit (\S+)/)
+               {
+                       $cur_commit = $1;
+               }
+               else
+               {
+                       $cur_msg .= "$_\n";
+               }
+       }
+       $cur_commit = $cur_msg = undef;
+       my @commits = ();
+       for((split /\n/, check_defined "git-log: $!", backtick 'git', 'log', '--topo-order', '--reverse', '--pretty=fuller', @datefilter, "$revprefix"."HEAD"), undef)
+       {
+               if(defined $cur_commit and (not defined $_ or /^commit (\S+)/))
+               {
+                       $cur_msg =~ s/\s+$//s;
+                       $logmsg{$cur_commit} = $cur_msg;
+                       push @commits, $cur_commit;
+                       $cur_commit = $cur_msg = undef;
+               }
+               last if not defined $_;
+               if(/^commit (\S+)/)
+               {
+                       $cur_commit = $1;
+               }
+               else
+               {
+                       $cur_msg .= "$_\n";
+               }
+       }
+       my $lastrebase = undef;
+       for(@commits)
+       {
+               my $data = $logmsg{$_};
+               if($data =~ /::stable-branch::unmerge=(\S+)/)
+               {
+                       push @logdata, ['unmerge', $1];
+               }
+               elsif($data =~ /::stable-branch::merge=(\S+)/)
+               {
+                       push @logdata, ['merge', $1];
+               }
+               elsif($data =~ /::stable-branch::reset=(\S+)/)
+               {
+                       @logdata = ();
+                       $base = $1;
+               }
+               elsif($data =~ /::stable-branch::rebase=(\S+)/)
+               {
+                       $lastrebase->[0] = 'ignore'
+                               if defined $lastrebase;
+                       push @logdata, ($lastrebase = ['rebase', $1]);
+               }
+       }
+
+       if(not defined $base)
+       {
+               warn 'This branch is not yet managed by git-branch-manager';
+               return
+               {
+                       logmsg => \%logmsg,
+                       order_a => \@history,
+                       order_h => \%history,
+               };
+       }
+       else
+       {
+               my $baseid = $history{$base};
+               my @bitmap = map
+               {
+                       $_ <= $baseid
+               }
+               0..@history-1;
+               my $i = 0;
+               while($i < @logdata)
+               {
+                       my ($cmd, $data) = @{$logdata[$i]};
+                       if($cmd eq 'merge')
+                       {
+                               $bitmap[$history{$data}] = 1;
+                       }
+                       elsif($cmd eq 'unmerge')
+                       {
+                               $bitmap[$history{$data}] = 0;
+                       }
+                       elsif($cmd eq 'rebase')
+                       {
+                               # the bitmap is fine, but generate a new log from the bitmap
+                               my $pseudolog =
+                               {
+                                       order_a => \@history,
+                                       order_h => \%history,
+                                       bitmap => \@bitmap,
+                               };
+                               my $rebasedlog = rebase_log $data, $pseudolog;
+                               my @l = grep { $_->[0] ne 'outstanding' } @{$rebasedlog->{log}};
+                               splice @logdata, 0, $i+1, @l;
+                               $i = @l-1;
+                               $base = $data;
+                               $baseid = $history{$base};
+                       }
+                       ++$i;
+               }
+
+               my @outstanding = ();
+               for($baseid+1 .. @history-1)
+               {
+                       push @outstanding, ['outstanding', $history[$_]]
+                               unless $bitmap[$_];
+               }
+
+               $logcache =
+               {
+                       logmsg => \%logmsg,
+                       order_a => \@history,
+                       order_h => \%history,
+
+                       bitmap => \@bitmap,
+                       base => $base,
+                       log => [
+                               @logdata,
+                               @outstanding
+                       ]
+               };
+               return $logcache;
+       }
+}
+
+our $pebkac = 0;
+our $done = 0;
+
+sub run_script(@);
+sub run_script(@)
+{
+       ++$done;
+       my (@commands) = @_;
+       for(@commands)
+       {
+               my ($cmd, $r) = @$_;
+               if($pebkac)
+               {
+                       $r = backtick 'git', 'rev-parse', $r
+                               or die "git-rev-parse: $!"
+                                       if defined $r;
+                       chomp $r
+                               if defined $r;
+               }
+               print "Executing: $cmd $r\n";
+               if($cmd eq 'reset')
+               {
+                       if($pebkac)
+                       {
+                               my $l = parse_log();
+                               die "PEBKAC: invalid revision number, cannot reset"
+                                       unless defined $l->{order_h}{$r};
+                       }
+                       reset_to_commit $r;
+               }
+               elsif($cmd eq 'hardreset')
+               {
+                       if($pebkac)
+                       {
+                               my $l = parse_log();
+                               die "PEBKAC: invalid revision number, cannot reset"
+                                       unless defined $l->{order_h}{$r};
+                       }
+                       run 'git', 'reset', '--hard', $r
+                               or die "git-reset: $!";
+                       reset_to_commit $r;
+               }
+               elsif($cmd eq 'merge')
+               {
+                       if($pebkac)
+                       {
+                               my $l = parse_log();
+                               die "PEBKAC: invalid revision number, cannot reset"
+                                       unless defined $l->{order_h}{$r} and not $l->{bitmap}[$l->{order_h}{$r}];
+                               die "PEBKAC: not initialized"
+                                       unless defined $l->{base};
+                       }
+                       merge_commit $r;
+               }
+               elsif($cmd eq 'unmerge')
+               {
+                       if($pebkac)
+                       {
+                               my $l = parse_log();
+                               die "PEBKAC: invalid revision number, cannot reset"
+                                       unless defined $l->{order_h}{$r} and $l->{bitmap}[$l->{order_h}{$r}];
+                               die "PEBKAC: not initialized"
+                                       unless defined $l->{base};
+                       }
+                       unmerge_commit $r;
+               }
+               elsif($cmd eq 'outstanding')
+               {
+               }
+               else
+               {
+                       die "Invalid command: $cmd $r";
+               }
+       }
+}
+
+sub opt_rebase($$)
+{
+       ++$done;
+       my ($cmd, $r) = @_;
+       if($pebkac)
+       {
+               $r = backtick 'git', 'rev-parse', $r
+                       or die "git-rev-parse: $!"
+                       if defined $r;
+               chomp $r
+                       if defined $r;
+               my $l = parse_log();
+               die "PEBKAC: invalid revision number, cannot reset"
+                       unless defined $l->{order_h}{$r};
+               die "PEBKAC: not initialized"
+                       unless defined $l->{base};
+       }
+       my $msg = backtick 'git', 'log', '-1', '--pretty=fuller', @datefilter, 'HEAD'
+               or die "git-log: $!";
+       $msg =~ /^commit (\S+)/s
+               or die "Invalid git log output";
+       my $commit_id = $1;
+       my $l = rebase_log $r, parse_log();
+       local $pebkac = 0;
+       local $do_commit = 0;
+       eval
+       {
+               reset_to_commit $r;
+               run_script @{$l->{log}};
+               run 'git', 'commit', '--allow-empty', '-m', "::stable-branch::rebase=$r"
+                       or die "git-commit: $!";
+               1;
+       }
+       or do
+       {
+               my $err = $@;
+               run 'git', 'reset', '--hard', $commit_id
+                       or die "$err, and then git-reset failed: $!";
+               die $err;
+       };
+}
+
+my $histsize = 20;
+sub opt_list($$)
+{
+       ++$done;
+       my ($cmd, $r) = @_;
+       $r = undef if $r eq '';
+       if($pebkac)
+       {
+               ($r = backtick 'git', 'rev-parse', $r
+                       or die "git-rev-parse: $!")
+                               if defined $r;
+               chomp $r
+                       if defined $r;
+               my $l = parse_log();
+               die "PEBKAC: invalid revision number, cannot reset"
+                       unless !defined $r or defined $l->{order_h}{$r};
+               die "PEBKAC: not initialized"
+                       unless defined $l->{base};
+       }
+       my $l = parse_log();
+       $l = rebase_log $r, $l
+               if defined $r;
+       my $last = $l->{order_h}{$l->{base}};
+       my $first = $last - $histsize;
+       $first = 0
+               if $first < 0;
+       my %seen = ();
+       for(@{$l->{log}})
+       {
+               ++$seen{$_->[1]};
+       }
+       my @l = (
+                       (map { $seen{$l->{order_a}[$_]} ? () : ['previous', $l->{order_a}[$_]] } $first..($last-1)),
+                       ['base', $l->{base}],
+                       @{$l->{log}}
+                       );
+       if($cmd eq 'chronology')
+       {
+               @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);
+       }
+       elsif($cmd eq 'outstanding')
+       {
+               my %seen = ();
+               @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);
+       }
+       for(@l)
+       {
+               my ($action, $r) = @$_;
+               my $m = $l->{logmsg}->{$r};
+               my $m_short = join ' ', map { s/^    (?!git-svn-id)(.)/$1/ ? $_ : () } split /\n/, $m;
+               $m_short = substr $m_short, 0, $width - 11 - 1 - 40 - 1;
+               printf "%s%-11s%s %s %s\n", $color{$action}, $name{$action}, $color{''}, $r, $m_short;
+       }
+}
+
+sub opt_help($$)
+{
+       my ($cmd, $one) = @_;
+       print STDERR <<EOF;
+Usage:
+       $0 [{--histsize|-s} n] {--chronology|-c}
+       $0 [{--histsize|-s} n] {--chronology|-c} revision-hash
+       $0 [{--histsize|-s} n] {--log|-l}
+       $0 [{--histsize|-s} n] {--log|-l} revision-hash
+       $0 {--merge|-m} revision-hash
+       $0 {--unmerge|-u} revision-hash
+       $0 {--reset|-R} revision-hash
+       $0 {--hardreset|-H} revision-hash
+       $0 {--rebase|-b} revision-hash
+EOF
+       exit 1;
+}
+
+sub handler($)
+{
+       my ($sub) = @_;
+       return sub
+       {
+               my $r;
+               eval
+               {
+                       $r = $sub->(@_);
+                       1;
+               }
+               or do
+               {
+                       warn "$@";
+                       exit 1;
+               };
+               return $r;
+       };
+}
+
+$pebkac = 1;
+my $result = GetOptions(
+       "chronology|c:s", handler \&opt_list,
+       "log|l:s", handler \&opt_list,
+       "outstanding|o:s", handler \&opt_list,
+       "rebase|b=s", handler \&opt_rebase,
+       "merge|m=s{,}", handler sub { run_script ['merge', $_[1]]; },
+       "unmerge|u=s{,}", handler sub { run_script ['unmerge', $_[1]]; },
+       "reset|R=s", handler sub { run_script ['reset', $_[1]]; },
+       "hardreset|H=s", handler sub { run_script ['hardreset', $_[1]]; },
+       "help|h", handler \&opt_help,
+       "histsize|s=i", \$histsize
+);
+if(!$done)
+{
+       opt_list("outstanding", "");
+}
+$pebkac = 0;
diff --git a/git-pullall b/git-pullall
new file mode 100755 (executable)
index 0000000..c54032c
--- /dev/null
@@ -0,0 +1,36 @@
+#!/bin/sh
+
+set -e
+#set -x
+
+cur=`git symbolic-ref HEAD`
+case "$cur" in
+       refs/heads/*)
+               cur=${cur#refs/heads/}
+               ;;
+       *)
+               echo >&2 "Not in a branch"
+               exit 1
+               ;;
+esac
+git update-index -q --refresh
+
+if git diff-index --quiet --cached HEAD --ignore-submodules -- && git diff-files --quiet --ignore-submodules; then
+       stashed=false
+else
+       git stash save
+       stashed=true
+fi
+
+eval `git for-each-ref --shell --format='
+       b=%(refname);
+       b=${b#refs/heads/};
+       if git config branch.$b.merge >/dev/null && git config branch.$b.remote >/dev/null; then
+               git checkout "$b";
+               git pull;
+       fi;
+' refs/heads/`
+git checkout "$cur"
+if $stashed; then
+       git stash pop --index
+fi
diff --git a/git-svn-checkout b/git-svn-checkout
new file mode 100755 (executable)
index 0000000..e06a59d
--- /dev/null
@@ -0,0 +1,43 @@
+#!/bin/sh
+
+set -ex
+
+gitpath=$1
+case "$gitpath" in
+       /*)
+               ;;
+       *)
+               gitpath="`pwd`/$gitpath"
+               ;;
+esac
+shift
+
+svnroot=$1
+shift
+svnpath=$1
+shift
+
+
+mkdir -p "$gitpath"
+(
+       cd "$gitpath"
+       git init --bare
+       git --bare svn init "$@" "$svnroot"
+       case "$svnpath" in
+               .)
+                       git --bare config svn-remote.svn.fetch "trunk:refs/remotes/trunk"
+                       git --bare config svn-remote.svn.branches "branches/*:refs/remotes/branches/*"
+                       git --bare config svn-remote.svn.tags "tags/*:refs/remotes/tags/*"
+                       ;;
+               *)
+                       git --bare config svn-remote.svn.fetch "trunk/$svnpath:refs/remotes/trunk"
+                       git --bare config svn-remote.svn.branches "branches/*/$svnpath:refs/remotes/branches/*"
+                       git --bare config svn-remote.svn.tags "tags/*/$svnpath:refs/remotes/tags/*"
+                       ;;
+       esac
+       git --bare config remote.origin.url .
+       git --bare config       remote.origin.fetch "+refs/remotes/trunk:refs/heads/master"
+       git --bare config --add remote.origin.fetch "+refs/remotes/branches/*:refs/heads/*"
+       git --bare config --add remote.origin.fetch "+refs/remotes/tags/*:refs/tags/*"
+)
+git-svn-update "$gitpath"
diff --git a/git-svn-update b/git-svn-update
new file mode 100755 (executable)
index 0000000..f1352e7
--- /dev/null
@@ -0,0 +1,21 @@
+#!/bin/sh
+
+set -ex
+
+gitpath=$1
+case "$gitpath" in
+       /*)
+               ;;
+       *)
+               gitpath="`pwd`/$gitpath"
+               ;;
+esac
+
+(
+       cd "$gitpath"
+       git --bare svn fetch
+       git --bare fetch
+       git --bare update-server-info
+       git --bare push mirror --all || true
+       git --bare push mirror --tags || true
+)