]> Kevux Git Server - rit/log
rit
8 months agoobject: fix performance regression when peeling tags
Patrick Steinhardt [Thu, 6 Nov 2025 08:52:54 +0000 (09:52 +0100)]
object: fix performance regression when peeling tags

Our Bencher dashboards [1] have recently alerted us about a bunch of
performance regressions when writing references, specifically with the
reftable backend. There is a 3x regression when writing many refs with
preexisting refs in the reftable format, and a 10x regression when
migrating refs between backends in either of the formats.

Bisecting the issue lands us at 6ec4c0b45b (refs: don't store peeled
object IDs for invalid tags, 2025-10-23). The gist of the commit is that
we may end up storing peeled objects in both reftables and packed-refs
for corrupted tags, where the claimed tagged object type is different
than the actual tagged object type. This will then cause us to create
the `struct object *` with a wrong type, as well, and obviously nothing
good comes out of that.

The fix for this issue was to introduce a new flag to `peel_object()`
that causes us to verify the tagged object's type before writing it into
the refdb -- if the tag is corrupt, we skip writing the peeled value.
To verify whether the peeled value is correct we have to look up the
object type via the ODB and compare the actual type with the claimed
type, and that additional object lookup is costly.

This also explains why we see the regression only when writing refs with
the reftable backend, but we see the regression with both backends when
migrating refs:

  - The reftable backend knows to store peeled values in the new table
    immediately, so it has to try and peel each ref it's about to write
    to the transaction. So the performance regression is visible for all
    writes.

  - The files backend only stores peeled values when writing the
    packed-refs file, so it wouldn't hit the performance regression for
    normal writes. But on ref migrations we know to write all new values
    into the packed-refs file immediately, and that's why we see the
    regression for both backends there.

Taking a step back though reveals an oddity in the new verification
logic: we not only verify the _tagged_ object's type, but we also verify
the type of the tag itself. But this isn't really needed, as we wouldn't
hit the bug in such a case anyway, as we only hit the issue with corrupt
tags claiming an invalid type for the tagged object.

The consequence of this is that we now started to look up the target
object of every single reference we're about to write, regardless of
whether it even is a tag or not. And that is of course quite costly.

Fix the issue by only verifying the type of the tagged objects. This
means that we of course still have a performance hit for actual tags.
But this only happens for writes anyway, and I'd claim it's preferable
to not store corrupted data in the refdb than to be fast here. Rename
the flag accordingly to clarify that we only verify the tagged object's
type.

This fix brings performance back to previous levels:

    Benchmark 1: baseline
      Time (mean ± σ):      46.0 ms ±   0.4 ms    [User: 40.0 ms, System: 5.7 ms]
      Range (min … max):    45.0 ms …  47.1 ms    54 runs

    Benchmark 2: regression
      Time (mean ± σ):     140.2 ms ±   1.3 ms    [User: 77.5 ms, System: 60.5 ms]
      Range (min … max):   138.0 ms … 142.7 ms    20 runs

    Benchmark 3: fix
      Time (mean ± σ):      46.2 ms ±   0.4 ms    [User: 40.2 ms, System: 5.7 ms]
      Range (min … max):    45.0 ms …  47.3 ms    55 runs

    Summary
      update-ref: baseline
        1.00 ± 0.01 times faster than fix
        3.05 ± 0.04 times faster than regression

[1]: https://bencher.dev/perf/git/plots

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
8 months agoMerge branch 'ps/ref-peeled-tags' into ps/ref-peeled-tags-fixes
Junio C Hamano [Thu, 6 Nov 2025 18:54:28 +0000 (10:54 -0800)]
Merge branch 'ps/ref-peeled-tags' into ps/ref-peeled-tags-fixes

* ps/ref-peeled-tags:
  t7004: do not chdir around in the main process
  ref-filter: fix stale parsed objects
  ref-filter: parse objects on demand
  ref-filter: detect broken tags when dereferencing them
  refs: don't store peeled object IDs for invalid tags
  object: add flag to `peel_object()` to verify object type
  refs: drop infrastructure to peel via iterators
  refs: drop `current_ref_iter` hack
  builtin/show-ref: convert to use `reference_get_peeled_oid()`
  ref-filter: propagate peeled object ID
  upload-pack: convert to use `reference_get_peeled_oid()`
  refs: expose peeled object ID via the iterator
  refs: refactor reference status flags
  refs: fully reset `struct ref_iterator::ref` on iteration
  refs: introduce `.ref` field for the base iterator
  refs: introduce wrapper struct for `each_ref_fn`

8 months agoGit 2.52-rc1
Junio C Hamano [Wed, 5 Nov 2025 21:41:41 +0000 (13:41 -0800)]
Git 2.52-rc1

Signed-off-by: Junio C Hamano <gitster@pobox.com>
8 months agoMerge branch 'jc/ci-use-macos-14'
Junio C Hamano [Wed, 5 Nov 2025 21:41:51 +0000 (13:41 -0800)]
Merge branch 'jc/ci-use-macos-14'

The version of macos image used in GitHub CI has been updated to
macos-14, as the macos-13 that we have been using got deprecated.

* jc/ci-use-macos-14:
  GitHub CI: macos-13 images are no more

8 months agoMerge branch 'rz/t0450-bisect-doc-update'
Junio C Hamano [Wed, 5 Nov 2025 21:41:51 +0000 (13:41 -0800)]
Merge branch 'rz/t0450-bisect-doc-update'

The help text and manual page of "git bisect" command have been
made consistent with each other.

* rz/t0450-bisect-doc-update:
  bisect: update usage and docs to match each other

8 months agoGitHub CI: macos-13 images are no more
Junio C Hamano [Tue, 4 Nov 2025 23:13:20 +0000 (15:13 -0800)]
GitHub CI: macos-13 images are no more

As this image was deprecated on Sep 22nd, and will be dropped on Dec
4th, replace these jobs to use macos-14 images instead.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
8 months agoA bit more before rc1
Junio C Hamano [Tue, 4 Nov 2025 15:47:51 +0000 (07:47 -0800)]
A bit more before rc1

Signed-off-by: Junio C Hamano <gitster@pobox.com>
8 months agoMerge branch 'jk/doc-backslash-in-exclude'
Junio C Hamano [Tue, 4 Nov 2025 15:48:10 +0000 (07:48 -0800)]
Merge branch 'jk/doc-backslash-in-exclude'

The patterns used in the .gitignore files use backslash in the way
documented for fnmatch(3); document as such to reduce confusion.

* jk/doc-backslash-in-exclude:
  doc: document backslash in gitignore patterns

8 months agoMerge branch 'jk/test-delete-gpgsig-leakfix'
Junio C Hamano [Tue, 4 Nov 2025 15:48:09 +0000 (07:48 -0800)]
Merge branch 'jk/test-delete-gpgsig-leakfix'

Leakfix.

* jk/test-delete-gpgsig-leakfix:
  test-tool: fix leak in delete-gpgsig command

8 months agoMerge branch 'eb/t1016-hash-transition-fix'
Junio C Hamano [Tue, 4 Nov 2025 15:48:09 +0000 (07:48 -0800)]
Merge branch 'eb/t1016-hash-transition-fix'

Test fix.

* eb/t1016-hash-transition-fix:
  t1016-compatObjectFormat: really freeze time for reproduciblity

8 months agoMerge branch 'kh/doc-checkout-markup-fix'
Junio C Hamano [Tue, 4 Nov 2025 15:48:08 +0000 (07:48 -0800)]
Merge branch 'kh/doc-checkout-markup-fix'

Doc mark-up fix.

* kh/doc-checkout-markup-fix:
  doc: git-checkout: fix placeholder markup

8 months agoMerge branch 'xr/ref-debug-remove-on-disk'
Junio C Hamano [Tue, 4 Nov 2025 15:48:08 +0000 (07:48 -0800)]
Merge branch 'xr/ref-debug-remove-on-disk'

The "debug" ref-backend was missing a method implementation, which
has been corrected.

* xr/ref-debug-remove-on-disk:
  refs: add missing remove_on_disk implementation for debug backend

8 months agoMerge branch 'qj/doc-my1stcontrib-email-verify'
Junio C Hamano [Tue, 4 Nov 2025 15:48:07 +0000 (07:48 -0800)]
Merge branch 'qj/doc-my1stcontrib-email-verify'

The "MyFirstContribution" tutorial tells the reader how to send out
their patches; the section gained a hint to verify the message
reached the mailing list.

* qj/doc-my1stcontrib-email-verify:
  MyFirstContribution: add note on confirming patches

8 months agoMerge branch 'tz/test-prepare-gnupghome'
Junio C Hamano [Tue, 4 Nov 2025 15:48:07 +0000 (07:48 -0800)]
Merge branch 'tz/test-prepare-gnupghome'

Tests did not set up GNUPGHOME correctly, which is fixed but some
flaky tests are exposed in t1016, which needs to be addressed
before this topic can move forward.

* tz/test-prepare-gnupghome:
  t/lib-gpg: call prepare_gnupghome() in GPG2 prereq
  t/lib-gpg: add prepare_gnupghome() to create GNUPGHOME dir

8 months agoMerge branch 'jt/repo-structure'
Junio C Hamano [Tue, 4 Nov 2025 15:48:06 +0000 (07:48 -0800)]
Merge branch 'jt/repo-structure'

"git repo structure", a new command.

* jt/repo-structure:
  builtin/repo: add progress meter for structure stats
  builtin/repo: add keyvalue and nul format for structure stats
  builtin/repo: add object counts in structure output
  builtin/repo: introduce structure subcommand
  ref-filter: export ref_kind_from_refname()
  ref-filter: allow NULL filter pattern
  builtin/repo: rename repo_info() to cmd_repo_info()

8 months agoMerge branch 'tu/credential-install'
Junio C Hamano [Tue, 4 Nov 2025 15:48:06 +0000 (07:48 -0800)]
Merge branch 'tu/credential-install'

Contributed credential helpers (obviously in contrib/) now have "cd
$there && make install" target.

* tu/credential-install:
  contrib/credential: add install target

8 months agoMerge branch 'cc/doc-submitting-patches-with-ai'
Junio C Hamano [Tue, 4 Nov 2025 15:48:06 +0000 (07:48 -0800)]
Merge branch 'cc/doc-submitting-patches-with-ai'

AI guidelines.

* cc/doc-submitting-patches-with-ai:
  SubmittingPatches: add section about AI

8 months agot7004: do not chdir around in the main process
Junio C Hamano [Tue, 4 Nov 2025 15:28:59 +0000 (07:28 -0800)]
t7004: do not chdir around in the main process

Move down to no-contains subdirectory inside a subshell, just like
the previous step that created and used it does.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
8 months agoref-filter: fix stale parsed objects
Patrick Steinhardt [Tue, 4 Nov 2025 14:36:13 +0000 (15:36 +0100)]
ref-filter: fix stale parsed objects

In 054f5f457e (ref-filter: parse objects on demand, 2025-10-23) we have
started to skip parsing some objects in case we don't need to access
their values in the first place. This was done by introducing a new
member `struct expand_data::maybe_object` that gets populated on demand
via `get_or_parse_object()`.

This has led to a regression though where the object now gets reused
because we don't reset it properly. The `oi` structure is declared in
global scope, and there is no single place where we reset it before
invoking `get_object()`. The consequence is that the `maybe_object`
member doesn't get reset across calls, so subsequent calls will end up
reusing the same object.

This is only an issue for a subset of retrieved values, as not all of
the infrastructure ends up calling `get_or_parse_object()`. So the
effect is limited, which is probably why the issue wasn't detected
earlier.

Fix the issue by resetting `maybe_object` in `get_object()`.

Reported-by: Junio C Hamano <gitster@pobox.com>
Based-on-patch-by: Jeff King <peff@peff.net>
Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
8 months agoref-filter: parse objects on demand
Patrick Steinhardt [Thu, 23 Oct 2025 07:16:23 +0000 (09:16 +0200)]
ref-filter: parse objects on demand

When formatting an arbitrary object we parse that object regardless of
whether or not we actually need any parsed data. In fact, many of the
atoms we have don't require any.

Refactor the code so that we parse the data on demand when we see an
atom that wants to access the objects. This leads to a small speedup,
for example in the Chromium repository with around 40000 refs:

    Benchmark 1: for-each-ref --format='%(raw)' (HEAD~)
      Time (mean ± σ):     388.7 ms ±   1.1 ms    [User: 322.2 ms, System: 65.0 ms]
      Range (min … max):   387.3 ms … 390.8 ms    10 runs

    Benchmark 2: for-each-ref --format='%(raw)' (HEAD)
      Time (mean ± σ):     344.7 ms ±   0.7 ms    [User: 287.8 ms, System: 55.1 ms]
      Range (min … max):   343.9 ms … 345.7 ms    10 runs

    Summary
      for-each-ref --format='%(raw)' (HEAD) ran
        1.13 ± 0.00 times faster than for-each-ref --format='%(raw)' (HEAD~)

With this change, we now spend ~90% of the time decompressing objects,
which is almost as good as it gets regarding git-for-each-ref(1)'s own
infrastructure.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
8 months agoref-filter: detect broken tags when dereferencing them
Patrick Steinhardt [Thu, 23 Oct 2025 07:16:22 +0000 (09:16 +0200)]
ref-filter: detect broken tags when dereferencing them

Users can ask git-for-each-ref(1) to peel tags and return information of
the tagged object by adding an asterisk to the format, like for example
"%(*$objectname)". If so, git-for-each-ref(1) peels that object to the
first non-tag object and then returns its values.

As mentioned in preceding commits, it can happen that the tagged object
type and the claimed object type differ, effectively resulting in a
corrupt tag. git-for-each-ref(1) would notice this mismatch, print an
error and then bail out when trying to peel the tag.

But we only notice this corruption in some very specific edge cases!
While we have a test in "t/for-each-ref-tests.sh" that verifies the
above scenario, this test is specifically crafted to detect the issue at
hand. Namely, we create two tags:

  - One tag points to a specific object with the correct type.

  - The other tag points to the *same* object with a different type.

The fact that both tags point to the same object is important here:
`peel_object()` wouldn't notice the corruption if the tagged objects
were different.

The root cause is that `peel_object()` calls `lookup_${type}()`
eventually, where the type is the same type declared in the tag object.
Consequently, when we have two tags pointing to the same object but with
different declared types we'll call two different lookup functions. The
first lookup will store the object with an unverified type A, whereas
the second lookup will try to look up the object with a different
unverified type B. And it is only now that we notice the discrepancy in
object types, even though type A could've already been the wrong type.

Fix the issue by verifying the object type in `populate_value()`. With
this change we'll also notice type mismatches when only dereferencing a
tag once.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
8 months agorefs: don't store peeled object IDs for invalid tags
Patrick Steinhardt [Thu, 23 Oct 2025 07:16:21 +0000 (09:16 +0200)]
refs: don't store peeled object IDs for invalid tags

Both the "files" and "reftable" backend store peeled object IDs for
references that point to tags:

  - The "files" backend stores the value when packing refs, where each
    peeled object ID is prefixed with "^".

  - The "reftable" backend stores the value whenever writing a new
    reference that points to a tag via a special ref record type.

Both of these backends use `peel_object()` to find the peeled object ID.
But as explained in the preceding commit, that function does not detect
the case where the tag's tagged object and its claimed type mismatch.

The consequence of storing these bogus peeled object IDs is that we're
less likely to detect such corruption in other parts of Git.
git-for-each-ref(1) for example does not notice anymore that the tag is
broken when using "--format=%(*objectname)" to dereference tags.

One could claim that this is good, because it still allows us to mostly
use the tag as intended. But the biggest problem here is that we now
have different behaviour for such a broken tag depending on whether or
not we have its peeled value in the refdb.

Fix the issue by verifying the object type when peeling the object. If
that verification fails we simply skip storing the peeled value in
either of the reference formats.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
8 months agoobject: add flag to `peel_object()` to verify object type
Patrick Steinhardt [Thu, 23 Oct 2025 07:16:20 +0000 (09:16 +0200)]
object: add flag to `peel_object()` to verify object type

When peeling a tag to a non-tag object we repeatedly call
`parse_object()` on the tagged object until we find the first object
that isn't a tag. While this feels sensible at first, there is a big
catch here: `parse_object()` doesn't actually verify the type of the
tagged object.

The relevant code path here eventually ends up in `parse_tag_buffer()`.
Here, we parse the various fields of the tag, including the "type". Once
we've figured out the type and the tagged object ID, we call one of the
`lookup_${type}()` functions for whatever type we have found. There is
two possible outcomes in the successful case:

  1. The object is already part of our cached objects. In that case we
     double-check whether the type we're trying to look up matches the
     type that was cached.

  2. The object is _not_ part of our cached objects. In that case, we
     simply create a new object with the expected type, but we don't
     parse that object.

In the first case we might notice type mismatches, but only in the case
where our cache has the object with the correct type. In the second
case, we'll blindly assume that the type is correct and then go with it.
We'll only notice that the type might be wrong when we try to parse the
object at a later point.

Now arguably, we could change `parse_tag_buffer()` to verify the tagged
object's type for us. But that would have the effect that such a tag
cannot be parsed at all anymore, and we have a small bunch of tests for
exactly this case that assert we still can open such tags. So this
change does not feel like something we can retroactively tighten, even
though one shouldn't ever hit such corrupted tags.

Instead, add a new `flags` field to `peel_object()` that allows the
caller to opt in to strict object verification. This will be wired up at
a subset of callsites over the next few commits.

Note that this change also inlines `deref_tag_noverify()`. There's only
been two callsites of that function, the one we're changing and one in
our test helpers. The latter callsite can trivially use `deref_tag()`
instead, so by inlining the function we avoid having to pass down the
flag.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
8 months agorefs: drop infrastructure to peel via iterators
Patrick Steinhardt [Thu, 23 Oct 2025 07:16:19 +0000 (09:16 +0200)]
refs: drop infrastructure to peel via iterators

Now that the peeled object ID gets propagated via the `struct reference`
there is no need anymore to call into the reference iterator itself to
dereference an object. Remove this infrastructure.

Most of the changes are straight-forward deletions of code. There is one
exception though in `refs/packed-backend.c::write_with_updates()`. Here
we stop peeling the iterator and instead just pass the peeled object ID
of that iterator directly.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
8 months agorefs: drop `current_ref_iter` hack
Patrick Steinhardt [Thu, 23 Oct 2025 07:16:18 +0000 (09:16 +0200)]
refs: drop `current_ref_iter` hack

In preceding commits we have refactored all callers of
`peel_iterated_oid()` to instead use `reference_get_peeled_oid()`. This
allows us to thus get rid of the former function.

Getting rid of that function is nice, but even nicer is that this also
allows us to get rid of the `current_ref_iter` hack. This global
variable tracked the currently-active ref iterator so that we can use it
to peel an object ID. Now that the peeled object ID is propagated via
`struct reference` though we don't have to depend on this hack anymore,
which makes for a more robust and easier-to-understand infrastructure.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
8 months agobuiltin/show-ref: convert to use `reference_get_peeled_oid()`
Patrick Steinhardt [Thu, 23 Oct 2025 07:16:17 +0000 (09:16 +0200)]
builtin/show-ref: convert to use `reference_get_peeled_oid()`

The git-show-ref(1) command has multiple different modes:

  - It knows to show all references matching a pattern.

  - It knows to list all references that are an exact match to whatever
    the user has provided.

  - It knows to check for reference existence.

The first two commands use mostly the same infrastructure to print the
references via `show_one()`. But while the former mode uses a proper
iterator and thus has a `struct reference` available in its context, the
latter calls `refs_read_ref()` and thus doesn't. Consequently, we cannot
easily use `reference_get_peeled_oid()` to print the peeled value.

Adapt the code so that we manually construct a `struct reference` when
verifying refs. We wouldn't ever have the peeled value available anyway
as we're not using an iterator here, so we can simply plug in the values
we _do_ have.

With this change we now have a `struct reference` available at both
callsites of `show_one()` and can thus pass it, which allows us to use
`reference_get_peeled_oid()` instead of `peel_iterated_oid()`.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
8 months agoref-filter: propagate peeled object ID
Patrick Steinhardt [Thu, 23 Oct 2025 07:16:16 +0000 (09:16 +0200)]
ref-filter: propagate peeled object ID

When queueing a reference in the "ref-filter" subsystem we end up
creating a new ref array item that contains the reference's info. One
bit of info that we always discard though is the peeled object ID, and
because of that we are forced to use `peel_iterated_oid()`.

Refactor the code to propagate the peeled object ID via the ref array,
if available. This allows us to manually peel tags without having to go
through the object database.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
8 months agoupload-pack: convert to use `reference_get_peeled_oid()`
Patrick Steinhardt [Thu, 23 Oct 2025 07:16:15 +0000 (09:16 +0200)]
upload-pack: convert to use `reference_get_peeled_oid()`

The `write_v0_ref()` callback is invoked from two callsites:

  - Once via `send_ref()` which is a callback passed to
    `for_each_namespaced_ref_1()` and `refs_head_ref_namespaced()`.

  - Once manually to announce capabilities.

When sending references to the client we also send the peeled value of
tags. As we don't have a `struct reference` available in the second
case, we cannot easily peel by calling `reference_get_peeled_oid()`, but
we instead have to depend on on global state via `peel_iterated_oid()`.

We do have a reference available though in the first case, it's only the
second case that keeps us from using `reference_get_peeled_oid()`. But
that second case only announces capabilities anyway, so we're not really
handling a reference at all here.

Adapt that case to construct a reference manually and pass that to
`write_v0_ref()`. Start to use `reference_get_peeled_oid()` now that we
always have a `struct reference` available.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
8 months agorefs: expose peeled object ID via the iterator
Patrick Steinhardt [Thu, 23 Oct 2025 07:16:14 +0000 (09:16 +0200)]
refs: expose peeled object ID via the iterator

Both the "files" and "reftable" backend are able to store peeled values
for tags in the respective formats. This allows for a more efficient
lookup of the target object of such a tag without having to manually
peel via the object database.

The infrastructure to access these peeled object IDs is somewhat funky
though. When iterating through objects, we store a pointer reference to
the current iterator in a global variable. The callbacks invoked by that
iterator are then expected to call `peel_iterated_oid()`, which checks
whether the globally-stored iterator's current reference refers to the
one handed into that function. If so, we ask the iterator to peel the
object, otherwise we manually peel the object via the object database.
Depending on global state like this is somewhat weird and also quite
fragile.

Introduce a new `struct reference::peeled_oid` field that can be
populated by the reference backends. This field can be accessed via a
new function `reference_get_peeled_oid()` that either uses that value,
if set, or alternatively peels via the ODB. With this change we don't
have to rely on global state anymore, but make the peeled object ID
available to the callback functions directly.

Adjust trivial callers that already have a `struct reference` available.
Remaining callers will be adjusted in subsequent commits.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
8 months agorefs: refactor reference status flags
Patrick Steinhardt [Thu, 23 Oct 2025 07:16:13 +0000 (09:16 +0200)]
refs: refactor reference status flags

The reference flags encode information like whether or not a reference
is a symbolic reference or whether it may be broken. This information is
stored in a `int flags` bitfield, which is in conflict with our modern
best practices; we tend to use an unsigned integer to store flags.

Change the type of the field to be `unsigned`. While at it, refactor the
individual flags to be part of an `enum` instead of using preprocessor
defines.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
8 months agorefs: fully reset `struct ref_iterator::ref` on iteration
Patrick Steinhardt [Thu, 23 Oct 2025 07:16:12 +0000 (09:16 +0200)]
refs: fully reset `struct ref_iterator::ref` on iteration

With the introduction of the `struct ref_iterator::ref` field it now is
a whole lot easier to introduce new fields that become accessible to the
caller without having to adapt every single callsite. But there's a
downside: when a new field is introduced we always have to adapt all
backends to set that field.

This isn't something we can avoid in the general case: when the new
field is expected to be populated by all backends we of course cannot
avoid doing so. But new fields may be entirely optional, in which case
we'd still have such churn. And furthermore, it is very easy right now
to leak state from a previous iteration into the next iteration.

Address this issue by ensuring that the reference backends all fully
reset the field on every single iteration. This ensures that no state
from previous iterations can leak into the next one. And it ensures that
any newly introduced fields will be zeroed out by default.

Note that we don't have to explicitly adapt the "files" backend, as it
uses the `cache_ref_iterator` internally. Furthermore, other "wrapping"
iterators like for example the `prefix_ref_iterator` copy around the
whole reference, so these don't need to be adapted either.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
8 months agorefs: introduce `.ref` field for the base iterator
Patrick Steinhardt [Thu, 23 Oct 2025 07:16:11 +0000 (09:16 +0200)]
refs: introduce `.ref` field for the base iterator

The base iterator has a couple of fields that tracks the name, target,
object ID and flags for the current reference. Due to this design we
have to create a new `struct reference` whenever we want to hand over
that reference to the callback function, which is tedious and not very
efficient.

Convert the structure to instead contain a `struct reference` as member.
This member is expected to be populated by the implementations of the
iterator and is handed over to the callback directly.

While at it, simplify `should_pack_ref()` to take a `struct reference`
directly instead of passing its respective fields.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
8 months agorefs: introduce wrapper struct for `each_ref_fn`
Patrick Steinhardt [Thu, 23 Oct 2025 07:16:10 +0000 (09:16 +0200)]
refs: introduce wrapper struct for `each_ref_fn`

The `each_ref_fn` callback function type is used across our code base
for several different functions that iterate through reference. There's
a bunch of callbacks implementing this type, which makes any changes to
the callback signature extremely noisy. An example of the required churn
is e8207717f1 (refs: add referent to each_ref_fn, 2024-08-09): adding a
single argument required us to change 48 files.

It was already proposed back then [1] that we might want to introduce a
wrapper structure to alleviate the pain going forward. While this of
course requires the same kind of global refactoring as just introducing
a new parameter, it at least allows us to more change the callback type
afterwards by just extending the wrapper structure.

One counterargument to this refactoring is that it makes the structure
more opaque. While it is obvious which callsites need to be fixed up
when we change the function type, it's not obvious anymore once we use
a structure. That being said, we only have a handful of sites that
actually need to populate this wrapper structure: our ref backends,
"refs/iterator.c" as well as very few sites that invoke the iterator
callback functions directly.

Introduce this wrapper structure so that we can adapt the iterator
interfaces more readily.

[1]: <ZmarVcF5JjsZx0dl@tanuki>

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
8 months agoGit 2.52-rc0
Junio C Hamano [Mon, 3 Nov 2025 05:40:21 +0000 (21:40 -0800)]
Git 2.52-rc0

Signed-off-by: Junio C Hamano <gitster@pobox.com>
8 months agoMerge branch 'rs/merge-base-optim'
Junio C Hamano [Mon, 3 Nov 2025 14:49:55 +0000 (06:49 -0800)]
Merge branch 'rs/merge-base-optim'

The code to walk revision graph to compute merge base has been
optimized.

* rs/merge-base-optim:
  commit-reach: avoid commit_list_insert_by_date()

8 months agoMerge branch 'jk/diff-patch-dry-run-cleanup'
Junio C Hamano [Mon, 3 Nov 2025 14:49:55 +0000 (06:49 -0800)]
Merge branch 'jk/diff-patch-dry-run-cleanup'

Finishing touches to fixes to the recent regression in "git diff -w
--quiet" and anything that needs to internally generate patch to
see if it turns empty.

* jk/diff-patch-dry-run-cleanup:
  diff: simplify run_external_diff() quiet logic
  diff: drop dry-run redirection to /dev/null
  diff: replace diff_options.dry_run flag with NULL file
  diff: drop save/restore of color_moved in dry-run mode
  diff: send external diff output to diff_options.file

8 months agoMerge branch 'ps/maintenance-geometric'
Junio C Hamano [Mon, 3 Nov 2025 14:49:55 +0000 (06:49 -0800)]
Merge branch 'ps/maintenance-geometric'

"git maintenance" command learns the "geometric" strategy where it
avoids doing maintenance tasks that rebuilds everything from
scratch.

* ps/maintenance-geometric:
  t7900: fix a flaky test due to git-repack always regenerating MIDX
  builtin/maintenance: introduce "geometric" strategy
  builtin/maintenance: make "gc" strategy accessible
  builtin/maintenance: extend "maintenance.strategy" to manual maintenance
  builtin/maintenance: run maintenance tasks depending on type
  builtin/maintenance: improve readability of strategies
  builtin/maintenance: don't silently ignore invalid strategy
  builtin/maintenance: make the geometric factor configurable
  builtin/maintenance: introduce "geometric-repack" task
  builtin/gc: make `too_many_loose_objects()` reusable without GC config
  builtin/gc: remove global `repack` variable

8 months agoMerge branch 'jk/match-pathname-fix'
Junio C Hamano [Mon, 3 Nov 2025 14:49:54 +0000 (06:49 -0800)]
Merge branch 'jk/match-pathname-fix'

The wildmatch code had a corner case bug that mistakenly makes
"foo**/bar" match with "foobar", which has been corrected.

* jk/match-pathname-fix:
  match_pathname(): give fnmatch one char of prefix context
  match_pathname(): reorder prefix-match check

8 months agoMerge branch 'kh/doc-patch-id-1'
Junio C Hamano [Mon, 3 Nov 2025 14:49:54 +0000 (06:49 -0800)]
Merge branch 'kh/doc-patch-id-1'

* kh/doc-patch-id-1:
  doc: patch-id: convert to the modern synopsis style

8 months agoMerge branch 'rs/add-patch-quit'
Junio C Hamano [Mon, 3 Nov 2025 14:49:54 +0000 (06:49 -0800)]
Merge branch 'rs/add-patch-quit'

The 'q'(uit) command in "git add -p" has been improved to quit
without doing any meaningless work before leaving, and giving EOF
(typically control-D) to the prompt is made to behave the same way.

* rs/add-patch-quit:
  add-patch: quit on EOF
  add-patch: quit without skipping undecided hunks

8 months agoThe 27th batch
Junio C Hamano [Thu, 30 Oct 2025 14:34:16 +0000 (07:34 -0700)]
The 27th batch

Signed-off-by: Junio C Hamano <gitster@pobox.com>
8 months agoMerge branch 'rz/bisect-help-unknown'
Junio C Hamano [Thu, 30 Oct 2025 15:00:20 +0000 (08:00 -0700)]
Merge branch 'rz/bisect-help-unknown'

"git bisect" command did not react correctly to "git bisect help"
and "git bisect unknown", which has been corrected.

* rz/bisect-help-unknown:
  bisect: fix handling of `help` and invalid subcommands

8 months agoMerge branch 'kf/log-shortlog-completion-fix'
Junio C Hamano [Thu, 30 Oct 2025 15:00:20 +0000 (08:00 -0700)]
Merge branch 'kf/log-shortlog-completion-fix'

"git shortlog" knows "--committer" and "--author" options, which
the command line completion (in contrib/) did not handle well,
which has been corrected.

* kf/log-shortlog-completion-fix:
  completion: complete some 'git log' options

8 months agoMerge branch 'ly/diff-name-only-with-diff-from-content'
Junio C Hamano [Thu, 30 Oct 2025 15:00:19 +0000 (08:00 -0700)]
Merge branch 'ly/diff-name-only-with-diff-from-content'

Regression fixes for a topic that has already been merged.

* ly/diff-name-only-with-diff-from-content:
  diff: stop output garbled message in dry run mode

8 months agoMerge branch 'ps/remove-packfile-store-get-packs'
Junio C Hamano [Thu, 30 Oct 2025 15:00:19 +0000 (08:00 -0700)]
Merge branch 'ps/remove-packfile-store-get-packs'

Two slightly different ways to get at "all the packfiles" in API
has been cleaned up.

* ps/remove-packfile-store-get-packs:
  packfile: rename `packfile_store_get_all_packs()`
  packfile: introduce macro to iterate through packs
  packfile: drop `packfile_store_get_packs()`
  builtin/grep: simplify how we preload packs
  builtin/gc: convert to use `packfile_store_get_all_packs()`
  object-name: convert to use `packfile_store_get_all_packs()`

8 months agoMerge branch 'ob/gpg-interface-cleanup'
Junio C Hamano [Thu, 30 Oct 2025 15:00:19 +0000 (08:00 -0700)]
Merge branch 'ob/gpg-interface-cleanup'

strbuf_split*() to split a string into multiple strbufs is often a
wrong API to use.  A few uses of it have been removed by
simplifying the code.

* ob/gpg-interface-cleanup:
  gpg-interface: do not use misdesigned strbuf_split*()
  gpg-interface: do not use misdesigned strbuf_split*()

8 months agoMerge branch 'ps/symlink-symref-deprecation'
Junio C Hamano [Thu, 30 Oct 2025 15:00:19 +0000 (08:00 -0700)]
Merge branch 'ps/symlink-symref-deprecation'

"Symlink symref" has been added to the list of things that will
disappear at Git 3.0 boundary.

* ps/symlink-symref-deprecation:
  refs/files: deprecate writing symrefs as symbolic links

8 months agoMerge branch 'ey/commit-graph-changed-paths-config'
Junio C Hamano [Thu, 30 Oct 2025 15:00:18 +0000 (08:00 -0700)]
Merge branch 'ey/commit-graph-changed-paths-config'

A new configuration variable commitGraph.changedPaths allows to
turn "--changed-paths" on by default for "git commit-graph".

* ey/commit-graph-changed-paths-config:
  commit-graph: add new config for changed-paths & recommend it in scalar

8 months agoThe 26th batch
Junio C Hamano [Wed, 29 Oct 2025 19:40:39 +0000 (12:40 -0700)]
The 26th batch

Signed-off-by: Junio C Hamano <gitster@pobox.com>
8 months agoMerge branch 'tb/incremental-midx-part-3.1'
Junio C Hamano [Wed, 29 Oct 2025 19:38:24 +0000 (12:38 -0700)]
Merge branch 'tb/incremental-midx-part-3.1'

Clean-up "git repack" machinery to prepare for incremental update
of midx files.

* tb/incremental-midx-part-3.1: (49 commits)
  builtin/repack.c: clean up unused `#include`s
  repack: move `write_cruft_pack()` out of the builtin
  repack: move `write_filtered_pack()` out of the builtin
  repack: move `pack_kept_objects` to `struct pack_objects_args`
  repack: move `finish_pack_objects_cmd()` out of the builtin
  builtin/repack.c: pass `write_pack_opts` to `finish_pack_objects_cmd()`
  repack: extract `write_pack_opts_is_local()`
  repack: move `find_pack_prefix()` out of the builtin
  builtin/repack.c: use `write_pack_opts` within `write_cruft_pack()`
  builtin/repack.c: introduce `struct write_pack_opts`
  repack: 'write_midx_included_packs' API from the builtin
  builtin/repack.c: inline packs within `write_midx_included_packs()`
  builtin/repack.c: pass `repack_write_midx_opts` to `midx_included_packs`
  builtin/repack.c: inline `remove_redundant_bitmaps()`
  builtin/repack.c: reorder `remove_redundant_bitmaps()`
  repack: keep track of MIDX pack names using existing_packs
  builtin/repack.c: use a string_list for 'midx_pack_names'
  builtin/repack.c: extract opts struct for 'write_midx_included_packs()'
  builtin/repack.c: remove ref snapshotting from builtin
  repack: remove pack_geometry API from the builtin
  ...

8 months agotest-tool: fix leak in delete-gpgsig command
Jeff King [Wed, 29 Oct 2025 19:10:31 +0000 (15:10 -0400)]
test-tool: fix leak in delete-gpgsig command

We read the input into a strbuf, so we must free it. Without this, t1016
complains in SANITIZE=leak mode.

The bug was introduced in 7673ecd2dc (t1016-compatObjectFormat: add
tests to verify the conversion between objects, 2023-10-01). But nobody
seems to have noticed, probably because CI did not run these tests until
the fix in 6cd8369ef3 (t/lib-gpg: call prepare_gnupghome() in GPG2
prereq, 2024-07-03).

Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
8 months agodoc: document backslash in gitignore patterns
Jeff King [Wed, 29 Oct 2025 15:32:37 +0000 (11:32 -0400)]
doc: document backslash in gitignore patterns

Because gitignore patterns are passed to fnmatch, the handling of
backslashes is the same as it is there: it can be used to escape
metacharacters. We do reference fnmatch(3) for more details, but it may
be friendlier to point out this implication explicitly (especially for
people who want to know about backslash handling and search the
documentation for that word). There are also two cases that I've seen
some other backslash-escaping systems handle differently, so let's
describe those:

  1. A backslash before any character treats that character literally,
     even if it's not otherwise a meta-character. As opposed to
     including the backslash itself (like "foo\bar" in shell expands to
     "foo\bar") or forbidding it ("foo\zar" is required to produce a
     diagnostic in C).

  2. A backslash at the end of the string is an invalid pattern (and not
     a literal backslash).

This second one in particular was a point of confusion between our
implementation and the one in JGit. Our wildmatch behavior matches what
POSIX specifies for fnmatch, so the code and documentation are in line.
But let's add a test to cover this case. Note that the behavior here
differs between wildmatch itself (which is what gitignore will use) and
pathspec matching (which will only turn to wildmatch if a literal match
fails). So we match "foo\" to "foo\" in pathspecs, but not via
gitignore.

Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
8 months agot1016-compatObjectFormat: really freeze time for reproduciblity
Eric W. Biederman [Tue, 28 Oct 2025 16:01:45 +0000 (11:01 -0500)]
t1016-compatObjectFormat: really freeze time for reproduciblity

The strategy in t1016-compatObjectFormat is to build two trees with
identical commits, one tree encoded in sha1 the other tree encoded
in sha256 and to use the compatibility code to test and see if
the two trees are identical.

GPG signatures include the current time as part of the signature.

To make gpg deterministic I forced the use of gpg --faked-system-time.
Unfortunately I did not look closely enough.

By default gpg still allows time to move forward with --faked-system-time.
So in those rare instances when the system is heavily loaded and gpg runs
slower than other times, signatures over the exact same data differ
due to timestamps with a minuscule difference.

Reading through the gpg documentation with a close eye, time can be
frozen by including an exclamation point at the end of the argument to
--faked-system-time.

Add the exclamation point so gpg really runs with a fixed notion of time,
resulting in the exact same data having identical gpg signatures.

That is enough that I can run "t1016-compatObjectFormat.sh --stress"
and I don't see any failures.

It is possible a future change to gpg will make replay protection more
robust and not provide a way to allow two separate runs of gpg to
produce exactly the same signature for exactly the same data.  If that
happens a deeper comparison of the two repositories will need to be
performed.  A comparison that simply verifies the signatures and
compares the data for equality.  For now that is a lot of work
for no gain so I am just documenting the possibility.

Signed-off-by: Eric W. Biederman <ebiederm@xmission.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
8 months agobisect: update usage and docs to match each other
Ruoyu Zhong [Tue, 28 Oct 2025 22:27:41 +0000 (22:27 +0000)]
bisect: update usage and docs to match each other

Update the usage string of `git bisect` and documentation to match each
other. While at it, also:

1. Move the synopsis of `git bisect` subcommands to the synopsis
   section, so that the test `t0450-txt-doc-vs-help.sh` can pass.

2. Document the `git bisect next` subcommand, which exists in the code
   but is missing from the documentation.

See also: [1].

[1]: https://lore.kernel.org/git/3DA38465-7636-4EEF-B074-53E4628F5355@gmail.com/

Suggested-by: Ben Knoble <ben.knoble@gmail.com>
Signed-off-by: Ruoyu Zhong <zhongruoyu@outlook.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
8 months agodoc: git-checkout: fix placeholder markup
Kristoffer Haugsbakk [Tue, 28 Oct 2025 18:19:19 +0000 (19:19 +0100)]
doc: git-checkout: fix placeholder markup

The placeholder markup is underscore (_), not backtick (`) as well.

The inline-verbatim markup (backticks) handle interior formatting. This
means in this case that it applies HTML `<code>` to the underscores and
`<em>` to the placeholder.

That is the effect, anyway; we can see from the rest of 042d6f34 (doc:
git-checkout: clarify `-b` and `-B`, 2025-09-10) that this was probably
an unintended mix-up.

Acked-by: Julia Evans <julia@jvns.ca>
Signed-off-by: Kristoffer Haugsbakk <code@khaugsbakk.name>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
8 months agoThe 25th batch
Junio C Hamano [Tue, 28 Oct 2025 17:28:54 +0000 (10:28 -0700)]
The 25th batch

Signed-off-by: Junio C Hamano <gitster@pobox.com>
8 months agoMerge branch 'ps/ci-rust'
Junio C Hamano [Tue, 28 Oct 2025 17:29:09 +0000 (10:29 -0700)]
Merge branch 'ps/ci-rust'

CI improvements to handle the recent Rust integration better.

* ps/ci-rust:
  rust: support for Windows
  ci: verify minimum supported Rust version
  ci: check for common Rust mistakes via Clippy
  rust/varint: add safety comments
  ci: check formatting of our Rust code
  ci: deduplicate calls to `apt-get update`

8 months agoMerge branch 'cc/fast-import-strip-signed-tags'
Junio C Hamano [Tue, 28 Oct 2025 17:29:09 +0000 (10:29 -0700)]
Merge branch 'cc/fast-import-strip-signed-tags'

"git fast-import" is taught to handle signed tags, just like it
recently learned to handle signed commits, in different ways.

* cc/fast-import-strip-signed-tags:
  fast-import: add '--signed-tags=<mode>' option
  fast-export: handle all kinds of tag signatures
  t9350: properly count annotated tags
  lib-gpg: allow tests with GPGSM or GPGSSH prereq first
  doc: git-tag: stop focusing on GPG signed tags

8 months agoMerge branch 'ds/sparse-checkout-clean'
Junio C Hamano [Tue, 28 Oct 2025 17:29:08 +0000 (10:29 -0700)]
Merge branch 'ds/sparse-checkout-clean'

"git sparse-checkout" subcommand learned a new "clean" action to
prune otherwise unused working-tree files that are outside the
areas of interest.

* ds/sparse-checkout-clean:
  sparse-index: improve advice message instructions
  t: expand tests around sparse merges and clean
  sparse-index: point users to new 'clean' action
  sparse-checkout: add --verbose option to 'clean'
  dir: add generic "walk all files" helper
  sparse-checkout: match some 'clean' behavior
  sparse-checkout: add basics of 'clean' command
  sparse-checkout: remove use of the_repository

8 months agot7900: fix a flaky test due to git-repack always regenerating MIDX
Patrick Steinhardt [Mon, 27 Oct 2025 08:30:50 +0000 (09:30 +0100)]
t7900: fix a flaky test due to git-repack always regenerating MIDX

When a supposedly no-op "git repack" runs across a second boundary,
because the command always touches the MIDX file and updates its
timestamp, "ls -l $GIT_DIR/objects/pack/" before and after the
operation can change, which causes such a test to fail.  Only
compare the *.pack files in the directory before and after the
operation to work around this flakyness.

Arguably, git-repack(1) should learn to not rewrite the MIDX in case
we know it is already up-to-date. But this is not a new problem
introduced via the new geometric maintenance task, so for now it
should be good enough to paper over the issue.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
[jc: taken from diff to v4 from v3 that was already merged to 'next']
Signed-off-by: Junio C Hamano <gitster@pobox.com>
8 months agoMyFirstContribution: add note on confirming patches
Queen Ediri Jessa [Mon, 27 Oct 2025 11:16:00 +0000 (12:16 +0100)]
MyFirstContribution: add note on confirming patches

Add a note after the `git send-email` section explaining how
contributors can confirm that their patches reached the mailing
list by checking https://lore.kernel.org/git/. This helps
contributors verify that their emails were successfully delivered.

Signed-off-by: Queen Ediri Jessa <qjessa662@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
8 months agorefs: add missing remove_on_disk implementation for debug backend
Xinyu Ruan [Fri, 24 Oct 2025 08:38:14 +0000 (08:38 +0000)]
refs: add missing remove_on_disk implementation for debug backend

The debug ref backend (refs_be_debug) was missing the remove_on_disk
function pointer, which caused a segmentation fault when running
'GIT_TRACE_REFS=1 git refs migrate --ref-format=reftable' commands.

Signed-off-by: Xinyu Ruan <r200981113@gmail.com>
Acked-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
8 months agoSync with Git 2.51.2
Junio C Hamano [Mon, 27 Oct 2025 03:05:40 +0000 (20:05 -0700)]
Sync with Git 2.51.2

Signed-off-by: Junio C Hamano <gitster@pobox.com>
8 months agoGit 2.51.2
Junio C Hamano [Mon, 27 Oct 2025 00:47:52 +0000 (17:47 -0700)]
Git 2.51.2

Signed-off-by: Junio C Hamano <gitster@pobox.com>
8 months agoMerge branch 'so/t2401-use-test-path-helpers' into maint-2.51
Junio C Hamano [Mon, 27 Oct 2025 02:48:21 +0000 (19:48 -0700)]
Merge branch 'so/t2401-use-test-path-helpers' into maint-2.51

Test modernization.

* so/t2401-use-test-path-helpers:
  t2401: update path checks using test_path helpers

8 months agoMerge branch 'js/ci-github-actions-update' into maint-2.51
Junio C Hamano [Mon, 27 Oct 2025 02:48:20 +0000 (19:48 -0700)]
Merge branch 'js/ci-github-actions-update' into maint-2.51

CI update.

* js/ci-github-actions-update:
  build(deps): bump actions/github-script from 7 to 8
  build(deps): bump actions/setup-python from 5 to 6
  build(deps): bump actions/checkout from 4 to 5
  build(deps): bump actions/download-artifact from 4 to 5

8 months agoMerge branch 'kh/doc-continued-paragraph-fix' into maint-2.51
Junio C Hamano [Mon, 27 Oct 2025 02:48:20 +0000 (19:48 -0700)]
Merge branch 'kh/doc-continued-paragraph-fix' into maint-2.51

Doc mark-up fixes.

* kh/doc-continued-paragraph-fix:
  doc: fix accidental literal blocks

8 months agoMerge branch 'js/unreachable-workaround-for-no-symlink-head' into maint-2.51
Junio C Hamano [Mon, 27 Oct 2025 02:48:20 +0000 (19:48 -0700)]
Merge branch 'js/unreachable-workaround-for-no-symlink-head' into maint-2.51

Code clean-up.

* js/unreachable-workaround-for-no-symlink-head:
  refs: forbid clang to complain about unreachable code

8 months agoMerge branch 'ps/t7528-ssh-agent-uds-workaround' into maint-2.51
Junio C Hamano [Mon, 27 Oct 2025 02:48:20 +0000 (19:48 -0700)]
Merge branch 'ps/t7528-ssh-agent-uds-workaround' into maint-2.51

Recent OpenSSH creates the Unix domain socket to communicate with
ssh-agent under $HOME instead of /tmp, which causes our test to
fail doe to overly long pathname in our test environment, which has
been worked around by using "ssh-agent -T".

* ps/t7528-ssh-agent-uds-workaround:
  t7528: work around ETOOMANY in OpenSSH 10.1 and newer

8 months agoMerge branch 'tb/unicode-width-table-17' into maint-2.51
Junio C Hamano [Mon, 27 Oct 2025 02:48:19 +0000 (19:48 -0700)]
Merge branch 'tb/unicode-width-table-17' into maint-2.51

Unicode width table update.

* tb/unicode-width-table-17:
  unicode: update the width tables to Unicode 17

8 months agoMerge branch 'jk/status-z-short-fix' into maint-2.51
Junio C Hamano [Mon, 27 Oct 2025 02:48:19 +0000 (19:48 -0700)]
Merge branch 'jk/status-z-short-fix' into maint-2.51

The "--short" option of "git status" that meant output for humans
and "-z" option to show NUL delimited output format did not mix
well, and colored some but not all things.  The command has been
updated to color all elements consistently in such a case.

* jk/status-z-short-fix:
  status: make coloring of "-z --short" consistent

8 months agoMerge branch 'jk/diff-no-index-with-pathspec-fix' into maint-2.51
Junio C Hamano [Mon, 27 Oct 2025 02:48:19 +0000 (19:48 -0700)]
Merge branch 'jk/diff-no-index-with-pathspec-fix' into maint-2.51

An earlier addition to "git diff --no-index A B" to limit the
output with pathspec after the two directories misbehaved when
these directories were given with a trailing slash, which has been
corrected.

* jk/diff-no-index-with-pathspec-fix:
  diff --no-index: fix logic for paths ending in '/'

8 months agoMerge branch 'ps/gitlab-ci-disable-windows-monitoring' into maint-2.51
Junio C Hamano [Mon, 27 Oct 2025 02:48:18 +0000 (19:48 -0700)]
Merge branch 'ps/gitlab-ci-disable-windows-monitoring' into maint-2.51

Windows "real-time monitoring" interferes with the execution of
tests and affects negatively in both correctness and performance,
which has been disabled in Gitlab CI.

* ps/gitlab-ci-disable-windows-monitoring:
  gitlab-ci: disable realtime monitoring to unbreak Windows jobs

8 months agoMerge branch 'jc/diff-from-contents-fix' into maint-2.51
Junio C Hamano [Mon, 27 Oct 2025 02:48:18 +0000 (19:48 -0700)]
Merge branch 'jc/diff-from-contents-fix' into maint-2.51

The code to squelch output from "git diff -w --name-status"
etc. for paths that "git diff -w -p" would have stayed silent
leaked output from dry-run patch generation, which has been
corrected.

* jc/diff-from-contents-fix:
  diff: make sure the other caller of diff_flush_patch_quietly() is silent

8 months agoMerge branch 'jk/diff-from-contents-fix' into maint-2.51
Junio C Hamano [Mon, 27 Oct 2025 02:48:18 +0000 (19:48 -0700)]
Merge branch 'jk/diff-from-contents-fix' into maint-2.51

Recently we attempted to improve "git diff -w" and friends to
handle cases where patch output would be suppressed, but it
introduced a bug that emits unnecessary output, which has been
corrected.

* jk/diff-from-contents-fix:
  diff: restore redirection to /dev/null for diff_from_contents

8 months agoadd-patch: quit on EOF
René Scharfe [Sat, 25 Oct 2025 05:48:28 +0000 (07:48 +0200)]
add-patch: quit on EOF

If we reach the end of the input, e.g. because the user pressed ctrl-D
on Linux, there is no point in showing any more prompts, as we won't get
any reply.  Do the same as option 'q' would: Quit.

Signed-off-by: René Scharfe <l.s.r@web.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
8 months agomatch_pathname(): give fnmatch one char of prefix context
Jeff King [Sun, 26 Oct 2025 15:42:22 +0000 (11:42 -0400)]
match_pathname(): give fnmatch one char of prefix context

In match_pathname(), which we use for matching .gitignore and
.gitattribute patterns, we are comparing paths with fnmatch patterns
(actually our extended wildmatch, which will be important).  There's an
extra optimization there: we pre-compute the number of non-wildcard
characters at the beginning of the pattern and do an fspathncmp() on
that prefix.

That lets us avoid fnmatch entirely on patterns without wildcards, and
shrinks the amount of work we hand off to fnmatch. For a pattern like
"foo*.txt" and a path "foobar.txt", we'd cut away the matching "foo"
prefix and just pass "*.txt" and "bar.txt" to fnmatch().

But this misses a subtle corner case. In fnmatch(), we'll think
"bar.txt" is the start of the path, but it's not. This doesn't matter
for the pattern above, but consider the wildmatch pattern "foo**/bar"
and the path "foobar". These two should not match, because there is no
file named "bar", and the "**" applies only to the containing directory
name. But after removing the "foo" prefix, fnmatch will get "**/bar" and
"bar", which it does consider a match, because "**/" can match zero
directories.

We can solve this by giving fnmatch a bit more context. As long as it
has one byte of the matched prefix, then it will know that "bar" is not
the start of the path. In this example it would get "o**/bar" and
"obar", and realize that they cannot match.

Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
8 months agomatch_pathname(): reorder prefix-match check
Jeff King [Sun, 26 Oct 2025 15:41:46 +0000 (11:41 -0400)]
match_pathname(): reorder prefix-match check

As an optimization, we use fspathncmp() to match a prefix of the pattern
that does not contain any wildcards, and then pass the remainder to
fnmatch(). If it has matched the whole thing, we can return early.

Let's shift this early-return check to before we tweak the pattern and
name strings. That will gives us more flexibility with that tweaking.

It might also save a few instructions, but I couldn't measure any
improvement in doing so (and I wouldn't be surprised if an optimizing
compiler could figure that out itself).

Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
8 months agocontrib/credential: add install target
Thomas Uhle [Sat, 25 Oct 2025 20:30:07 +0000 (22:30 +0200)]
contrib/credential: add install target

Add an install target rule to the Makefiles in contrib/credential in the
same manner as in other Makefiles in contrib such as for contacts or
subtree.

Signed-off-by: Thomas Uhle <thomas.uhle@mailbox.tu-dresden.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
8 months agoadd-patch: quit without skipping undecided hunks
René Scharfe [Sat, 25 Oct 2025 05:46:42 +0000 (07:46 +0200)]
add-patch: quit without skipping undecided hunks

Option q implies d, i.e., it marks any undecided hunks towards the
bottom of the hunk array as skipped.  This is unnecessary; later code
treats undecided and skipped hunks the same: The only functions that
use UNDECIDED_HUNK and SKIP_HUNK are patch_update_file() itself (but
not after its big for loop) and its helpers get_first_undecided() and
display_hunks().

Streamline the handling of option q by quitting immediately.

Signed-off-by: René Scharfe <l.s.r@web.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
8 months agoThe twenty-fourth batch
Junio C Hamano [Fri, 24 Oct 2025 20:47:53 +0000 (13:47 -0700)]
The twenty-fourth batch

Signed-off-by: Junio C Hamano <gitster@pobox.com>
8 months agoMerge branch 'ps/t7528-ssh-agent-uds-workaround'
Junio C Hamano [Fri, 24 Oct 2025 20:48:05 +0000 (13:48 -0700)]
Merge branch 'ps/t7528-ssh-agent-uds-workaround'

Recent OpenSSH creates the Unix domain socket to communicate with
ssh-agent under $HOME instead of /tmp, which causes our test to
fail doe to overly long pathname in our test environment, which has
been worked around by using "ssh-agent -T".

* ps/t7528-ssh-agent-uds-workaround:
  t7528: work around ETOOMANY in OpenSSH 10.1 and newer

8 months agoMerge branch 'rs/add-patch-document-p-for-pager'
Junio C Hamano [Fri, 24 Oct 2025 20:48:05 +0000 (13:48 -0700)]
Merge branch 'rs/add-patch-document-p-for-pager'

Show 'P'ipe command in "git add -p".

* rs/add-patch-document-p-for-pager:
  add-patch: fully document option P

8 months agoMerge branch 'jc/t1016-setup-fix'
Junio C Hamano [Fri, 24 Oct 2025 20:48:05 +0000 (13:48 -0700)]
Merge branch 'jc/t1016-setup-fix'

GPG signing test set-up has been broken for a year, which has been
corrected.

* jc/t1016-setup-fix:
  t1016: make sure to use specified GPG

8 months agoMerge branch 'tb/unicode-width-table-17'
Junio C Hamano [Fri, 24 Oct 2025 20:48:04 +0000 (13:48 -0700)]
Merge branch 'tb/unicode-width-table-17'

Unicode width table update.

* tb/unicode-width-table-17:
  unicode: update the width tables to Unicode 17

8 months agoMerge branch 'tu/credential-makefile-updates'
Junio C Hamano [Fri, 24 Oct 2025 20:48:04 +0000 (13:48 -0700)]
Merge branch 'tu/credential-makefile-updates'

Build procedure for a few credential helpers (in contrib/) have
been updated.

* tu/credential-makefile-updates:
  contrib/credential: harmonize Makefiles

8 months agoMerge branch 'jk/status-z-short-fix'
Junio C Hamano [Fri, 24 Oct 2025 20:48:04 +0000 (13:48 -0700)]
Merge branch 'jk/status-z-short-fix'

The "--short" option of "git status" that meant output for humans
and "-z" option to show NUL delimited output format did not mix
well, and colored some but not all things.  The command has been
updated to color all elements consistently in such a case.

* jk/status-z-short-fix:
  status: make coloring of "-z --short" consistent

8 months agoMerge branch 'js/t7500-pwd-windows-fix'
Junio C Hamano [Fri, 24 Oct 2025 20:48:04 +0000 (13:48 -0700)]
Merge branch 'js/t7500-pwd-windows-fix'

Test fix.

* js/t7500-pwd-windows-fix:
  t7500: fix tests with absolute path following ":(optional)" on Windows

8 months agoMerge branch 'rj/doc-technical-fixes'
Junio C Hamano [Fri, 24 Oct 2025 20:48:04 +0000 (13:48 -0700)]
Merge branch 'rj/doc-technical-fixes'

Documentation mark-up fixes.

* rj/doc-technical-fixes:
  doc: add large-object-promisors.adoc to the docs build
  doc: commit-graph.adoc: fix up some formatting
  doc: sparse-checkout.adoc: fix asciidoc warnings
  doc: remembering-renames.adoc: fix asciidoc warnings

8 months agobuiltin/maintenance: introduce "geometric" strategy
Patrick Steinhardt [Fri, 24 Oct 2025 06:57:23 +0000 (08:57 +0200)]
builtin/maintenance: introduce "geometric" strategy

We have two different repacking strategies in Git:

  - The "gc" strategy uses git-gc(1).

  - The "incremental" strategy uses multi-pack indices and `git
    multi-pack-index repack` to merge together smaller packfiles as
    determined by a specific batch size.

The former strategy is our old and trusted default, whereas the latter
has historically been used for our scheduled maintenance. But both
strategies have their shortcomings:

  - The "gc" strategy performs regular all-into-one repacks. Furthermore
    it is rather inflexible, as it is not easily possible for a user to
    enable or disable specific subtasks.

  - The "incremental" strategy is not a full replacement for the "gc"
    strategy as it doesn't know to prune stale data.

So today, we don't have a strategy that is well-suited for large repos
while being a full replacement for the "gc" strategy.

Introduce a new "geometric" strategy that aims to fill this gap. This
strategy invokes all the usual cleanup tasks that git-gc(1) does like
pruning reflogs and rerere caches as well as stale worktrees. But where
it differs from both the "gc" and "incremental" strategy is that it uses
our geometric repacking infrastructure exposed by git-repack(1) to
repack packfiles. The advantage of geometric repacking is that we only
need to perform an all-into-one repack when the object count in a repo
has grown significantly.

One downside of this strategy is that pruning of unreferenced objects is
not going to happen regularly anymore. Every geometric repack knows to
soak up all loose objects regardless of their reachability, and merging
two or more packs doesn't consider reachability, either. Consequently,
the number of unreachable objects will grow over time.

This is remedied by doing an all-into-one repack instead of a geometric
repack whenever we determine that the geometric repack would end up
merging all packfiles anyway. This all-into-one repack then performs our
usual reachability checks and writes unreachable objects into a cruft
pack. As cruft packs won't ever be merged during geometric repacks we
can thus phase out these objects over time.

Of course, this still means that we retain unreachable objects for far
longer than with the "gc" strategy. But the maintenance strategy is
intended especially for large repositories, where the basic assumption
is that the set of unreachable objects will be significantly dwarfed by
the number of reachable objects.

If this assumption is ever proven to be too disadvantageous we could for
example introduce a time-based strategy: if the largest packfile has not
been touched for longer than $T, we perform an all-into-one repack. But
for now, such a mechanism is deferred into the future as it is not clear
yet whether it is needed in the first place.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Acked-by: Taylor Blau <me@ttaylorr.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
8 months agobuiltin/maintenance: make "gc" strategy accessible
Patrick Steinhardt [Fri, 24 Oct 2025 06:57:22 +0000 (08:57 +0200)]
builtin/maintenance: make "gc" strategy accessible

While the user can pick the "incremental" maintenance strategy, it is
not possible to explicitly use the "gc" strategy. This has two
downsides:

  - It is impossible to use the default "gc" strategy for a specific
    repository when the strategy was globally set to a different strategy.

  - It is not possible to use git-gc(1) for scheduled maintenance.

Address these issues by making making the "gc" strategy configurable.
Furthermore, extend the strategy so that git-gc(1) runs for both manual
and scheduled maintenance.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Acked-by: Taylor Blau <me@ttaylorr.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
8 months agobuiltin/maintenance: extend "maintenance.strategy" to manual maintenance
Patrick Steinhardt [Fri, 24 Oct 2025 06:57:21 +0000 (08:57 +0200)]
builtin/maintenance: extend "maintenance.strategy" to manual maintenance

The "maintenance.strategy" configuration allows users to configure how
Git is supposed to perform repository maintenance. The idea is that we
provide a set of high-level strategies that may be useful in different
contexts, like for example when handling a large monorepo. Furthermore,
the strategy can be tweaked by the user by overriding specific tasks.

In its current form though, the strategy only applies to scheduled
maintenance. This creates something of a gap, as scheduled and manual
maintenance will now use _different_ strategies as the latter would
continue to use git-gc(1) by default. This makes the strategies way less
useful than they could be on the one hand. But even more importantly,
the two different strategies might clash with one another, where one of
the strategies performs maintenance in such a way that it discards
benefits from the other strategy.

So ideally, it should be possible to pick one strategy that then applies
globally to all the different ways that we perform maintenance. This
doesn't necessarily mean that the strategy always does the _same_ thing
for every maintenance type. But it means that the strategy can configure
the different types to work in tandem with each other.

Change the meaning of "maintenance.strategy" accordingly so that the
strategy is applied to both types, manual and scheduled. As preceding
commits have introduced logic to run maintenance tasks depending on this
type we can tweak strategies so that they perform those tasks depending
on the context.

Note that this raises the question of backwards compatibility: when the
user has configured the "incremental" strategy we would have ignored
that strategy beforehand. Instead, repository maintenance would have
continued to use git-gc(1) by default.

But luckily, we can match that behaviour by:

  - Keeping all current tasks of the incremental strategy as
    `MAINTENANCE_TYPE_SCHEDULED`. This ensures that those tasks will not
    run during manual maintenance.

  - Configuring the "gc" task so that it is invoked during manual
    maintenance.

Like this, the user shouldn't observe any difference in behaviour.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Acked-by: Taylor Blau <me@ttaylorr.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
8 months agobuiltin/maintenance: run maintenance tasks depending on type
Patrick Steinhardt [Fri, 24 Oct 2025 06:57:20 +0000 (08:57 +0200)]
builtin/maintenance: run maintenance tasks depending on type

We basically have three different ways to execute repository
maintenance:

  1. Manual maintenance via `git maintenance run`.

  2. Automatic maintenance via `git maintenance run --auto`.

  3. Scheduled maintenance via `git maintenance run --schedule=`.

At the moment, maintenance strategies only have an effect for the last
type of maintenance. This is about to change in subsequent commits, but
to do so we need to be able to skip some tasks depending on how exactly
maintenance was invoked.

Introduce a new maintenance type that discern between manual (1 & 2) and
scheduled (3) maintenance. Convert the `enabled` field into a bitset so
that it becomes possible to specifiy which tasks exactly should run in a
specific context.

The types picked for existing strategies match the status quo:

  - The default strategy is only ever executed as part of a manual
    maintenance run. It is not possible to use it for scheduled
    maintenance.

  - The incremental strategy is only ever executed as part of a
    scheduled maintenance run. It is not possible to use it for manual
    maintenance.

The strategies will be tweaked in subsequent commits to make use of this
new infrastructure.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Acked-by: Taylor Blau <me@ttaylorr.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
8 months agobuiltin/maintenance: improve readability of strategies
Patrick Steinhardt [Fri, 24 Oct 2025 06:57:19 +0000 (08:57 +0200)]
builtin/maintenance: improve readability of strategies

Our maintenance strategies are essentially a large array of structures,
where each of the tasks can be enabled and scheduled individually. With
the current layout though all the configuration sits on the same nesting
layer, which makes it a bit hard to discern which initialized fields
belong to what task.

Improve readability of the individual tasks by using nested designated
initializers instead.

Suggested-by: Taylor Blau <me@ttaylorr.com>
Signed-off-by: Patrick Steinhardt <ps@pks.im>
Acked-by: Taylor Blau <me@ttaylorr.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
8 months agobuiltin/maintenance: don't silently ignore invalid strategy
Patrick Steinhardt [Fri, 24 Oct 2025 06:57:18 +0000 (08:57 +0200)]
builtin/maintenance: don't silently ignore invalid strategy

When parsing maintenance strategies we completely ignore the
user-configured value in case it is unknown to us. This makes it
basically undiscoverable to the user that scheduled maintenance is
devolving into a no-op.

Change this to instead die when seeing an unknown maintenance strategy.
While at it, pull out the parsing logic into a separate function so that
we can reuse it in a subsequent commit.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Acked-by: Taylor Blau <me@ttaylorr.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
8 months agobuiltin/maintenance: make the geometric factor configurable
Patrick Steinhardt [Fri, 24 Oct 2025 06:57:17 +0000 (08:57 +0200)]
builtin/maintenance: make the geometric factor configurable

The geometric repacking task uses a factor of two for its geometric
sequence, meaning that each next pack must contain at least twice as
many objects as the next-smaller one. In some cases it may be helpful to
configure this factor though to reduce the number of packfile merges
even further, e.g. in very big repositories. But while git-repack(1)
itself supports doing this, the maintenance task does not give us a way
to tune it.

Introduce a new "maintenance.geometric-repack.splitFactor" configuration
to plug this gap.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Acked-by: Taylor Blau <me@ttaylorr.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
8 months agobuiltin/maintenance: introduce "geometric-repack" task
Patrick Steinhardt [Fri, 24 Oct 2025 06:57:16 +0000 (08:57 +0200)]
builtin/maintenance: introduce "geometric-repack" task

Introduce a new "geometric-repack" task. This task uses our geometric
repack infrastructure as provided by git-repack(1) itself, which is a
strategy that especially hosting providers tend to use to amortize the
costs of repacking objects.

There is one issue though with geometric repacks, namely that they
unconditionally pack all loose objects, regardless of whether or not
they are reachable. This is done because it means that we can completely
skip the reachability step, which significantly speeds up the operation.
But it has the big downside that we are unable to expire objects over
time.

To address this issue we thus use a split strategy in this new task:
whenever a geometric repack would merge together all packs, we instead
do an all-into-one repack. By default, these all-into-one repacks have
cruft packs enabled, so unreachable objects would now be written into
their own pack. Consequently, they won't be soaked up during geometric
repacking anymore and can be expired with the next full repack, assuming
that their expiry date has surpassed.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Acked-by: Taylor Blau <me@ttaylorr.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
8 months agobuiltin/gc: make `too_many_loose_objects()` reusable without GC config
Patrick Steinhardt [Fri, 24 Oct 2025 06:57:15 +0000 (08:57 +0200)]
builtin/gc: make `too_many_loose_objects()` reusable without GC config

To decide whether or not a repository needs to be repacked we estimate
the number of loose objects. If the number exceeds a certain threshold
we perform the repack, otherwise we don't.

This is done via `too_many_loose_objects()`, which takes as parameter
the `struct gc_config`. This configuration is only used to determine the
threshold. In a subsequent commit we'll add another caller of this
function that wants to pass a different limit than the one stored in
that structure.

Refactor the function accordingly so that we only take the limit as
parameter instead of the whole structure.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Acked-by: Taylor Blau <me@ttaylorr.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
8 months agobuiltin/gc: remove global `repack` variable
Patrick Steinhardt [Fri, 24 Oct 2025 06:57:14 +0000 (08:57 +0200)]
builtin/gc: remove global `repack` variable

The global `repack` variable is used to store all command line arguments
that we eventually want to pass to git-repack(1). It is being appended
to from multiple different functions, which makes it hard to follow the
logic. Besides being hard to follow, it also makes it unnecessarily hard
to reuse this infrastructure in new code.

Refactor the code so that we store this variable on the stack and pass
a pointer to it around as needed. This is done so that we can reuse
`add_repack_all_options()` in a subsequent commit.

The refactoring itself is straight-forward. One function that deserves
attention though is `need_to_gc()`: this function determines whether or
not we need to execute garbage collection for `git gc --auto`, but also
for `git maintenance run --auto`. But besides figuring out whether we
have to perform GC, the function also sets up the `repack` arguments.

For `git gc --auto` it's trivial to adapt, as we already have the
on-stack variable at our fingertips. But for the maintenance condition
it's less obvious what to do.

As it turns out, we can just use another temporary variable there that
we then immediately discard. If we need to perform GC we execute a child
git-gc(1) process to repack objects for us, and that process will have
to recompute the arguments anyway.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Acked-by: Taylor Blau <me@ttaylorr.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
8 months agodiff: simplify run_external_diff() quiet logic
Jeff King [Fri, 24 Oct 2025 17:25:07 +0000 (13:25 -0400)]
diff: simplify run_external_diff() quiet logic

We'd sometimes end up in run_external_diff() to do a dry-run diff (e.g.,
to find content-level changes for --quiet). We recognize this quiet mode
by seeing the lack of DIFF_FORMAT_PATCH in the output format.

But since introducing an explicit dry-run check via 3ed5d8bd73 (diff:
stop output garbled message in dry run mode, 2025-10-20), this logic can
never trigger. We can only get to this function by calling
diff_flush_patch(), and that comes from only two places:

  1. A dry-run flush comes from diff_flush_patch_quietly(), which is
     always in dry-run mode (so the other half of our "||" is true
     anyway).

  2. A regular flush comes from diff_flush_patch_all_file_pairs(),
     which is only called when output_format has DIFF_FORMAT_PATCH in
     it.

So we can simplify our "quiet" condition to just checking dry-run mode
(which used to be a specific flag, but recently became just a NULL
"file" pointer). And since it's so simple, we can just do that inline.
This makes the logic about o->file more obvious, since we handle the
NULL and non-stdout cases next to each other.

Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>