tags:

views:

54

answers:

3

In Git, given (1) a branch A and (2) a branch B derived from A at some point in the past and then merged back into A, how can I find all the commits now in A that originated in B?

The intent is to identify the changeset of work performed in B now in A to more quickly track down issues.

A squash commit would obviously and conveniently pack the entire changeset in one commit for easy reference, but the drawbacks (such as loss of information and individual attributability) make this option undesirable for us. Hence my question.

+1  A: 

Once a branch has been merged back in, the merge commit is the marker of its existence. Assuming you don't monkey around too much with your merge commit messages, you could do something like this:

#!/bin/bash

die_with_usage() {
    # add a git alias for merged-commits to be able to call it like this
    echo "usage: git merged-commits <branch-merged> <branch-merged-into>" 1>&2
    exit 1
}

if [ $# -ne 2 ]; then
    die_with_usage
fi

# Find the merge commits
merges=($(git log --pretty=%H --grep="Merge branch '$1' into $2"))

if [ ${#merges[@]} -eq 0 ]; then
    echo "error: no such merges found!" 1>&2
    die_with_usage
fi

for merge in ${merges[@]}; do
    # The first parent is the merged-into branch
    bar=$merge^1
    # The second is the merged branch
    foo=$merge^2

    # Find the merge base
    base=$(git merge-base $bar $foo)

    # Show the commits
    git log --pretty=%H $base..$foo
done

I figured it'd be helpful to just print the SHA1s and let you go off and do what you like with them afterward, but of course you can fiddle with the output format of that last git log.

(And I even tested this! Might keep it around; it's a cool little few-liner.)

Another thing you can do (in the future) is adopt a merge commit message along the lines of git.git, which essentially embeds the shortlog of the merged commits in the merged commit message (here's an example). There's a built-in way to do this coming someday (it's been merged to next but not master) but for now you'd have to roll your own, or make a gutsy move and build from next.

Jefromi
Thanks Jefromi, that's also very useful. It looks like the merge commit message cannot always be relied upon to include both branch names. Sometimes it's just "Merge branch 'gurke'", maybe because the merge is done into the master branch. Anyhow, adapting the pattern, your script works fine.
Michael Ludwig
+1  A: 

Assuming that B was fully merged into A, you can use:

git cherry -v <merge-commit>^ <topic-branch>

...where:

  • <merge-commit>^ is the parent of the commit where you merged the topic branch.
  • <topic-branch> is the branch that you want to check
Tim Henigan
Thanks, Tim. (And sorry for taking a while to get back to this.) This works very well for me. It requires an actual merge commit ("Merge made by recursive"), which is what you get unless a fast-forward has been sufficient because all of the master branch's commits had already been merged into the topic branch before merging the topic branch into the master branch.
Michael Ludwig
+1  A: 

Take a look at git-resurrect.sh script in contrib/ area of git sources.

usage: git resurrect [-a] [-r] [-m] [-t] [-n] [-b <newname>] <name>

    -b, --branch ...      save branch as  instead of <name>
    -a, --all             same as -l -r -m -t
    -k, --keep-going      full rev-list scan (instead of first match)
    -l, --reflog          scan reflog for checkouts (enabled by default)
    -r, --reflog-merges   scan for merges recorded in reflog
    -m, --merges          scan for merges into other branches (slow)
    -t, --merge-targets   scan for merges of other branches into 
    -n, --dry-run         don't recreate the branch

git-resurrect attempts to find traces of a branch tip called <name>,
and tries to resurrect it.  Currently, the reflog is searched for checkout
messages, and with `-r' also merge messages.  With `-m' and `-t', the
history of all refs is scanned for "Merge <name> into other" /
"Merge <other> into <name>" (respectively) in commit subjects,
which is rather slow but allows you to resurrect other people's
topic branches.
Jakub Narębski
Thanks, Jakub. Looks like this does a bit more than what I want to do, but maybe that's just my lack of understanding. I ran it with --dry-run and it came up with a list of commits that had originated in the branch I specified on the command-line; not all of them, however. To me, it looks like this is intended for some more advanced usage. Thanks anyway.
Michael Ludwig
@Michael: It probably specifies only topmost commits on branch that would be resurrected. You can try `git log $(git-resurrect.sh ... --dry-run) --not <topic-branch>` to get all of them.
Jakub Narębski
It outputs more than just the topmost commit, and then flags the most recent one. (I'm on Cygwin and using git-1.7.0.4.) It's okay for now, I might have more time to delve into this later. For the moment I feel I have yet to understand some of the basics.
Michael Ludwig