diff mbox series

[v2] object-name: fix reversed ordering with ":/<PATTERN>" revisions

Message ID 20241206-pks-rev-parse-fix-reversed-list-v2-1-190514278ead@pks.im (mailing list archive)
State Superseded
Headers show
Series [v2] object-name: fix reversed ordering with ":/<PATTERN>" revisions | expand

Commit Message

Patrick Steinhardt Dec. 6, 2024, 12:28 p.m. UTC
Recently it was reported [1] that "look for the youngest reachable
commit with log message that match the given pattern" syntax (e.g.
':/<PATTERN>' or 'HEAD^{/<PATTERN>}') started to return results in
reverse recency order. This regression was introduced in Git v2.47.0 and
is caused by a memory leak fix done in 57fb139b5e (object-name: fix
leaking commit list items, 2024-08-01).

The intent of the identified commit is to stop modifying the commit list
provided by the caller such that the caller can properly free all commit
list items, including those that the called function might potentially
remove from the list. This was done by creating a copy of the passed-in
commit list and modifying this copy instead of the caller-provided list.

We already knew to create such a copy beforehand with the `backup` list,
which was used to clear the `ONELINE_SEEN` commit mark after we were
done. So the refactoring simply renamed that list to `copy` and started
to operate on that list instead. There is a gotcha though: the backup
list, and thus now also the copied list, is always being prepended to,
so the resulting list is in reverse order! The end result is that we
pop commits from the wrong end of the commit list, returning commits in
reverse recency order.

Fix the bug by appending to the list instead.

[1]: <CAKOEJdcPYn3O01p29rVa+xv=Qr504FQyKJeSB-Moze04ViCGGg@mail.gmail.com>

Reported-by: Aarni Koskela <aarni@valohai.com>
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
This patch applies on top of v2.47.0, which is the first version which
had this regression.

Changes in v2:

  - Include the message ID of the report in the commit message.

  - Fix terminology used by the commit message.

  - Move the test from t4208 to t1500.

  - Link to v1: https://lore.kernel.org/r/20241206-pks-rev-parse-fix-reversed-list-v1-1-95a96564a4d7@pks.im

Thanks!

Patrick
---
 object-name.c        |  4 ++--
 t/t1500-rev-parse.sh | 15 +++++++++++++++
 2 files changed, 17 insertions(+), 2 deletions(-)


---
base-commit: 777489f9e09c8d0dd6b12f9d90de6376330577a2
change-id: 20241206-pks-rev-parse-fix-reversed-list-0f94a20a6165

Comments

Kristoffer Haugsbakk Dec. 6, 2024, 2:33 p.m. UTC | #1
On Fri, Dec 6, 2024, at 13:28, Patrick Steinhardt wrote:
> Recently it was reported [1] that "look for the youngest reachable
> commit with log message that match the given pattern" syntax (e.g.
> ':/<PATTERN>' or 'HEAD^{/<PATTERN>}') started to return results in

But the regression is only for `:/`.  Not for `HEAD^{/}`.  I’m sorry
that I wasn’t clear in my previous message[1] since I didn’t establish
the context properly:

    I have indeed noticed that `HEAD^{/}` returns a sensible thing while
    `:/` does something strange like finding the root commit.

What I had noticed myself for a little while was that `HEAD^{/}` on
Git 2.47.0 did something that I wanted (and which is documented) while
`:/` behaved (behaves) weirdly.  I just shrugged that off since the
second syntax is more useful anyway (like Junio said).


Patrick Steinhardt Dec. 6, 2024, 3:45 p.m. UTC | #2
On Fri, Dec 06, 2024 at 03:33:52PM +0100, Kristoffer Haugsbakk wrote:
> On Fri, Dec 6, 2024, at 13:28, Patrick Steinhardt wrote:
> > Recently it was reported [1] that "look for the youngest reachable
> > commit with log message that match the given pattern" syntax (e.g.
> > ':/<PATTERN>' or 'HEAD^{/<PATTERN>}') started to return results in
> 
> But the regression is only for `:/`.  Not for `HEAD^{/}`.  I’m sorry
> that I wasn’t clear in my previous message[1] since I didn’t establish
> the context properly:
> 
>     I have indeed noticed that `HEAD^{/}` returns a sensible thing while
>     `:/` does something strange like finding the root commit.
> 
> What I had noticed myself for a little while was that `HEAD^{/}` on
> Git 2.47.0 did something that I wanted (and which is documented) while
> `:/` behaved (behaves) weirdly.  I just shrugged that off since the
> second syntax is more useful anyway (like Junio said).
> 
> 
Junio C Hamano Dec. 6, 2024, 10:46 p.m. UTC | #3
Patrick Steinhardt <ps@pks.im> writes:

> Fair, I should've double checked. Anyway, verifying the behaviour of
> both in the added test is probably still sensible.

I'll queue with this range-diff on top.  :/<text> is explained in
"git help revisions" as "reachable from any ref", and it is a good
phrase to use there, I think.

Thanks, both, I should've double-checked, too.


      ## Commit message ##
         object-name: fix reversed ordering with ":/<PATTERN>" revisions
     
    -    Recently it was reported [1] that "look for the youngest reachable
    -    commit with log message that match the given pattern" syntax (e.g.
    -    ':/<PATTERN>' or 'HEAD^{/<PATTERN>}') started to return results in
    +    Recently it was reported [1] that "look for the youngest commit
    +    reachable from any ref with log message that match the given
    +    pattern" syntax (e.g. ':/<PATTERN>') started to return results in
         reverse recency order. This regression was introduced in Git v2.47.0 and
         is caused by a memory leak fix done in 57fb139b5e (object-name: fix
         leaking commit list items, 2024-08-01).
Justin Tobler Dec. 7, 2024, 2:05 a.m. UTC | #4
On 24/12/06 03:33PM, Kristoffer Haugsbakk wrote:
> On Fri, Dec 6, 2024, at 13:28, Patrick Steinhardt wrote:
> > Recently it was reported [1] that "look for the youngest reachable
> > commit with log message that match the given pattern" syntax (e.g.
> > ':/<PATTERN>' or 'HEAD^{/<PATTERN>}') started to return results in
> 
> But the regression is only for `:/`.  Not for `HEAD^{/}`.  I’m sorry
> that I wasn’t clear in my previous message[1] since I didn’t establish
> the context properly:
> 
>     I have indeed noticed that `HEAD^{/}` returns a sensible thing while
>     `:/` does something strange like finding the root commit.
> 
> What I had noticed myself for a little while was that `HEAD^{/}` on
> Git 2.47.0 did something that I wanted (and which is documented) while
> `:/` behaved (behaves) weirdly.  I just shrugged that off since the
> second syntax is more useful anyway (like Junio said).

I was a bit curious why the regression only affected the `:/` syntax but
not `HEAD^{/}` as they both use `get_oid_oneline()`. The commit list
that was getting reversed only ever contain the reference commits
themselves and not the commits being traversed to find the matching
commit (that part comes a bit later). This means it is not a problem
when there is only a single reference being traversed as in the case of
`HEAD^{/}`. So this fix looks good. :)

-Justin
diff mbox series

Patch

diff --git a/object-name.c b/object-name.c
index c892fbe80aa7173dfcc1995de5a75bc322c6adb7..34433d2a01d6a23ce6b4ca19b85c53b7b82fd0e5 100644
--- a/object-name.c
+++ b/object-name.c
@@ -1401,7 +1401,7 @@  static int get_oid_oneline(struct repository *r,
 			   const char *prefix, struct object_id *oid,
 			   const struct commit_list *list)
 {
-	struct commit_list *copy = NULL;
+	struct commit_list *copy = NULL, **copy_tail = &copy;
 	const struct commit_list *l;
 	int found = 0;
 	int negative = 0;
@@ -1423,7 +1423,7 @@  static int get_oid_oneline(struct repository *r,
 
 	for (l = list; l; l = l->next) {
 		l->item->object.flags |= ONELINE_SEEN;
-		commit_list_insert(l->item, &copy);
+		copy_tail = &commit_list_insert(l->item, copy_tail)->next;
 	}
 	while (copy) {
 		const char *p, *buf;
diff --git a/t/t1500-rev-parse.sh b/t/t1500-rev-parse.sh
index 30c31918fde6539d52800e18dfbb3423b5b73491..42c4a63cb95eed781ed7d3029c4ff5e600e6f8b8 100755
--- a/t/t1500-rev-parse.sh
+++ b/t/t1500-rev-parse.sh
@@ -310,4 +310,19 @@  test_expect_success '--short= truncates to the actual hash length' '
 	test_cmp expect actual
 '
 
+test_expect_success ':/ and HEAD^{/} favor more recent matching commits' '
+	test_when_finished "rm -rf repo" &&
+	git init repo &&
+	(
+		cd repo &&
+		test_commit common-old &&
+		test_commit --no-tag common-new &&
+		git rev-parse HEAD >expect &&
+		git rev-parse :/common >actual &&
+		test_cmp expect actual &&
+		git rev-parse HEAD^{/common} >actual &&
+		test_cmp expect actual
+	)
+'
+
 test_done