Making pathspec limited log play nicer with --first-parent

In a topic branch workflow, you often want to find the latest commit that
merged a side branch that touched a particular area of the system, so that
a new topic branch to work on that area can be forked from that commit.
For example, I wanted to find an appropriate fork-point to queue Luke's
changes related to git-p4 in contrib/fast-import/.

"git log --first-parent" traverses the first-parent chain, and "-m --stat"
shows the list of paths touched by commits including merge commits.  We
could ask the question this way:

    # What is the latest commit that touched that path?
    $ git log --first-parent --oneline -m --stat master |
      sed -e '/^ contrib\/fast-import\/git-p4 /q' | tail

The above finds that 8cbfc11 (Merge branch 'pw/p4-view-updates',
2012-01-06) was such a commit.

But a more natural way to spell this question is this:

    $ git log --first-parent --oneline -m --stat -1 master -- \
      contrib/fast-import/git-p4

Unfortunately, this does not work. It finds ecb7cf9 (git-p4: rewrite view
handling, 2012-01-02). This commit is a part of the merged topic branch
and is _not_ on the first-parent path from the 'master':

    $ git show-branch 8cbfc11 ecb7cf9
    ! [8cbfc11] Merge branch 'pw/p4-view-updates'
     ! [ecb7cf9] git-p4: rewrite view handling
    --
    -  [8cbfc11] Merge branch 'pw/p4-view-updates'
    +  [8cbfc11^2] git-p4: view spec documentation
    ++ [ecb7cf9] git-p4: rewrite view handling

The problem is caused by the merge simplification logic when it inspects
the merge commit 8cbfc11. In this case, the history leading to the tip of
'master' did not touch git-p4 since 'pw/p4-view-updates' topic forked, and
the result of the merge is simply a copy from the tip of the topic branch
in the view limited by the given pathspec.  The merge simplification logic
discards the history on the mainline side of the merge, and pretends as if
the sole parent of the merge is its second parent, i.e. the tip of the
topic. While this simplification is correct in the general case, it is at
least surprising if not outright wrong when the user explicitly asked to
show the first-parent history.

Here is an attempt to fix this issue, by not allowing us to compare the
merge result with anything but the first parent when --first-parent is in
effect, to avoid the history traversal veering off to the side branch.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
Junio C Hamano 2012-01-19 11:58:45 -08:00
parent 04f6785a08
commit 36ed1913e1
2 changed files with 10 additions and 1 deletions

View File

@ -368,7 +368,7 @@ static int rev_same_tree_as_empty(struct rev_info *revs, struct commit *commit)
static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit) static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)
{ {
struct commit_list **pp, *parent; struct commit_list **pp, *parent;
int tree_changed = 0, tree_same = 0; int tree_changed = 0, tree_same = 0, nth_parent = 0;
/* /*
* If we don't do pruning, everything is interesting * If we don't do pruning, everything is interesting
@ -396,6 +396,14 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)
while ((parent = *pp) != NULL) { while ((parent = *pp) != NULL) {
struct commit *p = parent->item; struct commit *p = parent->item;
/*
* Do not compare with later parents when we care only about
* the first parent chain, in order to avoid derailing the
* traversal to follow a side branch that brought everything
* in the path we are limited to by the pathspec.
*/
if (revs->first_parent_only && nth_parent++)
break;
if (parse_commit(p) < 0) if (parse_commit(p) < 0)
die("cannot simplify commit %s (because of %s)", die("cannot simplify commit %s (because of %s)",
sha1_to_hex(commit->object.sha1), sha1_to_hex(commit->object.sha1),

View File

@ -86,5 +86,6 @@ check_result 'I H E C B A' --full-history --date-order -- file
check_result 'I E C B A' --simplify-merges -- file check_result 'I E C B A' --simplify-merges -- file
check_result 'I B A' -- file check_result 'I B A' -- file
check_result 'I B A' --topo-order -- file check_result 'I B A' --topo-order -- file
check_result 'H' --first-parent -- another-file
test_done test_done