* [PATCH v2 1/6] doc: convert git-bisect to synopsis style
From: Jean-Noël Avila via GitGitGadget @ 2026-05-25 10:28 UTC (permalink / raw)
To: git; +Cc: Jean-Noël Avila, Jean-Noël Avila
In-Reply-To: <pull.2117.v2.git.1779704908.gitgitgadget@gmail.com>
From: =?UTF-8?q?Jean-No=C3=ABl=20Avila?= <jn.avila@free.fr>
Convert Documentation/git-bisect.adoc to the modern synopsis style.
- Replace [verse] with [synopsis] in the SYNOPSIS block
- Remove single quotes around command names in the synopsis
- Use backticks for inline commands, options, refs, and special values
- Apply [synopsis] attribute to in-body command-form code blocks
- Format OPTIONS entries with backtick-quoted terms and direct
- Add synopsis-style formatting to listing blocks
- Format man page references as `command`(N)
Signed-off-by: Jean-Noël Avila <jn.avila@free.fr>
---
Documentation/asciidoc.conf.in | 6 +++
Documentation/git-bisect.adoc | 90 ++++++++++++++++------------------
2 files changed, 48 insertions(+), 48 deletions(-)
diff --git a/Documentation/asciidoc.conf.in b/Documentation/asciidoc.conf.in
index 31b883a72c..93c63b284a 100644
--- a/Documentation/asciidoc.conf.in
+++ b/Documentation/asciidoc.conf.in
@@ -84,6 +84,9 @@ ifdef::doctype-manpage[]
[blockdef-open]
synopsis-style=template="verseparagraph",filter="sed 's!…\\(\\]\\|$\\)!<phrase>\\0</phrase>!g;s!\\([\\[ |()]\\|^\\|\\]\\|>\\)\\([-=a-zA-Z0-9:+@,\\/_^\\$.\\\\\\*]\\+\\|…\\)!\\1<literal>\\2</literal>!g;s!<[-a-zA-Z0-9.]\\+>!<emphasis>\\0</emphasis>!g'"
+[blockdef-listing]
+synopsis-style=template="verseparagraph",filter="sed 's!…\\(\\]\\|$\\)!<phrase>\\0</phrase>!g;s!\\([\\[ |()]\\|^\\|\\]\\|>\\)\\([-=a-zA-Z0-9:+@,\\/_^\\$.\\\\\\*]\\+\\|…\\)!\\1<literal>\\2</literal>!g;s!<[-a-zA-Z0-9.]\\+>!<emphasis>\\0</emphasis>!g'"
+
[paradef-default]
synopsis-style=template="verseparagraph",filter="sed 's!…\\(\\]\\|$\\)!<phrase>\\0</phrase>!g;s!\\([\\[ |()]\\|^\\|\\]\\|>\\)\\([-=a-zA-Z0-9:+@,\\/_^\\$.\\\\\\*]\\+\\|…\\)!\\1<literal>\\2</literal>!g;s!<[-a-zA-Z0-9.]\\+>!<emphasis>\\0</emphasis>!g'"
endif::doctype-manpage[]
@@ -93,6 +96,9 @@ ifdef::backend-xhtml11[]
[blockdef-open]
synopsis-style=template="verseparagraph",filter="sed 's!…\\(\\]\\|$\\)!<span>\\0</span>!g;s!\\([\\[ |()]\\|^\\|\\]\\|>\\)\\([-=a-zA-Z0-9:+@,\\/_^\\$.\\\\\\*]\\+\\|…\\)!\\1<code>\\2</code>!g;s!<[-a-zA-Z0-9.]\\+>!<em>\\0</em>!g'"
+[blockdef-listing]
+synopsis-style=template="verseparagraph",filter="sed 's!…\\(\\]\\|$\\)!<span>\\0</span>!g;s!\\([\\[ |()]\\|^\\|\\]\\|>\\)\\([-=a-zA-Z0-9:+@,\\/_^\\$.\\\\\\*]\\+\\|…\\)!\\1<code>\\2</code>!g;s!<[-a-zA-Z0-9.]\\+>!<em>\\0</em>!g'"
+
[paradef-default]
synopsis-style=template="verseparagraph",filter="sed 's!…\\(\\]\\|$\\)!<span>\\0</span>!g;s!\\([\\[ |()]\\|^\\|\\]\\|>\\)\\([-=a-zA-Z0-9:+@,\\/_^\\$.\\\\\\*]\\+\\|…\\)!\\1<code>\\2</code>!g;s!<[-a-zA-Z0-9.]\\+>!<em>\\0</em>!g'"
endif::backend-xhtml11[]
diff --git a/Documentation/git-bisect.adoc b/Documentation/git-bisect.adoc
index b0078dda0e..4765d3b969 100644
--- a/Documentation/git-bisect.adoc
+++ b/Documentation/git-bisect.adoc
@@ -8,20 +8,20 @@ git-bisect - Use binary search to find the commit that introduced a bug
SYNOPSIS
--------
-[verse]
-'git bisect' start [--term-(bad|new)=<term-new> --term-(good|old)=<term-old>]
- [--no-checkout] [--first-parent] [<bad> [<good>...]] [--] [<pathspec>...]
-'git bisect' (bad|new|<term-new>) [<rev>]
-'git bisect' (good|old|<term-old>) [<rev>...]
-'git bisect' terms [--term-(good|old) | --term-(bad|new)]
-'git bisect' skip [(<rev>|<range>)...]
-'git bisect' next
-'git bisect' reset [<commit>]
-'git bisect' (visualize|view)
-'git bisect' replay <logfile>
-'git bisect' log
-'git bisect' run <cmd> [<arg>...]
-'git bisect' help
+[synopsis]
+git bisect start [--term-(bad|new)=<term-new> --term-(good|old)=<term-old>]
+ [--no-checkout] [--first-parent] [<bad> [<good>...]] [--] [<pathspec>...]
+git bisect (bad|new|<term-new>) [<rev>]
+git bisect (good|old|<term-old>) [<rev>...]
+git bisect terms [--term-(good|old) | --term-(bad|new)]
+git bisect skip [(<rev>|<range>)...]
+git bisect next
+git bisect reset [<commit>]
+git bisect (visualize|view)
+git bisect replay <logfile>
+git bisect log
+git bisect run <cmd> [<arg>...]
+git bisect help
DESCRIPTION
-----------
@@ -94,7 +94,7 @@ Bisect reset
~~~~~~~~~~~~
After a bisect session, to clean up the bisection state and return to
-the original HEAD, issue the following command:
+the original `HEAD`, issue the following command:
------------------------------------------------
$ git bisect reset
@@ -107,9 +107,8 @@ that, as it cleans up the old bisection state.)
With an optional argument, you can return to a different commit
instead:
-------------------------------------------------
+[synopsis]
$ git bisect reset <commit>
-------------------------------------------------
For example, `git bisect reset bisect/bad` will check out the first
bad revision, while `git bisect reset HEAD` will leave you on the
@@ -143,23 +142,20 @@ To use "old" and "new" instead of "good" and bad, you must run `git
bisect start` without commits as argument and then run the following
commands to add the commits:
-------------------------------------------------
+[synopsis]
git bisect old [<rev>]
-------------------------------------------------
to indicate that a commit was before the sought change, or
-------------------------------------------------
+[synopsis]
git bisect new [<rev>...]
-------------------------------------------------
to indicate that it was after.
To get a reminder of the currently used terms, use
-------------------------------------------------
+[synopsis]
git bisect terms
-------------------------------------------------
You can get just the old term with `git bisect terms --term-old`
or `git bisect terms --term-good`; `git bisect terms --term-new`
@@ -171,9 +167,8 @@ If you would like to use your own terms instead of "bad"/"good" or
subcommands like `reset`, `start`, ...) by starting the
bisection using
-------------------------------------------------
+[synopsis]
git bisect start --term-old <term-old> --term-new <term-new>
-------------------------------------------------
For example, if you are looking for a commit that introduced a
performance regression, you might use
@@ -194,7 +189,7 @@ of `git bisect good` and `git bisect bad` to mark commits.
Bisect visualize/view
~~~~~~~~~~~~~~~~~~~~~
-To see the currently remaining suspects in 'gitk', issue the following
+To see the currently remaining suspects in `gitk`, issue the following
command during the bisection process (the subcommand `view` can be used
as an alternative to `visualize`):
@@ -203,12 +198,13 @@ $ git bisect visualize
------------
Git detects a graphical environment through various environment variables:
-`DISPLAY`, which is set in X Window System environments on Unix systems.
-`SESSIONNAME`, which is set under Cygwin in interactive desktop sessions.
-`MSYSTEM`, which is set under Msys2 and Git for Windows.
-`SECURITYSESSIONID`, which may be set on macOS in interactive desktop sessions.
-If none of these environment variables is set, 'git log' is used instead.
+`DISPLAY`:: which is set in X Window System environments on Unix systems.
+`SESSIONNAME`:: which is set under Cygwin in interactive desktop sessions.
+`MSYSTEM`:: which is set under Msys2 and Git for Windows.
+`SECURITYSESSIONID`:: which may be set on macOS in interactive desktop sessions.
+
+If none of these environment variables is set, `git log` is used instead.
You can also give command-line options such as `-p` and `--stat`.
------------
@@ -342,8 +338,8 @@ code between 1 and 127 (inclusive), except 125, if the current source
code is bad/new.
Any other exit code will abort the bisect process. It should be noted
-that a program that terminates via `exit(-1)` leaves $? = 255, (see the
-exit(3) manual page), as the value is chopped with `& 0377`.
+that a program that terminates via `exit(-1)` leaves `$?` = 255, (see the
+`exit`(3) manual page), as the value is chopped with `& 0377`.
The special exit code 125 should be used when the current source code
cannot be tested. If the script exits with this code, the current
@@ -355,12 +351,12 @@ details do not matter, as they are normal errors in the script, as far as
`bisect run` is concerned).
You may often find that during a bisect session you want to have
-temporary modifications (e.g. s/#define DEBUG 0/#define DEBUG 1/ in a
+temporary modifications (e.g. `s/#define DEBUG 0/#define DEBUG 1/` in a
header file, or "revision that does not have this commit needs this
patch applied to work around another problem this bisection is not
interested in") applied to the revision being tested.
-To cope with such a situation, after the inner 'git bisect' finds the
+To cope with such a situation, after the inner `git bisect` finds the
next revision to test, the script can apply the patch
before compiling, run the real test, and afterwards decide if the
revision (possibly with the needed patch) passed the test and then
@@ -370,20 +366,18 @@ determine the eventual outcome of the bisect session.
OPTIONS
-------
---no-checkout::
-+
-Do not checkout the new working tree at each iteration of the bisection
-process. Instead just update the reference named `BISECT_HEAD` to make
-it point to the commit that should be tested.
+`--no-checkout`::
+ Do not checkout the new working tree at each iteration of the bisection
+ process. Instead just update the reference named `BISECT_HEAD` to make
+ it point to the commit that should be tested.
+
This option may be useful when the test you would perform in each step
does not require a checked out tree.
+
If the repository is bare, `--no-checkout` is assumed.
---first-parent::
-+
-Follow only the first parent commit upon seeing a merge commit.
+`--first-parent`::
+ Follow only the first parent commit upon seeing a merge commit.
+
In detecting regressions introduced through the merging of a branch, the merge
commit will be identified as introduction of the bug and its ancestors will be
@@ -395,7 +389,7 @@ branch contained broken or non-buildable commits, but the merge itself was OK.
EXAMPLES
--------
-* Automatically bisect a broken build between v1.2 and HEAD:
+* Automatically bisect a broken build between v1.2 and `HEAD`:
+
------------
$ git bisect start HEAD v1.2 -- # HEAD is bad, v1.2 is good
@@ -403,7 +397,7 @@ $ git bisect run make # "make" builds the app
$ git bisect reset # quit the bisect session
------------
-* Automatically bisect a test failure between origin and HEAD:
+* Automatically bisect a test failure between origin and `HEAD`:
+
------------
$ git bisect start HEAD origin -- # HEAD is bad, origin is good
@@ -430,7 +424,7 @@ and `exit 1` otherwise.
+
It is safer if both `test.sh` and `check_test_case.sh` are
outside the repository to prevent interactions between the bisect,
-make and test processes and the scripts.
+`make` and test processes and the scripts.
* Automatically bisect with temporary modifications (hot-fix):
+
@@ -491,9 +485,9 @@ $ git bisect run sh -c '
$ git bisect reset # quit the bisect session
------------
+
-In this case, when 'git bisect run' finishes, bisect/bad will refer to a commit that
+In this case, when `git bisect run` finishes, `bisect/bad` will refer to a commit that
has at least one parent whose reachable graph is fully traversable in the sense
-required by 'git pack objects'.
+required by `git pack-objects`.
* Look for a fix instead of a regression in the code
+
--
gitgitgadget
^ permalink raw reply related
* [PATCH v2 0/6] doc: convert another batch of files to synopsis style
From: Jean-Noël Avila via GitGitGadget @ 2026-05-25 10:28 UTC (permalink / raw)
To: git; +Cc: Jean-Noël Avila
In-Reply-To: <pull.2117.git.1779049615.gitgitgadget@gmail.com>
This time, 5 new conversions:
* git-bisect
* git-grep
* git-am
* git-apply
* git-imap-send
This batch was an opportunity to test AI-helped conversion.
Changes since v1:
* clarify the use of synopsis vs code block in git-bisect, which also
include using '$'
Jean-Noël Avila (6):
doc: convert git-bisect to synopsis style
doc: git bisect: clarify the usage of the synopsis vs actual command
doc: convert git-grep synopsis and options to new style
doc: convert git-am synopsis and options to new style
doc: convert git-apply synopsis and options to new style
doc: convert git-imap-send synopsis and options to new style
Documentation/asciidoc.conf.in | 6 +
Documentation/config/am.adoc | 6 +-
Documentation/config/apply.adoc | 17 +-
Documentation/config/grep.adoc | 36 ++--
Documentation/config/imap.adoc | 30 +--
Documentation/format-patch-caveats.adoc | 2 +-
.../format-patch-end-of-commit-message.adoc | 4 +-
Documentation/git-am.adoc | 132 ++++++------
Documentation/git-apply.adoc | 125 +++++------
Documentation/git-bisect.adoc | 109 +++++-----
Documentation/git-grep.adoc | 196 +++++++++---------
Documentation/git-imap-send.adoc | 24 +--
12 files changed, 346 insertions(+), 341 deletions(-)
base-commit: 56a4f3c3a221adf1df9b39da69b8a6890f803157
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-2117%2Fjnavila%2Fbisect-synopsis-style-v2
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-2117/jnavila/bisect-synopsis-style-v2
Pull-Request: https://github.com/gitgitgadget/git/pull/2117
Range-diff vs v1:
1: dca7f192f1 ! 1: 7284281fe0 doc: convert git-bisect to synopsis style
@@ Documentation/git-bisect.adoc: that, as it cleans up the old bisection state.)
With an optional argument, you can return to a different commit
instead:
+-------------------------------------------------
+[synopsis]
- ------------------------------------------------
$ git bisect reset <commit>
- ------------------------------------------------
+-------------------------------------------------
+
+ For example, `git bisect reset bisect/bad` will check out the first
+ bad revision, while `git bisect reset HEAD` will leave you on the
@@ Documentation/git-bisect.adoc: To use "old" and "new" instead of "good" and bad, you must run `git
bisect start` without commits as argument and then run the following
commands to add the commits:
+-------------------------------------------------
+[synopsis]
- ------------------------------------------------
git bisect old [<rev>]
- ------------------------------------------------
+-------------------------------------------------
to indicate that a commit was before the sought change, or
+-------------------------------------------------
+[synopsis]
- ------------------------------------------------
git bisect new [<rev>...]
- ------------------------------------------------
-@@ Documentation/git-bisect.adoc: to indicate that it was after.
+-------------------------------------------------
+
+ to indicate that it was after.
To get a reminder of the currently used terms, use
+-------------------------------------------------
+[synopsis]
- ------------------------------------------------
git bisect terms
- ------------------------------------------------
+-------------------------------------------------
+
+ You can get just the old term with `git bisect terms --term-old`
+ or `git bisect terms --term-good`; `git bisect terms --term-new`
@@ Documentation/git-bisect.adoc: If you would like to use your own terms instead of "bad"/"good" or
subcommands like `reset`, `start`, ...) by starting the
bisection using
+-------------------------------------------------
+[synopsis]
- ------------------------------------------------
git bisect start --term-old <term-old> --term-new <term-new>
- ------------------------------------------------
+-------------------------------------------------
+
+ For example, if you are looking for a commit that introduced a
+ performance regression, you might use
@@ Documentation/git-bisect.adoc: of `git bisect good` and `git bisect bad` to mark commits.
Bisect visualize/view
~~~~~~~~~~~~~~~~~~~~~
-: ---------- > 2: 4fb33dd440 doc: git bisect: clarify the usage of the synopsis vs actual command
2: 1b4efce1b2 = 3: fceaf195e8 doc: convert git-grep synopsis and options to new style
3: 4ab60a95f4 = 4: b9c2adfa1d doc: convert git-am synopsis and options to new style
4: 437e3f99c7 = 5: 60a420ea38 doc: convert git-apply synopsis and options to new style
5: dbe4d20b4b = 6: d88824bf09 doc: convert git-imap-send synopsis and options to new style
--
gitgitgadget
^ permalink raw reply
* Re: [PATCH 1/3] commit-reach: deduplicate queue entries in paint_down_to_common
From: Jeff King @ 2026-05-25 10:02 UTC (permalink / raw)
To: Kristofer Karlsson
Cc: Junio C Hamano, Derrick Stolee,
Kristofer Karlsson via GitGitGadget, git
In-Reply-To: <CAL71e4ODJeCJctKg=3o9PKD6Rw3_xHnrjc+zT_MYFc=CdNc59A@mail.gmail.com>
On Mon, May 25, 2026 at 09:53:09AM +0200, Kristofer Karlsson wrote:
> Good catch Jeff! I think it's possible that I missed the flag cleanup case
> here, but it's also possible that I got lucky and it worked anyway.
Well, you did add it to ALL_FLAGS, so it might have been your
subconscious making you lucky. :)
> That said, I think the observation in the other email thread/commit is key
> here. I will reply back in that one, but it seems like this can all be
> simplified using Jeff's idea with an amortized O(1) solution by caching a
> known non-stale entry in the queue, and thus becomes obsolete. I will post
> a new patchset when the discussion slows down.
Nifty, thanks.
> As for general flag management, I will spend some more time thinking about it.
> I don't fully trust static code analysis to work, but some cheap assertion
> based model might give a nice trade-off.
I think it would be really nice if we had per-operation flags kept
outside of the structs completely. If you're a masochist, I fiddled
around a bit with using a hash instead in this thread:
https://lore.kernel.org/git/20250826055210.GA1031277@coredump.intra.peff.net/
It's sadly (but not surprisingly) quite slow. I do wonder how a slab
would work there, but it would take a bit more surgery. We only allocate
slab ids for commits, and we'd have to do so for all objects if we want
to hold flags.
Probably a dead-end, but it would be neat if all of these flag
allocation worries just went away.
-Peff
^ permalink raw reply
* Re: [PATCH 0/3] commit-reach: replace queue_has_nonstale with a counter
From: Jeff King @ 2026-05-25 9:55 UTC (permalink / raw)
To: Kristofer Karlsson; +Cc: Kristofer Karlsson via GitGitGadget, git
In-Reply-To: <CAL71e4MOH2iPve19dKixLHSgpC3ZAZz59zLWEWRoxW1a7vhMwg@mail.gmail.com>
On Mon, May 25, 2026 at 09:59:59AM +0200, Kristofer Karlsson wrote:
> That's an excellent approach! Much cleaner in general.
>
> I benchmarked it against the counter on a monorepo with wide-frontier DAGs
> (2.4M commits, component import merges). Using merge-base --all to bypass
> the early-exit optimization from kk/paint-down-to-common-optim:
>
> Baseline Cache Counter
> import(A) 8079ms 3686ms 3723ms
> import(B) 5498ms 3993ms 4038ms
> import(C) 4350ms 1748ms 1766ms
>
> The cache performs on par with the counter - within noise on all three
> cases. No new flags needed, much simpler diff.
> The amortized O(1) is just as good as true O(1) in practice, and it avoids
> the ENQUEUED flag and counter bookkeeping entirely.
I'm not sure if it's technically amortized O(1), as I think in the worst
case we are still quadratic. That would happen if we've cached some
non-stale X, then pop it and put on some new commit Y. And then the next
round we have no cache (X was popped), but have to walk the whole queue
to find Y.
So I think it's more of a "heuristically O(1)" or something.
> I went with back-to-front scanning as you suggested
Out of curiosity, did you also time it front-to-back? What I wonder is
if we might commonly hit that worst case for back-to-front when we're
continually popping and inserting one new commit at the front of the
queue. If there's a bunch of stale cruft in the back end of the queue,
we'll walk over it repeatedly to find the new commit, and our cache will
never (or seldom) remain valid. (I know it's a heap, not a real queue,
but I think the far end of the array will still tend to represent stuff
that is further away from being popped due to the heap property).
Whereas looking from front to back, we are likely to cache something
that is going to be popped soon. But in that case we find it quickly,
and the longer we search the more likely it is to hang around in the
queue and remain valid.
> and also clear the cache when the cached entry goes stale.
I think this happens naturally when we call into queue_has_nonstale().
We only use the cached value if it's still non-stale. If it's gone stale
then we either find a new commit, or if we can't then we return false
(everything is stale). I guess the stale commit is left in the cache in
the latter case, but it doesn't matter because the loop ends anyway (and
even if it didn't, it is OK to repeatedly ignore the stale commit, as
doing so is O(1) and we have nothing better to cache).
That said, it is probably only one line to explicitly set it to NULL in
queue_has_nonstale(), so I am OK with that. ;)
If you're proposing to notice when we set the STALE flag on a commit
which matches the cached value, I'd prefer to avoid that, just because
it muddies up the code.
> I can rewrite the patchset with this approach and add you as co-author or
> suggested-by? Or I think I can wait for you to push it yourself.
> You did all the work here, and just didn't have enough data points to
> motivate it?
I think testing and writing the commit messages will be more work than
the code. I am happy to live on in a trailer if you will do those other
parts. ;)
-Peff
^ permalink raw reply
* Re: [PATCH v5 0/2] includeIf: add "worktree" condition for matching working tree path
From: Junio C Hamano @ 2026-05-25 9:24 UTC (permalink / raw)
To: Chen Linxuan
Cc: Chen Linxuan via B4 Relay, git, Kristoffer Haugsbakk,
Patrick Steinhardt, Phillip Wood
In-Reply-To: <CAC1kPDPbyxs-aTrAOi_PNTZF7EApG31iLYwm+Eddpeh2hT8a-w@mail.gmail.com>
Chen Linxuan <me@black-desk.cn> writes:
> On Mon, May 25, 2026 at 3:31 PM Junio C Hamano <gitster@pobox.com> wrote:
>>
>> Chen Linxuan via B4 Relay <devnull+me.black-desk.cn@kernel.org>
>> writes:
>>
>> > Changes in v5:
>> > - Fix Windows CI failure: use `**` glob pattern instead of `/` in the
>> > "worktree without repository" tests, since `/` as a path pattern is
>> > Unix-specific and does not match Windows paths.
>>
>> Would it have worked if you used something like "[/\\].path",
>> instead of "/.path", to cover directory delimiters for both systems?
>>
>> I am not asking to make further changes. I am trying to understand
>> what the extent of the problem was.
>
> The root cause is that on Windows,
> strbuf_realpath() returns paths with a drive letter prefix (e.g.
> D:/a/git/...), which does not start with /.
Ahh, OK, so the "Changes in v5" description was misleading.
This exchange suggests that the use of **/.path in the test deserves
some in-code comment to explain why we use such an unusual and loose
construct.
Thanks.
^ permalink raw reply
* Re: [PATCH v3] config: suggest the correct form when key contains "=" in set context
From: Junio C Hamano @ 2026-05-25 9:15 UTC (permalink / raw)
To: Harald Nordgren via GitGitGadget
Cc: git, Kristoffer Haugsbakk, Harald Nordgren
In-Reply-To: <pull.2302.v3.git.git.1779697995418.gitgitgadget@gmail.com>
"Harald Nordgren via GitGitGadget" <gitgitgadget@gmail.com> writes:
> Emit a "did you mean ..." hint suggesting the split form. Restrict it
> to plausible-set contexts ("git config set", bare "git config <key>",
> and their 2-arg forms); explicit "get"/"unset" keep the existing error.
I understand that it would be a good idea to give this warning
against these two where $A is an arbitrary string with at least one
dot in it (making it a likely variable name), and $B is an arbitrary
string that may contain anything:
git config set "$A=$B"
git config "$A=$B"
It is plausible that the user wanted to make the value of the
variable "$A" to "$B", so telling them the right syntax would be
valuable.
If "$A" is a syntactically valid variable name, then I would imagine
that we want to say something like this:
$ git config set "$A=$B"
error: missing value to set to the variable "$A=$B"
hint: did you mean 'git config set "$A" "$B"'?
If "$A" is *not* a syntactically valid variable name, then giving a
hint to try to assing to it is a counter-productive. Ideally we
probably want something like:
$ git config set "foo=bar"
error: missing value to set to a variable with an invalid name 'foo=bar'
It is pointless to say the user may have meant "git config set foo bar",
as "foo" is clearly not a valid variable.
I do not understand what you mean by "their 2-arg forms". Do you
mean
git config set "$A=$B" "$C"
by that? If so, I doubt that user meant an assignment to "$A" by
this form with explicit "set". If "$A=$B" is a variable whose name
is valid (i.e. three-level name whose the second level component
contains a "="), we should just take it as asked. E.g.,
git config set "foo.bar=baz.boo" "some-string"
needs no hand holding. But
if "$A=$B" is not a valid variable name, we should just complain
that the user is trying to assign to a variable with an invalid
name.
$ git config set "foo.bar=baz" "some-string"
error: setting to a variable with invalid name 'foo.bar=baz'
I think
git config "$A=$B" "$C"
that implicitly uses the 'set' verb can be left as an exercise to
readers. If "$A=$B" is a valid name, we shouldn't do any complaint.
If it is not,
$ git config "foo.bar=baz" "some-string"
error: setting to a variable with invalid name 'foo.bar=baz'
It makes it clear to the user that (1) we interpreted the command
line to be "implicit set", (2) we interpreted the command line to
set variable 'foo.bar=baz', and (3) 'foo.bar=baz' is not a valid
name. I do not think there is anything more needed for this case.
> "=" is legal inside a subsection, so only fire when "=" lands after
> the last ".". When the user supplied a separate value, use it in the
> suggestion instead of the suffix after "=":
>
> $ git config set pull.rebase=false true
> error: invalid key: pull.rebase=false
> hint: did you mean "git config set pull.rebase true"?
I really do not think '=' needs *any* special casing in this case.
If we used "pull.rebase*false" as the variable instead, the message
would say that "pull.rebase*false" is an invalid key. Two important
things for this message to convey are (1) the command correctly
parsed the command line to mean that the user wants to assign to a
variable whose name is 'pull.rebase*false' and (2) that variable
name *is* invalid.
If you find the current message suboptimal, I think we should try to
clarify the message, as '=' or '*' or any letter that makes the
variable name invalid would benefit from the same improvement.
Perhaps something like:
$ git config set pull.rebase*false true
error: setting to a variable with invalid name: 'pull.rebase*false'
perhaps?
> Signed-off-by: Harald Nordgren <harald.nordgren@kostdoktorn.se>
> Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
Interesting. We typically do not do this.
^ permalink raw reply
* Re: [PATCH v5 0/2] includeIf: add "worktree" condition for matching working tree path
From: Chen Linxuan @ 2026-05-25 9:00 UTC (permalink / raw)
To: Junio C Hamano
Cc: Chen Linxuan via B4 Relay, git, Kristoffer Haugsbakk,
Patrick Steinhardt, Chen Linxuan, Phillip Wood
In-Reply-To: <xmqqjysseyid.fsf@gitster.g>
On Mon, May 25, 2026 at 3:31 PM Junio C Hamano <gitster@pobox.com> wrote:
>
> Chen Linxuan via B4 Relay <devnull+me.black-desk.cn@kernel.org>
> writes:
>
> > Changes in v5:
> > - Fix Windows CI failure: use `**` glob pattern instead of `/` in the
> > "worktree without repository" tests, since `/` as a path pattern is
> > Unix-specific and does not match Windows paths.
>
> Would it have worked if you used something like "[/\\].path",
> instead of "/.path", to cover directory delimiters for both systems?
>
> I am not asking to make further changes. I am trying to understand
> what the extent of the problem was.
The root cause is that on Windows,
strbuf_realpath() returns paths with a drive letter prefix (e.g.
D:/a/git/...), which does not start with /.
Here is the trace output from the Windows CI [1]:
include_by_path: text='D:/a/git/git/t/trash
directory.t1305-config-include', pattern='/**', prefix=0
The pattern worktree:/ becomes /** after add_trailing_starstar_for_dir().
Then wildmatch("/**", "D:/a/git/...", WM_PATHNAME) fails because the text
does not start with /.
[1] https://github.com/black-desk/git/actions/runs/26391768962/job/77683708185
^ permalink raw reply
* Re: [PATCH 2/3] commit-reach: optimize queue scan in paint_down_to_common
From: Kristofer Karlsson @ 2026-05-25 8:54 UTC (permalink / raw)
To: Derrick Stolee; +Cc: Kristofer Karlsson via GitGitGadget, git
In-Reply-To: <42aef000-7952-482d-8532-2287cf32b275@gmail.com>
I have been thinking a bit about encapsulation too - the problem is twofold:
1. ENQUEUED is a tag on the commit object but it represents membership
inside the queue and so we already have an implicit assumption that it only
matches one queue (at a time).
2. The counter is touched on enqueue/dequeue BUT also when mutating objects -
that last part is tricky to encapsulate as a part of the queue.
That said, I think if we go in the direction of Jeff's idea with an amortized
O(1) staleness check, this becomes simpler - and we can perhaps do something
to structure _that_ code instead. Something like this perhaps:
struct stale_prio_queue {
prio_queue pq;
commit *nonstale_cache;
}
and add the corresponding wrapper functions.
I think the encapsulation idea becomes even stronger with that approach than
with the counter based approach.
- Kristofer
On Mon, 25 May 2026 at 03:59, Derrick Stolee <stolee@gmail.com> wrote:
>
> On 5/24/26 1:42 PM, Kristofer Karlsson via GitGitGadget wrote:
> > From: Kristofer Karlsson <krka@spotify.com>
> >
> > paint_down_to_common() terminates when every commit remaining in its
> > priority queue is STALE. This was checked by queue_has_nonstale(),
> > which performed an O(n) linear scan of the entire queue on every
> > iteration, resulting in O(n*m) total overhead where n is the queue
> > size and m is the number of commits processed.
> >
> > Replace this with an O(1) nonstale_count that tracks the number of
> > non-stale commits currently in the queue. The counter is incremented
> > by maybe_enqueue() and decremented on dequeue and by mark_stale()
> > when a commit transitions to STALE while still in the queue. Since
> > each commit appears at most once (guaranteed by the ENQUEUED flag
> > from the previous commit), the counter is exact.
>
> This idea has a lot of merit, but I'm a bit concerned about the
> organization of data. My ideas of how to improve things may also
> impact patch 1's use of ENQUEUED.
>
> > -static void maybe_enqueue(struct prio_queue *queue, struct commit *c)
> > +static void maybe_enqueue(struct prio_queue *queue, struct commit *c,
> > + int *nonstale_count)
> > {
> > if (c->object.flags & ENQUEUED)
> > return;
> > c->object.flags |= ENQUEUED;
> > prio_queue_put(queue, c);
> > + if (!(c->object.flags & STALE))
> > + (*nonstale_count)++;
> > +}
> > +
> > +static void mark_stale(struct commit *c, unsigned queued_flag,
> > + int *nonstale_count)
> > +{
> > + if (!(c->object.flags & STALE)) {
> > + if (c->object.flags & queued_flag)
> > + (*nonstale_count)--;
> > + c->object.flags |= STALE;
> > + }
> > }
>
> These two methods have some concerns on my end:
>
> 1. We need to store the nonstale count somewhere other than the
> priority queue, even though it's necessarily representing a
> subset of the commits within the queue.
>
> 2. mark_stale() needs a queued_flag. (I need to check to see if
> this is indeed changing in multiple callers or should always
> be ENQUEUED).
>
> > static int queue_has_nonstale(struct prio_queue *queue)
> > @@ -68,6 +81,7 @@ static int paint_down_to_common(struct repository *r,
> > {
> > struct prio_queue queue = { compare_commits_by_gen_then_commit_date };
> > int i;
> > + int nonstale_count = 0;
>
> My preference would be to create a new struct that contains a
> prio_queue as a member _and_ a nonstale_count. It could initialize
> with compare_commits_by_gen_then_commit_date by default.
>
> The important thing is that consumers of such a "stale-tracking"
> queue would not be setting the STALE or ENQUEUED bits themselves,
> but instead the queue would be responsible for that.
>
> This could allow us to simplify callers by always assuming we can
> "add" an element to the queue and the queue will use its ENQUEUED
> bit to prevent duplicates from reaching its internal prio_queue.
>
> Such a data structure could be private to commit-reach.c for now,
> since all the methods that would use it seem to be colocated there.
>
> This is a big ask, but I'm interested to see if such an approach
> would simplify things here.
>
> Here's a potential breakdown of how to build such a thing in
> "small" patches:
>
> 1. Create the data structure and update paint_down_to_common and
> ahead_behind to use that structure, but still use the existing
> prio_queue methods on its internal member.
>
> 2. Add the ENQUEUED bit and methods on the new struct that add
> that bit as it adds commits to the inner prio_queue. It would
> also ignore commits that already have that bit. (Should it
> also remove the bit as commits are removed from the queue?)
>
> 3. Now add the nonstale_count (or stale count?) to the struct and
> have it control the STALE bit modifications, with increasing
> the stale count when ENQUEUED is live, and decreasing the stale
> count as such a STALE object is dequeued.
>
> I like the idea of this being encapsulated within the struct and
> its helper methods. But the proof will be in the implementation.
>
> Thanks,
> -Stolee
>
^ permalink raw reply
* Re: [PATCH 0/3] commit-reach: replace queue_has_nonstale with a counter
From: Junio C Hamano @ 2026-05-25 8:38 UTC (permalink / raw)
To: Kristofer Karlsson; +Cc: Jeff King, Kristofer Karlsson via GitGitGadget, git
In-Reply-To: <CAL71e4MOH2iPve19dKixLHSgpC3ZAZz59zLWEWRoxW1a7vhMwg@mail.gmail.com>
Kristofer Karlsson <krka@spotify.com> writes:
> That's an excellent approach! Much cleaner in general.
>
> I benchmarked it against the counter on a monorepo with wide-frontier DAGs
> (2.4M commits, component import merges). Using merge-base --all to bypass
> the early-exit optimization from kk/paint-down-to-common-optim:
>
> Baseline Cache Counter
> import(A) 8079ms 3686ms 3723ms
> import(B) 5498ms 3993ms 4038ms
> import(C) 4350ms 1748ms 1766ms
>
> The cache performs on par with the counter - within noise on all three
> cases. No new flags needed, much simpler diff.
> The amortized O(1) is just as good as true O(1) in practice, and it avoids
> the ENQUEUED flag and counter bookkeeping entirely.
Nice.
> I went with back-to-front scanning as you suggested, and also clear
> the cache when the cached entry goes stale. Applied to both
> paint_down_to_common and ahead_behind.
>
> I can rewrite the patchset with this approach and add you as co-author or
> suggested-by? Or I think I can wait for you to push it yourself.
> You did all the work here, and just didn't have enough data points to
> motivate it?
I can take from either of you two ;-). Thanks for working so well
together, as always.
^ permalink raw reply
* [PATCH v3] config: suggest the correct form when key contains "=" in set context
From: Harald Nordgren via GitGitGadget @ 2026-05-25 8:33 UTC (permalink / raw)
To: git; +Cc: Kristoffer Haugsbakk, Harald Nordgren, Harald Nordgren
In-Reply-To: <pull.2302.v2.git.git.1778935976330.gitgitgadget@gmail.com>
From: Harald Nordgren <haraldnordgren@gmail.com>
A user who types "git config pull.rebase=false" gets only "error:
invalid key: pull.rebase=false" with no clue what went wrong.
Emit a "did you mean ..." hint suggesting the split form. Restrict it
to plausible-set contexts ("git config set", bare "git config <key>",
and their 2-arg forms); explicit "get"/"unset" keep the existing error.
"=" is legal inside a subsection, so only fire when "=" lands after
the last ".". When the user supplied a separate value, use it in the
suggestion instead of the suffix after "=":
$ git config set pull.rebase=false true
error: invalid key: pull.rebase=false
hint: did you mean "git config set pull.rebase true"?
Signed-off-by: Harald Nordgren <harald.nordgren@kostdoktorn.se>
Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
---
config: suggest the correct form when key contains "="
* Skip the hint when the inferred value contains whitespace, so git
config set pull.rebase=false "hello world" no longer suggests a
malformed command.
* Replace the inline actions == 0 check with a named actions_implicit
flag, simplfied the code.
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2302%2FHaraldNordgren%2Fconfig-hint-equals-key-v3
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2302/HaraldNordgren/config-hint-equals-key-v3
Pull-Request: https://github.com/git/git/pull/2302
Range-diff vs v2:
1: 40d9eb3e5c ! 1: 6b9d66361d config: suggest the correct form when key contains "=" in set context
@@ Commit message
hint: did you mean "git config set pull.rebase true"?
Signed-off-by: Harald Nordgren <harald.nordgren@kostdoktorn.se>
+ Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
## builtin/config.c ##
@@
@@ builtin/config.c: static void check_argc(int argc, int min, int max)
+ return;
+ if (!value)
+ value = eq + 1;
++ if (!*value || strpbrk(value, " \t\n"))
++ return;
+ advise(_("did you mean \"git config set %.*s %s\"?"),
+ (int)(eq - key), key, value);
+}
@@ builtin/config.c: static int cmd_config_actions(int argc, const char **argv, con
exit(129);
}
+- if (actions == 0)
+ actions_implicit = (actions == 0);
- if (actions == 0)
++ if (actions_implicit)
switch (argc) {
case 1: actions = ACTION_GET; break;
+ case 2: actions = ACTION_SET; break;
@@ builtin/config.c: static int cmd_config_actions(int argc, const char **argv, const char *prefix)
if (ret == CONFIG_NOTHING_SET)
error(_("cannot overwrite multiple values with a single value\n"
@@ t/t1300-config.sh: test_expect_success 'invalid key' '
+ test_grep ! "did you mean" err
+'
+
++test_expect_success 'misplaced "=" in key: value with whitespace skips hint' '
++ test_must_fail git config set pull.rebase=false "hello world" 2>err &&
++ test_grep "invalid key: pull\\.rebase=false" err &&
++ test_grep ! "did you mean" err
++'
++
+test_expect_success '"=" inside subsection is valid, no hint' '
+ test_when_finished "rm -f subsection.cfg" &&
+ git config set -f subsection.cfg foo.bar=baz.boo qux 2>err &&
builtin/config.c | 34 +++++++++++++++++++++++++++++-
t/t1300-config.sh | 53 +++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 86 insertions(+), 1 deletion(-)
diff --git a/builtin/config.c b/builtin/config.c
index cf4ba0f7cc..8c7ab36fcb 100644
--- a/builtin/config.c
+++ b/builtin/config.c
@@ -1,6 +1,7 @@
#define USE_THE_REPOSITORY_VARIABLE
#include "builtin.h"
#include "abspath.h"
+#include "advice.h"
#include "config.h"
#include "color.h"
#include "date.h"
@@ -210,6 +211,24 @@ static void check_argc(int argc, int min, int max)
exit(129);
}
+static void advise_setting_with_equals(const char *key, const char *value)
+{
+ const char *last_dot = strrchr(key, '.');
+ const char *eq;
+
+ if (!last_dot)
+ return;
+ eq = strchr(last_dot + 1, '=');
+ if (!eq)
+ return;
+ if (!value)
+ value = eq + 1;
+ if (!*value || strpbrk(value, " \t\n"))
+ return;
+ advise(_("did you mean \"git config set %.*s %s\"?"),
+ (int)(eq - key), key, value);
+}
+
static void show_config_origin(const struct config_display_options *opts,
const struct key_value_info *kvi,
struct strbuf *buf)
@@ -1133,6 +1152,11 @@ static int cmd_config_set(int argc, const char **argv, const char *prefix,
argc = parse_options(argc, argv, prefix, opts, builtin_config_set_usage,
PARSE_OPT_STOP_AT_NON_OPTION);
+ if (argc == 1 && strchr(argv[0], '=')) {
+ error(_("wrong number of arguments, should be 2"));
+ advise_setting_with_equals(argv[0], NULL);
+ exit(129);
+ }
check_argc(argc, 2, 2);
if ((flags & CONFIG_FLAGS_FIXED_VALUE) && !value_pattern)
@@ -1160,6 +1184,8 @@ static int cmd_config_set(int argc, const char **argv, const char *prefix,
error(_("cannot overwrite multiple values with a single value\n"
" Use --value=<pattern>, --append or --all to change %s."), argv[0]);
}
+ if (ret == CONFIG_INVALID_KEY)
+ advise_setting_with_equals(argv[0], argv[1]);
location_options_release(&location_opts);
free(comment);
@@ -1371,6 +1397,7 @@ static int cmd_config_actions(int argc, const char **argv, const char *prefix)
};
char *value = NULL, *comment = NULL;
int ret = 0;
+ int actions_implicit;
struct key_value_info default_kvi = KVI_INIT;
argc = parse_options(argc, argv, prefix, opts,
@@ -1385,7 +1412,8 @@ static int cmd_config_actions(int argc, const char **argv, const char *prefix)
exit(129);
}
- if (actions == 0)
+ actions_implicit = (actions == 0);
+ if (actions_implicit)
switch (argc) {
case 1: actions = ACTION_GET; break;
case 2: actions = ACTION_SET; break;
@@ -1485,6 +1513,8 @@ static int cmd_config_actions(int argc, const char **argv, const char *prefix)
if (ret == CONFIG_NOTHING_SET)
error(_("cannot overwrite multiple values with a single value\n"
" Use a regexp, --add or --replace-all to change %s."), argv[0]);
+ else if (ret == CONFIG_INVALID_KEY)
+ advise_setting_with_equals(argv[0], argv[1]);
}
else if (actions == ACTION_SET_ALL) {
check_write(&location_opts.source);
@@ -1515,6 +1545,8 @@ static int cmd_config_actions(int argc, const char **argv, const char *prefix)
check_argc(argc, 1, 2);
ret = get_value(&location_opts, &display_opts, argv[0], argv[1],
0, flags);
+ if (ret == CONFIG_INVALID_KEY && actions_implicit)
+ advise_setting_with_equals(argv[0], NULL);
}
else if (actions == ACTION_GET_ALL) {
check_argc(argc, 1, 2);
diff --git a/t/t1300-config.sh b/t/t1300-config.sh
index 11fc976f3a..4e12b78536 100755
--- a/t/t1300-config.sh
+++ b/t/t1300-config.sh
@@ -469,6 +469,59 @@ test_expect_success 'invalid key' '
test_must_fail git config inval.2key blabla
'
+test_expect_success 'misplaced "=" in key: bare 1-arg form hints' '
+ test_must_fail git config pull.rebase=false 2>err &&
+ test_grep "invalid key: pull\\.rebase=false" err &&
+ test_grep "did you mean .git config set pull\\.rebase false." err
+'
+
+test_expect_success 'misplaced "=" in key: bare 2-arg form uses given value' '
+ test_must_fail git config pull.rebase=false true 2>err &&
+ test_grep "did you mean .git config set pull\\.rebase true." err
+'
+
+test_expect_success 'misplaced "=" in key: set subcommand uses given value' '
+ test_must_fail git config set pull.rebase=false true 2>err &&
+ test_grep "did you mean .git config set pull\\.rebase true." err
+'
+
+test_expect_success 'misplaced "=" in key: set with single arg hints' '
+ test_must_fail git config set pull.rebase=false 2>err &&
+ test_grep "wrong number of arguments" err &&
+ test_grep "did you mean .git config set pull\\.rebase false." err
+'
+
+test_expect_success 'misplaced "=" in key: explicit --get does not hint' '
+ test_must_fail git config --get pull.rebase=false 2>err &&
+ test_grep "invalid key: pull\\.rebase=false" err &&
+ test_grep ! "did you mean" err
+'
+
+test_expect_success 'misplaced "=" in key: get subcommand does not hint' '
+ test_must_fail git config get pull.rebase=false 2>err &&
+ test_grep ! "did you mean" err
+'
+
+test_expect_success 'misplaced "=" in key: unset subcommand does not hint' '
+ test_must_fail git config unset pull.rebase=false 2>err &&
+ test_grep ! "did you mean" err
+'
+
+test_expect_success 'misplaced "=" in key: value with whitespace skips hint' '
+ test_must_fail git config set pull.rebase=false "hello world" 2>err &&
+ test_grep "invalid key: pull\\.rebase=false" err &&
+ test_grep ! "did you mean" err
+'
+
+test_expect_success '"=" inside subsection is valid, no hint' '
+ test_when_finished "rm -f subsection.cfg" &&
+ git config set -f subsection.cfg foo.bar=baz.boo qux 2>err &&
+ test_grep ! "did you mean" err &&
+ echo qux >expect &&
+ git config get -f subsection.cfg foo.bar=baz.boo >actual &&
+ test_cmp expect actual
+'
+
test_expect_success 'correct key' '
git config 123456.a123 987
'
base-commit: 6a4418c36d6bad69a599044b3cf49dcbd049cb45
--
gitgitgadget
^ permalink raw reply related
* Re: [PATCH 0/3] commit-reach: replace queue_has_nonstale with a counter
From: Kristofer Karlsson @ 2026-05-25 7:59 UTC (permalink / raw)
To: Jeff King; +Cc: Kristofer Karlsson via GitGitGadget, git
In-Reply-To: <20260525064755.GA2737798@coredump.intra.peff.net>
That's an excellent approach! Much cleaner in general.
I benchmarked it against the counter on a monorepo with wide-frontier DAGs
(2.4M commits, component import merges). Using merge-base --all to bypass
the early-exit optimization from kk/paint-down-to-common-optim:
Baseline Cache Counter
import(A) 8079ms 3686ms 3723ms
import(B) 5498ms 3993ms 4038ms
import(C) 4350ms 1748ms 1766ms
The cache performs on par with the counter - within noise on all three
cases. No new flags needed, much simpler diff.
The amortized O(1) is just as good as true O(1) in practice, and it avoids
the ENQUEUED flag and counter bookkeeping entirely.
I went with back-to-front scanning as you suggested, and also clear
the cache when the cached entry goes stale. Applied to both
paint_down_to_common and ahead_behind.
I can rewrite the patchset with this approach and add you as co-author or
suggested-by? Or I think I can wait for you to push it yourself.
You did all the work here, and just didn't have enough data points to
motivate it?
- Kristofer
On Mon, 25 May 2026 at 08:47, Jeff King <peff@peff.net> wrote:
>
> On Sun, May 24, 2026 at 05:42:17PM +0000, Kristofer Karlsson via GitGitGadget wrote:
>
> > paint_down_to_common() and ahead_behind() terminate when every commit in
> > their priority queue is STALE. The current check, queue_has_nonstale(), does
> > an O(n) linear scan of the queue on every iteration, costing O(n*m) total
> > where n is the queue size and m is the number of commits processed. This
> > series replaces that scan with an O(1) counter.
>
> We faced a similar problem in limit_list() but solved it a bit
> differently (mostly because I was worried about keeping the counter up
> to date in all cases).
>
> It's described in more detail in b6e8a3b540 (limit_list: avoid quadratic
> behavior from still_interesting, 2015-04-17), but the general idea is to
> just cache the interesting element we found, and invalidate the cache
> when it gets removed from the queue or gets marked UNINTERESTING.
>
> The equivalent code for the STALE flag here is something like this:
>
> diff --git a/commit-reach.c b/commit-reach.c
> index d3a9b3ed6f..d1621be89f 100644
> --- a/commit-reach.c
> +++ b/commit-reach.c
> @@ -39,12 +39,25 @@ static int compare_commits_by_gen(const void *_a, const void *_b)
> return 0;
> }
>
> -static int queue_has_nonstale(struct prio_queue *queue)
> +static int queue_has_nonstale(struct prio_queue *queue,
> + struct commit **nonstale_cache)
> {
> + if (*nonstale_cache) {
> + struct commit *commit = *nonstale_cache;
> + if (!(commit->object.flags & STALE))
> + return 1;
> + }
> +
> + /*
> + * This might also benefit from looking back-to-front, since
> + * earlier commits are more likely to get popped sooner.
> + */
> for (size_t i = 0; i < queue->nr; i++) {
> struct commit *commit = queue->array[i].data;
> - if (!(commit->object.flags & STALE))
> + if (!(commit->object.flags & STALE)) {
> + *nonstale_cache = commit;
> return 1;
> + }
> }
> return 0;
> }
> @@ -61,6 +74,7 @@ static int paint_down_to_common(struct repository *r,
> int i;
> timestamp_t last_gen = GENERATION_NUMBER_INFINITY;
> struct commit_list **tail = result;
> + struct commit *nonstale_cache = NULL;
>
> if (!min_generation && !corrected_commit_dates_enabled(r))
> queue.compare = compare_commits_by_commit_date;
> @@ -77,12 +91,15 @@ static int paint_down_to_common(struct repository *r,
> prio_queue_put(&queue, twos[i]);
> }
>
> - while (queue_has_nonstale(&queue)) {
> + while (queue_has_nonstale(&queue, &nonstale_cache)) {
> struct commit *commit = prio_queue_get(&queue);
> struct commit_list *parents;
> int flags;
> timestamp_t generation = commit_graph_generation(commit);
>
> + if (nonstale_cache == commit)
> + nonstale_cache = NULL;
> +
> if (min_generation && generation > last_gen)
> BUG("bad generation skip %"PRItime" > %"PRItime" at %s",
> generation, last_gen,
> @@ -1053,6 +1070,7 @@ void ahead_behind(struct repository *r,
> {
> struct prio_queue queue = { .compare = compare_commits_by_gen_then_commit_date };
> size_t width = DIV_ROUND_UP(commits_nr, BITS_IN_EWORD);
> + struct commit *nonstale_cache = NULL;
>
> if (!commits_nr || !counts_nr)
> return;
> @@ -1074,11 +1092,14 @@ void ahead_behind(struct repository *r,
> insert_no_dup(&queue, c);
> }
>
> - while (queue_has_nonstale(&queue)) {
> + while (queue_has_nonstale(&queue, &nonstale_cache)) {
> struct commit *c = prio_queue_get(&queue);
> struct commit_list *p;
> struct bitmap *bitmap_c = get_bit_array(c, width);
>
> + if (c == nonstale_cache)
> + nonstale_cache = NULL;
> +
> for (size_t i = 0; i < counts_nr; i++) {
> int reach_from_tip = !!bitmap_get(bitmap_c, counts[i].tip_index);
> int reach_from_base = !!bitmap_get(bitmap_c, counts[i].base_index);
>
>
> I don't have a repo handy which reproduces the problem, so I can't see
> if it improves things. But if it's easy to do, can you report on the
> timing change with your monorepo?
>
> I do think what I've shown here is a bit hacky (just like the
> limit_list() one), as we are relying on heuristics about the order in
> which items are taken from the queue. So even if it performs well, we
> may still prefer the counter version for being truly O(1). But having
> timing numbers would be useful for comparing the two approaches.
>
> -Peff
^ permalink raw reply
* Re: [PATCH 1/3] commit-reach: deduplicate queue entries in paint_down_to_common
From: Kristofer Karlsson @ 2026-05-25 7:53 UTC (permalink / raw)
To: Junio C Hamano; +Cc: Derrick Stolee, Kristofer Karlsson via GitGitGadget, git
In-Reply-To: <xmqqse7gez5l.fsf@gitster.g>
Good catch Jeff! I think it's possible that I missed the flag cleanup case
here, but it's also possible that I got lucky and it worked anyway.
That said, I think the observation in the other email thread/commit is key
here. I will reply back in that one, but it seems like this can all be
simplified using Jeff's idea with an amortized O(1) solution by caching a
known non-stale entry in the queue, and thus becomes obsolete. I will post
a new patchset when the discussion slows down.
As for general flag management, I will spend some more time thinking about it.
I don't fully trust static code analysis to work, but some cheap assertion
based model might give a nice trade-off.
Thanks for all the feedback!
- Kristofer
On Mon, 25 May 2026 at 09:17, Junio C Hamano <gitster@pobox.com> wrote:
>
> Kristofer Karlsson <krka@spotify.com> writes:
>
> > While doing the audit I noticed that reasoning about flag safety is
> > currently entirely manual. Would there be interest in something more
> > systematic (e.g. runtime registration/assertion, dynamic allocation or static
> > analysis of flag usage)? I have some local work on that already, but I was
> > not sure if this was something worth spending time on or not.
>
> If there weren't existing code that are so tied to their current
> uses of fixed flag bits and assumption that nobody else uses these
> bits outside their intended use, I'd love to have any of these.
> Uncolliding and unbounded number of usable bits per object that are
> *fast* to access would be good (and commit-slab was an attempt to
> introduce a framework that can be used as the basis for such a
> system). Independent of that, if we can statically analyze the uses
> of these bits to prove that the same flag bits are never used at the
> same time for colliding purposes, that would really be valuable.
^ permalink raw reply
* Re: [PATCH v5 0/2] includeIf: add "worktree" condition for matching working tree path
From: Junio C Hamano @ 2026-05-25 7:31 UTC (permalink / raw)
To: Chen Linxuan via B4 Relay
Cc: git, Kristoffer Haugsbakk, Patrick Steinhardt, Chen Linxuan,
Phillip Wood
In-Reply-To: <20260525-includeif-worktree-v5-0-1efe525d025a@black-desk.cn>
Chen Linxuan via B4 Relay <devnull+me.black-desk.cn@kernel.org>
writes:
> Changes in v5:
> - Fix Windows CI failure: use `**` glob pattern instead of `/` in the
> "worktree without repository" tests, since `/` as a path pattern is
> Unix-specific and does not match Windows paths.
Would it have worked if you used something like "[/\\].path",
instead of "/.path", to cover directory delimiters for both systems?
I am not asking to make further changes. I am trying to understand
what the extent of the problem was.
There are tons of [includeIf] that spells path patterns with the
assumption that '/' can be used as the directory separator, like
these lines taken from <master:t/t1305-config-include.sh>:
echo "[includeIf \"gitdir:foo/\"]path=bar" >>.git/config &&
echo "[includeIf \"gitdir:~/foo/\"]path=bar2" >>.git/config &&
echo "[includeIf \"gitdir:**/foo/**\"]path=bar3" >>.git/config &&
echo "[includeIf \"gitdir:./foo/.git\"]path=bar4" >>.gitconfig &&
echo "[includeIf \"gitdir/i:FOO/\"]path=bar5" >>.git/config &&
echo "[includeIf \"gitdir:foo/\"]path=bar6" >>.git/config &&
[includeIf "gitdir:**/foo/**/bar/**"]
echo "[includeIf \"gitdir:~/foo/\"]path=bar2" >>.git/config &&
echo "[includeIf \"gitdir:./foo/.git\"]path=bar4" >home/.gitconfig &&
echo "[includeIf \"gitdir:bar/\"]path=bar7" >>.git/config &&
echo "[includeIf \"gitdir/i:BAR/\"]path=bar8" >>.git/config &&
echo "[includeIf \"onbranch:foo-branch\"]path=bar9" >>.git/config &&
echo "[includeIf \"onbranch:?oo-*/**\"]path=bar10" >>.git/config &&
echo "[includeIf \"onbranch:foo-dir/\"]path=bar11" >>.git/config &&
and there is none, as far as I can tell, that uses a backslash as
directory separator.
Shoudln't the new worktree location code normalize the pathname
before doing a pattern matching so that it would allow '/'-separated
path pattern to match?
FWIW, here is the diff between v4 and v5.
t/t1305-config-include.sh | 17 +++++++++++++++--
1 file changed, 15 insertions(+), 2 deletions(-)
diff --git c/t/t1305-config-include.sh w/t/t1305-config-include.sh
index 07b6fb649c..25f484eec5 100755
--- c/t/t1305-config-include.sh
+++ w/t/t1305-config-include.sh
@@ -462,6 +462,19 @@ test_expect_success SYMLINKS 'conditional include, worktree resolves symlinks' '
)
'
+test_expect_success !CASE_INSENSITIVE_FS 'conditional include, worktree, case sensitive' '
+ git init wt-case &&
+ (
+ cd wt-case &&
+ test_commit initial &&
+ wt_path="$(pwd)" &&
+ wt_upper=$(echo "$wt_path" | tr a-z A-Z) &&
+ echo "[includeIf \"worktree:$wt_upper\"]path=case-inc" >>.git/config &&
+ echo "[test]wtcase=1" >.git/case-inc &&
+ test_must_fail git config test.wtcase
+ )
+'
+
test_expect_success 'conditional include, worktree, icase' '
git init wt-icase &&
(
@@ -495,7 +508,7 @@ test_expect_success 'conditional include, worktree does not match in early confi
test_expect_success 'conditional include, worktree without repository' '
test_when_finished "rm -f .gitconfig config.inc" &&
- git config set -f .gitconfig "includeIf.worktree:/.path" config.inc &&
+ git config set -f .gitconfig "includeIf.worktree:**.path" config.inc &&
git config set -f config.inc foo.bar baz &&
git config get foo.bar &&
test_must_fail nongit git config get foo.bar
@@ -503,7 +516,7 @@ test_expect_success 'conditional include, worktree without repository' '
test_expect_success 'conditional include, worktree without repository but explicit nonexistent Git directory' '
test_when_finished "rm -f .gitconfig config.inc" &&
- git config set -f .gitconfig "includeIf.worktree:/.path" config.inc &&
+ git config set -f .gitconfig "includeIf.worktree:**.path" config.inc &&
git config set -f config.inc foo.bar baz &&
git config get foo.bar &&
test_must_fail nongit git --git-dir=nonexistent config get foo.bar
^ permalink raw reply related
* Re: Expected test suite behavior
From: Jeff King @ 2026-05-25 7:27 UTC (permalink / raw)
To: Michael Montalbo; +Cc: amoghdambal1, git
In-Reply-To: <CAC2QwmKgQW2c6_OhepsB1hzXYHxpX0X4eyQS0dPcxRZLOnCdig@mail.gmail.com>
On Sun, May 24, 2026 at 11:20:54PM -0700, Michael Montalbo wrote:
> Amogh Dambal <amoghdambal1@gmail.com> writes:
> > Hey folks,
> >
> > I wanted to get started hacking on/poking around the Git source, but I'm
> > seeing some behavior with the tests that I can't quite figure out.
> > [...]
> > Is there a README/documentation I've missed reading that can help
> > explain the behavior I'm seeing?
>
> Hello. If you run `make test GIT_TEST_OPTS=--verbose` or uncomment
> L16 of t/Makefile is there more information describing the issue?
You can't use --verbose when running under the "prove" TAP harness
(which the OP seems to be doing). You can use --verbose-log instead, and
then output is in t/test-results/t1234-whatever.out.
However, when debugging tests I find it easier to focus on a single
failing test by running it individually, like:
cd t
./t1234-whatever.sh -v -i -x
That will stop at the first failure (-i), showing the output of all
commands (-v), and additionally enabling shell tracing (-x) so you can
see which command in the test failed.
-Peff
^ permalink raw reply
* Re: [PATCH 1/3] commit-reach: deduplicate queue entries in paint_down_to_common
From: Junio C Hamano @ 2026-05-25 7:17 UTC (permalink / raw)
To: Kristofer Karlsson
Cc: Derrick Stolee, Kristofer Karlsson via GitGitGadget, git
In-Reply-To: <CAL71e4NxpbM8QZYhVA_SSC4vDmAFv-Kpe6qDcurefgPkSSdSnQ@mail.gmail.com>
Kristofer Karlsson <krka@spotify.com> writes:
> While doing the audit I noticed that reasoning about flag safety is
> currently entirely manual. Would there be interest in something more
> systematic (e.g. runtime registration/assertion, dynamic allocation or static
> analysis of flag usage)? I have some local work on that already, but I was
> not sure if this was something worth spending time on or not.
If there weren't existing code that are so tied to their current
uses of fixed flag bits and assumption that nobody else uses these
bits outside their intended use, I'd love to have any of these.
Uncolliding and unbounded number of usable bits per object that are
*fast* to access would be good (and commit-slab was an attempt to
introduce a framework that can be used as the basis for such a
system). Independent of that, if we can statically analyze the uses
of these bits to prove that the same flag bits are never used at the
same time for colliding purposes, that would really be valuable.
^ permalink raw reply
* Re: [PATCH 1/3] commit-reach: deduplicate queue entries in paint_down_to_common
From: Jeff King @ 2026-05-25 7:15 UTC (permalink / raw)
To: Kristofer Karlsson via GitGitGadget; +Cc: git, Kristofer Karlsson
In-Reply-To: <20260525070114.GB2737798@coredump.intra.peff.net>
On Mon, May 25, 2026 at 03:01:14AM -0400, Jeff King wrote:
> When we drop all of those queue elements, they'll all be left with the
> ENQUEUED flag set. Should we clear those?
>
> The ahead_behind() variant doesn't have the same problem, because it
> uses PARENT2 to check for queueing, and then does:
>
> /* STALE is used here, PARENT2 is used by insert_no_dup(). */
> repo_clear_commit_marks(r, PARENT2 | STALE);
>
> So it's cleaning up both flags, whereas paint_down_to_common() is
> already leaving the STALE flag set. I'm not sure how much that matters
> (or if it is even an intentional thing communicated to the caller). But
> now we'd be adding ENQUEUED.
Ah, hmm. We do clear flags in the callers using clear_commit_marks(),
which walks down parent pointers until nobody has a flag we care about
(from all_flags). I'm not 100% sure that ENQUEUED flags will always be
caught that way, but I think the reasoning is roughly: every thing we
queue will either have PARENT1 or PARENT2 set, so we'll keep walking and
clearing flags until we stop seeing those.
-Peff
^ permalink raw reply
* Re: [PATCH 3/3] commit-reach: optimize queue scan in ahead_behind
From: Jeff King @ 2026-05-25 7:11 UTC (permalink / raw)
To: Kristofer Karlsson via GitGitGadget; +Cc: git, Kristofer Karlsson
In-Reply-To: <711a0e2235103489f17ff867439e007abd0e4291.1779644541.git.gitgitgadget@gmail.com>
On Sun, May 24, 2026 at 05:42:20PM +0000, Kristofer Karlsson via GitGitGadget wrote:
> ahead_behind() already deduplicates queue entries using the PARENT2
> flag (via insert_no_dup), so the counter is maintained through
> insert_no_dup() and mark_stale() using PARENT2 as the queued_flag.
That makes sense, but it does raise one question: since we do not clear
the PARENT2 flag upon popping, is it possible to consider a commit a
second time, after it has been popped?
I suspect the answer is "yes", if you have commits with out-of-order
dates (so we visit X, which has PARENT2 set, and then later visit its
descendant, and try to add X again as a parent).
I guess your counter does not make anything worse, though, because the
same PARENT2 flag that prevents us from incrementing the counter also
prevents us from actually adding it to the queue again.
And I think the current code is OK because we do not care about
de-duping the queue, but about not double-counting commits in the global
space. So PARENT2 effectively acts as a "seen" flag here.
-Peff
^ permalink raw reply
* Re: [PATCH 1/3] commit-reach: deduplicate queue entries in paint_down_to_common
From: Jeff King @ 2026-05-25 7:01 UTC (permalink / raw)
To: Kristofer Karlsson via GitGitGadget; +Cc: git, Kristofer Karlsson
In-Reply-To: <1d3751569ba3a5f0c353fb468578d6c5bcd0b738.1779644541.git.gitgitgadget@gmail.com>
On Sun, May 24, 2026 at 05:42:18PM +0000, Kristofer Karlsson via GitGitGadget wrote:
> +static void maybe_enqueue(struct prio_queue *queue, struct commit *c)
> +{
> + if (c->object.flags & ENQUEUED)
> + return;
> + c->object.flags |= ENQUEUED;
> + prio_queue_put(queue, c);
> +}
OK, so we mark each commit with ENQUEUED when we queue it, and then...
> @@ -83,6 +92,8 @@ static int paint_down_to_common(struct repository *r,
> int flags;
> timestamp_t generation = commit_graph_generation(commit);
>
> + commit->object.flags &= ~ENQUEUED;
> +
...clear that when we pop it. But the loop may terminate early before
popping everything, and we get to this cleanup code at the end:
clear_prio_queue(&queue);
When we drop all of those queue elements, they'll all be left with the
ENQUEUED flag set. Should we clear those?
The ahead_behind() variant doesn't have the same problem, because it
uses PARENT2 to check for queueing, and then does:
/* STALE is used here, PARENT2 is used by insert_no_dup(). */
repo_clear_commit_marks(r, PARENT2 | STALE);
So it's cleaning up both flags, whereas paint_down_to_common() is
already leaving the STALE flag set. I'm not sure how much that matters
(or if it is even an intentional thing communicated to the caller). But
now we'd be adding ENQUEUED.
-Peff
^ permalink raw reply
* Re: How does git track history overwrites?
From: Junio C Hamano @ 2026-05-25 6:51 UTC (permalink / raw)
To: Jens Tröger; +Cc: git
In-Reply-To: <089615C1-6526-4ADC-926A-6A232F330DA2@light-speed.de>
Jens Tröger <jens.troeger@light-speed.de> writes:
> Hello,
>
> I’m looking for details and some clarification on a `git fetch` behavior I observed, but can’t quite explain. More context is in this Github comment:
>
> https://github.com/jenstroeger/python-package-template/pull/1190#discussion_r3288253713
>
> but it boils down to this:
>
> /tmp/bla > git -c protocol.version=2 fetch origin dda8db18cfc68df532abf33b185ecd12d5b7b326 --depth=1
>
> It seems that sha dda8db1 (tag 1.20.0 previously pointed at it) was replaced due to a suspected history overwrite with fda7769 (tag 1.20.0 now points at it) and git figures that out:
>
> ...
>
> From https://github.com/adamchainz/blacken-docs
> * branch dda8db18cfc68df532abf33b185ecd12d5b7b326 -> FETCH_HEAD
>
> And then:
>
> /tmp/bla > git checkout FETCH_HEAD
> Note: switching to 'FETCH_HEAD’
>
> ...
>
> HEAD is now at fda7769 Version 1.20.0
>
> And:
>
> /tmp/bla > cat .git/HEAD
> fda77690955e9b63c6687d8806bafd56a526e45f
> /tmp/bla > cat .git/FETCH_HEAD
> dda8db18cfc68df532abf33b185ecd12d5b7b326 'dda8db18cfc68df532abf33b185ecd12d5b7b326' of https://github.com/adamchainz/blacken-docs
>
> I’d like to understand the details some more, and how I could manually make that connection?
Where does this line in your discussion page at GitHub (which is
omitted from the post to this list) come from?
commit fda77690955e9b63c6687d8806bafd56a526e45f (grafted, HEAD)
Are you doing anything funky with .git/info/grafts by any chance?
^ permalink raw reply
* Re: [PATCH 1/3] commit-reach: deduplicate queue entries in paint_down_to_common
From: Kristofer Karlsson @ 2026-05-25 6:50 UTC (permalink / raw)
To: Derrick Stolee; +Cc: Junio C Hamano, Kristofer Karlsson via GitGitGadget, git
In-Reply-To: <ca39c8ca-ca4c-4954-a1ab-633bfa55f64b@gmail.com>
I ran an audit of the flag allocation table and found three stale entries:
1. sha1-name.c was renamed to object-name.c.
2. builtin/show-branch.c uses bits 0 and 2-28, not 0-26.
3. negotiator/skipping.c is missing — it uses bits 2-5 like
negotiator/default.c, with ADVERTISED on bit 3 instead of
COMMON_REF.
I have a fixup commit ready - mini-preview below. I can submit
it on top of this patchset or create a separate patch if you prefer that?
+ * negotiator/skipping.c: 2--5
- * sha1-name.c: 20
+ * object-name.c: 20
- * builtin/show-branch.c: 0-------------------------------------------26
+ * builtin/show-branch.c: 0-----------------------------------------------28
While doing the audit I noticed that reasoning about flag safety is
currently entirely manual. Would there be interest in something more
systematic (e.g. runtime registration/assertion, dynamic allocation or static
analysis of flag usage)? I have some local work on that already, but I was
not sure if this was something worth spending time on or not.
- Kristofer
On Mon, 25 May 2026 at 03:43, Derrick Stolee <stolee@gmail.com> wrote:
>
> On 5/24/26 7:40 PM, Junio C Hamano wrote:
> > "Kristofer Karlsson via GitGitGadget" <gitgitgadget@gmail.com>
> > writes:
> >
> >> diff --git a/commit-reach.c b/commit-reach.c
> >> index d3a9b3ed6f..c16d4b061c 100644
> >> --- a/commit-reach.c
> >> +++ b/commit-reach.c
> >> @@ -17,8 +17,9 @@
> >> #define PARENT2 (1u<<17)
> >> #define STALE (1u<<18)
> >> #define RESULT (1u<<19)
> >> +#define ENQUEUED (1u<<20)
> >>
> >> -static const unsigned all_flags = (PARENT1 | PARENT2 | STALE | RESULT);
> >> +static const unsigned all_flags = (PARENT1 | PARENT2 | STALE | RESULT | ENQUEUED);
> >> ...
> >> diff --git a/object.h b/object.h
> >> index d814647ebe..05cbf728e9 100644
> >> --- a/object.h
> >> +++ b/object.h
> >> @@ -74,7 +74,7 @@ void object_array_init(struct object_array *array);
> >> * bundle.c: 16
> >> * http-push.c: 11-----14
> >> * commit-graph.c: 15
> >> - * commit-reach.c: 16-----19
> >> + * commit-reach.c: 16-------20
> >> * builtin/last-modified.c: 1617
> >> * sha1-name.c: 20
> >> * list-objects-filter.c: 21
> >
> > Not directly the fault of this series, but we'd need to audit and
> > update this table of bit assignment to match more recent reality.
> >
> > For example, there no longer exists sha1-name.c but the table claims
> > that bit 20 is in use for its own purpose, and it being stale makes
> > it harder to audit and ensure that this new use would not crash with
> > these existing uses (note. there are other uses of bit 20 in other
> > subsystems).
>
> It would be worth adding an update patch before this patch, that
> only makes these adjustments
>
> > FWIW, object-name.c, which was formerly known as sha1-name.c, uses
> > the bit 20 as ONELINE_SEEN bit, which is used to turn textual object
> > names like :/string (i.e., commit with that string in its message)
> > into raw object name, and bit 20 is cleared from all the objects
> > involved in the search before the helper function returns.
>
> This appears to me like the only interaction that _could_ have
> overlap with paint_down_to_common().
>
> > Presumably, once commit-reach.c starts queueing commits and reuses
> > this bit for its own purpose, we will never try to parse a textual
> > commit object name to clobber what we thought is ENQUEUED bit,
> > breaking the code introduced here, so we are probably safe against
> > its use.
> >
> > I didn't check all other uses of bit 20, though.
>
> FLAG_LINK in builtin/index-pack.c and FLAG_OPEN in
> builtin/unpack-objects.c both seem to be completely independent from
> this use in commit-reach.c.
>
> Thanks,
> -Stolee
>
>
>
^ permalink raw reply
* Re: [PATCH 0/3] commit-reach: replace queue_has_nonstale with a counter
From: Jeff King @ 2026-05-25 6:47 UTC (permalink / raw)
To: Kristofer Karlsson via GitGitGadget; +Cc: git, Kristofer Karlsson
In-Reply-To: <pull.2124.git.1779644541.gitgitgadget@gmail.com>
On Sun, May 24, 2026 at 05:42:17PM +0000, Kristofer Karlsson via GitGitGadget wrote:
> paint_down_to_common() and ahead_behind() terminate when every commit in
> their priority queue is STALE. The current check, queue_has_nonstale(), does
> an O(n) linear scan of the queue on every iteration, costing O(n*m) total
> where n is the queue size and m is the number of commits processed. This
> series replaces that scan with an O(1) counter.
We faced a similar problem in limit_list() but solved it a bit
differently (mostly because I was worried about keeping the counter up
to date in all cases).
It's described in more detail in b6e8a3b540 (limit_list: avoid quadratic
behavior from still_interesting, 2015-04-17), but the general idea is to
just cache the interesting element we found, and invalidate the cache
when it gets removed from the queue or gets marked UNINTERESTING.
The equivalent code for the STALE flag here is something like this:
diff --git a/commit-reach.c b/commit-reach.c
index d3a9b3ed6f..d1621be89f 100644
--- a/commit-reach.c
+++ b/commit-reach.c
@@ -39,12 +39,25 @@ static int compare_commits_by_gen(const void *_a, const void *_b)
return 0;
}
-static int queue_has_nonstale(struct prio_queue *queue)
+static int queue_has_nonstale(struct prio_queue *queue,
+ struct commit **nonstale_cache)
{
+ if (*nonstale_cache) {
+ struct commit *commit = *nonstale_cache;
+ if (!(commit->object.flags & STALE))
+ return 1;
+ }
+
+ /*
+ * This might also benefit from looking back-to-front, since
+ * earlier commits are more likely to get popped sooner.
+ */
for (size_t i = 0; i < queue->nr; i++) {
struct commit *commit = queue->array[i].data;
- if (!(commit->object.flags & STALE))
+ if (!(commit->object.flags & STALE)) {
+ *nonstale_cache = commit;
return 1;
+ }
}
return 0;
}
@@ -61,6 +74,7 @@ static int paint_down_to_common(struct repository *r,
int i;
timestamp_t last_gen = GENERATION_NUMBER_INFINITY;
struct commit_list **tail = result;
+ struct commit *nonstale_cache = NULL;
if (!min_generation && !corrected_commit_dates_enabled(r))
queue.compare = compare_commits_by_commit_date;
@@ -77,12 +91,15 @@ static int paint_down_to_common(struct repository *r,
prio_queue_put(&queue, twos[i]);
}
- while (queue_has_nonstale(&queue)) {
+ while (queue_has_nonstale(&queue, &nonstale_cache)) {
struct commit *commit = prio_queue_get(&queue);
struct commit_list *parents;
int flags;
timestamp_t generation = commit_graph_generation(commit);
+ if (nonstale_cache == commit)
+ nonstale_cache = NULL;
+
if (min_generation && generation > last_gen)
BUG("bad generation skip %"PRItime" > %"PRItime" at %s",
generation, last_gen,
@@ -1053,6 +1070,7 @@ void ahead_behind(struct repository *r,
{
struct prio_queue queue = { .compare = compare_commits_by_gen_then_commit_date };
size_t width = DIV_ROUND_UP(commits_nr, BITS_IN_EWORD);
+ struct commit *nonstale_cache = NULL;
if (!commits_nr || !counts_nr)
return;
@@ -1074,11 +1092,14 @@ void ahead_behind(struct repository *r,
insert_no_dup(&queue, c);
}
- while (queue_has_nonstale(&queue)) {
+ while (queue_has_nonstale(&queue, &nonstale_cache)) {
struct commit *c = prio_queue_get(&queue);
struct commit_list *p;
struct bitmap *bitmap_c = get_bit_array(c, width);
+ if (c == nonstale_cache)
+ nonstale_cache = NULL;
+
for (size_t i = 0; i < counts_nr; i++) {
int reach_from_tip = !!bitmap_get(bitmap_c, counts[i].tip_index);
int reach_from_base = !!bitmap_get(bitmap_c, counts[i].base_index);
I don't have a repo handy which reproduces the problem, so I can't see
if it improves things. But if it's easy to do, can you report on the
timing change with your monorepo?
I do think what I've shown here is a bit hacky (just like the
limit_list() one), as we are relying on heuristics about the order in
which items are taken from the queue. So even if it performs well, we
may still prefer the counter version for being truly O(1). But having
timing numbers would be useful for comparing the two approaches.
-Peff
^ permalink raw reply related
* Re: Expected test suite behavior
From: Michael Montalbo @ 2026-05-25 6:20 UTC (permalink / raw)
To: amoghdambal1; +Cc: git
Amogh Dambal <amoghdambal1@gmail.com> writes:
> Hey folks,
>
> I wanted to get started hacking on/poking around the Git source, but I'm
> seeing some behavior with the tests that I can't quite figure out.
> [...]
> Is there a README/documentation I've missed reading that can help
> explain the behavior I'm seeing?
>
Hello. If you run `make test GIT_TEST_OPTS=--verbose` or uncomment
L16 of t/Makefile is there more information describing the issue?
^ permalink raw reply
* Expected test suite behavior
From: Amogh Dambal @ 2026-05-25 5:38 UTC (permalink / raw)
To: git
[-- Attachment #1: Type: text/plain, Size: 2761 bytes --]
Hey folks,
I wanted to get started hacking on/poking around the Git source, but I'm
seeing some behavior with the tests that I can't quite figure out.
I've downloaded the full source tree from the GitHub mirror:
https://github.com/git/git/tree/master. I'm using a Docker container as
a development environment with the following information:
root@5d8a4c9110e2:~/git# uname -a
Linux 5d8a4c9110e2 6.10.14-linuxkit #1 SMP Mon Feb 24 16:35:16 UTC 2025
aarch64 GNU/Linux
root@5d8a4c9110e2:~/git# cat /etc/os-release
PRETTY_NAME="Debian GNU/Linux 13 (trixie)"
NAME="Debian GNU/Linux"
VERSION_ID="13"
VERSION="13 (trixie)"
VERSION_CODENAME=trixie
DEBIAN_VERSION_FULL=13.5
ID=debian
HOME_URL="https://www.debian.org/"
SUPPORT_URL="https://www.debian.org/support"
BUG_REPORT_URL="https://bugs.debian.org/"
The Docker container is run like so: `docker run -it -v $(PWD):/root/git
-w /root/git git-debian:latest` from an image built from the following
Dockerfile:
FROM debian:stable
LABEL Description="Debian (stable) build environment for Git"
# Install Git library dependencies
# https://git-scm.com/book/en/v2/Getting-Started-Installing-Git
RUN apt-get update && apt-get upgrade && \
apt-get install -y dh-autoreconf libcurl4-gnutls-dev libexpat1-dev \
gettext libz-dev libssl-dev asciidoc xmlto docbook2x \
make cargo cvs subversion install-info
# Set environment variables
ENV HOME=/root
# Launch the bash shell by default
CMD ["/bin/bash"]
I'm able to successfully run `make` + `make configure` + `make all doc
info` but `make test` fails, the following tests specifically:
not ok 1 - plain
not ok 2 - plain nested in bare
not ok 3 - plain through aliased command, outside any git repo
not ok 4 - plain nested through aliased command
not ok 5 - plain nested in bare through aliased command
not ok 8 - plain bare
not ok 10 - GIT_DIR bare
not ok 11 - init --bare
not ok 12 - GIT_DIR non-bare
not ok 13 - GIT_DIR & GIT_WORK_TREE (1)
not ok 20 - init --bare/--shared overrides system/global config
not ok 28 - init creates a new deep directory (umask vs. shared)
not ok 63 - extensions.refStorage with files backend
as does `prove -j$(nproc) --shuffle t[0-9]*.sh`. I've attached the full
test summary which includes all failed tests (it's 17KB, hence why not
inline). All the failures report `(Wstat: 256 (exited 1)`.
I've read through Documentation/MyFirstContribution.adoc and t/README,
and gone through the mailing list archives, but I'm not seeing anything
outlining why these tests would fail, nor am I seeing a common pattern
in the failing test scripts that'd point to e.g. a missing dependency.
Is there a README/documentation I've missed reading that can help
explain the behavior I'm seeing?
Thanks,
Amogh
[-- Attachment #2: git-failed-tests.tmp --]
[-- Type: text/plain, Size: 17341 bytes --]
Test Summary Report
-------------------
t4253-am-keep-cr-dos.sh (Wstat: 256 (exited 1) Tests: 7 Failed: 2)
Failed tests: 3-4
Non-zero exit status: 1
t3437-rebase-fixup-options.sh (Wstat: 256 (exited 1) Tests: 13 Failed: 11)
Failed tests: 2-4, 6-13
Non-zero exit status: 1
t1301-shared-repo.sh (Wstat: 256 (exited 1) Tests: 21 Failed: 6)
Failed tests: 6-8, 12, 14, 16
Non-zero exit status: 1
t3436-rebase-more-options.sh (Wstat: 256 (exited 1) Tests: 19 Failed: 14)
Failed tests: 3-15, 18
Non-zero exit status: 1
t7012-skip-worktree-writing.sh (Wstat: 256 (exited 1) Tests: 11 Failed: 2)
Failed tests: 4, 11
Non-zero exit status: 1
t9350-fast-export.sh (Wstat: 256 (exited 1) Tests: 0 Failed: 0)
Non-zero exit status: 1
Parse errors: No plan found in TAP output
t3507-cherry-pick-conflict.sh (Wstat: 256 (exited 1) Tests: 44 Failed: 4)
Failed tests: 17-18, 33-34
Non-zero exit status: 1
t4137-apply-submodule.sh (Wstat: 256 (exited 1) Tests: 28 Failed: 3)
Failed tests: 3, 17-18
Non-zero exit status: 1
t6432-merge-recursive-space-options.sh (Wstat: 256 (exited 1) Tests: 11 Failed: 7)
Failed tests: 3, 6-11
Non-zero exit status: 1
t3401-rebase-and-am-rename.sh (Wstat: 256 (exited 1) Tests: 10 Failed: 3)
Failed tests: 7-9
Non-zero exit status: 1
t5517-push-mirror.sh (Wstat: 256 (exited 1) Tests: 13 Failed: 2)
Failed tests: 8, 13
Non-zero exit status: 1
t3429-rebase-edit-todo.sh (Wstat: 256 (exited 1) Tests: 7 Failed: 1)
Failed test: 5
Non-zero exit status: 1
t5545-push-options.sh (Wstat: 256 (exited 1) Tests: 10 Failed: 1)
Failed test: 10
Non-zero exit status: 1
t6427-diff3-conflict-markers.sh (Wstat: 256 (exited 1) Tests: 9 Failed: 1)
Failed test: 7
Non-zero exit status: 1
t0610-reftable-basics.sh (Wstat: 256 (exited 1) Tests: 91 Failed: 2)
Failed tests: 32, 62
Non-zero exit status: 1
t6041-bisect-submodule.sh (Wstat: 256 (exited 1) Tests: 14 Failed: 2)
Failed tests: 3-4
Non-zero exit status: 1
t3910-mac-os-precompose.sh (Wstat: 256 (exited 1) Tests: 29 Failed: 10)
Failed tests: 1, 4, 6, 8, 11-16
TODO passed: 23
Non-zero exit status: 1
t4012-diff-binary.sh (Wstat: 256 (exited 1) Tests: 13 Failed: 1)
Failed test: 11
Non-zero exit status: 1
t4128-apply-root.sh (Wstat: 256 (exited 1) Tests: 12 Failed: 5)
Failed tests: 3-7
Non-zero exit status: 1
t3428-rebase-signoff.sh (Wstat: 256 (exited 1) Tests: 7 Failed: 2)
Failed tests: 6-7
Non-zero exit status: 1
t5527-fetch-odd-refs.sh (Wstat: 256 (exited 1) Tests: 5 Failed: 2)
Failed tests: 4-5
Non-zero exit status: 1
t4151-am-abort.sh (Wstat: 256 (exited 1) Tests: 20 Failed: 2)
Failed tests: 5-6
Non-zero exit status: 1
t6430-merge-recursive.sh (Wstat: 256 (exited 1) Tests: 36 Failed: 6)
Failed tests: 19-20, 23-26
Non-zero exit status: 1
t1450-fsck.sh (Wstat: 256 (exited 1) Tests: 96 Failed: 2)
Failed tests: 71, 74
Non-zero exit status: 1
t4114-apply-typechange.sh (Wstat: 256 (exited 1) Tests: 12 Failed: 8)
Failed tests: 2-6, 8-9, 12
Non-zero exit status: 1
t1423-ref-backend.sh (Wstat: 256 (exited 1) Tests: 36 Failed: 1)
Failed test: 22
Non-zero exit status: 1
t3403-rebase-skip.sh (Wstat: 256 (exited 1) Tests: 20 Failed: 4)
Failed tests: 5, 10-12
Non-zero exit status: 1
t0050-filesystem.sh (Wstat: 256 (exited 1) Tests: 13 Failed: 1)
Failed test: 7
Non-zero exit status: 1
t3453-history-fixup.sh (Wstat: 256 (exited 1) Tests: 28 Failed: 2)
Failed tests: 11, 20
Non-zero exit status: 1
t4129-apply-samemode.sh (Wstat: 256 (exited 1) Tests: 23 Failed: 3)
Failed tests: 11, 21, 23
Non-zero exit status: 1
t3404-rebase-interactive.sh (Wstat: 256 (exited 1) Tests: 133 Failed: 62)
Failed tests: 17-23, 26-43, 46-48, 50, 52-77, 81, 86-88
101-102, 118
Non-zero exit status: 1
t3452-history-split.sh (Wstat: 256 (exited 1) Tests: 25 Failed: 3)
Failed tests: 4, 13, 16
Non-zero exit status: 1
t3510-cherry-pick-sequence.sh (Wstat: 256 (exited 1) Tests: 55 Failed: 3)
Failed tests: 9, 24, 33
Non-zero exit status: 1
t6438-submodule-directory-file-conflicts.sh (Wstat: 256 (exited 1) Tests: 56 Failed: 7)
Failed tests: 3-4, 17-18, 31, 45-46
Non-zero exit status: 1
t4150-am.sh (Wstat: 256 (exited 1) Tests: 87 Failed: 8)
Failed tests: 25, 27-32, 70
Non-zero exit status: 1
t5702-protocol-v2.sh (Wstat: 256 (exited 1) Tests: 60 Failed: 1)
Failed test: 52
Non-zero exit status: 1
t0602-reffiles-fsck.sh (Wstat: 256 (exited 1) Tests: 23 Failed: 1)
Failed test: 3
Non-zero exit status: 1
t1091-sparse-checkout-builtin.sh (Wstat: 256 (exited 1) Tests: 77 Failed: 1)
Failed test: 31
Non-zero exit status: 1
t4051-diff-function-context.sh (Wstat: 256 (exited 1) Tests: 42 Failed: 6)
Failed tests: 3, 11, 17, 28, 34, 39
Non-zero exit status: 1
t4126-apply-empty.sh (Wstat: 256 (exited 1) Tests: 8 Failed: 1)
Failed test: 5
Non-zero exit status: 1
t2400-worktree-add.sh (Wstat: 256 (exited 1) Tests: 232 Failed: 9)
Failed tests: 109, 115, 130, 140, 144, 164, 176, 178
209
Non-zero exit status: 1
t9300-fast-import.sh (Wstat: 256 (exited 1) Tests: 256 Failed: 1)
Failed test: 101
Non-zero exit status: 1
t1410-reflog.sh (Wstat: 256 (exited 1) Tests: 41 Failed: 1)
Failed test: 34
Non-zero exit status: 1
t1001-read-tree-m-2way.sh (Wstat: 256 (exited 1) Tests: 29 Failed: 8)
Failed tests: 3-6, 14, 17-19
Non-zero exit status: 1
t4255-am-submodule.sh (Wstat: 256 (exited 1) Tests: 33 Failed: 3)
Failed tests: 3, 17-18
Non-zero exit status: 1
t2080-parallel-checkout-basics.sh (Wstat: 256 (exited 1) Tests: 11 Failed: 2)
Failed tests: 1, 3
Non-zero exit status: 1
t3408-rebase-multi-line.sh (Wstat: 256 (exited 1) Tests: 2 Failed: 1)
Failed test: 2
Non-zero exit status: 1
t3513-revert-submodule.sh (Wstat: 256 (exited 1) Tests: 14 Failed: 2)
Failed tests: 3-4
Non-zero exit status: 1
t3508-cherry-pick-many-commits.sh (Wstat: 256 (exited 1) Tests: 14 Failed: 12)
Failed tests: 2-3, 5-14
Non-zero exit status: 1
t7512-status-help.sh (Wstat: 256 (exited 1) Tests: 47 Failed: 1)
Failed test: 9
Non-zero exit status: 1
t1015-read-index-unmerged.sh (Wstat: 256 (exited 1) Tests: 6 Failed: 1)
Failed test: 5
Non-zero exit status: 1
t3700-add.sh (Wstat: 256 (exited 1) Tests: 58 Failed: 1)
Failed test: 49
Non-zero exit status: 1
t4116-apply-reverse.sh (Wstat: 256 (exited 1) Tests: 7 Failed: 2)
Failed tests: 2-3
Non-zero exit status: 1
t7600-merge.sh (Wstat: 256 (exited 1) Tests: 83 Failed: 1)
Failed test: 63
Non-zero exit status: 1
t1002-read-tree-m-u-2way.sh (Wstat: 256 (exited 1) Tests: 22 Failed: 3)
Failed tests: 2, 19, 22
Non-zero exit status: 1
t7503-pre-commit-and-pre-merge-commit-hooks.sh (Wstat: 256 (exited 1) Tests: 22 Failed: 1)
Failed test: 16
Non-zero exit status: 1
t4301-merge-tree-write-tree.sh (Wstat: 256 (exited 1) Tests: 44 Failed: 3)
Failed tests: 7, 33, 37
Non-zero exit status: 1
t1004-read-tree-m-u-wf.sh (Wstat: 256 (exited 1) Tests: 17 Failed: 2)
Failed tests: 10, 15
Non-zero exit status: 1
t3012-ls-files-dedup.sh (Wstat: 256 (exited 1) Tests: 3 Failed: 2)
Failed tests: 2-3
Non-zero exit status: 1
t7110-reset-merge.sh (Wstat: 256 (exited 1) Tests: 21 Failed: 7)
Failed tests: 3-4, 7, 9-10, 12, 19
Non-zero exit status: 1
t3426-rebase-submodule.sh (Wstat: 256 (exited 1) Tests: 29 Failed: 17)
Failed tests: 2-6, 11-13, 15-20, 25-27
Non-zero exit status: 1
t5526-fetch-submodules.sh (Wstat: 256 (exited 1) Tests: 56 Failed: 11)
Failed tests: 27-31, 40-45
Non-zero exit status: 1
t3400-rebase.sh (Wstat: 256 (exited 1) Tests: 39 Failed: 2)
Failed tests: 12-13
Non-zero exit status: 1
t0020-crlf.sh (Wstat: 256 (exited 1) Tests: 0 Failed: 0)
Non-zero exit status: 1
Parse errors: No plan found in TAP output
t3405-rebase-malformed.sh (Wstat: 256 (exited 1) Tests: 5 Failed: 2)
Failed tests: 3-4
Non-zero exit status: 1
t2201-add-update-typechange.sh (Wstat: 256 (exited 1) Tests: 6 Failed: 5)
Failed tests: 2-6
Non-zero exit status: 1
t4014-format-patch.sh (Wstat: 256 (exited 1) Tests: 226 Failed: 31)
Failed tests: 1, 3-4, 6-7, 10-11, 77, 162-179, 182, 184-185
197-198
Non-zero exit status: 1
t2013-checkout-submodule.sh (Wstat: 256 (exited 1) Tests: 74 Failed: 13)
Failed tests: 9-10, 14, 24, 30, 34, 41-43, 49-50, 63-64
Non-zero exit status: 1
t7900-maintenance.sh (Wstat: 256 (exited 1) Tests: 75 Failed: 1)
Failed test: 74
Non-zero exit status: 1
t1013-read-tree-submodule.sh (Wstat: 256 (exited 1) Tests: 68 Failed: 12)
Failed tests: 3-4, 8, 18, 23-24, 28, 36-37, 44, 57-58
Non-zero exit status: 1
t7611-merge-abort.sh (Wstat: 256 (exited 1) Tests: 19 Failed: 6)
Failed tests: 7-10, 12-13
Non-zero exit status: 1
t3903-stash.sh (Wstat: 256 (exited 1) Tests: 144 Failed: 3)
Failed tests: 18, 57, 136
Non-zero exit status: 1
t3406-rebase-message.sh (Wstat: 256 (exited 1) Tests: 32 Failed: 6)
Failed tests: 12-14, 17-19
Non-zero exit status: 1
t5408-send-pack-stdin.sh (Wstat: 256 (exited 1) Tests: 10 Failed: 1)
Failed test: 6
Non-zero exit status: 1
t3402-rebase-merge.sh (Wstat: 256 (exited 1) Tests: 13 Failed: 7)
Failed tests: 6-10, 12-13
Non-zero exit status: 1
t6423-merge-rename-directories.sh (Wstat: 256 (exited 1) Tests: 82 Failed: 1)
Failed test: 59
Non-zero exit status: 1
t7814-grep-recurse-submodules.sh (Wstat: 256 (exited 1) Tests: 34 Failed: 1)
Failed test: 19
Non-zero exit status: 1
t7505-prepare-commit-msg-hook.sh (Wstat: 256 (exited 1) Tests: 23 Failed: 1)
Failed test: 16
Non-zero exit status: 1
t5326-multi-pack-bitmaps.sh (Wstat: 256 (exited 1) Tests: 357 Failed: 2)
Failed tests: 219, 356
Non-zero exit status: 1
t7031-verify-tag-signed-ssh.sh (Wstat: 256 (exited 1) Tests: 14 Failed: 7)
Failed tests: 1, 3, 8-11, 14
Non-zero exit status: 1
t5520-pull.sh (Wstat: 256 (exited 1) Tests: 80 Failed: 3)
Failed tests: 25-26, 79
Non-zero exit status: 1
t3415-rebase-autosquash.sh (Wstat: 256 (exited 1) Tests: 28 Failed: 23)
Failed tests: 2-24
Non-zero exit status: 1
t3512-cherry-pick-submodule.sh (Wstat: 256 (exited 1) Tests: 15 Failed: 2)
Failed tests: 3-4
Non-zero exit status: 1
t3600-rm.sh (Wstat: 256 (exited 1) Tests: 82 Failed: 0)
TODO passed: 73
Non-zero exit status: 1
t3407-rebase-abort.sh (Wstat: 256 (exited 1) Tests: 17 Failed: 2)
Failed tests: 13-14
Non-zero exit status: 1
t5570-git-daemon.sh (Wstat: 256 (exited 1) Tests: 16 Failed: 0)
Non-zero exit status: 1
Parse errors: No plan found in TAP output
t7615-diff-algo-with-mergy-operations.sh (Wstat: 256 (exited 1) Tests: 7 Failed: 1)
Failed test: 3
Non-zero exit status: 1
t0021-conversion.sh (Wstat: 256 (exited 1) Tests: 42 Failed: 1)
Failed test: 24
Non-zero exit status: 1
t0001-init.sh (Wstat: 256 (exited 1) Tests: 0 Failed: 0)
Non-zero exit status: 1
Parse errors: No plan found in TAP output
t2009-checkout-statinfo.sh (Wstat: 256 (exited 1) Tests: 3 Failed: 2)
Failed tests: 2-3
Non-zero exit status: 1
t3905-stash-include-untracked.sh (Wstat: 256 (exited 1) Tests: 34 Failed: 1)
Failed test: 1
Non-zero exit status: 1
t7528-signed-commit-ssh.sh (Wstat: 256 (exited 1) Tests: 29 Failed: 13)
Failed tests: 1, 4-5, 10-11, 13, 16-17, 19-23
Non-zero exit status: 1
t3420-rebase-autostash.sh (Wstat: 256 (exited 1) Tests: 52 Failed: 17)
Failed tests: 26-32, 35-36, 38-45
Non-zero exit status: 1
t3418-rebase-continue.sh (Wstat: 256 (exited 1) Tests: 30 Failed: 8)
Failed tests: 3, 8, 20, 26-30
Non-zero exit status: 1
t3434-rebase-i18n.sh (Wstat: 256 (exited 1) Tests: 6 Failed: 3)
Failed tests: 4-6
Non-zero exit status: 1
t4108-apply-threeway.sh (Wstat: 256 (exited 1) Tests: 18 Failed: 2)
Failed tests: 16-17
Non-zero exit status: 1
t3417-rebase-whitespace-fix.sh (Wstat: 256 (exited 1) Tests: 4 Failed: 1)
Failed test: 4
Non-zero exit status: 1
t3900-i18n-commit.sh (Wstat: 256 (exited 1) Tests: 0 Failed: 0)
Non-zero exit status: 1
Parse errors: No plan found in TAP output
t4045-diff-relative.sh (Wstat: 256 (exited 1) Tests: 39 Failed: 1)
Failed test: 38
Non-zero exit status: 1
t4127-apply-same-fn.sh (Wstat: 256 (exited 1) Tests: 7 Failed: 1)
Failed test: 5
Non-zero exit status: 1
t6422-merge-rename-corner-cases.sh (Wstat: 256 (exited 1) Tests: 26 Failed: 1)
Failed test: 20
Non-zero exit status: 1
t7504-commit-msg-hook.sh (Wstat: 256 (exited 1) Tests: 30 Failed: 2)
Failed tests: 19-20
Non-zero exit status: 1
t5407-post-rewrite-hook.sh (Wstat: 256 (exited 1) Tests: 17 Failed: 10)
Failed tests: 7-8, 10-17
Non-zero exit status: 1
t5572-pull-submodule.sh (Wstat: 256 (exited 1) Tests: 69 Failed: 7)
Failed tests: 3-4, 17-18, 31-32, 46
Non-zero exit status: 1
t6428-merge-conflicts-sparse.sh (Wstat: 256 (exited 1) Tests: 2 Failed: 2)
Failed tests: 1-2
Non-zero exit status: 1
t7519-status-fsmonitor.sh (Wstat: 256 (exited 1) Tests: 33 Failed: 5)
Failed tests: 13, 18, 21, 25, 28
Non-zero exit status: 1
t3440-rebase-trailer.sh (Wstat: 256 (exited 1) Tests: 10 Failed: 6)
Failed tests: 5-10
Non-zero exit status: 1
t7001-mv.sh (Wstat: 256 (exited 1) Tests: 54 Failed: 3)
Failed tests: 1, 48-49
Non-zero exit status: 1
t7402-submodule-rebase.sh (Wstat: 256 (exited 1) Tests: 6 Failed: 3)
Failed tests: 2-3, 6
Non-zero exit status: 1
t7520-ignored-hook-warning.sh (Wstat: 256 (exited 1) Tests: 5 Failed: 2)
Failed tests: 3-4
Non-zero exit status: 1
t5516-fetch-push.sh (Wstat: 256 (exited 1) Tests: 125 Failed: 4)
Failed tests: 23, 28, 67, 83
Non-zero exit status: 1
t3705-add-sparse-checkout.sh (Wstat: 256 (exited 1) Tests: 20 Failed: 1)
Failed test: 11
Non-zero exit status: 1
t5329-pack-objects-cruft.sh (Wstat: 256 (exited 1) Tests: 25 Failed: 1)
Failed test: 4
Non-zero exit status: 1
t7112-reset-submodule.sh (Wstat: 256 (exited 1) Tests: 82 Failed: 15)
Failed tests: 3-4, 8, 18, 23-24, 28, 36-37, 43-44, 57-58
71-72
Non-zero exit status: 1
t5310-pack-bitmaps.sh (Wstat: 256 (exited 1) Tests: 236 Failed: 1)
Failed test: 203
Non-zero exit status: 1
t1092-sparse-checkout-compatibility.sh (Wstat: 256 (exited 1) Tests: 106 Failed: 41)
Failed tests: 2, 10-11, 13, 15, 22, 24-25, 28, 32-40
42, 44, 49-52, 55, 57, 65, 71, 80, 86, 88-89
91, 93, 96, 98-100, 102-104
Non-zero exit status: 1
Files=1044, Tests=32622, 878 wallclock secs ( 4.92 usr 1.95 sys + 263.79 cusr 1013.76 csys = 1284.42 CPU)
Result: FAIL
^ permalink raw reply
* [PATCH] test-lib: fix typo in test summary message
From: Amogh @ 2026-05-25 5:36 UTC (permalink / raw)
To: git; +Cc: Amogh
There's a small typo ("passin", should be "passing") in the
summary/description message for t0000-basic. Even though this isn't a
user-facing string it should improve the developer experience + reduce
confusion when working on the codebase.
---
t/t0000-basic.sh | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/t/t0000-basic.sh b/t/t0000-basic.sh
index 2b63e1c86c..9f6991a9ab 100755
--- a/t/t0000-basic.sh
+++ b/t/t0000-basic.sh
@@ -141,7 +141,7 @@ test_expect_success 'subtest: a passing TODO test' '
EOF
'
-test_expect_success 'subtest: 2 TODO tests, one passin' '
+test_expect_success 'subtest: 2 TODO tests, one passing' '
write_and_run_sub_test_lib_test_err partially-passing-todos <<-\EOF &&
test_expect_failure "pretend we have a known breakage" "false"
test_expect_success "pretend we have a passing test" "true"
--
2.39.5 (Apple Git-154)
^ permalink raw reply related
* Re: How does git track history overwrites?
From: Chris Torek @ 2026-05-25 3:46 UTC (permalink / raw)
To: Jens Tröger; +Cc: git
In-Reply-To: <089615C1-6526-4ADC-926A-6A232F330DA2@light-speed.de>
On Sun, May 24, 2026 at 4:44 PM Jens Tröger <jens.troeger@light-speed.de> wrote:
> I’m looking for details and some clarification on a `git fetch` behavior I observed, but can’t quite explain. ...
This isn't really specific to "git fetch" at all, except for the
usage of FETCH_HEAD.
To really understand this properly, we need to understand
the root of a seeming contradiction:
1. Once saved in Git, no commit (in fact, no internal object of any sort)
can ever be changed.
2. And yet, "git rebase" and force-push operations seem to rewrite
history.
How can commits be immutable and yet rewrite-able? The trick here
lies in how we (humans) *find* commits.
Inside a Git repository, the "true name" of any commit (or indeed
any internal object) is its raw hash ID, such as your example of
dda8db18cfc68df532abf33b185ecd12d5b7b326. The hash ID (or
"object ID", though right now there are only two forms, a SHA1
hash or a SHA256 hash) is specific to that one object once it is
created, and forever more can never be used for any other object.
It will always mean that original object, as long as that object
exists.
Thus, as long as that commit exists, it's *that* commit, with *that*
ID, and no other.
But we (humans) don't *use* hash IDs. They're too cumbersome.
So Git provides us with the ability to translate a name to an ID:
> It seems that sha dda8db1 (tag 1.20.0 previously pointed at it)
The *name* refs/tags/1.20.0 used to produce the above ID.
> was replaced ... with fda7769 (tag 1.20.0 now points at it)
Some human directed Git to forcibly replace the hash ID associated
with the tag, in some repository or repositories.
(As the manuals note, this kind of forcible replacement of tags is
often a bad idea. It's usually better, once the tag has escaped the
confinement of a single repository anyway, to just admit that you
goofed up and make a new tag.)
If you use raw hash IDs, you can never be bitten by this kind of
tag replacement, but of course that's a bad idea for different
(and presumably obvious) reasons. I couldn't possibly name the
hash ID without using cut-and-paste here. I can *type* "1.20.0"
repeatedly without error though.
(There are additional considerations, having to do with how Git
cleans up unwanted leftover junk, via git gc / git maintenance. In
particular Git uses the human-readable names to figure out which
objects are useful, and which are unwanted junk. So you have to
identify *some* commits with names, or they'll eventually get
garbage-collected.)
[At this point, you ran git fetch with a raw hash ID, and:]
> From https://github.com/adamchainz/blacken-docs
> * branch dda8db18cfc68df532abf33b185ecd12d5b7b326 -> FETCH_HEAD
When git fetch obtains something from another different Git repository,
the new things have the same IDs in both repositories. Normally we do
this by *name* (branch or tag name), but for historical reasons, the fetch
operation deposits a hash ID (often along with additional information)
in the file `.git/FETCH_HEAD`. This file then works as a pseudo-name
for the branch, tag, or commit(s) thus obtained:
> And then:
>
> /tmp/bla > git checkout FETCH_HEAD
> Note: switching to 'FETCH_HEAD’
This gives you a "detached HEAD" state, using the hash ID stored in
.git/FETCH_HEAD. That hash ID will be overwritten (thus lost) by the
*next* git fetch, so you're expected to save it in some more-permanent
name if you want it to stick around.
The key difference between a branch name and a tag name is that
branch names are *expected* to map to different hash IDs over time,
with updates adding new commits to the branch causing the branch
name to remember the latest commit's ID. Each commit in turn
remembers the IDs of its parent commit or commits, so knowing
the *last* one suffices to allow Git to find *every* one.
Rewriting history with rebase consists of copying old (presumably
bad) commits to new (presumably good/better) ones, whose backwards
links to each previous commit chain through the new-and-improved
commits until you reach the point where the rewrite joins existing
history. Then we update the branch name to remember the latest
of the new-and-improved commits, and it *seems* that we've changed
history. The old history is still in there, and will stick around for quite
a while (at least a month by default, in standard clones) "just in case".
Tag names are not supposed to move, and whether someone else's tag
update to their clone changes your own clone's tags is something
you can control to some extent. It's not a good idea to depend on
other people's clones to follow tag changes, but it's also not a
good idea to depend on your own or other people's clones *not* to
follow such changes, since both behaviors are possible.
Chris
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox