oops; forgot the program... On Sat, Dec 17, 2011 at 03:40:09PM +0530, Sitaram Chamarty wrote: > On Sat, Dec 10, 2011 at 01:26:32PM +0100, Gelonida N wrote: > > Hi, > > > > What is the best way to fastforward all fastforwardable tracking > > branches after a git fetch? > > I know this is a somewhat closed topic, but I took some time to > clean up a program I have been using for a while, including some > changes based upon ideas elsewhere in this thread. The program > "git-branch-check" is attached, and requires perl > 5.10.0. > > Note that this does a lot more than just fast-forward all > branches, although it can do that as well. > > I alias it (in ~/.gitconfig) to 'bc', so I just run "git bc". > Running with "-h" shows usage: > > Usage: /home/sitaram/bin/git-branch-check [options] [branches] > > Check or fast forward branches. Default: act upon all local branches if no > arguments supplied, or just the current branch if '-c' is passed. > -c act upon current branch only > -ff don't just check, try to fast forward also > -md max diff (default 100; see below for details) > -h help > 'max diff': > hide output for two branches different by more than so many commits > > My usual usage is just "git bc -c", which may give me: > > 1 pu...origin/pu > 1 pu...github/pu > 13 pu...master > 5 pu...q > 7 pu...vrs > > This quickly tells me my 'pu' is one ahead of both my own > gitolite server as well as github's copy, and that it is 13 > commits ahead of master. The (unreleased and frequently > rebased) feature branches 'q' and 'vrs' are ahead of pu, which > means a rebase is not pending. Without the "-c" I may see the > status of master versus its own upstream and other remotes, > etc., also. > > The purpose of the max diff limit (default 100) is to hide, for > example, the pair 'master' and 'man' from the git.git repo. > Otherwise you'd see something like: > > 27249 973 master...man > > which is pretty meaningless. The sum of those two numbers > should be less than the max. > > "git bc -ff" will attempt to fast forward all selected branches > that are ancestors of their respective upstreams. The current > branch will not be ff-ed if the tree is dirty, since you can't > do this by 'git branch -f'; it has to be an actual merge > command. > > The output is not (currently) pipable to other programs because > I use colors (obtained from 'git config --get-color') and > currently it is not conditional on STDOUT being a tty.
#!/usr/bin/perl -s use 5.10.0; use strict; use warnings; # ---------------------------------------------------------------------- # bare-minimum subset of 'Tsh' (see github.com/sitaramc/tsh) { my($rc, $text); sub rc { return $rc || 0; } sub text { return $text || ''; } sub lines { return split /\n/, $text; } sub try { my $cmd = shift; die "try: expects only one argument" if @_; $text = `( $cmd ) 2>&1; echo -n RC=\$?`; if ($text =~ s/RC=(\d+)$//) { $rc = $1; return (not $rc); } die "couldnt find RC= in result; this should not happen:\n$text\n\n...\n"; } } # ---------------------------------------------------------------------- # options; the "-s" above sets one or more of these # (the format of the lines below is special; it is used by usage() to generate # help text for the options) # BEGIN OPTIONS our $c; # act upon current branch only our $ff; # don't just check, try to fast forward also our $md; # max diff (default 100; see below for details) our $h; # help # END OPTIONS $md ||= 100; # get this over with; usage() exits so don't worry usage() if $h; # get current branch my $current = ''; try "git symbolic-ref HEAD" or die "DETACHED HEAD or no repo"; ($current = (lines)[0]) =~ s(refs/heads/)(); # get branch names # first, all local branches as keys of a hash with upstream name if any, as the value my %upstream; try "git for-each-ref --perl '--format=\$upstream{%(refname:short)} = %(upstream:short);' refs/heads" or die "for-each-ref 1 failed"; eval text; # local branches as a list; keep $current at the top, and the rest sorted my @local = ($current, grep { $_ ne $current } sort keys %upstream); # remote branches as a list try "git for-each-ref '--format=%(refname:short)' refs/remotes" or die "for-each-ref 2 failed"; my @remote = lines; # decide what branches to act upon. Default: all local branches. If any # arguments are given, then those. If '-c' is passed, only current branch. my @branches = @local; @branches = @ARGV if @ARGV; @branches = ($current) if $c; # ---------------------------------------------------------------------- # show the tree state if it's dirty print "dirty:\n", text if dirty(); # process selected branches for my $b (@branches) { # attempt a fast-forward if -ff is passed ff($b, $upstream{$b}, $current) if ($ff); # check against its own upstream check($b, $upstream{$b}); # then against all remote branches of the same name (I typically have # my own gitolite server as 'upstream' but also have github and google # code as additional remotes that I push my branches to) check($b, grep(m(^[^/]+/$b$), @remote)); } # ...then against all local branches. We do this in a separate loop # so their output is kept separate from the remote compares above. for my $b (@branches) { check($b, @local); } # DONE... # ---------------------------------------------------------------------- # subroutines # ---------------------------------------------------------------------- sub ff { # b=branch, u=upstream, c=current my ($b, $u, $c) = @_; unless ($u) { say "$b does not have an upstream"; return; } if ($b eq $c and dirty()) { say "working tree is dirty; skipping ff for (current branch) $b"; return; } # $l = number of commits "l"eft side has over the "r"ight (similarly $r...) my($l, $r) = compare($b, $u); if ($r and not $l) { # there is something to update, and ff is possible if ($b eq $c) { # current branch; needs an actual merge try("git merge --ff-only $u") or die "$b: 'git merge --ff-only $u' failed:\n" . text; } else { # other branches can be forced try("git branch -f $b $u") or die "$b: 'git branch -f $b $u' failed:\n" . text; } } } sub check { my ($b, @list) = @_; state %seen; for my $u (@list) { next unless $u; next if $b eq $u or $seen{$b}{$u}; # seeing a...b is as good as seeing b...a also $seen{$b}{$u} = 1; $seen{$u}{$b} = 1; my ($l, $r) = compare($b, $u); my $abs = $l + $r; next unless $abs; # if they're equal, don't show it next if $abs >= $md; # if they're too far apart, don't show it print spacepad(4, $l) . color('green') . ($l || ' '); print spacepad(4, $r) . color('red') . ($r || ' '); say color('reset') . " $b...$u"; } } sub compare { my ($b, $u) = @_; try("git rev-list $u..$b") or die "'git rev-list $u..$b' failed:\n" . text; my $l = lines; try("git rev-list $b..$u") or die "'git rev-list $b..$u' failed:\n" . text; my $r = lines; return($l, $r); } sub dirty { try "git status -s -uno | cut -c1-2 | sort | uniq -c; /./"; } sub color { my $color = shift; return `git config --get-color "" $color`; } sub spacepad { return " " x ($_[0] - length($_[1])); } sub usage { print " Usage: $0 [options] [branches] Check or fast forward branches. Default: act upon all local branches if no arguments supplied, or just the current branch if '-c' is passed. "; @ARGV=($0); for ( grep { /BEGIN OPTIONS/../END OPTIONS/ and not /OPTION/ } <> ) { s/our \$/\t-/; s/; *#/\t/; print; } say "\'max diff':\n hide output for two branches different by more than so many commits"; exit 1; }
Attachment:
pgp9G9qxrjhWf.pgp
Description: PGP signature