git/list[1] front-page[2] threads[3] people[4] search[5] about
 

Re: breakage in revision traversal with pathspec

From
Junio C Hamano <gitster@pobox.com>
Date
Sep 19, 2013, 21:35 UTC
Message-ID
<xmqq38p0sdeb.fsf@gitster.dls.corp.google.com>
In-Reply-To
<xmqqa9jjrrfb.fsf@gitster.dls.corp.google.com>
Junio C Hamano <gitster@pobox.com> writes:
Show 12 quoted lines
> Kevin Bracey <kevin@bracey.fi> writes:
>
>> To see the effect at the command line: "git log v1.8.3..v.1.8.4" hides
>> the merge, but "git log ^v1.8.3 v1.8.4" shows it. Whoops. A new
>> example of a dotty shorthand not being exactly equivalent.
>>
>> In the ".." case the v1.8.3 tag gets peeled before being sent to
>> add_rev_cmdline , and the "mark bottom commits" logic works. But in
>> the "^" case, the v1.8.3 doesn't get peeled.
>
> That sounds like a bug.  ^v1.8.3 should mark v1.8.3^0 as
> uninteresting.

OK, so "git rev-list ^v1.8.3 v1.8.4" throws two objects into revs->pending.objects[] array. Two tags, v1.8.3 marked as UNINTERESTING and v1.8.4. The revision walking machinery will peel the tag by calling handle_commit() (which by the way arguably is misnamed because it has to be called for any type of object) when it starts to walk in prepare_revision_walk().

But "git rev-list v1.8.3..v1.8.4" throws two commits (v1.8.3^0 with UNINTERESTING bit and v1.8.4^0) to revs->pending.objects[] after peeling. I _think_ it is wrong. Because the range is only defined over commit DAG, and because the same codepath handles the symmetric difference v1.8.3...v1.8.4 as well, both ends of dots operator do need to be peeled to commits, but I think it is wrong to throw these peeled results into revs->pending.objects[].

Where it makes a difference is when rev-list is used with --objects.
    $ git rev-list --objects v1.8.4^1..v1.8.4 | grep $(git rev-parse v1.8.4)
    $ git rev-list --objects v1.8.4 ^v1.8.4^1 | grep $(git rev-parse v1.8.4)
    04f013dc38d7512eadb915eba22efc414f18b869 v1.8.4
-- >8 --
Subject: revision: do not peel tags used in range notation

A range notation "A..B" means exactly the same thing as what "^A B" means, i.e. the set of commits that are reachable from B but not from A. But the internal representation after the revision parser parsed these two notations are subtly different.

 - "rev-list ^A B" leaves A and B in the revs->pending.objects[]
   array, with the former marked as UNINTERESTING and the revision
   traversal machinery propagates the mark to underlying commit
   objects A^0 and B^0.
 - "rev-list A..B" peels tags and leaves A^0 (marked as
   UNINTERESTING) and B^0 in revs->pending.objects[] array before
   the traversal machinery kicks in.

This difference usually does not matter, but starts to matter when the --objects option is used. For example, we see this:

    $ git rev-list --objects v1.8.4^1..v1.8.4 | grep $(git rev-parse v1.8.4)
    $ git rev-list --objects v1.8.4 ^v1.8.4^1 | grep $(git rev-parse v1.8.4)
    04f013dc38d7512eadb915eba22efc414f18b869 v1.8.4

With the former invocation, the revision traversal machinery never hears about the tag v1.8.4 (it only sees the result of peeling it, i.e. the commit v1.8.4^0), and the tag itself does not appear in the output. The latter does send the tag object itself to the output.

Make the range notation keep the unpeeled objects and feed them to the traversal machinery to fix this inconsistency.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 * Made against maint-1.8.0 just in case we may want to fix older
   maintenance series.
 revision.c               | 19 +++++++++++++------
 t/t6000-rev-list-misc.sh |  8 ++++++++
 2 files changed, 21 insertions(+), 6 deletions(-)
diff --git a/revision.c b/revision.c
index 68545c8..a6d2150 100644
--- a/revision.c
+++ b/revision.c
@@ -1159,6 +1159,7 @@ int handle_revision_arg(const char *arg_, struct rev_info *revs, int flags, unsi
 		    !get_sha1_committish(next, sha1)) {
 			struct commit *a, *b;
 			struct commit_list *exclude;
+			struct object *a_obj, *b_obj;
 
 			a = lookup_commit_reference(from_sha1);
 			b = lookup_commit_reference(sha1);
@@ -1184,14 +1185,20 @@ int handle_revision_arg(const char *arg_, struct rev_info *revs, int flags, unsi
 				a_flags = flags | SYMMETRIC_LEFT;
 			} else
 				a_flags = flags_exclude;
-			a->object.flags |= a_flags;
-			b->object.flags |= flags;
-			add_rev_cmdline(revs, &a->object, this,
+			a_obj = (!hashcmp(a->object.sha1, from_sha1)
+				 ? &a->object
+				 : lookup_object(from_sha1));
+			b_obj = (!hashcmp(b->object.sha1, sha1)
+				 ? &b->object
+				 : lookup_object(sha1));
+			a_obj->flags |= a_flags;
+			b_obj->flags |= flags;
+			add_rev_cmdline(revs, a_obj, this,
 					REV_CMD_LEFT, a_flags);
-			add_rev_cmdline(revs, &b->object, next,
+			add_rev_cmdline(revs, b_obj, next,
 					REV_CMD_RIGHT, flags);
-			add_pending_object(revs, &a->object, this);
-			add_pending_object(revs, &b->object, next);
+			add_pending_object(revs, a_obj, this);
+			add_pending_object(revs, b_obj, next);
 			return 0;
 		}
 		*dotdot = '.';
diff --git a/t/t6000-rev-list-misc.sh b/t/t6000-rev-list-misc.sh
index b10685a..15e3d64 100755
--- a/t/t6000-rev-list-misc.sh
+++ b/t/t6000-rev-list-misc.sh
@@ -48,4 +48,12 @@ test_expect_success 'rev-list --objects with pathspecs and copied files' '
 	! grep one output
 '
 
+test_expect_success 'rev-list A..B and rev-list ^A B are the same' '
+	git commit --allow-empty -m another &&
+	git tag -a -m "annotated" v1.0 &&
+	git rev-list --objects ^v1.0^ v1.0 >expect &&
+	git rev-list --objects v1.0^..v1.0 >actual &&
+	test_cmp expect actual
+'
+
 test_done
Previous: Junio C HamanoNext: Jeff King
Message 9 of 14 in “breakage in revision traversal with pathspec”
  1. Junio C HamanoSep 10, 2013
  2. Kevin BraceySep 10, 2013
  3. Junio C HamanoSep 10, 2013
  4. Kevin BraceySep 11, 2013
  5. Jonathan NiederSep 11, 2013
  6. Junio C HamanoSep 11, 2013
  7. Kevin BraceySep 11, 2013
  8. Junio C HamanoSep 11, 2013
  9. Junio C HamanoSep 19, 2013
  10. Jeff KingSep 20, 2013
  11. Junio C HamanoSep 20, 2013
  12. Jeff KingSep 20, 2013
  13. Junio C HamanoSep 20, 2013
  14. Jeff KingSep 25, 2013

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.