├── .gitignore ├── ChangeLog ├── Makefile ├── README └── git-what-branch /.gitignore: -------------------------------------------------------------------------------- 1 | git-what-branch.1 2 | -------------------------------------------------------------------------------- /ChangeLog: -------------------------------------------------------------------------------- 1 | 2010-10-28 Seth Robertson 2 | 3 | * v0.2.4 release 4 | 5 | * git-what-branch: Add --first-parent and --first-parent-simple options 6 | to allow more easy programmatic detection of failure if a commit was 7 | not made directly on any named branch and programmatic parse of output. 8 | 9 | * Exit with errors if things go wrong or at least not as the user 10 | probably desired (e.g. commit has not merged with a named branch, etc) 11 | 12 | * Bugfix: Prevent duplicate refs from being researched multiple times. 13 | Prevent first-parent outputs from being discussed again during 14 | merge-path output if --all is used. 15 | 16 | * Bugfix: If multiple commits are listed for research, do not have subsequent 17 | commits inherit bad or duplicate information from previous commits. 18 | 19 | 2010-10-12 Seth Robertson 20 | 21 | * git-what-branch: Handle case when you run git-what-branch when 22 | you are not on a branch (no branch). 23 | 24 | 2010-09-23 Seth Robertson 25 | 26 | * v0.2.1 release 27 | * Make output shorter and better aligned 28 | * Massive speedup when many targets need to be searched (Thanks Artur Skawina) 29 | * Allow auto-target specification --branches, --allbranches, --tags, --allref 30 | * Allow --reference to be comma separated list of target references 31 | * Delete outdated --reference-branch documentation 32 | * Delete extended help (RTFM) 33 | * Use better(?) ref->name conversion 34 | * Correct sorting if multiple commits of interest are specified for searching 35 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | prefix=/usr/local 2 | bindir=${prefix}/bin 3 | mandir=${prefix}/share/man 4 | man1dir=${mandir}/man1 5 | 6 | PROG=git-what-branch 7 | PACKAGE=git-what-branch 8 | TARGETS=$(PROG).1 README $(PROG) 9 | JUNK=$(PROG).1 10 | 11 | all: $(TARGETS) 12 | 13 | %.1: % 14 | pod2man < $^ > $@ 15 | 16 | install: $(TARGETS) 17 | mkdir -p $(DESTDIR)/$(man1dir) $(DESTDIR)/$(bindir) 18 | install -m 444 $(PROG).1 $(DESTDIR)/$(man1dir) 19 | if [ -d .git ]; then \ 20 | VERSION=`git describe --tags --match 'v[0-9]*'`; \ 21 | sed "s/{UNTAGGED}/$${VERSION}/" $(PROG) > $(DESTDIR)/$(bindir)/$(PROG); \ 22 | chmod 755 $(DESTDIR)/$(bindir)/$(PROG); \ 23 | else \ 24 | install -m 755 $(PROG) $(DESTDIR)/$(bindir)/; \ 25 | fi 26 | 27 | README: $(PROG) 28 | pod2text < $(PROG) > README 29 | 30 | release: README 31 | @VERSION=`git describe --exact-match --match 'v[0-9]*' 2>/dev/null | sed 's/^v//'`; \ 32 | if [ $$? -ne 0 -o "$$VERSION" = "" ]; then echo "Not a tagged version, you may not release"; exit 3; fi; \ 33 | if [ `git status --porcelain | wc -l` -gt 0 ]; then echo "Uncommitted changes, you may not release"; exit 2; fi; \ 34 | git checkout-index -a -f --prefix=/tmp/$(PACKAGE)-$$VERSION/; \ 35 | cd /tmp/$(PACKAGE)-$$VERSION; \ 36 | sed -i "s/{UNTAGGED}/$${VERSION}/" $(PROG); \ 37 | cd ..; \ 38 | tar czf $(PACKAGE)-$$VERSION.tar.gz $(PACKAGE)-$$VERSION; \ 39 | rm -rf /tmp/$(PACKAGE)-$$VERSION; \ 40 | echo /tmp/$(PACKAGE)-$$VERSION.tar.gz 41 | 42 | clean nuke: 43 | rm -rf $(JUNK) *~ core* \#* 44 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | NAME 2 | git-what-branch - Discover what branch a particular commit was made on 3 | or near 4 | 5 | SYNOPSIS 6 | git-what-branch [[--branches] | [--allbranches] | [--tags] | [--allref]] 7 | [--first-parent] [--first-parent-simple] [--all] [--topo-order | 8 | --date-order ] [--quiet] [--reference=reference,reference,reference] 9 | ... 10 | 11 | OVERVIEW 12 | Tell us (by default) the earliest causal path of commits and merges to 13 | cause the requested commit got onto a named branch. If a commit was made 14 | directly on a named branch, that obviously is the earliest path. 15 | 16 | By earliest causal path, we mean the path which merged into a named 17 | branch the earliest, by commit time (unless --topo-order is specified). 18 | 19 | You may specify a particular reference branch or tag or revision to look 20 | at instead of searching (by default) the path for all named branches. 21 | Searching the path for all named branches can take a long time for an 22 | early commit occurring on many branches. If you specifically name a 23 | reference branch or commit, it should normally take seconds. 24 | 25 | DESCRIPTION 26 | --branches 27 | The default mode of check the path to any local branch. 28 | 29 | --allbranches 30 | Check the path to any local or remote branch. 31 | 32 | --tags 33 | Check the path to any known tags 34 | 35 | --allref 36 | Check the path to any local or remote branch and to any tag. 37 | 38 | --all 39 | If the commit in question was not made directly on a named branch (in 40 | which case all branch names would be printed), the system picks the 41 | named branch which the commit was merged to first and prints only that 42 | path. With this argument all paths from the commit in question to all 43 | named branches that it was committed onto are printed. 44 | 45 | --first-parent 46 | If the commit in question was not made directly on a named branch, fail. 47 | If the commit was made directly on a named branch, print the branch name 48 | or names. 49 | 50 | --first-parent-simple 51 | Same as --first-parent except instead of outputting in the "(aka)" 52 | format if there are multiple differently spelled but otherwise identical 53 | branches, print all directly reachable branches/references out with one 54 | line per branch/reference. 55 | 56 | --topo-order 57 | Instead of selecting the merge path which resulted in the earliest 58 | commit to a named branch, select the merge path which resulted in the 59 | fewest merges. If multiple merge paths have the same distance, use 60 | earliest merge to break ties. 61 | 62 | --date-order 63 | The default ordering where the merge path which resulted in the earliest 64 | commit to a named branch is displayed. 65 | 66 | --quiet 67 | If the commit was not made on a branch, do not print the path from the 68 | commit to the named branch, just print the branch name. 69 | 70 | --reference 71 | Instead of auto-generating the list of branches/tags to check to see how 72 | the commit in question got there, specify the (comma seperated) list of 73 | tags, branch names, commits, or other references that this program 74 | should use to try and find minimal and early paths to from the command 75 | line references. 76 | 77 | PERFORMANCE 78 | If many branches (e.g. hundreds) contain the commit, the system may take 79 | a long time (for a particular commit in the linux tree, it took 8 second 80 | to explore a branch, but there were over 200 candidate branches) to 81 | track down the path to each commit. Selection of a particular 82 | --reference-branch --reference tag to examine will be hundreds of times 83 | faster (if you have hundreds of candidate branches). 84 | 85 | EXAMPLES 86 | # git-what-branch --all 1f9c381fa3e0b9b9042e310c69df87eaf9b46ea4 87 | v2.6.12-rc3-450-g1f9c381 used the following minimal temporal path: 88 | merged to v2.6.12-rc3-461-g84e48b6 @Tue May 3 18:27:24 2005 89 | merged to v2.6.12-rc3-590-gbfd4bda @Thu May 5 08:59:37 2005 90 | v2.6.12-rc3-590-gbfd4bda is on v2.6.12-n 91 | v2.6.12-rc3-590-gbfd4bda is on v2.6.12-rc4-n 92 | [...] 93 | v2.6.12-rc3-590-gbfd4bda is on v2.6.36-rc4-n 94 | v2.6.12-rc3-590-gbfd4bda is on v2.6.36-rc5-n(aka master) 95 | 96 | BUGS 97 | git fast-forward merges make changes to branches without reflecting that 98 | history in a merge commit. This means that when later reviewing that 99 | history, git may label (via --first-parent) the wrong branch as being 100 | named a specific name. Any lies which git makes are reflected in the 101 | output of this program. 102 | 103 | Branches which are created after the commit you are interested in has 104 | been merged into another named branch you are interested in cannot be 105 | distinguished from the original branch. Example if you have master 106 | branch, you make commit A, then make a release branch named v1.0, after 107 | branch v1.0 has been created there is no way to know that v1.0 was 108 | created later and so both branches will be listed as the branches that 109 | commit A was made on. If git recorded when a branch was created, we 110 | could avoid this problem. 111 | 112 | If multiple branches (say due to the previous bug) are candidates and 113 | the commit was NOT made directly on a named branch but rather on an 114 | anonymous branch that was merged, unless you request --all, a 115 | pseudo-random branch will be chosen as the branch advertised via the 116 | merge path. 117 | 118 | This program does not take into account the effects of cherry-picking 119 | the commit of interest, only merge operations. 120 | 121 | ACKNOWLEDGMENTS 122 | Thanks to Artur Skawina for his assistance in developing some of the 123 | algorithms used by this script. 124 | 125 | COPYRIGHT/LICENSE 126 | License: GPL v2 Copyright (c) 2010 Seth Robertson 127 | 128 | -------------------------------------------------------------------------------- /git-what-branch: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | # 3 | # Tell us (by default) the earliest causal path of commits and merges to 4 | # cause the requested commit got onto a named branch. If a commit was 5 | # made directly on a named branch, that obviously is the earliest path. 6 | # 7 | # See the pod documentation below for more information 8 | # 9 | # Thanks to Artur Skawina for his assistance in developing some 10 | # of the algorithms used by this script. 11 | # 12 | # License: GPL v2 13 | # Copyright (c) 2010 Seth Robertson 14 | # 15 | use warnings; 16 | no warnings "uninitialized"; 17 | use Getopt::Long; 18 | use strict; 19 | 20 | my $USAGE="$0: [[--branches] | [--allbranches] | [--tags] | [--allref]] 21 | [--first-parent] [--first-parent-simple] 22 | [--all] [--quiet] [--reference=reference[,reference]...] [--version] 23 | ... 24 | "; 25 | 26 | my(%OPTIONS); 27 | Getopt::Long::Configure("bundling", "no_ignore_case", "no_auto_abbrev", "no_getopt_compat", "require_order"); 28 | GetOptions(\%OPTIONS, 'first-parent', 'first-parent-simple', 'branches|b', 'allbranches', 'tags|t', 'allref|a', 'all', 'quiet', 'debug+', 'reference|references=s', 'verbose|v+', 'version', 'topo-order', 'date-order') || die $USAGE; 29 | 30 | if ($OPTIONS{'version'}) 31 | { 32 | print "$0 version is {UNTAGGED}\n"; 33 | exit(0); 34 | } 35 | 36 | 37 | if ( $#ARGV < 0 ) 38 | { 39 | print STDERR $USAGE; 40 | exit(2); 41 | } 42 | 43 | my ($MULTI); 44 | $MULTI=1 if ( $#ARGV > 0 ); 45 | my(%translation,%TRANSLATION); 46 | our($exitcode) = 0; 47 | $OPTIONS{'first-parent'} = 1 if ($OPTIONS{'first-parent-simple'}); 48 | 49 | 50 | 51 | ######################################## 52 | # 53 | # Describe a hash if necessary 54 | # 55 | sub describep($) 56 | { 57 | my ($ref) = @_; 58 | my ($ret) = $ref; 59 | 60 | if ($ref =~ /^[0-9a-f]{40}$/) 61 | { 62 | if ($translation{$ref}) 63 | { 64 | my @tmp = @{ $translation{$ref} }; 65 | if ($OPTIONS{'first-parent-simple'}) 66 | { 67 | $ret = join("\n",@tmp); 68 | } 69 | else 70 | { 71 | $ret = pop(@tmp); 72 | if ($#tmp >= 0) 73 | { 74 | $ret .= "(aka ".join(", ",@tmp).")"; 75 | } 76 | } 77 | } 78 | else 79 | { 80 | my $newref; 81 | chomp($newref = `git describe --tags --always $ref`); 82 | $ret = $newref if ($newref && $? == 0); 83 | } 84 | } 85 | $ret; 86 | } 87 | 88 | 89 | 90 | ######################################## 91 | # 92 | # Find shortest path through a dag 93 | # Return array of shortest path 94 | # 95 | sub find_shortest($$$$); 96 | sub find_shortest($$$$) 97 | { 98 | my ($id,$target,$tree,$mark) = @_; 99 | 100 | print STDERR "Looking at node $id\n" if ($OPTIONS{'debug'}); 101 | 102 | while ($id ne $target) 103 | { 104 | # Is this a merge commit? 105 | if ($#{$tree->{$id}->{'parent'}} > 0) 106 | { 107 | # Is the first parent not a descendant? 108 | if (!$mark->{$tree->{$id}->{'parent'}->[0]}) 109 | { 110 | my (@minp); 111 | my ($mindef); 112 | 113 | # See which parent is the best connected 114 | foreach my $parent (@{$tree->{$id}->{'parent'}}) 115 | { 116 | next unless $mark->{$parent}; 117 | 118 | my (@tmp) = find_shortest($parent,$target,$tree,$mark); 119 | 120 | if (!$mindef || $#minp > $#tmp) 121 | { 122 | @minp = @tmp; 123 | $mindef = 1; 124 | } 125 | } 126 | push(@minp,$id); 127 | return(@minp); 128 | } 129 | } 130 | 131 | $id = $tree->{$id}->{'parent'}->[0]; 132 | } 133 | (); 134 | } 135 | 136 | 137 | # topo/date sort order 138 | sub myorder 139 | { 140 | my $ret; 141 | if ($OPTIONS{'topo-order'}) 142 | { 143 | $ret = $::brt{$a}->{'cnt'} <=> $::brt{$b}->{'cnt'}; 144 | return($ret) if ($ret); 145 | } 146 | $ret = $::brt{$a}->{'tstamp'} <=> $::brt{$b}->{'tstamp'}; 147 | $ret = (describep($a) cmp describep($b)) if (!$ret); 148 | $ret; 149 | }; 150 | 151 | 152 | my(@references,@REFERENCES); 153 | if ($OPTIONS{'reference'}) 154 | { 155 | foreach my $ref (split(',',$OPTIONS{'reference'})) 156 | { 157 | my $tmp = `git rev-list -n 1 $ref 2>/dev/null`; 158 | die "Unknown --reference $ref\n" if ($?); 159 | chomp($tmp); 160 | push(@{$TRANSLATION{$tmp}}, $ref); 161 | push(@REFERENCES,$tmp); 162 | } 163 | } 164 | 165 | 166 | foreach my $f (@ARGV) 167 | { 168 | print "Looking for $f\n++++++++++++++++++++++++++++++++++++++++\n" if ($MULTI); 169 | 170 | %translation = %TRANSLATION; 171 | @references = @REFERENCES; 172 | 173 | # Translate into a commit hash 174 | my ($TARGET)=`git rev-list -n 1 $f 2>/dev/null`; 175 | die "Unknown reference $f\n" if ($?); 176 | chomp($TARGET); 177 | 178 | my (%first,@second); 179 | 180 | if ($OPTIONS{'reference'}) 181 | { 182 | map($first{$_}=1,@references); 183 | } 184 | else 185 | { 186 | # Generate first pass list of candidate branches 187 | my $cmd; 188 | my $error; 189 | 190 | if ($OPTIONS{'allref'}) 191 | { 192 | $cmd = "git tag --contains $f; git branch --no-color -a --contains $f"; 193 | $error = "named ref"; 194 | } 195 | elsif ($OPTIONS{'tags'}) 196 | { 197 | $cmd = "git tag --contains $f"; 198 | $error = "tag"; 199 | } 200 | elsif ($OPTIONS{'allbranches'}) 201 | { 202 | $cmd = "git branch --no-color -a --contains $f"; 203 | $error = "branch"; 204 | } 205 | else 206 | { 207 | $cmd = "git branch --no-color --contains $f"; 208 | $error = "local branch"; 209 | } 210 | 211 | print STDERR "Running $cmd\n" if ($OPTIONS{'debug'} > 1); 212 | 213 | foreach my $ref (grep(s/^\*?\s*// && s/\n// && !/\(no branch\)/ && !/ -\> /,`$cmd`)) 214 | { 215 | my $tmp = `git rev-list -n 1 $ref 2>/dev/null`; 216 | die "Unknown --reference $ref\n" if ($?); 217 | chomp($tmp); 218 | push(@{$translation{$tmp}}, $ref); 219 | $first{$tmp} = 1; 220 | } 221 | 222 | if (!%first) 223 | { 224 | warn "Commit $f has not merged with any $error yet\n"; 225 | $exitcode = 2; 226 | next; 227 | } 228 | } 229 | 230 | print STDERR "Considering @{[join(',',map(describep($_),keys %first))]}\n" if ($OPTIONS{'debug'} > 1); 231 | 232 | # Look for merge intos to exclude 233 | foreach my $br (keys %first) 234 | { 235 | # Exclude branches that this commit was merged into 236 | if (grep(/$TARGET/,`git rev-list --first-parent $br`)) 237 | { 238 | delete($first{$br}); 239 | push(@second,$br); 240 | } 241 | } 242 | 243 | if ($#second >= 0) 244 | { 245 | # If branch was subsequently forked via `git branch ` 246 | # we might have multiple answers. Only one is right, but we 247 | # cannot figure out which is the privledged branch because the 248 | # branch creation information is not preserved. 249 | 250 | print join("\n",map(describep($_),@second))."\n"; 251 | } 252 | 253 | if (($#second < 0 || $OPTIONS{'all'}) && %first) 254 | { 255 | # Commit is on an anonymous branch, find out where it merged 256 | if ($OPTIONS{'first-parent'}) 257 | { 258 | warn "Commit $f was not directly on any candidates\n"; 259 | $exitcode = 1; 260 | next; 261 | } 262 | 263 | my (%brtree); 264 | my (%commits,@commits); 265 | 266 | # Discover all "ancestry-path" commits between target and sources/branches 267 | my $cmd = qq(git rev-list --ancestry-path --date-order --format=raw ^"$TARGET" "@{[join('" "',keys %first)]}"); 268 | my ($commit); 269 | foreach my $line (`$cmd`) 270 | { 271 | my (@f) = split(/\s+/,$line); 272 | if ($f[0] eq "commit") 273 | { 274 | $commit = $f[1]; 275 | $commit =~ s/^-//; # I have never seen this myself, but Artur Skawina wrote code to defend against it 276 | unshift(@commits,$commit); 277 | } 278 | if ($f[0] eq "parent") 279 | { 280 | push(@{$commits{$commit}->{'parent'}},$f[1]); 281 | } 282 | if ($f[0] eq "committer") 283 | { 284 | $commits{$commit}->{'committime'} = $f[$#f-1]; 285 | } 286 | } 287 | 288 | if ($#commits < 0) 289 | { 290 | warn qq^Cannot get from @{[describep($TARGET)]} to @{[join(", ",map(describep($_),keys %first))]}\n^; 291 | next; 292 | } 293 | 294 | print STDERR qq^Found $#commits+1, going from @{[describep($TARGET)]} to @{[join(", ",map(describep($_),keys %first))]}\n^ if ($OPTIONS{'debug'}); 295 | 296 | my (@path); 297 | 298 | # Go through commit list (in forward chonological order) 299 | my (%mark,$cnt); 300 | $mark{$TARGET} = ++$cnt; 301 | foreach my $id (@commits) 302 | { 303 | next unless $commits{$id}->{'parent'}; 304 | 305 | print STDERR "Forward $id with ".(join(",",@{$commits{$id}->{'parent'}})) if ($OPTIONS{'debug'}); 306 | 307 | # Check to see if this commit is actually a descent of $TARGET 308 | if (my @old = grep($mark{$_},@{$commits{$id}->{'parent'}})) 309 | { 310 | $mark{$id} = $mark{$old[0]}; 311 | print STDERR " mark $mark{$id}" if ($OPTIONS{'debug'}); 312 | } 313 | 314 | # Is this a merge commit? 315 | if ($#{$commits{$id}->{'parent'}} > 0) 316 | { 317 | # Is the first parent not a descendant? (earliest merge) 318 | if (!$mark{$commits{$id}->{'parent'}->[0]}) 319 | { 320 | $mark{$id} = ++$cnt; 321 | } 322 | } 323 | print STDERR "\n" if ($OPTIONS{'debug'}); 324 | } 325 | 326 | my ($direct); 327 | foreach my $br (keys %first) 328 | { 329 | # Check to make sure we have gone from TARGET or SOURCE via parents 330 | if (!$mark{$br}) 331 | { 332 | print STDERR "Did not reach @{[describep($br)]} from @{[describep($TARGET)]}\n" if ($OPTIONS{'debug'}); 333 | # Not connected 334 | next; 335 | } 336 | 337 | 338 | if ($mark{$br} > 1) 339 | { 340 | @path = find_shortest($br,$TARGET,\%commits,\%mark); 341 | $brtree{$br}->{'path'} = \@path; 342 | $brtree{$br}->{'cnt'} = $#path; 343 | $brtree{$br}->{'tstamp'} = $commits{$path[$#path]}->{'committime'}; 344 | 345 | foreach my $mp (@{$brtree{$br}->{'path'}}) 346 | { 347 | push(@{$brtree{$br}->{'committimes'}},$commits{$mp}->{'committime'}); 348 | } 349 | } 350 | else 351 | { 352 | if ($OPTIONS{'all'}) 353 | { 354 | print "$f is on @{[describep($br)]}\n"; 355 | } 356 | else 357 | { 358 | print describep($br)."\n"; 359 | } 360 | $direct = 1; 361 | } 362 | } 363 | 364 | if (!$direct || $OPTIONS{'all'}) 365 | { 366 | 367 | local %::brt = %brtree; 368 | my (@brlist) = sort myorder (keys %brtree); 369 | 370 | my ($lastts,$last); 371 | foreach my $br (@brlist) 372 | { 373 | next unless (exists($brtree{$br}->{'cnt'})); 374 | 375 | if ($lastts != $brtree{$br}->{'tstamp'}) 376 | { 377 | last if (!$OPTIONS{'all'} && $lastts); 378 | 379 | print "\n" if ($lastts || $direct); 380 | 381 | $lastts = $brtree{$br}->{'tstamp'}; 382 | 383 | if (!$OPTIONS{'quiet'}) 384 | { 385 | print "@{[describep($f)]} used the following minimal".($OPTIONS{'topo-order'}?"":" temporal")." path:\n"; 386 | my ($maxlen); 387 | foreach my $mp (@{$brtree{$br}->{'path'}}) 388 | { 389 | my $newm = describep($mp); 390 | $maxlen = length($newm) if (length($newm) > $maxlen); 391 | } 392 | $last = describep($TARGET); 393 | foreach my $mp (@{$brtree{$br}->{'path'}}) 394 | { 395 | my $newm = describep($mp); 396 | my $ctime = shift(@{$brtree{$br}->{'committimes'}}); 397 | printf(" merged to %-${maxlen}s \@@{[scalar(localtime($ctime))]}\n",$newm); 398 | $last = $newm; 399 | } 400 | print " $last is on @{[describep($br)]}\n"; 401 | next; 402 | } 403 | } 404 | 405 | if ($OPTIONS{'quiet'}) 406 | { 407 | print describep($br)."\n"; 408 | } 409 | else 410 | { 411 | print " $last is on @{[describep($br)]}\n"; 412 | } 413 | } 414 | } 415 | } 416 | print "----------------------------------------\n" if ($MULTI); 417 | } 418 | exit($exitcode); 419 | 420 | =pod 421 | 422 | 423 | =head1 NAME 424 | 425 | git-what-branch - Discover what branch a particular commit was made on or near 426 | 427 | 428 | =head1 SYNOPSIS 429 | 430 | git-what-branch [[--branches] | [--allbranches] | [--tags] | [--allref]] [--first-parent] [--first-parent-simple] [--all] [--topo-order | --date-order ] [--quiet] [--reference=reference,reference,reference] ... 431 | 432 | 433 | =head1 OVERVIEW 434 | 435 | Tell us (by default) the earliest causal path of commits and merges to 436 | cause the requested commit got onto a named branch. If a commit was 437 | made directly on a named branch, that obviously is the earliest path. 438 | 439 | By earliest causal path, we mean the path which merged into a named 440 | branch the earliest, by commit time (unless --topo-order is 441 | specified). 442 | 443 | You may specify a particular reference branch or tag or revision to 444 | look at instead of searching (by default) the path for all named 445 | branches. Searching the path for all named branches can take a long 446 | time for an early commit occurring on many branches. If you 447 | specifically name a reference branch or commit, it should normally 448 | take seconds. 449 | 450 | 451 | =head1 DESCRIPTION 452 | 453 | =head2 --branches 454 | 455 | The default mode of check the path to any local branch. 456 | 457 | =head2 --allbranches 458 | 459 | Check the path to any local or remote branch. 460 | 461 | =head2 --tags 462 | 463 | Check the path to any known tags 464 | 465 | =head2 --allref 466 | 467 | Check the path to any local or remote branch and to any tag. 468 | 469 | =head2 --all 470 | 471 | If the commit in question was not made directly on a named branch (in 472 | which case all branch names would be printed), the system picks the 473 | named branch which the commit was merged to first and prints only that 474 | path. With this argument all paths from the commit in question to all 475 | named branches that it was committed onto are printed. 476 | 477 | =head2 --first-parent 478 | 479 | If the commit in question was not made directly on a named branch, fail. 480 | If the commit was made directly on a named branch, print the branch name or names. 481 | 482 | =head2 --first-parent-simple 483 | 484 | Same as --first-parent except instead of outputting in the "(aka)" 485 | format if there are multiple differently spelled but otherwise 486 | identical branches, print all directly reachable branches/references 487 | out with one line per branch/reference. 488 | 489 | =head2 --topo-order 490 | 491 | Instead of selecting the merge path which resulted in the earliest 492 | commit to a named branch, select the merge path which resulted in the 493 | fewest merges. If multiple merge paths have the same distance, use 494 | earliest merge to break ties. 495 | 496 | =head2 --date-order 497 | 498 | The default ordering where the merge path which resulted in the 499 | earliest commit to a named branch is displayed. 500 | 501 | =head2 --quiet 502 | 503 | If the commit was not made on a branch, do not print the path from the 504 | commit to the named branch, just print the branch name. 505 | 506 | =head2 --reference 507 | 508 | Instead of auto-generating the list of branches/tags to check to see 509 | how the commit in question got there, specify the (comma seperated) 510 | list of tags, branch names, commits, or other references that this 511 | program should use to try and find minimal and early paths to from the 512 | command line references. 513 | 514 | 515 | =head1 PERFORMANCE 516 | 517 | If many branches (e.g. hundreds) contain the commit, the system may 518 | take a long time (for a particular commit in the linux tree, it took 8 519 | second to explore a branch, but there were over 200 candidate 520 | branches) to track down the path to each commit. Selection of a 521 | particular --reference-branch --reference tag to examine will be 522 | hundreds of times faster (if you have hundreds of candidate branches). 523 | 524 | 525 | =head1 EXAMPLES 526 | 527 | # git-what-branch --all 1f9c381fa3e0b9b9042e310c69df87eaf9b46ea4 528 | v2.6.12-rc3-450-g1f9c381 used the following minimal temporal path: 529 | merged to v2.6.12-rc3-461-g84e48b6 @Tue May 3 18:27:24 2005 530 | merged to v2.6.12-rc3-590-gbfd4bda @Thu May 5 08:59:37 2005 531 | v2.6.12-rc3-590-gbfd4bda is on v2.6.12-n 532 | v2.6.12-rc3-590-gbfd4bda is on v2.6.12-rc4-n 533 | [...] 534 | v2.6.12-rc3-590-gbfd4bda is on v2.6.36-rc4-n 535 | v2.6.12-rc3-590-gbfd4bda is on v2.6.36-rc5-n(aka master) 536 | 537 | 538 | =head1 BUGS 539 | 540 | git fast-forward merges make changes to branches without reflecting 541 | that history in a merge commit. This means that when later reviewing 542 | that history, git may label (via --first-parent) the wrong branch as 543 | being named a specific name. Any lies which git makes are reflected 544 | in the output of this program. 545 | 546 | Branches which are created after the commit you are interested in has 547 | been merged into another named branch you are interested in cannot be 548 | distinguished from the original branch. Example if you have master 549 | branch, you make commit A, then make a release branch named v1.0, 550 | after branch v1.0 has been created there is no way to know that v1.0 551 | was created later and so both branches will be listed as the branches 552 | that commit A was made on. If git recorded when a branch was created, 553 | we could avoid this problem. 554 | 555 | If multiple branches (say due to the previous bug) are candidates and 556 | the commit was NOT made directly on a named branch but rather on an 557 | anonymous branch that was merged, unless you request --all, a 558 | pseudo-random branch will be chosen as the branch advertised via the 559 | merge path. 560 | 561 | This program does not take into account the effects of cherry-picking 562 | the commit of interest, only merge operations. 563 | 564 | 565 | =head1 ACKNOWLEDGMENTS 566 | 567 | Thanks to Artur Skawina for his assistance in developing some 568 | of the algorithms used by this script. 569 | 570 | 571 | =head1 COPYRIGHT/LICENSE 572 | 573 | License: GPL v2 574 | Copyright (c) 2010 Seth Robertson 575 | --------------------------------------------------------------------------------