tags:

views:

40

answers:

2

I'd like to run git filter-branch on all children of a given commit. This doesn't seem to be an easy task, since there doesn't appear to be a way to tell git rev-list to only return children of a particular commit. Using the .. syntax won't work because it will also include the parent commits of any merge within that range. Am I missing something here?

edit

To clarify: I've got a branch called base which has been merged into derivative multiple times. I'd like to list, and eventually run filter-branch on, just the commits that descend from its most recent commit. I'd also like to list similar commits on other branches.

The (simplified) situation (derivative is the branch on the left):

$ git rev-list --graph --oneline base derivative
*   f16fd151b374b2aeec8b9247489c518a6347d001 merged in the latest from the base branch
|\
| * 564d795afb63eab4ffe758dbcd726ca8b790f9f6 spelling correction
* |   2f2ed5f1d429492e1491740e30be5a58ec20af21 snogg before flarg
|\ \
| |/
| * 0c520ea33ef57b30846b122781d41fcef5e62f9b now with added flarg
* | 5068c34c3862856f511b6b4d48f2adb766e1bde4 now with added snogg
|/
* b51f227e931f830cbda042bc372087398ae7e50d initial commit

@araqnid's suggestion doesn't seem to work, unless I've botched reading it:

$ git rev-list --oneline derivative --not base
f16fd151b374b2aeec8b9247489c518a6347d001 merged in the latest from the base branch
2f2ed5f1d429492e1491740e30be5a58ec20af21 snogg before flarg
5068c34c3862856f511b6b4d48f2adb766e1bde4 now with added snogg

as this gives all commits in derivative except for the commits in base. This kind of makes sense, since all it's doing is removing the ancestors of the negated commit. What I want it to show is just the first line.

+1  A: 

well, "--branches" or "--all" will include all branch tips, and then "--not A" will exclude A and all its ancestry. However, "--branches --not A" will include branches that don't include A, which I think is not what you need.

There's no easy way to get everything you want, although you can work it out. This will list all the branches containing commit A:

git for-each-ref refs/heads/* | while read rev type name; do
  git rev-list $rev | grep $(git rev-parse A) >/dev/null && echo $rev
done

then you need to list all the revisions reachable from any of those, but not A itself and any revisions back

git rev-list $(git for-each-ref refs/heads/* | while read rev type name; do
      git rev-list $rev | grep $(git rev-parse A) >/dev/null && echo $rev
    done) --not A

Note that I've only given this a very brief test :p But I think the fundamentals are sound.

araqnid
Impressive... +1
VonC
I figured it would be something like that. It seems strange that there isn't a more straightforward way to do this. I guess this is because git commits are always linked to their parents and not their children. I'll give it a spin and let you know how it goes.
intuited
hmm.. doesn't seem to work. I've got a `base` (eg development) branch that's been merged multiple times into the `derivative` (eg testing) branch(es). I want to list just the commits that are descendants of the most recent `base` commit. However `git rev-list derivative --not base --oneline` shows me that this list includes `derivative` commits that have common ancestry with the `base` HEAD. IE anything that has a merge from base as an ancestor. I'll update my question to clarify this.
intuited
A: 

This seems to do it:

$ for rev in $(git rev-list --branches); do
    git rev-list $rev \
      | grep $(git rev-parse base) &>/dev/null \
    && echo $rev;
  done \
  | xargs -L 1 git rev-list -n 1 --oneline
f16fd151b374b2aeec8b9247489c518a6347d001 merged in the latest from the base branch
564d795afb63eab4ffe758dbcd726ca8b790f9f6 spelling correction

The last pipe section with xargs is just to show the commit messages for each of those commits.

This lists the target commit in addition to its children, which is actually what I wanted. It's pretty durn slow though: it took over 3 seconds to run on a 40-commit repo. This is unsurprising since it's about as brute force as André the Giant. I guess a more elegant method would go through the rev-list for each ref and build a tree of the hashes of each commit linked to its children, then walk it starting at the target commit. But that's kind of complicated, so I was hoping that there was a git command for that :)

I still need to figure out how to get this set of commits into filter-branch. I guess I can just grep the rev-list of $GIT_COMMIT from within my --tree-filter command.

intuited