From: Rudolf Polzer Date: Mon, 11 Jan 2010 13:29:46 +0000 (+0100) Subject: initial import of git tools... X-Git-Url: https://git.xonotic.org/?p=xonotic%2Fdiv0-gittools.git;a=commitdiff_plain;h=d7dbc151256644e2e4db3313b2fb199d6d6dbd70;ds=sidebyside initial import of git tools... --- d7dbc151256644e2e4db3313b2fb199d6d6dbd70 diff --git a/git-branch-manager b/git-branch-manager new file mode 100755 index 0000000..f551155 --- /dev/null +++ b/git-branch-manager @@ -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 <(@_); + 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 index 0000000..c54032c --- /dev/null +++ b/git-pullall @@ -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 index 0000000..e06a59d --- /dev/null +++ b/git-svn-checkout @@ -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 index 0000000..f1352e7 --- /dev/null +++ b/git-svn-update @@ -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 +)