Raman Raman - 4 months ago 14
Git Question

Use git rev-list to exclude a branch, but keep common ancestors with the included branches

Say I have the following git repo:

--all

I want to specify a git rev-list of all branches (

--all
) but excluding a given branch (say,
feature-D
), but I want to show common ancestors of
feature-D
and the other branches i.e. commit
Initial
and
1
.

If I try
git rev-list --all ^feature-D
commits
Initial
and
1
are excluded:

--all ^feature-D

Of course I can list all branches except for
feature-D
explicitly (
git rev-list feature-A feature-B feature-C
) to get what I want:

feature-A feature-B feature-C

But is there a way to specify a rev-list that obtains the prior result, only by reference to
feature-D
?

(Note that this question was inspired by this answer: http://stackoverflow.com/a/20978568/430128)

Answer

This is a bit indirect but seems to work. We start with:

git rev-list --no-walk --all ^feature-D

which produces a list of all SHA-1s directly pointed to by any ref (including tags, branches, stashes, etc; use --branches rather than --all if you want only branches) except those under feature-D:

$ git log --oneline --decorate --graph --all
* 74e0d3e (feature-D) 7
* 0448a13 6
| * ab3a532 (feature-C) 5
|/  
| * 50477c7 (feature-B) 4
| * 28717e5 3
|/  
* 2ac5cef (HEAD, master, feature-A) 2
* c6a10b4 1
* 76c511f Initial
$ git rev-list --no-walk --all ^feature-D
ab3a5320e792e945b896634d667df5ace4a8b871
50477c7b28b5479587e45fe97292e71a3c0851c5
28717e5628ad111e8b68323dc485fd190a780446

Now we feed this to git rev-list again to get the history-walking:

$ git rev-list --no-walk --all ^feature-D | git rev-list --stdin
ab3a5320e792e945b896634d667df5ace4a8b871
50477c7b28b5479587e45fe97292e71a3c0851c5
28717e5628ad111e8b68323dc485fd190a780446
2ac5cefb971c7f5c5be33a77a6db71127982eaf5
c6a10b4568136d65b31b7e262ef7745db4962460
76c511fcf38076e0ea98db73a4923de6fc806b4a

Pipe that to git log --oneline --decorate --graph --stdin to verify that those are the right commits, if eyeballing it is not sufficiently clear:

* ab3a532 (feature-C) 5
| * 50477c7 (feature-B) 4
| * 28717e5 3
|/  
* 2ac5cef (HEAD, master, feature-A) 2
* c6a10b4 1
* 76c511f Initial

(and one side note: if using git log --oneline --decorate --graph to view your middle version, i.e., git rev-list --all ^feature-D, you need to add --boundary to see commit 2; gitk probably adds --boundary to make it show up, not that I tested this).

There's a defect here: if feature-C and feature-B branches did not exist you would not see commits 2 through Initial. I'm not sure, but I think the only real fix for this is to use something much more complex.


Edit: ok, not that much more complex. Start with git for-each-ref to print branches. Pipe through grep -v ^feature-D$ to eliminate feature-D. Now you have all the branches you want as arguments:

$ git log --oneline --graph --decorate $(git for-each-ref \
>  --format '%(refname:short)' refs/heads/ | grep -v '^feature-D$')
* ab3a532 (feature-C) 5
| * 50477c7 (feature-B) 4
| * 28717e5 3
|/  
* 2ac5cef (master, feature-A) 2
* c6a10b4 1
* 76c511f Initial