Git development
 help / color / mirror / Atom feed
* Re: .mailmap etiquette (was "Re: [PATCH 1/7] parse-options: introduce OPT_HIDDEN_GROUP")
From: Junio C Hamano @ 2026-07-16 22:31 UTC (permalink / raw)
  To: D. Ben Knoble
  Cc: Christian Couder, Git, Patrick Steinhardt, Elijah Newren,
	Jeff King, brian m . carlson, Johannes Schindelin, Justin Tobler,
	Christian Couder
In-Reply-To: <CALnO6CCauJhCmgKjV6QJ44cZD=2ah6=rJaV0+2ZMYTo7ycghEA@mail.gmail.com>

"D. Ben Knoble" <ben.knoble@gmail.com> writes:

> Speaking of .mailmap… if I were going to send future patches under a
> new email address, would you prefer
>
> (a) a series with the 1st commit being a .mailmap update (subsequent
> commits bearing the new email address, of course, but unrelated to the
> .mailmap update)
> (b) a one-patch email with a .mailmap update
> (c) the same as (b), but only after commits with the new email address
> have stabilized in next or master

I am not sure what sort of complexity you are anticipating, but
having you send (b) and me applying it directly to 'master'
would be the simplest approach, wouldn't it?  After all, adding
a new entry to .mailmap does not invalidate your old identity;
it merely links the new one to the same person.





^ permalink raw reply

* What's cooking in git.git (Jul 2026, #07)
From: Junio C Hamano @ 2026-07-16 23:30 UTC (permalink / raw)
  To: git

Here are the topics that have been cooking in my tree.  Commits
prefixed with '+' are in 'next' (being in 'next' is a sign that a
topic is stable enough to be used and is a candidate to be in a
future release).  Commits prefixed with '-' are only in 'seen', and
aren't considered "accepted" at all.  They may be annotated with a URL
to a message that raises issues but they are by no means exhaustive.
A topic without enough support may be discarded after a long period
of no activity (of course, it can be resubmitted when new interest
arises).

The second batch of topics have now graduated to the 'master'
branch.

Copies of the source code to Git live in many repositories, and the
following is a list of the ones I push into or their mirrors.  Some
repositories have only a subset of branches.

With maint, master, next, seen, todo:

	git://git.kernel.org/pub/scm/git/git.git/
	git://repo.or.cz/alt-git.git/
	https://kernel.googlesource.com/pub/scm/git/git/
	https://github.com/git/git/
	https://gitlab.com/git-scm/git/

With all the integration branches and topics broken out:

	https://github.com/gitster/git/

Even though the preformatted documentation in HTML and man format
are not sources, they are published in these repositories for
convenience (replace "htmldocs" with "manpages" for the manual
pages):

	git://git.kernel.org/pub/scm/git/git-htmldocs.git/
	https://github.com/gitster/git-htmldocs.git/

Release tarballs are available at:

	https://www.kernel.org/pub/software/scm/git/

--------------------------------------------------
[Graduated to 'master']

* jc/history-message-prep-fix (2026-06-29) 1 commit
  (merged to 'next' on 2026-07-06 at 00534a21ce)
 + history: streamline message preparation and plug file stream leak

 A write file stream resource leak has been fixed as part of a code
 cleanup.

 Graduated to 'master'.
 cf. <akO1mhi2u2PntLbt@pks.im>
 source: <xmqqmrwdxrat.fsf@gitster.g>


* jk/bloom-leak-fixes (2026-06-30) 3 commits
  (merged to 'next' on 2026-07-08 at 3b9a1cda3f)
 + line-log: drop extra copy of range with bloom filters
 + revision: avoid leaking bloom keyvecs with multiple traversals
 + bloom: make bloom-filter slab initialization idempotent

 Various memory leaks in the Bloom-filter code paths that are exposed
 when running tests with the 'GIT_TEST_COMMIT_GRAPH_CHANGED_PATHS=1'
 environment variable have been plugged.

 Graduated to 'master'.
 cf. <b641aed4-ad52-477b-b1d8-9d8e470be46f@gmail.com>
 cf. <xmqqo6gqobrt.fsf@gitster.g>
 source: <20260701063538.GA2579765@coredump.intra.peff.net>


* mg/meson-hook-list-buildfix (2026-07-01) 1 commit
  (merged to 'next' on 2026-07-08 at 10763a0ebc)
 + meson: restore hook-list.h to builtin_sources

 A racy build failure under Meson has been corrected by ensuring that
 the generated header file 'hook-list.h' is built before compiling
 files in 'builtin_sources' that depend on it.

 Graduated to 'master'.
 cf. <akZGJP1kVtjBFN_e@pks.im>
 source: <20260701193928.358825-1-floppym@gentoo.org>


* ps/history-drop (2026-07-01) 11 commits
  (merged to 'next' on 2026-07-08 at 6fb84708a4)
 + builtin/history: implement "drop" subcommand
 + builtin/history: split handling of ref updates into two phases
 + replay: expose `replay_result_queue_update()`
 + reset: stop assuming that the caller passes in a clean index
 + reset: allow the caller to specify the current HEAD object
 + reset: introduce ability to skip updating HEAD
 + reset: introduce dry-run mode
 + reset: modernize flags passed to `reset_working_tree()`
 + reset: rename `reset_head()`
 + reset: drop `USE_THE_REPOSITORY_VARIABLE`
 + read-cache: split out function to drop unmerged entries to stage 0

 The experimental 'git history' command has been taught a new 'drop'
 subcommand to remove a commit, with its descendants replayed onto its
 parent.

 Graduated to 'master'.
 cf. <xmqq1pdmprbk.fsf@gitster.g>
 cf. <CAP8UFD3OAktVQsLuqBNFH2uhEO31PH8ZF3ZT1ZW8k++XE8YLPw@mail.gmail.com>
 source: <20260701-b4-pks-history-drop-v8-0-19b5cdf1facd@pks.im>


* ps/odb-drop-whence (2026-07-02) 7 commits
  (merged to 'next' on 2026-07-08 at f43ee51cc3)
 + odb: document object info fields
 + odb: drop `whence` field from object info
 + treewide: convert users of `whence` to the new source field
 + odb: add `source` field to struct object_info_source
 + odb: make backend-specific fields optional
 + packfile: thread odb_source_packed through packed_object_info()
 + Merge branch 'ps/odb-source-packed' into ps/odb-drop-whence
 (this branch is used by ps/odb-for-each-object-filter.)

 The 'whence' field in 'struct object_info' has been removed.  The
 backend-specific object information retrieval has been refactored into
 an opt-in 'struct object_info_source' structure.

 Graduated to 'master'.
 cf. <xmqqv7b0rmt6.fsf@gitster.g>
 source: <20260702-b4-pks-odb-drop-whence-v2-0-b0af7468ad95@pks.im>


* ps/refs-writing-subcommands (2026-07-06) 5 commits
  (merged to 'next' on 2026-07-08 at f001147283)
 + builtin/refs: add "rename" subcommand
 + builtin/refs: add "create" subcommand
 + builtin/refs: add "update" subcommand
 + builtin/refs: add "delete" subcommand
 + builtin/refs: drop `the_repository`
 (this branch is used by ps/refs-wo-the-repository.)

 The 'git refs' toolbox has been extended with new 'create', 'delete',
 'update', and 'rename' subcommands to create, delete, update, and
 rename references, respectively.

 Graduated to 'master'.
 source: <20260706-pks-refs-writing-subcommands-v4-0-d51f6ce7f830@pks.im>


* rs/blame-abbrev-marks (2026-07-06) 1 commit
  (merged to 'next' on 2026-07-08 at e4962bd3d5)
 + blame: reserve mark column only if necessary

 The alignment of commit object name abbreviations in 'git blame'
 output has been optimized to reserve a column for marks (caret,
 question mark, or asterisk) only when such marks are actually shown.

 Graduated to 'master'.
 cf. <xmqqzf0397u1.fsf@gitster.g>
 source: <92991b5e-0667-4315-89d5-1514a5499297@web.de>

--------------------------------------------------
[New Topics]

* js/coverity-unchecked-returns-fix (2026-07-14) 11 commits
 - bisect: handle dup() failure when redirecting stdout
 - bisect: check get_terms return at all call sites
 - bisect: check strbuf_getline_lf return when reading terms
 - transport-helper: warn when export-marks file cannot be finalized
 - transport-helper: check dup() return in get_exporter
 - compat/pread: check initial lseek for errors
 - last-modified: handle repo_parse_commit() failures
 - reftable tests: check reftable_table_init_ref_iterator() return
 - reftable/block: check deflateInit() return value
 - config: propagate launch_editor() failure in show_editor()
 - http: die on curl_easy_duphandle failure in get_active_slot

 A handful of code paths have been corrected to check return values
 from functions like 'curl_easy_duphandle()', 'deflateInit()',
 'lseek()', 'dup()', and 'strbuf_getline_lf()', resolving several
 Coverity warnings about unchecked returns.

 Waiting for response.
 cf. <xmqqldbdqciy.fsf@gitster.g>
 cf. <xmqqh5m1qcfh.fsf@gitster.g>
 cf. <alcvmX3b6y92KE4y@pks.im>
 cf. <alcvnm0xiOv5W0w_@pks.im>
 source: <pull.2179.git.1784069325.gitgitgadget@gmail.com>


* jk/diff-relative-cached-unmerged (2026-07-14) 1 commit
 - diff: ignore unmerged paths outside prefix with --relative --cached

 'git diff --relative' running with '--cached' has been corrected to
 avoid a segfault when encountering unmerged paths outside the
 prefix.

 Needs review.
 source: <20260715060523.GA517940@coredump.intra.peff.net>


* jc/submodule-helper-avoid-zu (2026-07-15) 1 commit
 - submodule--helper: avoid use of %zu for now

 An accidental use of '%zu' format flag in 'git submodule--helper'
 has been corrected to use 'PRIuMAX' and cast the value to
 'uintmax_t', to avoid portability issues.

 Will merge to 'next'?
 cf. <87a4rrxg3r.fsf@gentoo.mail-host-address-is-not-set>
 source: <xmqq4ii0ko9t.fsf@gitster.g>


* sk/t7614-do-not-hide-git-exit-status (2026-07-15) 1 commit
  (merged to 'next' on 2026-07-16 at 0d143986e7)
 + t7614: avoid hiding git's exit code in a pipe

 The test script 't/t7614-merge-signoff.sh' has been updated to avoid
 suppressing the exit code of 'git' commands in a pipe.

 Will merge to 'master'.
 cf. <xmqq1pd4m4ea.fsf@gitster.g>
 source: <20260715113344.3490-1-diy2903@gmail.com>


* ds/trace2-tolerate-failed-timestamp (2026-07-15) 1 commit
 - trace2: tolerate failed timestamp formatting

 The trace2 telemetry library has been updated to tolerate failures
 from system calls like 'gettimeofday()' and datetime formatting
 functions, replacing potential program crashes with blank placeholder
 timestamps in the traces.

 Needs review.
 source: <pull.2178.git.1784131932489.gitgitgadget@gmail.com>


* mm/revision-pure-get-commit-action (2026-07-15) 1 commit
 - revision: make get_commit_action() a pure predicate

 The 'get_commit_action()' function has been refactored to be a pure
 predicate by moving the side-effecting line-level log range folding to
 'simplify_commit()'.  This ensures that evaluating a commit's action
 before the walk reaches it does not prematurely mutate its tracked
 line ranges, making it safer for potential lookahead evaluations.

 Needs review.
 source: <pull.2169.git.1784143793613.gitgitgadget@gmail.com>


* rs/remote-curl-simplify-push-specs (2026-07-14) 1 commit
 - remote-curl: simplify passing of push specs

 The passing of push destination specifications in the 'remote-curl'
 helper has been simplified by removing the explicit 'count' parameter
 and relying on the NULL-termination of the array.

 Will merge to 'next'?
 cf. <alhr2bb0lUTHtvjO@pks.im>
 cf. <xmqqpl0nhutx.fsf@gitster.g>
 source: <935883f3-3be4-4c51-9711-5208b9ef9ca1@web.de>


* js/wincred-fixes (2026-07-16) 2 commits
  (merged to 'next' on 2026-07-16 at 8c5927f06f)
 + wincred: prevent silent credential loss when storing OAuth tokens
 + wincred: avoid memory corruption when erasing a credential

 The wincred credential helper has been updated to avoid memory
 corruption when erasing credentials and to prevent silent
 credential loss when storing OAuth tokens, by correcting buffer
 allocations and arguments passed to safe-CRT APIs.

 Will merge to 'master'.
 source: <pull.2182.git.1784212072.gitgitgadget@gmail.com>


* kk/no-walk-pathspec-fix (2026-07-16) 2 commits
 - revision: fix --no-walk path filtering regression
 - Merge branch 'kk/streaming-walk-pqueue' into kk/no-walk-pathspec-fix

 The 'git rev-list --no-walk' command lost pathspec filtering when the
 streaming walk was refactored, which has been corrected.

 Will merge to 'next'.
 source: <pull.2181.git.1784198879711.gitgitgadget@gmail.com>


* cc/fast-import-usage (2026-07-16) 7 commits
 - fast-import: use struct option for usage string
 - fast-import: move command state globals into 'struct fast_import_state'
 - fast-import: introduce 'struct fast_import_state'
 - fast-import: localize 'i' into the 'for' loops using it
 - api-parse-options.adoc: document hidden and OPT_*_F option macros
 - api-parse-options.adoc: document per-option flags
 - parse-options: introduce OPT_HIDDEN_GROUP

 The usage string of 'git fast-import' has been updated to use the
 'parse_options' API for displaying help, and its SYNOPSIS in the
 documentation has been standardized to match.

 Needs review.
 cf. <xmqq4ihyehyb.fsf@gitster.g>
 cf. <xmqqcxwmeiwq.fsf@gitster.g>
 source: <20260716165517.433849-1-christian.couder@gmail.com>


* ps/copy-wo-the-repository (2026-07-16) 1 commit
 - copy: drop dependency on `the_repository`

 The 'copy_file()' and 'copy_file_with_time()' functions have been
 refactored to take a repository parameter, allowing the removal of the
 implicit dependency on the global 'the_repository' variable in
 'copy.c'.

 Needs review.
 source: <20260716-pks-copy-wo-the-repository-v2-1-8f5e32942929@pks.im>


* ps/refspec-wo-the-repository (2026-07-16) 3 commits
 - refspec: stop depending on `the_repository`
 - refspec: let callers pass in hash algorithm when parsing items
 - refspec: group related structures and functions

 The dependency on the global 'the_repository' variable in the
 'refspec.c' API has been removed by passing the hash algorithm
 explicitly to refspec-parsing functions and storing it in 'struct
 refspec'.

 Needs review.
 source: <20260716-pks-refspec-wo-the-repository-v1-0-aa40844d067f@pks.im>


* ps/writev (2026-07-16) 5 commits
 - fast-import: use writev(3p) to send cat-blob responses
 - sideband: use writev(3p) to send pktlines
 - wrapper: properly handle MAX_IO_SIZE in writev(3p)
 - wrapper: introduce writev(3p) wrappers
 - compat/posix: introduce writev(3p) wrapper

 A compatibility wrapper for 'writev(3p)' has been reintroduced,
 including fixes for CMake build and 'MAX_IO_SIZE' limits on NonStop.
 Calls to 'write(3p)' in 'send_sideband()' and 'cat_blob()' have been
 refactored to use 'writev(3p)' wrappers to reduce syscall overhead.

 Needs review.
 cf. <f8050598-392f-44c9-8d66-0454740a7a12@kdbg.org>
 cf. <a2676ec6-39d5-4220-8549-10a17daec668@hogyros.de>
 source: <20260716-pks-reintroduce-writev-v1-0-ea9038c884bc@pks.im>


* sc/wt-status-avoid-quadratic-insertion (2026-07-16) 1 commit
 - wt-status: avoid quadratic insertion for untracked paths

 The enumeration of untracked and ignored files in 'git status' has
 been optimized by avoiding quadratic complexity insertion into string
 lists, reducing the construction cost from O(n^2) to O(n log n).

 Needs review.
 source: <20260716185045.229320-1-sahityajb@gmail.com>

--------------------------------------------------
[Stalled]

* kh/doc-trailers (2026-06-10) 10 commits
 - doc: interpret-trailers: document comment line treatment
 - doc: interpret-trailers: commit to “trailer block” term
 - doc: interpret-trailers: join new-trailers again
 - doc: interpret-trailers: add key format example
 - doc: interpret-trailers: explain key format
 - doc: interpret-trailers: explain the format after the intro
 - doc: interpret-trailers: not just for commit messages
 - doc: interpret-trailers: use “metadata” in Name as well
 - doc: interpret-trailers: replace “lines” with “metadata”
 - doc: interpret-trailers: stop fixating on RFC 822

 Documentation for 'git interpret-trailers' has been updated to explain
 the format of trailer keys (alphanumeric characters and hyphens),
 replace outdated terminology, define key terms upfront, and document
 how comment lines in the input are treated.

 Expecting a reroll for too long, stalled.
 cf. <729baf6b-53ea-4e8d-95ab-5935667e66c2@app.fastmail.com>
 source: <V3_CV_doc_int-tr_key_format.8a3@msgid.xyz>


* sn/rebase-update-refs-symrefs (2026-06-03) 1 commit
 - rebase: skip branch symref aliases

 'git rebase --update-refs' has been taught to resolve local branch
 symrefs to their referents before queuing updates, ensuring aliases of
 the current branch are skipped and duplicate updates are avoided to
 prevent failures when branch aliases are present.

 Waiting for response for too long, stalled.
 cf. <f982c386-e329-4ab0-b695-e540bcb9de3d@gmail.com>
 source: <pull.2126.v2.git.1780482436865.gitgitgadget@gmail.com>


* ap/http-redirect-wwwauth-fix (2026-06-02) 1 commit
 - http: preserve wwwauth_headers across redirects

 When 'cURL' follows a redirect, the 'WWW-Authenticate' headers from
 the redirect target were lost because 'credential_from_url()' cleared
 the credential state.  This has been fixed by preserving the collected
 headers across the redirect update.

 Will discard.
 cf. <xmqqmrw2zavx.fsf@gitster.g>
 source: <20260602161150.1527493-1-aplattner@nvidia.com>


* jt/config-lock-timeout (2026-05-17) 1 commit
 - config: retry acquiring config.lock, configurable via core.configLockTimeout

 Configuration file locking has been updated to retry for a short
 period, avoiding failures when multiple processes attempt to update
 the configuration simultaneously.

 Waiting for response for too long, stalled.
 cf. <agrIrGwSMFlKTx9x@pks.im>
 source: <20260517132111.1014901-1-joerg@thalheim.io>

--------------------------------------------------
[Cooking]

* tb/send-pack-no-ref-delta (2026-07-12) 4 commits
 - send-pack: honor `no-ref-delta` capability
 - pack-objects: support reuse with `--no-ref-delta`
 - pack-objects: introduce `--no-ref-delta`
 - t/helper: teach pack-deltas to list delta entries

 'git send-pack' has been taught to refrain from sending 'REF_DELTA'
 encoded packfiles when the other side asks it to.

 Needs review.
 source: <alQ7WKITYDXfiVn9@com-79390>


* cc/doc-fast-export-synopsis-fix (2026-07-13) 1 commit
  (merged to 'next' on 2026-07-16 at b1dbc0cb3f)
 + fast-export: standardize usage string and SYNOPSIS

 The usage string and SYNOPSIS for 'git fast-export' have been
 standardized to make them consistent with each other and with other
 commands.

 Will merge to 'master'.
 cf. <alX5Nl8uX4ctVqo3@pks.im>
 cf. <xmqq4ii228dd.fsf@gitster.g>
 source: <20260713124153.245268-1-christian.couder@gmail.com>


* sk/t1100-modernize (2026-07-14) 2 commits
  (merged to 'next' on 2026-07-16 at 621ca4ca5f)
 + t1100: move creation of expected output into setup test
 + t1100: modernize test style

 The test script 't/t1100-commit-tree-options.sh' has been modernized
 by converting test cases to the modern style (using single quotes and
 tab indentation) and moving the creation of the expected file inside
 the setup test so it runs under the protection of the test harness.

 Will merge to 'master'.
 cf. <xmqq4ii1v7x0.fsf@gitster.g>
 source: <20260714122033.61947-1-diy2903@gmail.com>


* tn/packfile-uri-concurrency (2026-07-13) 2 commits
 - fetch-pack: accept "pack" output for packfile URIs
 - http: use unique tempfiles for packfile URI downloads

 Concurrent downloads of packfiles via packfile URIs have been
 supported by using unique temporary files, preventing corruption when
 multiple processes fetch the same pack.  The 'fetch-pack' command has
 also been updated to tolerate pre-existing '.keep' files.

 Expecting a reroll.
 cf. <alaAi4vNwi-KabYV@com-76773>
 source: <alVn-QmK3K91_tkH@com-76773>


* rs/strbuf-avoid-redundant-reset (2026-07-14) 1 commit
  (merged to 'next' on 2026-07-16 at f258ce38ba)
 + strbuf: avoid redundant reset in strbuf_getwholeline()

 A redundant 'strbuf_reset()' call in the 'HAVE_GETDELIM' path of
 'strbuf_getwholeline()' has been removed, as 'getdelim()' overwrites
 the buffer and the length is updated afterward.

 Will merge to 'master'.
 cf. <xmqq8q7dv82b.fsf@gitster.g>
 cf. <20260714214941.GB4095533@coredump.intra.peff.net>
 source: <d4ffe7fb-f782-4f06-9e3b-f72729d1e225@web.de>


* rs/tempfile-wo-the-repository (2026-07-14) 5 commits
 - use repo_hold_lock_file_for_update{,_mode,_timeout}() with custom repos
 - tempfile: stop using the_repository
 - lockfile: add repo_hold_lock_file_for_update{,_timeout}{,_mode}()
 - refs/packed: use repo_create_tempfile()
 - tempfile: add repo_create_tempfile{,_mode}()

 The tempfile and lockfile APIs have been refactored to stop depending
 on the 'the_repository' global variable, and their callers have been
 updated to use the repository-aware variants.

 Waiting for response.
 cf. <aldYW4TPUqgDMRcf@pks.im>
 source: <20260714175956.54601-1-l.s.r@web.de>


* js/pack-objects-delta-size-t (2026-07-09) 12 commits
 - git-zlib: widen `git_deflate_bound()` to `size_t`
 - t/helper/test-pack-deltas: widen `do_compress()`'s maxsize local to `size_t`
 - http-push: widen `start_put()`'s size local from `ssize_t` to `size_t`
 - diff: widen `deflate_it()`'s bound local from int to `size_t`
 - archive-zip: widen `zlib_deflate_raw()`'s maxsize local to `size_t`
 - packfile, git-zlib: widen `use_pack()` and zstream avail fields to `size_t`
 - delta: widen `create_delta()` and `diff_delta()` to `size_t`
 - pack-objects: widen `mem_usage` and `try_delta()`'s out-param to `size_t`
 - pack-objects: widen `free_unpacked()` return to `size_t`
 - pack-objects: widen delta-cache accounting to `size_t`
 - delta: widen `create_delta_index()` parameter to `size_t`
 - diff-delta: widen `struct delta_index`' size fields to `size_t`

 The 'pack-objects' and delta-encoding code paths have been updated to
 use 'size_t' instead of 'unsigned long' for object sizes and offset
 limits, avoiding potential truncation issues on 64-bit Windows.

 Needs review.
 source: <pull.2175.git.1783615780.gitgitgadget@gmail.com>


* cl/b4-cover-change-id (2026-07-10) 1 commit
  (merged to 'next' on 2026-07-13 at 15c7ad9a3f)
 + b4: include change-id in cover template

 The in-tree 'b4' cover letter template has been updated to include the
 'change-id' trailer, ensuring that sent tags generated by 'b4' contain
 the required tracking information for subsequent runs.

 Will merge to 'master'.
 source: <20260710-add-change-id-to-b4-template-v1-1-1bd37a25064e@black-desk.cn>


* ps/odb-stream-double-close-fix (2026-07-10) 1 commit
  (merged to 'next' on 2026-07-13 at dd2c5795b7)
 + object-file: fix closing object stream twice

 The stream-based object signature verification path has been
 corrected to avoid double-closing the stream on read errors.

 Will merge to 'master'.
 source: <20260710-pks-odb-stream-double-close-v1-1-d5fa233a37c7@pks.im>


* pz/fetch-submodule-errors-config (2026-07-16) 2 commits
 - fetch: add fetch.submoduleErrors to make submodule fetch errors non-fatal
 - submodule: fix premature failure in recursive submodule fetch

 The 'git fetch' command has been updated to allow configuring how
 submodule fetch errors are handled.  A new configuration variable
 'fetch.submoduleErrors' and a corresponding '--submodule-errors'
 command-line option have been introduced, allowing users to make
 submodule fetch errors non-fatal (warn instead of fail).
 Additionally, a premature failure during recursive submodule fetches
 has been fixed by deferring the error until the OID-based retry phase
 also fails.

 Needs review.
 source: <20260716140956.1023740-1-paulius.zaleckas@gmail.com>


* gr/add-e-use-apply-api (2026-07-10) 1 commit
 - builtin/add.c: replace run_command() with direct apply_all_patches() call

 The application of the edited patch in 'git add -e' has been
 refactored to use the internal apply API directly, avoiding the need
 to spawn a 'git apply' subprocess.

 Needs review.
 source: <20260711061246.58079-1-gatlavishweshwarreddy26@gmail.com>


* fz/rebase-autosquash-empty (2026-07-11) 1 commit
 . sequencer: honor --empty when a fixup!/squash! empties its target

 A commit that is emptied by melding a 'fixup!' or 'squash!' commit
 during 'git rebase --autosquash' is now handled according to the
 '--empty' option, allowing it to be dropped, kept, or to halt the
 rebase.

 Waiting for response.
 cf. <690b965e-5f07-4aa4-a64c-96e60a86d73b@gmail.com>
 source: <20260711-fz-autosquash-empty-v3-1-d227b63eb511@gmail.com>


* dm/submodule-update-i-shorthand (2026-07-07) 1 commit
  (merged to 'next' on 2026-07-15 at 55ef0fb748)
 + submodule--helper: accept '-i' shorthand for update --init

 The '-i' shorthand for the '--init' option, which was accepted by the
 'git submodule update' command until it was broken in a modernization
 of the option-parsing code, has been restored.

 Will merge to 'master'.
 cf. <xmqq8q7ltf51.fsf@gitster.g>
 source: <20260708-submodule-init-v1-1-719456077262@atmark-techno.com>


* hf/unpack-trees-quadratic-scan (2026-07-08) 1 commit
  (merged to 'next' on 2026-07-12 at 744f1aede4)
 + unpack-trees: avoid quadratic index scan in next_cache_entry()

 The cache-scanning loop in 'next_cache_entry()' has been optimized
 to avoid rescanning already-unpacked index entries, preventing a
 quadratic performance slow-down when diffing the working tree
 against a commit with a pathspec matching early index entries.

 Will merge to 'master'.
 cf. <xmqqpl0xqh3n.fsf@gitster.g>
 source: <pull.2353.v2.git.git.1783546933992.gitgitgadget@gmail.com>


* jc/relnotes-2.55-rust-fix (2026-07-07) 1 commit
  (merged to 'next' on 2026-07-10 at 444d202a75)
 + Rust: fix description in Release Notes to 2.55

 A description in the release notes for Git 2.55.0 has been
 retroactively updated to clarify that Rust support is enabled by
 default, but still optional, and will become mandatory in Git 3.0.

 Will merge to 'master'.
 source: <xmqqpl0y4rpg.fsf@gitster.g>


* jc/submitting-patches-abandoning (2026-07-08) 1 commit
  (merged to 'next' on 2026-07-10 at 41b9b65b23)
 + SubmittingPatches: document how to retract a topic

 The 'SubmittingPatches' document has been updated to explicitly
 describe the expectation for contributors to retract or abandon their
 patch series when they are no longer pursuing it.

 Will merge to 'master'.
 cf. <ak6U07K1dQPlXxIp@nixos>
 source: <xmqqpl0xv25e.fsf@gitster.g>


* jk/git-hash-cleanups (2026-07-07) 8 commits
  (merged to 'next' on 2026-07-09 at 12a4856545)
 + hash: check ctx->active flag in all wrapper functions
 + http: use idempotent git_hash_discard()
 + csum-file: use idempotent git_hash_discard()
 + hash: make git_hash_discard() idempotent
 + hash: document function pointers and wrappers
 + hash: convert remaining direct function calls
 + hash: use git_hash_init() consistently
 + Merge branch 'jk/hash-algo-leak-fixes' into jk/git-hash-cleanups
 (this branch uses jk/hash-algo-leak-fixes.)

 The 'git_hash_*()' wrappers have been updated to be used consistently
 across the codebase instead of direct calls to members of 'struct
 git_hash_algo', and 'git_hash_discard()' has been made idempotent to
 simplify cleanups.

 Will merge to 'master'.
 cf. <ak4E4-jmgYFSI75O@pks.im>
 source: <20260708035235.GA41491@coredump.intra.peff.net>


* mm/lib-httpd-cgi-safe (2026-07-10) 3 commits
 - t/README: document writing concurrency-safe helpers
 - t/lib-httpd: make http-429 first-request check atomic
 - t/lib-httpd: fix apply-one-time-script race under concurrent requests

 CGI helper scripts used by HTTP-related test scripts have been updated
 to use atomic filesystem operations, preventing race conditions when
 Apache handles concurrent requests.

 Needs review.
 source: <pull.2171.v2.git.1783704657.gitgitgadget@gmail.com>


* mm/sideband-ansi-sgr-colon-fix (2026-05-13) 1 commit
  (merged to 'next' on 2026-07-09 at fd2b979b73)
 + sideband: allow ANSI SGR with colon-separated subfields

 The sideband demultiplexer has been updated to recognize ANSI SGR
 escape sequences that use colon-separated subfields (e.g., for
 256-color or true-color codes).

 Will merge to 'master'.
 cf. <8addf7c0-ae39-f1c0-20ab-52114702aaf6@gmx.de>
 source: <20260513070803.163546-1-grawity@nullroute.lt>


* ps/odb-pluggable-housekeeping (2026-07-12) 12 commits
 - odb: make optimizations pluggable
 - builtin/gc: fix signedness issues in ODB-related functionality
 - builtin/gc: refactor ODB optimizations to operate on "files" source
 - builtin/gc: introduce `odb_optimize_required()`
 - builtin/gc: move geometric repacking into `odb_optimize()`
 - builtin/gc: introduce object database optimization options
 - builtin/gc: inline config values specific to the "files" backend
 - builtin/gc: make repack arguments self-contained
 - builtin/gc: extract object database optimizations into separate function
 - builtin/gc: move worktree and rerere tasks before object optimizations
 - odb: run "pre-auto-gc" hook for all maintenance tasks
 - t7900: simplify how we check for maintenance tasks

 Object database housekeeping in 'git gc' and 'git maintenance' has
 been refactored to be pluggable.  The files-backend specific logic,
 including incremental and geometric repacking as well as object
 pruning, has been moved out of the command implementation and into the
 files object database source, enabling future alternative object
 database backends to implement their own housekeeping services.

 Waiting for response.
 cf. <xmqqwluyyhv1.fsf@gitster.g>
 source: <20260713-b4-pks-odb-optimize-v2-0-9c2c3ee94b38@pks.im>


* tc/bundle-uri-empty-fix (2026-07-08) 2 commits
  (merged to 'next' on 2026-07-12 at 9da32fdaf7)
 + bundle-uri: stop sending invalid bundle configuration
 + bundle-uri: drain remaining response on invalid bundle-uri lines

 The client-side parser of the server-advertised bundle-URI list has
 been updated to drain the remaining response in order to avoid
 protocol desynchronization when the server sends a misconfigured list.
 Also, the server-side has been taught to omit empty configuration
 values instead of sending invalid key-value lines.

 Will merge to 'master'.
 cf. <xmqqtsq9qj5k.fsf@gitster.g>
 source: <20260708-toon-bundle-uri-no-uri-v2-0-09a03d8db556@iotcl.com>


* gr/t1410-reflog-exit-code (2026-07-08) 1 commit
  (merged to 'next' on 2026-07-10 at d0cf55ea54)
 + t1410-reflog.sh: avoid suppressing git's exit code in pipelines

 The pipelines in 't1410-reflog.sh' have been replaced with the
 'test_stdout_line_count' helper to avoid suppressing the exit code of
 'git' commands, ensuring failures are not hidden from the test suite.

 Will merge to 'master'.
 cf. <xmqqtsq8p18x.fsf@gitster.g>
 source: <20260709051229.40363-1-gatlavishweshwarreddy26@gmail.com>


* js/coverity-fixes-null-safety (2026-07-10) 12 commits
  (merged to 'next' on 2026-07-12 at 8d093f411d)
 + shallow: give write_one_shallow() its own hex buffer
 + shallow: fix NULL dereference
 + bisect: ensure non-NULL `head` before using it
 + pack-bitmap: handle missing bitmap for base MIDX
 + revision: avoid dereferencing NULL in `add_parents_only()`
 + replay: die when --onto does not peel to a commit
 + bisect: handle NULL commit in `bisect_successful()`
 + mailsplit: move NULL check before first use of file handle
 + reftable/stack: guard against NULL list_file in stack_destroy
 + remote: guard `remote_tracking()` against NULL remote
 + diff: handle NULL return from repo_get_commit_tree()
 + diffcore-break: guard against NULLed queue entries in merge loop

 Various code paths have been hardened against potential NULL-pointer
 dereferences and invalid file descriptor accesses flagged by
 Coverity.

 Will merge to 'master'.
 cf. <xmqqa4ryg84e.fsf@gitster.g>
 source: <pull.2174.v2.git.1783683577.gitgitgadget@gmail.com>


* ps/odb-for-each-object-filter (2026-07-14) 10 commits
  (merged to 'next' on 2026-07-16 at 8f30e80d33)
 + builtin/cat-file: filter objects via object database
 + odb: introduce object filters to `odb_for_each_object()`
 + pack-bitmap: introduce function to open bitmap for a single source
 + pack-bitmap: drop `_1` suffix from functions that open bitmaps
 + pack-bitmap: iterate object sources when opening bitmaps
 + pack-bitmap: allow aborting iteration of bitmapped objects
 + pack-objects: drop unused return value from add_object_entry()
 + pack-bitmap: mark object filter as `const`
 + odb/source-packed: improve lookup when enumerating objects
 + Merge branch 'ps/odb-drop-whence' into ps/odb-for-each-object-filter

 The object database enumeration interface 'odb_for_each_object()'
 has been taught to accept object filters, allowing the underlying
 backends to optimize the traversal by using reachability bitmaps
 when available.  'git cat-file --batch-all-objects' has been updated
 to use this generic interface, simplifying its code and avoiding
 direct access to ODB backend internals.

 Will merge to 'master'.
 cf. <874ii0h2uf.fsf@emacs.iotcl.com>
 source: <20260715-pks-odb-for-each-object-filter-v4-0-616d7adf7fb7@pks.im>


* ps/refs-wo-the-repository (2026-07-15) 7 commits
 - refs: remove remaining uses of `the_repository`
 - worktree: pass repository to public functions
 - worktree: pass repository to file-local functions
 - worktree: refactor code to use available repositories
 - refs/files: drop `USE_THE_REPOSITORY_VARIABLE`
 - refs/packed: de-globalize handling of "core.packedRefsTimeout"
 - Merge branch 'ps/refs-writing-subcommands' into ps/refs-wo-the-repository

 The ref subsystem and the worktree API have been refactored to pass a
 repository pointer down the call chain, allowing them to drop
 references to the global 'the_repository' variable.  As part of this,
 the handling of the 'core.packedRefsTimeout' configuration has been
 moved into the per-repository ref store structure.

 Will merge to 'next'?
 cf. <xmqqldbbhuh0.fsf@gitster.g>
 source: <20260716-pks-refs-wo-the-repository-v3-0-db0a804e0224@pks.im>


* kk/commit-graph-topo-levels-fix (2026-07-09) 2 commits
  (merged to 'next' on 2026-07-12 at 295a5f9b34)
 + commit-graph: propagate topo_levels slab to all chain layers
 + commit-graph: add trace2 instrumentation for generation DFS

 The 'topo_levels' slab was propagated only to the topmost layer of a
 split commit-graph chain, causing topological levels for commits in
 base layers to be recomputed during incremental writes.  This has been
 corrected.

 Will merge to 'master'.
 cf. <alFu8gZURKhYr1VE@com-79390>
 source: <pull.2170.v2.git.1783609382.gitgitgadget@gmail.com>


* ds/sparse-index-ita-crash (2026-07-06) 1 commit
 - sparse-index: avoid crash on intent-to-add entry outside the cone

 A crash in the sparse-index collapse code when encountering an
 invalidated cache-tree node (due to an intent-to-add path) has been
 fixed by avoiding collapsing such subtrees.

 Needs review.
 source: <pull.2167.git.1783345853272.gitgitgadget@gmail.com>


* ij/subtree-reject-v2-config (2026-07-06) 2 commits
 - git-subtree: Bail out if we find output from Rust rewrite (test)
 - git-subtree: Bail out if we find output from Rust rewrite

 The shell script implementation of 'git subtree' has been updated to
 check for the presence of the configuration file of the new Rust
 implementation, preventing users from accidentally running the old
 script on repositories already managed by the new tool.

 Expecting a reroll.
 cf. <27219.20156.438730.881821@chiark.greenend.org.uk>
 source: <20260706115816.20267-1-ijackson@chiark.greenend.org.uk>


* kk/reftable-tombstone-quadratic-fix (2026-07-10) 2 commits
  (merged to 'next' on 2026-07-12 at 4e60bb0027)
 + reftable: fix quadratic behavior in the presence of tombstones
 + t/perf: add perf test for ref tombstone scenarios

 The performance of ref updates and reads using the 'reftable' backend
 in the presence of many deletion tombstone records has been optimized
 by removing the tombstone suppression flag from the merged iterator
 and instead skipping tombstones at higher-level call sites where
 iteration bounds are known.

 Will merge to 'master'.
 cf. <alECc90WZ9RPqMaA@pks.im>
 source: <pull.2166.v3.git.1783679767.gitgitgadget@gmail.com>


* jm/t0213-skip-emulated-ancestry-tests (2026-07-06) 1 commit
 - t0213: skip ancestry tests under user-mode emulation

 The 'TRACE2_ANCESTRY' prerequisite in the 't0213' test script has been
 refined to avoid failures under user-mode emulation, by verifying that
 the ancestry collector reports the expected process names rather than
 the emulator binary name.

 Needs review.
 source: <pull.2168.git.1783359242130.gitgitgadget@gmail.com>


* bc/parse-options-exit-0-on-help (2026-07-07) 4 commits
  (merged to 'next' on 2026-07-10 at 775654e447)
 + parse-options: exit 0 on -h
 + rev-parse: have --parseopt callers exit 0 on --help
 + parse-options: add a separate case for help output on error
 + t1517: skip svn tests if svn is not installed

 Option parsing with 'git rev-parse --parseopt' and in most 'git'
 subcommands has been updated to exit with 0 (instead of 129) when the
 help option ('-h' or '--help') is requested directly by the user,
 aligning with standard Unix convention.

 Will merge to 'master'.
 cf. <20260708035930.GB41684@coredump.intra.peff.net>
 source: <20260708001557.3581080-1-sandals@crustytoothpaste.net>


* zy/apply-abandoned-header-fix (2026-07-01) 1 commit
 - apply: avoid leaking abandoned git-header state

 A candidate 'git diff' header parsed by 'git apply' has been isolated
 in a temporary structure, preventing any partially parsed state from
 polluting the main patch structure and causing assertions to trip if
 the header is ultimately rejected.

 Needs review.
 source: <20260702041759.51572-1-zhihao.yao@njit.edu>


* jk/hash-algo-leak-fixes (2026-07-02) 9 commits
  (merged to 'next' on 2026-07-09 at 7db7b74972)
 + hash: add platform-specific discard functions
 + hash: fix memory leak copying sha256 gcrypt handles
 + http: discard hash in dumb-http http_object_request
 + check_stream_oid(): discard hash on read error
 + patch-id: discard hash when done
 + csum-file: provide a function to release checkpoints
 + csum-file: always finalize or discard hash
 + hash: add discard primitive
 + csum-file: drop discard_hashfile()
 (this branch is used by jk/git-hash-cleanups.)

 Various code paths that initialize a cryptographic hash context but
 bail out or finish without calling 'git_hash_final()' have been taught
 to call 'git_hash_discard()' to release allocated resources, fixing
 memory leaks when Git is built with non-default backends like
 'OpenSSL' or 'libgcrypt'.

 Will merge to 'master'.
 cf. <aktIIKuReMxJmDsi@pks.im>
 source: <20260702075234.GA1548258@coredump.intra.peff.net>


* ml/t9811-replace-test-f (2026-07-11) 2 commits
  (merged to 'next' on 2026-07-15 at ffb7fcad15)
 + t9811: replace 'test -f' and '! test -f' with 'test_path_*'
 + t9811: break long && chains into multiple lines

 The test script 't/t9811-git-p4-label-import.sh' has been
 modernized to use 'test_path_is_file' and 'test_path_is_missing'
 instead of raw 'test -f' and '! test -f' calls.

 Will merge to 'master'.
 cf. <alTHrUEh4_O5ROeu@pks.im>
 source: <20260711160447.99708-1-marcelomlage@usp.br>


* ps/t-fixes-for-git-test-long (2026-07-05) 9 commits
  (merged to 'next' on 2026-07-09 at c5b13248c8)
 + gitlab-ci: enable "GIT_TEST_LONG"
 + gitlab-ci: disable RAM disk on macOS jobs
 + t: use `test_bool_env` to parse GIT_TEST_LONG
 + t7900: clean up large EXPENSIVE repository
 + t7508: skip EXPENSIVE test that is broken without SIZE_T_IS_64BIT
 + t5608: reduce maximum disk usage
 + t4141: fix inefficient use of dd(1)
 + t0021: skip EXPENSIVE test that is broken without SIZE_T_IS_64BIT
 + README: add GitLab CI badge to make it more discoverable

 Various test scripts have been updated to clean up large temporary
 files and repositories, reducing peak disk usage during testing.
 Also, expensive tests have been disabled on platforms that lack
 sufficient resources (like 32-bit platforms and Windows CI runners),
 and the long test suite has been enabled in GitLab CI.

 Will merge to 'master'.
 cf. <20260707043026.GB677056@coredump.intra.peff.net>
 source: <20260706-b4-pks-t-fixes-for-GIT-TEST-LONG-v3-0-4f6c5a37fd1f@pks.im>


* ih/precompose-flex-array (2026-07-04) 1 commit
  (merged to 'next' on 2026-07-09 at 737a87f65e)
 + precompose_utf8: use a flex array for d_name

 The UTF-8 precomposition wrapper on macOS has been updated to use a
 flexible array member to represent the name of a directory entry,
 preventing fortified libc checks from failing when the name is
 reallocated to be larger than 'NAME_MAX' bytes.

 Will merge to 'master'.
 cf. <20260703050800.GA29216@tb-raspi4>
 source: <20260704233724.16928-1-ihar.hrachyshka@gmail.com>


* sn/osxkeychain-rust-universal (2026-07-07) 3 commits
  (merged to 'next' on 2026-07-10 at fe82b5d188)
 + contrib: wire up osxkeychain in contrib/Makefile on macOS
 + Makefile: support universal macOS builds via RUST_TARGETS
 + Makefile: add $(RUST_LIB) prerequisite to osxkeychain

 The build system has been updated to support building universal macOS
 binaries when 'Rust' is enabled, by compiling separate static archives
 for each target triple listed in 'RUST_TARGETS' and combining them
 using the macOS 'lipo' tool.  The 'git-credential-osxkeychain' helper
 has been updated to link against '$(RUST_LIB)' when 'Rust' is enabled.

 Will merge to 'master'.
 cf. <xmqq4ii9teym.fsf@gitster.g>
 source: <pull.2288.v8.git.git.1783480879.gitgitgadget@gmail.com>


* cl/conditional-config-on-worktree-path (2026-07-09) 2 commits
  (merged to 'next' on 2026-07-15 at 86ca33c437)
 + config: add "worktree" and "worktree/i" includeIf conditions
 + config: refactor include_by_gitdir() into include_by_path()

 The '[includeIf "condition"]' conditional inclusion facility for
 configuration files has been taught to use the location of the
 worktree in its condition.

 Will merge to 'master'.
 cf. <alTJCTKR9jOWfgbk@pks.im>
 source: <20260710-includeif-worktree-v8-0-04686d8a616c@black-desk.cn>


* kk/commit-reach-find-all-fix (2026-06-29) 2 commits
  (merged to 'next' on 2026-07-10 at 0444c74d81)
 + commit-reach: guard !FIND_ALL early exit with generation ordering check
 + t6600: add test for merge-base early exit with clock skew
 (this branch is used by kk/merge-base-exhaustion.)

 The early-exit optimization in 'paint_down_to_common()' has been
 gated on the queue being generation-ordered, fixing a bug where
 'git merge-base' (without '--all') could return incorrect results
 on repositories with v1 commit graphs and clock skew.

 Will merge to 'master'.
 cf. <xmqqjyr5v1gu.fsf@gitster.g>
 source: <pull.2162.git.1782739162.gitgitgadget@gmail.com>


* bl/t7412-use-test-path-helpers (2026-06-29) 1 commit
 - submodule absorbgitdirs tests: use test_* helper functions

 The test script 't7412' that tests 'git submodule absorbgitdirs' has
 been modernized to use 'test_path_is_file', 'test_path_is_dir', and
 'test_path_is_missing' helper functions instead of raw 'test -[fde]'
 commands.

 Waiting for response.
 cf. <akTKHfKPsP3-Rn31@pks.im>
 source: <20260630020220.1559190-1-bblima@usp.br>


* ps/setup-split-discovery-and-setup (2026-07-07) 16 commits
  (merged to 'next' on 2026-07-10 at 1691a942ab)
 + setup: mark `set_git_work_tree()` as file-local
 + setup: pass worktree to `init_db()`
 + setup: drop redundant configuration of `startup_info->have_repository`
 + setup: make repository discovery self-contained
 + setup: propagate prefix via repository discovery
 + setup: drop static `cwd` variable
 + setup: move prefix into repository
 + setup: embed repository format in discovery
 + setup: introduce explicit repository discovery
 + setup: split up concerns of `setup_git_env_internal()`
 + setup: unify setup of shallow file
 + setup: mark bogus worktree in `apply_repository_format()`
 + setup: rename `check_repository_format_gently()`
 + Merge branch 'jk/repo-info-path-keys' into ps/setup-split-discovery-and-setup
 + Merge branch 'ps/setup-drop-global-state' into ps/setup-split-discovery-and-setup
 + Merge branch 'ps/refs-onbranch-fixes' into ps/setup-split-discovery-and-setup

 The repository discovery and repository configuration phases, which
 were previously intertwined in 'setup.c', have been split.  Repository
 discovery has been updated to populate a 'struct repo_discovery'
 without modifying the repository state, which is then taken by
 repository configuration to initialize the repository, paving the way
 for clean unification of repository configuration.

 Will merge to 'master'.
 cf. <87h5m9om0j.fsf@emacs.iotcl.com>
 source: <20260707-pks-setup-split-discovery-and-setup-v2-0-aab372cd227c@pks.im>


* pw/rebase-drop-notes-with-commit (2026-07-15) 9 commits
 - sequencer: do not record dropped commits as rewritten
 - sequencer: use an enum to represent result of picking a commit
 - sequencer: simplify pick_one_commit()
 - sequencer: remove unnecessary condition in pick_one_commit()
 - sequencer: simplify handling of fixup with conflicts
 - sequencer: remove unnecessary "or" in pick_one_commit()
 - sequencer: never reschedule on failed commit
 - sequencer: be more careful with external merge
 - t3400: restore coverage for note copying with apply backend

 The rebase post-rewrite notes-copying logic has been corrected.  When
 a commit is dropped during rebase (e.g., because its changes are
 already upstream), it is no longer recorded as rewritten, preventing
 its notes from being copied to an unrelated commit.

 Needs review.
 source: <cover.1784128921.git.phillip.wood@dunelm.org.uk>


* js/ci-dockerized-pid-limit (2026-07-04) 1 commit
  (merged to 'next' on 2026-07-09 at cd80e673a5)
 + ci(dockerized): raise the PID limit for private repositories

 Dockerized CI jobs running in private GitHub repositories have been
 adjusted to use explicit process and file limits, preventing resource
 exhaustion errors on private runners.

 Will merge to 'master'.
 cf. <xmqqh5medmzh.fsf@gitster.g>
 source: <pull.2164.v2.git.1783155124926.gitgitgadget@gmail.com>


* js/coverity-fixes (2026-07-05) 12 commits
  (merged to 'next' on 2026-07-09 at 1823fe297c)
 + mingw: make `exit_process()` own the process handle on all paths
 + fsmonitor: plug token-data leak on early daemon-startup failures
 + reftable/table: release filter on error path
 + imap-send: avoid leaking the IMAP upload buffer
 + worktree: fix resource leaks when branch creation fails
 + submodule: fix cwd leak in `get_superproject_working_tree()`
 + dir: free allocations on parse-error paths in `read_one_dir()`
 + line-log: avoid redundant copy that leaks in process_ranges
 + run-command: avoid `close(-1)` in `start_command()` error paths
 + download_https_uri_to_file(): do not leak fd upon failure
 + loose: avoid closing invalid fd on error path
 + load_one_loose_object_map(): fix resource leak

 Various resource leaks, invalid file descriptor closures, and process
 handle ownership issues flagged by Coverity have been fixed.

 Will merge to 'master'.
 cf. <xmqqa4s238lg.fsf@gitster.g>
 source: <pull.2163.v2.git.1783239870.gitgitgadget@gmail.com>


* tb/repack-geometric-cruft (2026-06-28) 11 commits
 - SQUASH??? bare grep !???
 - repack: support combining '--geometric' with '--cruft'
 - pack-objects: support '--refs-snapshot' with 'follow-reachable'
 - pack-objects: introduce '--stdin-packs=follow-reachable'
 - pack-objects: extract `stdin_packs_add_all_pack_entries()`
 - repack-geometry: drop unused redundant-pack removal
 - repack: delete geometric packs via existing_packs
 - repack: teach MIDX retention about geometric rollups
 - repack: mark geometric progression of packs as retained
 - repack: extract `locate_existing_pack()` helper
 - repack: unconditionally exclude non-kept packs

 'git repack' has been taught to accept '--geometric' and '--cruft'
 together.  When both are given, non-cruft packs are rolled up by the
 geometric repack as usual, while a separate cruft pack is written to
 collect unreachable objects.

 Waiting for response.
 cf. <aj8cOhH6hGVZIFft@nand.local>
 source: <cover.1782500507.git.me@ttaylorr.com>


* jt/receive-pack-use-odb-transactions (2026-07-10) 11 commits
  (merged to 'next' on 2026-07-15 at aba57e3365)
 + builtin/receive-pack: stage incoming objects via ODB transactions
 + builtin/receive-pack: drop redundant tmpdir env
 + odb/transaction: introduce ODB transaction flags
 + odb/transaction: add transaction env interface
 + odb/transaction: propagate commit errors
 + odb/transaction: propagate begin errors
 + object-file: propagate files transaction errors
 + object-file: drop check for inflight transactions
 + object-file: embed transaction flush logic in commit function
 + object-file: rename files transaction fsync function
 + object-file: rename files transaction prepare function

 'git receive-pack' has been refactored to use ODB transaction
 interfaces instead of directly managing 'tmp_objdir' for staging
 incoming objects, bringing it closer to being ODB backend agnostic.

 Will merge to 'master'.
 cf. <alR1P-RGZNmjyiUE@pks.im>
 source: <20260710163722.2962278-1-jltobler@gmail.com>


* ps/reftable-hardening (2026-07-03) 12 commits
  (merged to 'next' on 2026-07-10 at b8f4dd0ab9)
 + reftable/table: fix OOB read on truncated table
 + reftable/table: fix NULL pointer access when seeking to bogus offsets
 + reftable/block: fix OOB read with bogus restart offset
 + reftable/block: fix use of uninitialized memory when binsearch fails
 + reftable/block: fix OOB read with bogus restart count
 + reftable/block: fix OOB read with bogus block size
 + reftable/block: fix OOB write with bogus inflated log size
 + t/unit-tests: introduce test helper to write reftable blocks
 + reftable/record: don't abort when decoding invalid ref value type
 + reftable/basics: fix OOB read on binary search of empty range
 + oss-fuzz: add fuzzer for parsing reftables
 + meson: support building fuzzers with libFuzzer

 The reftable code has been hardened against corrupted tables by
 fixing out-of-bounds writes, out-of-bounds reads, and abort calls
 during parsing.

 Will merge to 'master'.
 cf. <877bn5obz9.fsf@emacs.iotcl.com>
 source: <20260703-pks-reftable-hardening-v3-0-b87c555b9920@pks.im>


* ty/migrate-excludes-file (2026-07-13) 10 commits
 - repository: adjust the comment of config_values_private_
 - environment: move object_creation_mode into repo_config_values
 - environment: move autorebase into repo_config_values
 - environment: move push_default into repo_config_values
 - environment: migrate apply_default_whitespace and apply_default_ignorewhitespace
 - environment: move askpass_program into repo_config_values
 - environment: move pager_program into repo_config_values
 - environment: move editor_program into repo_config_values
 - environment: move excludes_file into repo_config_values
 - repository: introduce repo_config_values_clear()

 The 'excludes_file' and various other global configuration variables
 (including 'editor_program', 'pager_program', 'askpass_program', and
 'push_default') have been migrated into the per-repository structure.

 Needs review.
 source: <20260714032525.1611141-1-cat@malon.dev>


* dk/meson-enable-use-nsec-build (2026-06-20) 1 commit
 - meson: wire up USE_NSEC build knob

 The 'USE_NSEC' build knob, which enables support for sub-second file
 timestamp resolution, has been wired up to the Meson build system.

 Will discard.
 cf. <45F2C180-1DE1-4371-869B-BF605B64E01A@gmail.com>
 source: <c4c5ade901ff95b0f95939ea818870e4f3d59da1.1781971201.git.ben.knoble+github@gmail.com>


* ps/libgit-in-subdir (2026-07-12) 3 commits
 . Move libgit.a sources into separate "lib/" directory
 . t/helper: prepare "test-example-tap.c" for introduction of "lib/"
 . Merge branch 'ps/odb-source-packed' into ps/libgit-in-subdir

 The source files for 'libgit.a' have been moved into a new 'lib/'
 directory to clean up the top-level directory and clearly separate
 library code.

 Ejected for now, as it causes too many evil merges with other topics.

 Waiting for response.
 cf. <alR9GDNTbdjWB4dq@szeder.dev>
 source: <20260713-pks-libgit-in-subdir-v4-0-696240876eb1@pks.im>


* ty/migrate-ignorecase (2026-06-19) 2 commits
  (merged to 'next' on 2026-07-12 at 39e9fdb93f)
 + config: use repo_ignore_case() to access core.ignorecase
 + environment: move ignore_case into repo_config_values

 The global configuration variable 'ignore_case' (representing the
 'core.ignorecase' configuration) has been migrated into 'struct
 repo_config_values' to tie it to a specific repository instance.

 Will merge to 'master'.
 cf. <xmqqechaga7p.fsf@gitster.g>
 source: <20260619155152.642760-1-cat@malon.dev>


* mm/line-log-limited-ops (2026-06-27) 7 commits
 - diffcore-pickaxe: scope -G to the -L tracked range
 - diff: support --check with -L line ranges
 - line-log: support diff stat formats with -L
 - diff: extract a line-range diff helper for reuse
 - diff: emit -L hunk headers via xdiff's formatter
 - diff: simplify the line-range filter by classifying removals immediately
 - diff: rename and group the line-range filter for clarity

 The 'git log -L<range>:<path>' command has been taught to limit
 various 'diff' operations, such as '--stat', '--check', and '-G', to
 the specified range and path.

 Needs review.
 source: <pull.2152.v2.git.1782581342.gitgitgadget@gmail.com>


* hn/history-squash (2026-07-15) 5 commits
 - history: re-edit a squash with every message
 - sequencer: share the squash message marker helpers and flags
 - history: add squash subcommand to fold a range
 - history: give commit_tree_ext a message template
 - history: extract helper for a commit's parent tree

 The experimental 'git history' command has been taught a new 'squash'
 subcommand to fold a range of commits into a single commit, with any
 descendants replayed on top.

 Needs review.
 source: <pull.2337.v9.git.git.1784128573.gitgitgadget@gmail.com>


* wy/doc-myfirstcontribution-trim-quotes (2026-06-11) 1 commit
  (merged to 'next' on 2026-07-12 at adeaa999b6)
 + MyFirstContribution: mention trimming quoted text in replies

 The contributor guide has been updated to advise new contributors to
 trim irrelevant quoted text when replying to review comments, matching
 the existing advice given to reviewers.

 Will merge to 'master'.
 cf. <xmqqcxxwljue.fsf@gitster.g>
 source: <080402ff0ac8127b654dccea59a1bf643df62a5c.1781186476.git.wy@wyuan.org>


* tb/midx-incremental-custom-base (2026-06-12) 3 commits
 - midx-write: include packs above custom incremental base
 - midx: pass custom '--base' through incremental writes
 - t5334: expose shared `nth_line()` helper

 The 'git multi-pack-index write --incremental' command has been
 corrected to properly honor the '--base' option.  Previously, the
 custom base was ignored by the normal write path; packs from layers
 above the selected base were incorrectly skipped by the pack exclusion
 logic, and reachability closure for bitmaps was broken.

 Needs review.
 source: <cover.1781294771.git.me@ttaylorr.com>


* mm/test-grep-lint (2026-07-05) 6 commits
  (merged to 'next' on 2026-07-10 at 1916c07bf5)
 + t: add greplint to detect bare grep assertions
 + t: convert grep assertions to test_grep
 + t: fix Lexer line count for $() inside double-quoted strings
 + t: extract chainlint's parser into shared module
 + t: fix grep assertions missing file arguments
 + t/README: document test_grep helper

 The test suite has been updated to use the 'test_grep' helper instead
 of bare 'grep' for test assertions, allowing file contents to be
 printed on failure for easier debugging.  A new 'greplint' linter has
 been introduced to detect and prevent new bare 'grep' assertions from
 being added to the test suite.

 Will merge to 'master'.
 cf. <xmqqtsqedxmt.fsf@gitster.g>
 source: <pull.2135.v4.git.1783314119.gitgitgadget@gmail.com>


* td/ref-filter-memoize-contains (2026-06-12) 3 commits
 - commit-reach: die on contains walk errors
 - ref-filter: memoize --contains with generations
 - commit-reach: reject cycles in contains walk

 'git branch --contains' and 'git for-each-ref --contains' have been
 optimized to use the memoized commit traversal previously used only by
 'git tag --contains', significantly speeding up connectivity checks
 across many candidate refs with shared history.

 Will merge to 'next'?
 cf. <20260716091924.GB1212956@coredump.intra.peff.net>
 source: <20260612-ref-filter-memoized-contains-v4-0-5ed39fd001dd@gmail.com>


* tc/replay-linearize (2026-07-07) 3 commits
  (merged to 'next' on 2026-07-09 at 371c2e9c3b)
 + replay: offer an option to linearize the commit topology
 + replay: resolve the replay base outside pick_regular_commit()
 + replay: add helper to put entry into replayed_commits

 The 'git replay' command has been taught the '--linearize' option to
 drop merge commits and linearize the replayed history, mimicking 'git
 rebase --no-rebase-merges'.

 Will merge to 'master'?
 cf. <xmqq5x2qz42z.fsf@gitster.g>
 cf. <CABPp-BGzU9KHGF1nipi2HZaa1AiikMKGGaapQzHVH06wO4V1ww@mail.gmail.com>
 source: <20260707-toon-git-replay-drop-merges-v7-0-808ab9b4afa6@iotcl.com>


* ps/cat-file-remote-object-info (2026-07-15) 13 commits
 - cat-file: make remote-object-info allow-list dynamic
 - cat-file: validate remote atoms with an allow-list
 - cat-file: add remote-object-info to batch-command
 - transport: add client support for object-info
 - serve: advertise object-info feature
 - fetch-pack: move fetch initialization
 - connect: make write_fetch_command_and_capabilities() more generic
 - fetch-pack: move write_fetch_command_and_capabilities() to connect.c
 - fetch-pack: drop static advertise_sid variable
 - fetch-pack: fix hash_algo variable type
 - t1006: split test utility functions into new 'lib-cat-file.sh'
 - cat-file: declare loop counter inside for()
 - transport-helper: fix memory leak of helper on disconnect

 The 'remote-object-info' command has been added to 'git cat-file
 --batch-command', allowing clients to request object metadata
 (currently size) from a remote server via protocol v2 without
 downloading the entire object.  Format placeholders are dynamically
 filtered on the client based on server-advertised capabilities,
 returning empty strings for inapplicable or unsupported fields.

 Expecting a reroll.
 cf. <DJZM80YDCMQG.SXP22P43W1J3@gmail.com>
 source: <20260715-ps-eric-work-rebase-v18-0-34d7adb051bb@gmail.com>


* mm/diff-process-hunks (2026-07-15) 9 commits
 . line-log: consult diff process for range tracking
 . diff: consult diff process for --stat counts
 . blame: consult diff process for no-hunk detection
 . diff: bypass diff process with --no-ext-diff and in format-patch
 . diff: add long-running diff process via diff.<driver>.process
 . sub-process: separate process lifecycle from hashmap management
 . userdiff: add diff.<driver>.process config
 . xdiff: support external hunks via xpparam_t
 . gitattributes: document how external diff drivers relate to diff features

 A new 'diff.<driver>.process' configuration has been introduced to
 allow a long-running external process to act as a hunk provider,
 enabling external tools to control which lines Git considers changed
 while leaving all output formatting (word diff, color, blame, etc.) to
 Git's standard pipeline.

 Ejected for now, as conflicts badly with mm/line-log-limited-ops.

 Expecting a reroll.
 cf. <xmqq8q7aj3b0.fsf@gitster.g>
 cf. <CAC2QwmKRp90hmBAckug9PPvvD53Pi53q5csZhi15LRhzdQasQg@mail.gmail.com>
 source: <pull.2120.v5.git.1784149323.gitgitgadget@gmail.com>


* ty/migrate-trust-executable-bit (2026-07-16) 4 commits
 - environment: move has_symlinks into repo_config_values
 - environment: move trust_executable_bit into repo_config_values
 - read-cache: move 'ce_mode_from_stat()' to 'read-cache.c'
 - read-cache: remove redundant extern declarations

 The 'trust_executable_bit' (coming from 'core.filemode'
 configuration) has been migrated into 'repo_config_values' to tie it
 to a specific repository instance.

 Needs review.
 cf. <xmqqa4rqfzys.fsf@gitster.g>
 source: <20260716084941.1101918-1-cat@malon.dev>


* za/completion-hide-dotfiles (2026-06-20) 2 commits
 - completion: hide dotfiles by default for path completion
 - completion: hide dotfiles for selected path completion

 Path completion for commands like 'git rm' and 'git mv' has been
 updated to hide dotfiles by default unless the user explicitly starts
 the path with a dot, matching standard shell-completion behavior.

 Waiting for response, stalled.
 cf. <xmqqik71t3nr.fsf@gitster.g>
 source: <pull.2311.v3.git.git.1781978156.gitgitgadget@gmail.com>


* ec/commit-fixup-options (2026-05-26) 2 commits
 - commit: allow -c/-C for all kinds of --fixup
 - commit: allow -m/-F for all kinds of --fixup

 Support for '-m', '-F', '-c', or '-C' options to supply a commit log
 message from outside the editor has been added for all 'git commit
 --fixup' variations.

 Needs review.
 source: <cover.1779792311.git.erik@cervined.in>


* kh/doc-replay-config (2026-06-05) 4 commits
 - doc: replay: move “default” to the right-hand side
 - doc: replay: use a nested description list
 - doc: replay: improve config description
 - doc: link to config for git-replay(1)

 Documentation for 'git replay' has been updated to refer to its
 configuration variables.

 Waiting for response for too long, stalled.
 cf. <87cxwxofgv.fsf@emacs.iotcl.com>
 source: <V3_CV_doc_replay_config.780@msgid.xyz>


* hn/branch-delete-merged (2026-07-14) 7 commits
 - branch: add --dry-run for --delete-merged
 - branch: add branch.<name>.deleteMerged opt-out
 - branch: add --delete-merged <branch>
 - branch: prepare delete_branches for a bulk caller
 - branch: let delete_branches skip unmerged branches on bulk refusal
 - branch: convert delete_branches() to a flags argument
 - branch: add --forked filter for --list mode

 The 'git branch' command has been taught the '--delete-merged' option
 to remove local branches that are already merged to their tracked
 remote-tracking branches.

 Needs review.
 source: <pull.2285.v19.git.git.1784053493.gitgitgadget@gmail.com>


* hn/checkout-track-fetch (2026-06-24) 2 commits
 - checkout: extend --track with a "fetch" mode to refresh start-point
 - branch: expose helpers for finding the remote owning a tracking ref

 The 'git checkout --track=...' command has been taught to optionally
 fetch the branch from the remote the new branch will work with.

 Needs review.
 source: <pull.2281.v15.git.git.1782338098.gitgitgadget@gmail.com>


* ps/shift-root-in-graph (2026-07-14) 7 commits
 - graph: add --[no-]graph-indent and log.graphIndent
 - graph: move config reading into graph_read_config()
 - graph: wrap cascading commits after 4 columns
 - graph: indent visual root in graph
 - graph: add a 2 commit buffer for lookahead
 - revision: add next_commit_to_show()
 - lib-log-graph: move check_graph function

 'git log --graph' has been modified to visually distinguish parentless
 'root' commits (and commits that become roots due to history
 simplification) by indenting them, preventing them from appearing
 falsely related to unrelated commits rendered immediately above them.

 Will merge to 'next'?
 cf. <CA+J6zkQNzEAhhY74qDrOwfFVrshEF7YFxWRRkwE3ttJo15ZbAg@mail.gmail.com>
 source: <20260714-ps-pre-commit-indent-v12-0-d50938e006df@gmail.com>


* kk/merge-base-exhaustion (2026-07-11) 11 commits
 - commit-reach: remove commit-date ordering fallback
 - commit-reach: move min_generation check into paint_queue_get()
 - commit-reach: terminate merge-base walk when one paint side is exhausted
 - commit-reach: introduce struct paint_state with per-side counters
 - t6600: add clock-skew topologies and step counts for edge cases
 - commit-reach: add trace2 instrumentation to paint_down_to_common()
 - t6099, t6600: add side-exhaustion regression tests
 - t6600: add test cases for side-exhaustion edge cases
 - test-lib-functions: improve diagnostic output for trace2 data assertions
 - Documentation/technical: add paint-down-to-common doc
 - Merge branch 'kk/commit-reach-find-all-fix' into kk/merge-base-exhaustion
 (this branch uses kk/commit-reach-find-all-fix.)

 The merge-base computation has been optimized by stopping the walk
 early when one side's exclusive commits in the queue are exhausted,
 yielding significant speedups for queries with one-sided histories.

 Needs review.
 source: <pull.2149.v6.git.1783776466.gitgitgadget@gmail.com>

--------------------------------------------------
[Discarded]

* kk/prio-queue-cascade-sift (2026-07-08) 3 commits
 . prio-queue: use cascade for unfused gets
 . prio-queue: extract sift_up() from prio_queue_put()
 . Merge branch 'kk/prio-queue-get-put-fusion' into kk/prio-queue-cascade-sift

 'prio_queue_get()' has been optimized by using a cascade-down approach
 (promoting the smaller child at each level and sifting up the last
 element from the leaf vacancy), whereby the number of comparisons per
 extract-min operation is halved in the common case.

 Retracted.
 cf. <CAL71e4PRVYfUWc-c+6XHTwtADqrbub9ykbo+rPyramDhJw=Rfg@mail.gmail.com>
 source: <pull.2132.v3.git.1783532989.gitgitgadget@gmail.com>

^ permalink raw reply

* Re: git-last-modified(1) slower than git-log(1)?
From: Gusted @ 2026-07-17  0:19 UTC (permalink / raw)
  To: Toon Claes, git, Jeff King
In-Reply-To: <87v7afffpa.fsf@emacs.iotcl.com>



On 7/16/26 11:26 AM, Toon Claes wrote:
> Hi Gusted,
> 
> Thanks for reaching out.
> 
> You're actually not the first to notice this, and I've been aware of
> this.
> 
> The thing is, you're testing the difference on a single file. For us at
> GitLab, it wasn't very useful to optimize that use-case, because usually
> we want to see the last commit for a bunch of files at once.
> So the use-case for git-last-modified(1) for us has been to replace
> (pseudo code):
> 
> $ FILES=$(git ls-tree $COMMIT $PATH)
> $ foreach $FILE in $FILES; do git log -1 $COMMIT -- $FILE; end
> 
> GitLab is batching files 25 at once, and in my benchmarking, it was
> shown git-last-modified(1) is faster:
> 
> $ git last-modified $COMMIT -- <files
> 
> (I did this benchmarking in our Gitaly component to have a real-world
> experience and you can visit the results at:
> https://gitlab.com/gitlab-org/gitaly/-/merge_requests/7999#note_2850505479
> )
> 
> So we left the door open for future improvement, although I never have
> gotten to it. At some point I was trying to chase down when git-log(1)
> was doing differently, but I never figured it out.
> 
> But this email challenged me already. And with some help of AI, I
> managed to work on some improvements. You can expect a patch series
> soon.
> 
> (Right before sending out this mail I noticed Peff sent out some changes
> as well. I'll coordinate how to combine.)
> 

Hi Toon and Jeff,

Thanks for having a look at this!

The use case for Forgejo is the same as Gitlab then, we only use it to
get the last-modified of each entry in a directory. Looking a bit closer
I missed that in Forgejo's code it's considered the output might not
have the answer of all entries, and happily calls it as many time is
needed on the 'remaining paths', especially when using gitlab as
repository this required a lot of git-log calls.

Looking at the mentioned benchmark, yeah this is where Forgejo's
implementation with git-log would fail in terms of performance
(seemingly even slower than doing N-1 git-log calls, by using your
benchmark numbers). It's somewhere in the minutes :')

Kind Regards
Gusted

^ permalink raw reply

* Re: [PATCH] stash: add 'rename' subcommand
From: Junio C Hamano @ 2026-07-17  1:11 UTC (permalink / raw)
  To: brian m. carlson
  Cc: Patrick Steinhardt, Emin Özata via GitGitGadget, git,
	Greg Hewgill, Micheil Smith, Michael Haggerty,
	Ævar Arnfjörð Bjarmason, Emin Özata
In-Reply-To: <allISNh-b6Sc6y5-@fruit.crustytoothpaste.net>

"brian m. carlson" <sandals@crustytoothpaste.net> writes:

> I have a large number of stashes in my git.git repository (211 at
> present) and it may be that I, in a mad rush to solve some problem or
> another, went with the default message.  I do this kind of thing with
> repositories at work all the time.  But it's inconvenient to have to
> search through `git stash list -p` to see which change it was and which
> stash I need to pop to resume my work, so I can see the utility of this
> approach.

Hmph, are you saying that you are going to wade through 211
stash entries one by one and reword them?

> In my case, the stash is likely on an existing branch (such as my
> sha256-interop branch), so I don't want to create another branch right
> now, but I do want to distinguish a couple of different stashes.

This is not necessarily related to what Emin's patch does or tries
to do, but it suggests to me that it would be ultra-nice if we could
have a stash that is associated with each individual branch.  It is
as if we have been living in a world with only detached HEADs and
you suddenly invented named branches.  ;-)

Well, the original design motivation behind "git stash" was the
"boss is here" emergency mode, and the expectation was that such
an emergency would not nest to leave anyone with more than a
handful of entries.  Thus, a single stash shared repository-wide
across your branches was not a huge problem.  But our workflows
have evolved, so our tool support may have to evolve alongside
them.

Thanks for the food for thought.

^ permalink raw reply

* Re: [PATCH 3/3] bisect: add --auto-reset to leave when done
From: Junio C Hamano @ 2026-07-17  5:00 UTC (permalink / raw)
  To: Harald Nordgren; +Cc: Harald Nordgren via GitGitGadget, git
In-Reply-To: <CAHwyqnUFfewFm7tr-Busv1rKP=4Rqnq+vJ7mEdgbaRLKbpbo=g@mail.gmail.com>

Harald Nordgren <haraldnordgren@gmail.com> writes:

>> > Add an "--auto-reset" option, accepted by both "git bisect start" and
>> > "git bisect run", that resets as soon as the first bad commit is found,
>> > returning to the commit checked out before "git bisect start". The flag
>> > is persisted in a BISECT_AUTO_RESET state file and the restoring
>> > checkout is done quietly.
>>
>> I often find myself, after the culprit is found, running 'git
>> reset --hard' or 'git bisect reset' to jump to the problematic
>> commit to investigate further.  If '--auto-reset' leaves me
>> checked out on that bad commit, that would be a very welcome
>> change.
>
> No it's the opposite, returns to where we started before the bisection.
>
> I don't mind changing it assuming no one likes the original idea. I
> guess the name shouldn't be '--auto-reset' then.

Since "git bisect reset <goto>" is just as common as a plain "git
bisect reset" (which implicitly uses the original branch as the
target), I suspect that an option like "--auto-reset=<where>" with
values like "original" or "found" might be appropriate.  And I would
not mind if omitting the value defaulted to "original".

The point I was trying to make is that where to reset depends
more on the situation the user is in, rather than on their
personal preference.  I would mind if you changed it to always
reset to the culprit, just as much as I would mind if it always
reset to the original.

Thanks.

^ permalink raw reply

* Re: [PATCH 2/2] t9502: test gitweb index hash formatting with modes
From: Junio C Hamano @ 2026-07-17  5:50 UTC (permalink / raw)
  To: Travor; +Cc: "Liu <travor_lzh", git, Travor Liu
In-Reply-To: <SA1PR10MB9977159B51766C2D9FFE98FD37F1F82@SA1PR10MB997715.namprd10.prod.outlook.com>

Travor@web.codeaurora.org, "Liu <travor_lzh"@outlook.com writes:

> From: Travor Liu <travor_lzh@outlook.com>
>
> gitweb should shorten and link the object IDs in commitdiff index lines
> even when Git includes the trailing file mode:
>
>     index <old>..<new> 100644
>
> Add coverage for that common form by rendering a commitdiff for a
> regular file modification.  Check that the visible index line contains
> linked short blob IDs followed by the mode and file-type annotation,
> and that the full unlinked form is not emitted.
>
> Signed-off-by: Travor Liu <travor_lzh@outlook.com>
> ---
>  t/t9502-gitweb-standalone-parse-output.sh | 14 ++++++++++++++
>  1 file changed, 14 insertions(+)

If the new test added by this patch validates the "fix" in
[PATCH 1/2], reviewing the change might be easier if the two
were squashed into a single patch.

> diff --git a/t/t9502-gitweb-standalone-parse-output.sh b/t/t9502-gitweb-standalone-parse-output.sh
> index 81d5625..7f37e26 100755
> --- a/t/t9502-gitweb-standalone-parse-output.sh
> +++ b/t/t9502-gitweb-standalone-parse-output.sh
> @@ -115,6 +115,20 @@ test_expect_success 'snapshot: hierarchical branch name (xx/test)' '
>  '
>  test_debug 'cat gitweb.headers'
>  
> +test_expect_success 'commitdiff: index line shortens hashes with mode' '
> +	old_blob=$(git rev-parse HEAD:foo) &&
> +	old_short=$(git rev-parse --short=7 HEAD:foo) &&
> +	echo changed >foo &&
> +	git commit -am "change foo" &&
> +	new_blob=$(git rev-parse HEAD:foo) &&
> +	new_short=$(git rev-parse --short=7 HEAD:foo) &&
> +	gitweb_run "p=.git;a=commitdiff;h=HEAD" &&
> +	grep ">${old_short}</a>\\.\\.<a [^>]*>${new_short}</a> 100644" \
> +		gitweb.body >index_line &&
> +	grep "<span class=\"info\"> (file)</span>" index_line &&
> +	! grep "index ${old_blob}\\.\\.${new_blob} 100644" gitweb.body
> +'

Can we use "test_grep" (for positive "this string must be there") and
"test_grep !" (for negative "it is an error if this string appears"
(note that exclamation point comes after test_grep))?  It would make
it easier to diagnose a failing test.

Also, there is a topic in flight that enforces the use of test_grep in
these test scripts, and use of raw grep like the above would break
under those stricter rules.

Thanks.


^ permalink raw reply

* Re: [PATCH v6] show-branch: convert per-branch flags to commit-slab
From: Junio C Hamano @ 2026-07-17  6:00 UTC (permalink / raw)
  To: Gatla Vishweshwar Reddy; +Cc: git
In-Reply-To: <20260715184241.56635-1-gatlavishweshwarreddy26@gmail.com>

Gatla Vishweshwar Reddy <gatlavishweshwarreddy26@gmail.com> writes:

> show-branch uses commit->object.flags to store per-branch
> reachability bits, one bit per branch starting at REV_SHIFT.
> The flags word has only a fixed number of available bits, limiting
> the number of branches that can be shown simultaneously to MAX_REVS.
>
> Convert the per-branch bits to a dedicated commit-slab using uint64_t
> as the element type, initialized with a stride via
> init_commit_rev_flags_with_stride(). Keep the UNINTERESTING bit in
> object.flags where it belongs, as it is used for revision walking and
> does not need to be in the per-branch slab. With UNINTERESTING removed
> from the slab, REV_SHIFT becomes 0 and all 64 bits of uint64_t are
> available for branch tracking, lifting MAX_REVS from 27 to 64 branches.

Thanks.  This version looks much cleaner.  I appreciate your
addressing the correctness issues around UNINTERESTING
propagation that we spotted in the previous round.

I do have a slight worry about a potential performance regression,
though.  We might run the risk of slowing down the traversal in
how we skip parents.

> @@ -226,39 +285,43 @@ static void join_revs(struct prio_queue *queue,
> ...

In the original code, we avoided parsing and re-queueing the parent 'p'
if we knew it already had all the flags we were trying to propagate.

> -			int this_flag = p->object.flags;
> -			parents = parents->next;
> -			if ((this_flag & flags) == flags)
> -				continue;
> -			repo_parse_commit(the_repository, p);
> ...
> +		{
> +			int commit_is_merge_base = has_all_rev_flags(commit, num_rev);
> +			parents = commit->parents;
> +
> +			while (parents) {
> +				struct commit *p = parents->item;
> +				parents = parents->next;
> +				if (has_all_rev_flags(p, num_rev) &&
> +				    (!commit_is_merge_base || (p->object.flags & UNINTERESTING)))
> +					continue;

With the new slab-based approach, we skip only when 'p' already has
all possible revision flags, num_rev.  If 'p' already carries all
the flags that the current 'commit' has (even if it lacks some of
the other num_rev flags), the traversal could be pruned early, but
the proposed change fails to do so.

Consequently, we proceed to propagate the flags (which amounts to a
no-op on the slab anyway) and, worse, re-queue 'p' for further
processing.  In a densely tangled history with many merges, this
would lead to significant redundant work and queue thrashing.  We
instead should check whether the flags of 'commit' are a subset of
those of 'p'.  Since the flags_stride is known, introducing a
helper, perhaps has_subset_rev_flags(commit, p), to perform this
check should be a straightforward exercise.

Also, looking at the bigger picture ...

> -#define REV_SHIFT	 2
> -#define MAX_REVS	(FLAG_BITS - REV_SHIFT) /* should not exceed bits_per_int - REV_SHIFT */
> -
> +#define REV_SHIFT	 0
> +#define MAX_REVS	(sizeof(uint64_t) * 8)

While lifting the limit from 27 to 64 is a welcome improvement, I
wonder why we stop there and still tolerate a hardcoded MAX_REVS
limit.

The introduction of flags_stride and init_commit_rev_flags_with_stride
already lays the groundwork for supporting an arbitrary number of
flags.  The only remaining blockages that keep MAX_REVS alive are:

 - The static ref_name[] array; and

 - The stack-allocated arrays rev[] and reflog_msg[] in the
   cmd_show_branch() function.

If we

 - dynamically grow the ref_name[] array (perhaps using the
   ALLOC_GROW macro),

 - dynamically allocate rev[] and reflog_msg[] in cmd_show_branch()
   once options are parsed (and thus ref_name_cnt and the reflog
   flag are known), and

 - calculate flags_stride at runtime as (ref_name_cnt + 63) / 64,

then we can get rid of MAX_REVS and the associated boundary checks
entirely.  Since the proposed patch already does 90% of the work
needed to support an arbitrary stride, it feels like a missed
opportunity not to take that final step.

Thoughts?

^ permalink raw reply

* Re: [PATCH 3/3] refspec: stop depending on `the_repository`
From: Patrick Steinhardt @ 2026-07-17  6:02 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git
In-Reply-To: <xmqqpl0mejlc.fsf@gitster.g>

On Thu, Jul 16, 2026 at 01:59:59PM -0700, Junio C Hamano wrote:
> Patrick Steinhardt <ps@pks.im> writes:
> 
> > The only remaining user of `the_hash_algo` in "refspec.c" is
> > `refspec_append()`, which needs to know the hash algorithm so that it
> > can parse the appended refspec item. In contrast to the functions
> > adapted in the preceding commit, this function always operates on a
> > `struct refspec`. As that structure is expected to only ever contain
> > refspecs that all use the same hash function it doesn't make sense
> > though to adapt each caller.
> >
> > Instead, adapt the structure itself so that it gets initialized with a
> > hash function and use that hash function to parse new refspec items.
> > Adapt callers accordingly.
> >
> > This removes the final dependency on the global repository variable in
> > "refspec.c", so we can drop `USE_THE_REPOSITORY_VARIABLE`.
> 
> While we lost some references to the_repository, we gained
> more references to the_hash_algo in exchange.  Because
> the_hash_algo is defined in terms of the_repository->hash_algo,
> it is only available when the_repository is still in use.
> So these changes do not really help callers, and only leave
> more for them to clean up later.
> 
> Which is probably fine.  We have to start somewhere, and
> refspec parsing is a fairly well-isolated corner of the
> universe that serves as a good starting point.

Yup, this patch series follows our typical approach of making one
subsystem `the_repository`-clean, but bumping that dependency up into
the next-higher level.

I've got a bunch of follow-up patch series that'll also convert some of
those higher-up dependencies. Most importantly, I'm converting all
subsystems that relate to the transport layer, as I'm on a very naive
quest to try and get git-clone(1) working without `the_repository`.
Let's see how far I get.

Patrick

^ permalink raw reply

* Re: [PATCH] wt-status: avoid quadratic insertion for untracked paths
From: Patrick Steinhardt @ 2026-07-17  6:27 UTC (permalink / raw)
  To: Sahitya Chandra; +Cc: git, gitster, avarab, stolee, peff
In-Reply-To: <20260716185045.229320-1-sahityajb@gmail.com>

On Fri, Jul 17, 2026 at 12:20:45AM +0530, Sahitya Chandra wrote:
> wt_status_collect_untracked() copies entries from dir.entries and
> dir.ignored into string_lists using string_list_insert(). That keeps the
> destination lists sorted and deduplicated, but each insertion may shift
> the backing array, making construction O(n^2) in the number of paths.
> 
> Collect the entries with string_list_append() instead, then sort and
> deduplicate each list once. This preserves the sorted, duplicate-free
> result while reducing the construction cost to O(n log n).
> 
> Signed-off-by: Sahitya Chandra <sahityajb@gmail.com>
> ---
> Notes for reviewers:
> 
> fill_directory() currently sorts dir.entries and dir.ignored
> before returning, so another possible approach would be to append the
> entries directly and rely on that order, reducing this copy step to O(n).
> That would require relying on these arrays not containing duplicate
> entries, though, which I have not been able to verify yet. This patch
> takes the safer approach of preserving the existing duplicate-removal
> behavior from `string_list_insert()` by sorting and deduplicating once
> after appending.

Out of curiosity: is this something that you have encountered in the
real world as inefficient, or is this rather a theoretical inefficiency?
If the former it would be great to add a small benchmark to the commit
message.

> diff --git a/wt-status.c b/wt-status.c
> index 58461e02f8..13a7cf7946 100644
> --- a/wt-status.c
> +++ b/wt-status.c
> @@ -832,14 +832,18 @@ static void wt_status_collect_untracked(struct wt_status *s)
>  	for (i = 0; i < dir.nr; i++) {
>  		struct dir_entry *ent = dir.entries[i];
>  		if (index_name_is_other(istate, ent->name, ent->len))
> -			string_list_insert(&s->untracked, ent->name);
> +			string_list_append(&s->untracked, ent->name);
>  	}
> +	string_list_sort(&s->untracked);
> +	string_list_remove_duplicates(&s->untracked, 0);

Instead of sorting and then deduplicating you can call
`string_list_sort_u()`. It does the exact same thing as you do here, but
I guess it makes sense to use that interface anyway.

>  	for (i = 0; i < dir.ignored_nr; i++) {
>  		struct dir_entry *ent = dir.ignored[i];
>  		if (index_name_is_other(istate, ent->name, ent->len))
> -			string_list_insert(&s->ignored, ent->name);
> +			string_list_append(&s->ignored, ent->name);
>  	}
> +	string_list_sort(&s->ignored);
> +	string_list_remove_duplicates(&s->ignored, 0);

Likewise.

Overall this looks like a sensible thing to do though. Thanks!

Patrick

^ permalink raw reply

* [PATCH v7 0/4] environment: migrate 'trust_executable_bit' and 'has_symlinks' into 'repo_config_values'
From: Tian Yuchen @ 2026-07-17  6:35 UTC (permalink / raw)
  To: git; +Cc: ps, Tian Yuchen
In-Reply-To: <20260716084941.1101918-1-cat@malon.dev>

This series moves 'trust_executable_bit' and 'has_symlinks' into
'struct repo_config_values' to tie them to the specific repository
instance they were read from. Eager parsing is maintained because
these two flags are heavily consulted in hot paths.

Note: 'repo_config_values()' still does not support any struct
repository other than the_repository due to how deeply these flags
are accessed. In other words, this series of patches is laying
the groundwork for the eventual elimination of the_repository.

Previous related work:

[PATCH 2/6] config: add trust_executable_bit to global config [1]
[PATCH] Refactor 'trust_executable_bit' to repository-scoped setting [2]
(This previous attempt was unsuccessful because the target location
selected was 'struct repo_settings', which our analysis indicated
was not the optimal choice. For further details, please see: [3])

[PATCH 5/6] config: move has_symlinks [4]

RFC:

 - Is the locations of the newly introduced definitions/macros
 appropriate?

Changes since V6:

 - In commit 2/4, keep ce_mode_from_stat() static inline. Teach it to
 take a 'struct repository *' parameter instead.

 - In commit 4/4, reword the commit message.

Thanks!

[1] https://lore.kernel.org/git/837b5360b40f992351f489a0ae05fedf49884c6e.1685716420.git.gitgitgadget@gmail.com/
[2] https://lore.kernel.org/git/20260301190017.53539-1-dronarajgyawali@gmail.com/
[3] https://lore.kernel.org/git/xmqq1pht6nyx.fsf@gitster.g/
[4] https://lore.kernel.org/git/a154008619790f7a60f2bba91db7b0fe29e67e1a.1685716420.git.gitgitgadget@gmail.com/
[5] https://lore.kernel.org/git/xmqq7bokebct.fsf@gitster.g/

Tian Yuchen (4):
  read-cache: remove redundant extern declarations
  read-cache: pass 'repo' to 'ce_mode_from_stat()'
  environment: move trust_executable_bit into repo_config_values
  environment: move has_symlinks into repo_config_values

 apply.c                |  6 +++---
 builtin/update-index.c |  2 +-
 combine-diff.c         |  2 +-
 compat/mingw.c         | 17 +++++++++++++----
 compat/mingw.h         |  3 +++
 diff-lib.c             | 10 +++++-----
 entry.c                |  2 +-
 environment.c          | 27 +++++++++++++++++++++++----
 environment.h          |  8 ++++++--
 git-compat-util.h      |  4 ++++
 read-cache.c           | 15 +++++++--------
 read-cache.h           | 16 ++++++++++++----
 12 files changed, 79 insertions(+), 33 deletions(-)

-- 
2.43.0


^ permalink raw reply

* [PATCH v7 2/4] read-cache: pass 'repo' to 'ce_mode_from_stat()'
From: Tian Yuchen @ 2026-07-17  6:35 UTC (permalink / raw)
  To: git; +Cc: ps, Tian Yuchen, Christian Couder, Ayush Chandekar,
	Olamide Caleb Bello
In-Reply-To: <20260717063559.1633567-1-cat@malon.dev>

The ce_mode_from_stat() function is a performance-critical static
inline helper in 'read-cache.h'. As we migrate configuration
variables into the repository struct, this helper needs access
to the repository context.

Update the signature of ce_mode_from_stat() to take a 'struct
repository *' parameter, and update all callers to pass the
appropriate repository instance.

To prepare for the overhead of replacing cheap global variable
accesses with getter functions, the boolean expressions are
reordered to evaluate 'S_ISREG(mode)' first.

While at it, add a comment for ce_mode_from_stat().

Mentored-by: Christian Couder <christian.couder@gmail.com>
Mentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>
Mentored-by: Olamide Caleb Bello <belkid98@gmail.com>
Signed-off-by: Tian Yuchen <cat@malon.dev>
---
 apply.c                |  2 +-
 builtin/update-index.c |  2 +-
 diff-lib.c             | 10 +++++-----
 read-cache.c           |  2 +-
 read-cache.h           | 15 ++++++++++++---
 5 files changed, 20 insertions(+), 11 deletions(-)

diff --git a/apply.c b/apply.c
index 249248d4f2..26286eb57b 100644
--- a/apply.c
+++ b/apply.c
@@ -3894,7 +3894,7 @@ static int check_preimage(struct apply_state *state,
 			BUG("ce_mode == 0 for path '%s'", old_name);
 
 		if (trust_executable_bit || !S_ISREG(st->st_mode))
-			st_mode = ce_mode_from_stat(*ce, st->st_mode);
+			st_mode = ce_mode_from_stat(state->repo, *ce, st->st_mode);
 		else if (*ce)
 			st_mode = (*ce)->ce_mode;
 		else
diff --git a/builtin/update-index.c b/builtin/update-index.c
index 8a5907767b..7917bd286f 100644
--- a/builtin/update-index.c
+++ b/builtin/update-index.c
@@ -293,7 +293,7 @@ static int add_one_path(const struct cache_entry *old, const char *path, int len
 	ce->ce_flags = create_ce_flags(0);
 	ce->ce_namelen = len;
 	fill_stat_cache_info(the_repository->index, ce, st);
-	ce->ce_mode = ce_mode_from_stat(old, st->st_mode);
+	ce->ce_mode = ce_mode_from_stat(the_repository, old, st->st_mode);
 
 	if (index_path(the_repository->index, &ce->oid, path, st,
 		       info_only ? 0 : INDEX_WRITE_OBJECT)) {
diff --git a/diff-lib.c b/diff-lib.c
index ae91027a02..46cae637ec 100644
--- a/diff-lib.c
+++ b/diff-lib.c
@@ -160,7 +160,7 @@ void run_diff_files(struct rev_info *revs, unsigned int option)
 
 			changed = check_removed(ce, &st);
 			if (!changed)
-				wt_mode = ce_mode_from_stat(ce, st.st_mode);
+				wt_mode = ce_mode_from_stat(revs->repo, ce, st.st_mode);
 			else {
 				if (changed < 0) {
 					perror(ce->name);
@@ -193,7 +193,7 @@ void run_diff_files(struct rev_info *revs, unsigned int option)
 					num_compare_stages++;
 					oidcpy(&dpath->parent[stage - 2].oid,
 					       &nce->oid);
-					dpath->parent[stage-2].mode = ce_mode_from_stat(nce, mode);
+					dpath->parent[stage-2].mode = ce_mode_from_stat(revs->repo, nce, mode);
 					dpath->parent[stage-2].status =
 						DIFF_STATUS_MODIFIED;
 				}
@@ -262,7 +262,7 @@ void run_diff_files(struct rev_info *revs, unsigned int option)
 				continue;
 			} else if (revs->diffopt.ita_invisible_in_index &&
 				   ce_intent_to_add(ce)) {
-				newmode = ce_mode_from_stat(ce, st.st_mode);
+				newmode = ce_mode_from_stat(revs->repo, ce, st.st_mode);
 				diff_addremove(&revs->diffopt, '+', newmode,
 					       null_oid(the_hash_algo), 0, ce->name, 0);
 				continue;
@@ -270,7 +270,7 @@ void run_diff_files(struct rev_info *revs, unsigned int option)
 
 			changed = match_stat_with_submodule(&revs->diffopt, ce, &st,
 							    ce_option, &dirty_submodule);
-			newmode = ce_mode_from_stat(ce, st.st_mode);
+			newmode = ce_mode_from_stat(revs->repo, ce, st.st_mode);
 		}
 
 		if (!changed && !dirty_submodule) {
@@ -338,7 +338,7 @@ static int get_stat_data(const struct cache_entry *ce,
 		changed = match_stat_with_submodule(diffopt, ce, &st,
 						    0, dirty_submodule);
 		if (changed) {
-			mode = ce_mode_from_stat(ce, st.st_mode);
+			mode = ce_mode_from_stat(diffopt->repo, ce, st.st_mode);
 			oid = null_oid(the_hash_algo);
 		}
 	}
diff --git a/read-cache.c b/read-cache.c
index c44e4d128f..b37bf688ec 100644
--- a/read-cache.c
+++ b/read-cache.c
@@ -749,7 +749,7 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st,
 		int pos = index_name_pos_also_unmerged(istate, path, namelen);
 
 		ent = (0 <= pos) ? istate->cache[pos] : NULL;
-		ce->ce_mode = ce_mode_from_stat(ent, st_mode);
+		ce->ce_mode = ce_mode_from_stat(istate->repo, ent, st_mode);
 	}
 
 	/* When core.ignorecase=true, determine if a directory of the same name but differing
diff --git a/read-cache.h b/read-cache.h
index 043da1f1aa..94b8d3e547 100644
--- a/read-cache.h
+++ b/read-cache.h
@@ -4,15 +4,24 @@
 #include "read-cache-ll.h"
 #include "object.h"
 #include "pathspec.h"
+#include "environment.h"
 
-static inline unsigned int ce_mode_from_stat(const struct cache_entry *ce,
+/*
+ * Determine the appropriate index mode for a file based on its stat()
+ * information and the existing cache entry (if any).
+ *
+ * This function handles degradation for filesystems that lack
+ * symlink support or reliable executable bits.
+ */
+static inline unsigned int ce_mode_from_stat(struct repository *repo,
+					     const struct cache_entry *ce,
 					     unsigned int mode)
 {
 	extern int trust_executable_bit, has_symlinks;
-	if (!has_symlinks && S_ISREG(mode) &&
+	if (S_ISREG(mode) && !has_symlinks &&
 	    ce && S_ISLNK(ce->ce_mode))
 		return ce->ce_mode;
-	if (!trust_executable_bit && S_ISREG(mode)) {
+	if (S_ISREG(mode) && !trust_executable_bit) {
 		if (ce && S_ISREG(ce->ce_mode))
 			return ce->ce_mode;
 		return create_ce_mode(0666);
-- 
2.43.0


^ permalink raw reply related

* [PATCH v7 1/4] read-cache: remove redundant extern declarations
From: Tian Yuchen @ 2026-07-17  6:35 UTC (permalink / raw)
  To: git; +Cc: ps, Tian Yuchen, Christian Couder, Ayush Chandekar,
	Olamide Caleb Bello
In-Reply-To: <20260717063559.1633567-1-cat@malon.dev>

The 'read-cache.c' file already includes 'environment.h', which provides
the extern declarations for variables like 'trust_executable_bit' and
'has_symlinks'.

Remove the redundant extern declarations inside 'st_mode_from_ce()' to
clean up the code.

Mentored-by: Christian Couder <christian.couder@gmail.com>
Mentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>
Mentored-by: Olamide Caleb Bello <belkid98@gmail.com>
Signed-off-by: Tian Yuchen <cat@malon.dev>
---
 read-cache.c | 2 --
 1 file changed, 2 deletions(-)

diff --git a/read-cache.c b/read-cache.c
index 38a04b8de3..c44e4d128f 100644
--- a/read-cache.c
+++ b/read-cache.c
@@ -204,8 +204,6 @@ void fill_stat_cache_info(struct index_state *istate, struct cache_entry *ce, st
 
 static unsigned int st_mode_from_ce(const struct cache_entry *ce)
 {
-	extern int trust_executable_bit, has_symlinks;
-
 	switch (ce->ce_mode & S_IFMT) {
 	case S_IFLNK:
 		return has_symlinks ? S_IFLNK : (S_IFREG | 0644);
-- 
2.43.0


^ permalink raw reply related

* [PATCH v7 3/4] environment: move trust_executable_bit into repo_config_values
From: Tian Yuchen @ 2026-07-17  6:35 UTC (permalink / raw)
  To: git; +Cc: ps, Tian Yuchen, Christian Couder, Ayush Chandekar,
	Olamide Caleb Bello
In-Reply-To: <20260717063559.1633567-1-cat@malon.dev>

Move the global 'trust_executable_bit' configuration
into the repository-specific 'repo_config_values'
struct.

To ensure code readability, the getter function
'repo_trust_executable_bit()' has been introduced.
Callers access this configuration by passing in 'repo'
when possible, and explicitly fall back to 'the_repository'
the rest of time.

Mentored-by: Christian Couder <christian.couder@gmail.com>
Mentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>
Mentored-by: Olamide Caleb Bello <belkid98@gmail.com>
Signed-off-by: Tian Yuchen <cat@malon.dev>
---
 apply.c       |  2 +-
 environment.c | 11 +++++++++--
 environment.h |  4 +++-
 read-cache.c  |  6 +++---
 read-cache.h  |  4 ++--
 5 files changed, 18 insertions(+), 9 deletions(-)

diff --git a/apply.c b/apply.c
index 26286eb57b..edb1502414 100644
--- a/apply.c
+++ b/apply.c
@@ -3893,7 +3893,7 @@ static int check_preimage(struct apply_state *state,
 		if (*ce && !(*ce)->ce_mode)
 			BUG("ce_mode == 0 for path '%s'", old_name);
 
-		if (trust_executable_bit || !S_ISREG(st->st_mode))
+		if (repo_trust_executable_bit(state->repo) || !S_ISREG(st->st_mode))
 			st_mode = ce_mode_from_stat(state->repo, *ce, st->st_mode);
 		else if (*ce)
 			st_mode = (*ce)->ce_mode;
diff --git a/environment.c b/environment.c
index fc3ed8bb1c..75069a884d 100644
--- a/environment.c
+++ b/environment.c
@@ -41,7 +41,6 @@
 static int pack_compression_seen;
 static int zlib_compression_seen;
 
-int trust_executable_bit = 1;
 int trust_ctime = 1;
 int check_stat = 1;
 int has_symlinks = 1;
@@ -142,6 +141,13 @@ int is_bare_repository(void)
 	return is_bare_repository_cfg && !repo_get_work_tree(the_repository);
 }
 
+int repo_trust_executable_bit(struct repository *repo)
+{
+	return repo->gitdir?
+		repo_config_values(repo)->trust_executable_bit :
+		1;
+}
+
 int have_git_dir(void)
 {
 	return startup_info->have_repository
@@ -305,7 +311,7 @@ int git_default_core_config(const char *var, const char *value,
 
 	/* This needs a better name */
 	if (!strcmp(var, "core.filemode")) {
-		trust_executable_bit = git_config_bool(var, value);
+		cfg->trust_executable_bit = git_config_bool(var, value);
 		return 0;
 	}
 	if (!strcmp(var, "core.trustctime")) {
@@ -720,5 +726,6 @@ void repo_config_values_init(struct repo_config_values *cfg)
 {
 	cfg->attributes_file = NULL;
 	cfg->apply_sparse_checkout = 0;
+	cfg->trust_executable_bit = 1;
 	cfg->branch_track = BRANCH_TRACK_REMOTE;
 }
diff --git a/environment.h b/environment.h
index 123a71cdc8..72b59fd89c 100644
--- a/environment.h
+++ b/environment.h
@@ -91,6 +91,7 @@ struct repo_config_values {
 	/* section "core" config values */
 	char *attributes_file;
 	int apply_sparse_checkout;
+	int trust_executable_bit;
 
 	/* section "branch" config values */
 	enum branch_track branch_track;
@@ -123,6 +124,8 @@ int git_default_config(const char *, const char *,
 int git_default_core_config(const char *var, const char *value,
 			    const struct config_context *ctx, void *cb);
 
+int repo_trust_executable_bit(struct repository *repo);
+
 void repo_config_values_init(struct repo_config_values *cfg);
 
 /*
@@ -160,7 +163,6 @@ int is_bare_repository(void);
 extern char *git_work_tree_cfg;
 
 /* Environment bits from configuration mechanism */
-extern int trust_executable_bit;
 extern int trust_ctime;
 extern int check_stat;
 extern int has_symlinks;
diff --git a/read-cache.c b/read-cache.c
index b37bf688ec..1f8b5ed15f 100644
--- a/read-cache.c
+++ b/read-cache.c
@@ -208,7 +208,7 @@ static unsigned int st_mode_from_ce(const struct cache_entry *ce)
 	case S_IFLNK:
 		return has_symlinks ? S_IFLNK : (S_IFREG | 0644);
 	case S_IFREG:
-		return (ce->ce_mode & (trust_executable_bit ? 0755 : 0644)) | S_IFREG;
+		return (ce->ce_mode & (repo_trust_executable_bit(the_repository) ? 0755 : 0644)) | S_IFREG;
 	case S_IFGITLINK:
 		return S_IFDIR | 0755;
 	case S_IFDIR:
@@ -318,7 +318,7 @@ static int ce_match_stat_basic(const struct cache_entry *ce, struct stat *st)
 		/* We consider only the owner x bit to be relevant for
 		 * "mode changes"
 		 */
-		if (trust_executable_bit &&
+		if (repo_trust_executable_bit(the_repository) &&
 		    (0100 & (ce->ce_mode ^ st->st_mode)))
 			changed |= MODE_CHANGED;
 		break;
@@ -739,7 +739,7 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st,
 		ce->ce_flags |= CE_INTENT_TO_ADD;
 
 
-	if (trust_executable_bit && has_symlinks) {
+	if (repo_trust_executable_bit(istate->repo) && has_symlinks) {
 		ce->ce_mode = create_ce_mode(st_mode);
 	} else {
 		/* If there is an existing entry, pick the mode bits and type
diff --git a/read-cache.h b/read-cache.h
index 94b8d3e547..4b54cfc57c 100644
--- a/read-cache.h
+++ b/read-cache.h
@@ -17,11 +17,11 @@ static inline unsigned int ce_mode_from_stat(struct repository *repo,
 					     const struct cache_entry *ce,
 					     unsigned int mode)
 {
-	extern int trust_executable_bit, has_symlinks;
+	extern int has_symlinks;
 	if (S_ISREG(mode) && !has_symlinks &&
 	    ce && S_ISLNK(ce->ce_mode))
 		return ce->ce_mode;
-	if (S_ISREG(mode) && !trust_executable_bit) {
+	if (S_ISREG(mode) && !repo_trust_executable_bit(repo)) {
 		if (ce && S_ISREG(ce->ce_mode))
 			return ce->ce_mode;
 		return create_ce_mode(0666);
-- 
2.43.0


^ permalink raw reply related

* [PATCH v7 4/4] environment: move has_symlinks into repo_config_values
From: Tian Yuchen @ 2026-07-17  6:35 UTC (permalink / raw)
  To: git; +Cc: ps, Tian Yuchen, Christian Couder, Ayush Chandekar,
	Olamide Caleb Bello
In-Reply-To: <20260717063559.1633567-1-cat@malon.dev>

Move the global 'has_symlinks' configuration into the
repository-specific 'repo_config_values' struct.

Introduce 'repo_has_symlinks()' getter for readability.
Callers access this configuration by passing in 'repo'
when possible, and explicitly fall back to
'the_repository' the rest of the time.

Introduce 'platform_has_symlinks()' macro to allow
platform specific-customization, primarily to help MinGW.
Platforms can override this in their respective headers.

Mentored-by: Christian Couder <christian.couder@gmail.com>
Mentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>
Mentored-by: Olamide Caleb Bello <belkid98@gmail.com>
Signed-off-by: Tian Yuchen <cat@malon.dev>
---
 apply.c           |  2 +-
 combine-diff.c    |  2 +-
 compat/mingw.c    | 17 +++++++++++++----
 compat/mingw.h    |  3 +++
 entry.c           |  2 +-
 environment.c     | 16 ++++++++++++++--
 environment.h     |  4 +++-
 git-compat-util.h |  4 ++++
 read-cache.c      |  7 ++++---
 read-cache.h      |  3 +--
 10 files changed, 45 insertions(+), 15 deletions(-)

diff --git a/apply.c b/apply.c
index edb1502414..b748192ee2 100644
--- a/apply.c
+++ b/apply.c
@@ -4511,7 +4511,7 @@ static int try_create_file(struct apply_state *state, const char *path,
 		return !!mkdir(path, 0777);
 	}
 
-	if (has_symlinks && S_ISLNK(mode))
+	if (repo_has_symlinks(state->repo) && S_ISLNK(mode))
 		/* Although buf:size is counted string, it also is NUL
 		 * terminated.
 		 */
diff --git a/combine-diff.c b/combine-diff.c
index b799862068..80e5c46e9b 100644
--- a/combine-diff.c
+++ b/combine-diff.c
@@ -1078,7 +1078,7 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent,
 			/* if symlinks don't work, assume symlink if all parents
 			 * are symlinks
 			 */
-			is_file = has_symlinks;
+			is_file = repo_has_symlinks(rev->repo);
 			for (i = 0; !is_file && i < num_parent; i++)
 				is_file = !S_ISLNK(elem->parent[i].mode);
 			if (!is_file)
diff --git a/compat/mingw.c b/compat/mingw.c
index aa7525f419..4781911929 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -7,6 +7,7 @@
 #include "config.h"
 #include "dir.h"
 #include "environment.h"
+#include "repository.h"
 #include "gettext.h"
 #include "run-command.h"
 #include "strbuf.h"
@@ -1043,7 +1044,7 @@ int mingw_chdir(const char *dirname)
 	if (xutftowcs_path(wdirname, dirname) < 0)
 		return -1;
 
-	if (has_symlinks) {
+	if (repo_has_symlinks(the_repository)) {
 		HANDLE hnd = CreateFileW(wdirname, 0,
 				FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL,
 				OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
@@ -2903,7 +2904,7 @@ int symlink(const char *target, const char *link)
 	int len;
 
 	/* fail if symlinks are disabled or API is not supported (WinXP) */
-	if (!has_symlinks) {
+	if (!repo_has_symlinks(the_repository)) {
 		errno = ENOSYS;
 		return -1;
 	}
@@ -3173,15 +3174,23 @@ static void setup_windows_environment(void)
 		if (!tmp && (tmp = getenv("USERPROFILE")))
 			setenv("HOME", tmp, 1);
 	}
+}
 
+int mingw_platform_has_symlinks(void)
+{
+	static int has_symlinks = -1;
 	/*
 	 * Change 'core.symlinks' default to false, unless native symlinks are
 	 * enabled in MSys2 (via 'MSYS=winsymlinks:nativestrict'). Thus we can
 	 * run the test suite (which doesn't obey config files) with or without
 	 * symlink support.
 	 */
-	if (!(tmp = getenv("MSYS")) || !strstr(tmp, "winsymlinks:nativestrict"))
-		has_symlinks = 0;
+	if (has_symlinks < 0) {
+		const char *tmp = getenv("MSYS");
+		has_symlinks = (tmp && strstr(tmp, "winsymlinks:nativestrict")) ? 1 : 0;
+	}
+
+	return has_symlinks;
 }
 
 static void get_current_user_sid(PSID *sid, HANDLE *linked_token)
diff --git a/compat/mingw.h b/compat/mingw.h
index 444daedfa5..df02aeb632 100644
--- a/compat/mingw.h
+++ b/compat/mingw.h
@@ -208,6 +208,9 @@ void open_in_gdb(void);
  */
 int err_win_to_posix(DWORD winerr);
 
+int mingw_platform_has_symlinks(void);
+#define platform_has_symlinks() mingw_platform_has_symlinks()
+
 #ifndef NO_UNIX_SOCKETS
 int mingw_have_unix_sockets(void);
 #undef have_unix_sockets
diff --git a/entry.c b/entry.c
index 7817aee362..f2854b4cd8 100644
--- a/entry.c
+++ b/entry.c
@@ -321,7 +321,7 @@ static int write_entry(struct cache_entry *ce, char *path, struct conv_attrs *ca
 		 * We can't make a real symlink; write out a regular file entry
 		 * with the symlink destination as its contents.
 		 */
-		if (!has_symlinks || to_tempfile)
+		if (!repo_has_symlinks(state->istate ? state->istate->repo : NULL) || to_tempfile)
 			goto write_file_entry;
 
 		ret = symlink(new_blob, path);
diff --git a/environment.c b/environment.c
index 75069a884d..760689d6e7 100644
--- a/environment.c
+++ b/environment.c
@@ -43,7 +43,6 @@ static int zlib_compression_seen;
 
 int trust_ctime = 1;
 int check_stat = 1;
-int has_symlinks = 1;
 int minimum_abbrev = 4, default_abbrev = -1;
 int ignore_case;
 int assume_unchanged;
@@ -148,6 +147,17 @@ int repo_trust_executable_bit(struct repository *repo)
 		1;
 }
 
+int repo_has_symlinks(struct repository *repo)
+{
+	if (!repo)
+		repo = the_repository;
+
+	if (!repo->gitdir)
+		return platform_has_symlinks();
+
+	return repo_config_values(repo)->has_symlinks;
+}
+
 int have_git_dir(void)
 {
 	return startup_info->have_repository
@@ -336,7 +346,8 @@ int git_default_core_config(const char *var, const char *value,
 	}
 
 	if (!strcmp(var, "core.symlinks")) {
-		has_symlinks = git_config_bool(var, value);
+		struct repo_config_values *cfg = repo_config_values(the_repository);
+		cfg->has_symlinks = git_config_bool(var, value);
 		return 0;
 	}
 
@@ -727,5 +738,6 @@ void repo_config_values_init(struct repo_config_values *cfg)
 	cfg->attributes_file = NULL;
 	cfg->apply_sparse_checkout = 0;
 	cfg->trust_executable_bit = 1;
+	cfg->has_symlinks = platform_has_symlinks();
 	cfg->branch_track = BRANCH_TRACK_REMOTE;
 }
diff --git a/environment.h b/environment.h
index 72b59fd89c..ef64a783b0 100644
--- a/environment.h
+++ b/environment.h
@@ -92,6 +92,7 @@ struct repo_config_values {
 	char *attributes_file;
 	int apply_sparse_checkout;
 	int trust_executable_bit;
+	int has_symlinks;
 
 	/* section "branch" config values */
 	enum branch_track branch_track;
@@ -126,6 +127,8 @@ int git_default_core_config(const char *var, const char *value,
 
 int repo_trust_executable_bit(struct repository *repo);
 
+int repo_has_symlinks(struct repository *repo);
+
 void repo_config_values_init(struct repo_config_values *cfg);
 
 /*
@@ -165,7 +168,6 @@ extern char *git_work_tree_cfg;
 /* Environment bits from configuration mechanism */
 extern int trust_ctime;
 extern int check_stat;
-extern int has_symlinks;
 extern int minimum_abbrev, default_abbrev;
 extern int ignore_case;
 extern int assume_unchanged;
diff --git a/git-compat-util.h b/git-compat-util.h
index 5024814bd4..333a5acf33 100644
--- a/git-compat-util.h
+++ b/git-compat-util.h
@@ -245,6 +245,10 @@ static inline int git_is_dir_sep(int c)
 #define is_dir_sep git_is_dir_sep
 #endif
 
+#ifndef platform_has_symlinks
+#define platform_has_symlinks() 1
+#endif
+
 #ifndef offset_1st_component
 static inline int git_offset_1st_component(const char *path)
 {
diff --git a/read-cache.c b/read-cache.c
index 1f8b5ed15f..c2c3c2e6cc 100644
--- a/read-cache.c
+++ b/read-cache.c
@@ -206,7 +206,7 @@ static unsigned int st_mode_from_ce(const struct cache_entry *ce)
 {
 	switch (ce->ce_mode & S_IFMT) {
 	case S_IFLNK:
-		return has_symlinks ? S_IFLNK : (S_IFREG | 0644);
+		return repo_has_symlinks(the_repository) ? S_IFLNK : (S_IFREG | 0644);
 	case S_IFREG:
 		return (ce->ce_mode & (repo_trust_executable_bit(the_repository) ? 0755 : 0644)) | S_IFREG;
 	case S_IFGITLINK:
@@ -324,7 +324,7 @@ static int ce_match_stat_basic(const struct cache_entry *ce, struct stat *st)
 		break;
 	case S_IFLNK:
 		if (!S_ISLNK(st->st_mode) &&
-		    (has_symlinks || !S_ISREG(st->st_mode)))
+		    (repo_has_symlinks(the_repository) || !S_ISREG(st->st_mode)))
 			changed |= TYPE_CHANGED;
 		break;
 	case S_IFGITLINK:
@@ -739,7 +739,8 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st,
 		ce->ce_flags |= CE_INTENT_TO_ADD;
 
 
-	if (repo_trust_executable_bit(istate->repo) && has_symlinks) {
+	if (repo_trust_executable_bit(istate->repo) &&
+	    repo_has_symlinks(istate->repo)) {
 		ce->ce_mode = create_ce_mode(st_mode);
 	} else {
 		/* If there is an existing entry, pick the mode bits and type
diff --git a/read-cache.h b/read-cache.h
index 4b54cfc57c..ab9d40aa81 100644
--- a/read-cache.h
+++ b/read-cache.h
@@ -17,8 +17,7 @@ static inline unsigned int ce_mode_from_stat(struct repository *repo,
 					     const struct cache_entry *ce,
 					     unsigned int mode)
 {
-	extern int has_symlinks;
-	if (S_ISREG(mode) && !has_symlinks &&
+	if (S_ISREG(mode) && !repo_has_symlinks(repo) &&
 	    ce && S_ISLNK(ce->ce_mode))
 		return ce->ce_mode;
 	if (S_ISREG(mode) && !repo_trust_executable_bit(repo)) {
-- 
2.43.0


^ permalink raw reply related

* [PATCH v7] show-branch: convert per-branch flags to commit-slab
From: Gatla Vishweshwar Reddy @ 2026-07-17  7:42 UTC (permalink / raw)
  To: gitster; +Cc: git, Gatla Vishweshwar Reddy
In-Reply-To: <xmqqfr1i6tqu.fsf@gitster.g>

show-branch uses commit->object.flags to store per-branch
reachability bits, one bit per branch starting at REV_SHIFT.
The flags word has only a fixed number of available bits, limiting
the number of branches that can be shown simultaneously.

Convert the per-branch bits to a dedicated commit-slab using uint64_t
as the element type, initialized with a stride via
init_commit_rev_flags_with_stride(). Keep the UNINTERESTING bit in
object.flags where it belongs, as it is used for revision walking and
does not need to be in the per-branch slab. With UNINTERESTING removed
from the slab, REV_SHIFT becomes 0 and all 64 bits of uint64_t are
available for branch tracking.

Add helper functions get_rev_flags_ptr(), peek_rev_flags_ptr(),
has_any_rev_flags(), or_rev_flag_bit(), test_rev_flag_bit(),
has_all_rev_flags(), has_only_rev_flag_bit(), and
has_subset_rev_flags() to encapsulate per-bit slab access cleanly.
Use has_only_rev_flag_bit() in show_independent() to preserve the
original semantics: a commit is independent only if reachable from
exactly one tip. Use has_subset_rev_flags() in join_revs() to skip
parents whose flags already include all of the current commit's flags,
avoiding redundant propagation and queue thrashing in dense histories.

Fix join_revs() to correctly propagate UNINTERESTING to parents using
a local commit_is_merge_base variable, without smudging the commit
itself, matching the original behavior.

Remove the hardcoded MAX_REVS limit entirely by dynamically growing
the ref_name[] array with ALLOC_GROW, and allocating rev[] and
reflog_msg[] dynamically after all refs are collected. Compute
flags_stride at runtime as (ref_name_cnt + 63) / 64 so the slab
supports an arbitrary number of branches.

Update documentation to remove the hardcoded branch limit. Add tests
to verify show-branch works correctly with more than 27 branches.
Include revision.h for the shared UNINTERESTING definition.

Signed-off-by: Gatla Vishweshwar Reddy <gatlavishweshwarreddy26@gmail.com>
---

Thank you very much for the thorough and insightful review —
the performance observation about subset checking and the
suggestion to remove MAX_REVS entirely were both excellent
points that significantly improve this patch.

> With the new slab-based approach, we skip only when 'p' already
> has all possible revision flags, num_rev. If 'p' already carries
> all the flags that the current 'commit' has (even if it lacks some
> of the other num_rev flags), the traversal could be pruned early,
> but the proposed change fails to do so. Consequently, we proceed
> to propagate the flags (which amounts to a no-op on the slab
> anyway) and, worse, re-queue 'p' for further processing. In a
> densely tangled history with many merges, this would lead to
> significant redundant work and queue thrashing. We instead should
> check whether the flags of 'commit' are a subset of those of 'p'.
> Since the flags_stride is known, introducing a helper, perhaps
> has_subset_rev_flags(commit, p), to perform this check should be
> a straightforward exercise.

Done. Added has_subset_rev_flags(src, dst) which checks whether
every bit set in src's slab words is also set in dst's slab words
using word-level (src[i] & ~dst[i]) == 0 checks across all
flags_stride words. The parent skip condition in join_revs() now
uses this helper:

    if (has_subset_rev_flags(commit, p) &&
        (!commit_is_merge_base || (p->object.flags & UNINTERESTING)))
            continue;

This avoids re-queuing parents that already carry all of the current
commit's reachability bits, preventing redundant propagation and
queue thrashing in dense histories.

> While lifting the limit from 27 to 64 is a welcome improvement,
> I wonder why we stop there and still tolerate a hardcoded MAX_REVS
> limit.

Agreed. MAX_REVS is removed entirely in v7.

> The introduction of flags_stride and
> init_commit_rev_flags_with_stride already lays the groundwork for
> supporting an arbitrary number of flags. The only remaining
> blockages that keep MAX_REVS alive are:
>  - The static ref_name[] array; and
>  - The stack-allocated arrays rev[] and reflog_msg[] in the
>    cmd_show_branch() function.

> If we
>  - dynamically grow the ref_name[] array (perhaps using the
>    ALLOC_GROW macro),
>  - dynamically allocate rev[] and reflog_msg[] in cmd_show_branch()
>    once options are parsed (and thus ref_name_cnt and the reflog
>    flag are known), and
>  - calculate flags_stride at runtime as (ref_name_cnt + 63) / 64,
> then we can get rid of MAX_REVS and the associated boundary checks
> entirely.

All three steps implemented in v7:
- ref_name[] is now a dynamically grown pointer array using
  ALLOC_GROW(ref_name, ref_name_cnt + 2, ref_name_alloc)
- rev[] and reflog_msg[] are allocated with CALLOC_ARRAY after
  all refs are collected and ref_name_cnt is known
- flags_stride is computed at runtime as (ref_name_cnt + 63) / 64

MAX_REVS and all associated boundary checks are removed.

> Since the proposed patch already does 90% of the work needed to
> support an arbitrary stride, it feels like a missed opportunity
> not to take that final step.
>
> Thoughts?

Fully agreed — taking that final step makes the patch complete and
removes a limitation that was only there due to the original
object.flags approach. v7 does exactly that.

---
Changes in v7:
- Add has_subset_rev_flags() and use it in join_revs() to skip
  parents that already carry all of commit's reachability bits,
  avoiding redundant propagation and queue thrashing (Junio)
- Remove MAX_REVS entirely; grow ref_name[] dynamically with
  ALLOC_GROW; allocate rev[] and reflog_msg[] dynamically after
  ref collection; compute flags_stride at runtime (Junio)

 Documentation/git-show-branch.adoc |   2 +-
 builtin/show-branch.c              | 234 ++++++++++++++++++-----------
 t/t3202-show-branch.sh             |  32 ++++
 3 files changed, 180 insertions(+), 88 deletions(-)

diff --git a/Documentation/git-show-branch.adoc b/Documentation/git-show-branch.adoc
index 7e86d54a24..fe65c0a95a 100644
--- a/Documentation/git-show-branch.adoc
+++ b/Documentation/git-show-branch.adoc
@@ -22,7 +22,7 @@ Shows the commit ancestry graph starting from the commits named
 with <rev>s or <glob>s (or all refs under refs/heads
 and/or refs/tags) semi-visually.
 
-It cannot show more than 26 branches and commits at a time.
+It cannot show more than 64 branches and commits at a time.
 
 It uses `showbranch.default` multi-valued configuration items if
 no <rev> or <glob> is given on the command line.
diff --git a/builtin/show-branch.c b/builtin/show-branch.c
index f02831b085..4f49a8d05b 100644
--- a/builtin/show-branch.c
+++ b/builtin/show-branch.c
@@ -9,6 +9,7 @@
 #include "hex.h"
 #include "pretty.h"
 #include "refs.h"
+#include "revision.h"
 #include "color.h"
 #include "strvec.h"
 #include "object-name.h"
@@ -35,15 +36,11 @@ static enum git_colorbool showbranch_use_color = GIT_COLOR_UNKNOWN;
 static struct strvec default_args = STRVEC_INIT;
 
 /*
- * TODO: convert this use of commit->object.flags to commit-slab
- * instead to store a pointer to ref name directly. Then use the same
- * UNINTERESTING definition from revision.h here.
+ * TODO: store a pointer to ref name directly in the commit-slab
+ * instead, and use the UNINTERESTING definition from revision.h
+ * here once that is done.
  */
-#define UNINTERESTING	01
-
-#define REV_SHIFT	 2
-#define MAX_REVS	(FLAG_BITS - REV_SHIFT) /* should not exceed bits_per_int - REV_SHIFT */
-
+#define REV_SHIFT	 0
 #define DEFAULT_REFLOG	4
 
 static const char *get_color_code(int idx)
@@ -79,11 +76,87 @@ struct commit_name {
 define_commit_slab(commit_name_slab, struct commit_name *);
 static struct commit_name_slab name_slab;
 
+define_commit_slab(commit_rev_flags, uint64_t);
+static struct commit_rev_flags rev_flags_slab;
+static int flags_stride; /* number of uint64_t words per commit */
+
 static struct commit_name *commit_to_name(struct commit *commit)
 {
 	return *commit_name_slab_at(&name_slab, commit);
 }
 
+static uint64_t *get_rev_flags_ptr(struct commit *commit)
+{
+	return commit_rev_flags_at(&rev_flags_slab, commit);
+}
+
+static uint64_t *peek_rev_flags_ptr(struct commit *commit)
+{
+	return commit_rev_flags_peek(&rev_flags_slab, commit);
+}
+
+static int has_any_rev_flags(struct commit *commit)
+{
+	uint64_t *f = peek_rev_flags_ptr(commit);
+	int i;
+	if (!f)
+		return 0;
+	for (i = 0; i < flags_stride; i++)
+		if (f[i])
+			return 1;
+	return 0;
+}
+
+static void or_rev_flag_bit(struct commit *commit, int branch)
+{
+	get_rev_flags_ptr(commit)[branch / 64] |= UINT64_C(1) << (branch % 64);
+}
+
+static int test_rev_flag_bit(struct commit *commit, int branch)
+{
+	uint64_t *f = peek_rev_flags_ptr(commit);
+	return f && !!(f[branch / 64] & (UINT64_C(1) << (branch % 64)));
+}
+
+static int has_all_rev_flags(struct commit *commit, int num_rev)
+{
+	int i;
+	for (i = 0; i < num_rev; i++)
+		if (!test_rev_flag_bit(commit, i))
+			return 0;
+	return 1;
+}
+
+static int has_only_rev_flag_bit(struct commit *commit, int branch)
+{
+	uint64_t *f = peek_rev_flags_ptr(commit);
+	int i;
+	if (!f)
+		return 0;
+	for (i = 0; i < flags_stride; i++) {
+		uint64_t expected = (i == branch / 64)
+				    ? (UINT64_C(1) << (branch % 64))
+				    : 0;
+		if (f[i] != expected)
+			return 0;
+	}
+	return 1;
+}
+
+static int has_subset_rev_flags(struct commit *src, struct commit *dst)
+{
+	uint64_t *s = peek_rev_flags_ptr(src);
+	uint64_t *d = peek_rev_flags_ptr(dst);
+	int i;
+	if (!s)
+		return 1; /* src has no flags, trivially a subset */
+	if (!d)
+		return 0; /* dst has no flags but src has some */
+	for (i = 0; i < flags_stride; i++)
+		if (s[i] & ~d[i])
+			return 0;
+	return 1;
+}
 
 /* Name the commit as nth generation ancestor of head_name;
  * we count only the first-parent relationship for naming purposes.
@@ -215,7 +288,7 @@ static void name_commits(struct commit_list *list,
 
 static int mark_seen(struct commit *commit, struct commit_list **seen_p)
 {
-	if (!commit->object.flags) {
+	if (!has_any_rev_flags(commit)) {
 		commit_list_insert(commit, seen_p);
 		return 1;
 	}
@@ -226,39 +299,43 @@ static void join_revs(struct prio_queue *queue,
 		      struct commit_list **seen_p,
 		      int num_rev, int extra)
 {
-	int all_mask = ((1u << (REV_SHIFT + num_rev)) - 1);
-	int all_revs = all_mask & ~((1u << REV_SHIFT) - 1);
-
 	while (queue->nr) {
 		struct commit_list *parents;
 		int still_interesting = !!interesting(queue);
 		struct commit *commit = prio_queue_peek(queue);
 		bool get_pending = true;
-		int flags = commit->object.flags & all_mask;
 
 		if (!still_interesting && extra <= 0)
 			break;
 
 		mark_seen(commit, seen_p);
-		if ((flags & all_revs) == all_revs)
-			flags |= UNINTERESTING;
-		parents = commit->parents;
-
-		while (parents) {
-			struct commit *p = parents->item;
-			int this_flag = p->object.flags;
-			parents = parents->next;
-			if ((this_flag & flags) == flags)
-				continue;
-			repo_parse_commit(the_repository, p);
-			if (mark_seen(p, seen_p) && !still_interesting)
-				extra--;
-			p->object.flags |= flags;
-			if (get_pending)
-				prio_queue_replace(queue, p);
-			else
-				prio_queue_put(queue, p);
-			get_pending = false;
+		{
+			int commit_is_merge_base = has_all_rev_flags(commit, num_rev);
+			parents = commit->parents;
+
+			while (parents) {
+				struct commit *p = parents->item;
+				parents = parents->next;
+				if (has_subset_rev_flags(commit, p) &&
+				    (!commit_is_merge_base || (p->object.flags & UNINTERESTING)))
+					continue;
+				repo_parse_commit(the_repository, p);
+				if (mark_seen(p, seen_p) && !still_interesting)
+					extra--;
+				{
+					int _b;
+					for (_b = 0; _b < num_rev; _b++)
+						if (test_rev_flag_bit(commit, _b))
+							or_rev_flag_bit(p, _b);
+				}
+				if (commit_is_merge_base)
+					p->object.flags |= UNINTERESTING;
+				if (get_pending)
+					prio_queue_replace(queue, p);
+				else
+					prio_queue_put(queue, p);
+				get_pending = false;
+			}
 		}
 		if (get_pending)
 			prio_queue_get(queue);
@@ -278,7 +355,7 @@ static void join_revs(struct prio_queue *queue,
 			struct commit *c = s->item;
 			struct commit_list *parents;
 
-			if (((c->object.flags & all_revs) != all_revs) &&
+			if (!has_all_rev_flags(c, num_rev) &&
 			    !(c->object.flags & UNINTERESTING))
 				continue;
 
@@ -335,8 +412,9 @@ static void show_one_commit(struct commit *commit, int no_name)
 	strbuf_release(&pretty);
 }
 
-static char *ref_name[MAX_REVS + 1];
+static char **ref_name;
 static int ref_name_cnt;
+static int ref_name_alloc;
 
 static const char *find_digit_prefix(const char *s, int *v)
 {
@@ -409,12 +487,7 @@ static int append_ref(const char *refname, const struct object_id *oid,
 			if (!strcmp(refname, ref_name[i]))
 				return 0;
 	}
-	if (MAX_REVS <= ref_name_cnt) {
-		warning(Q_("ignoring %s; cannot handle more than %d ref",
-			   "ignoring %s; cannot handle more than %d refs",
-			   MAX_REVS), refname, MAX_REVS);
-		return 0;
-	}
+	ALLOC_GROW(ref_name, ref_name_cnt + 2, ref_name_alloc);
 	ref_name[ref_name_cnt++] = xstrdup(refname);
 	ref_name[ref_name_cnt] = NULL;
 	return 0;
@@ -511,15 +584,12 @@ static int rev_is_head(const char *head, const char *name)
 
 static int show_merge_base(const struct commit_list *seen, int num_rev)
 {
-	int all_mask = ((1u << (REV_SHIFT + num_rev)) - 1);
-	int all_revs = all_mask & ~((1u << REV_SHIFT) - 1);
 	int exit_status = 1;
 
 	for (const struct commit_list *s = seen; s; s = s->next) {
 		struct commit *commit = s->item;
-		int flags = commit->object.flags & all_mask;
-		if (!(flags & UNINTERESTING) &&
-		    ((flags & all_revs) == all_revs)) {
+		if (!(commit->object.flags & UNINTERESTING) &&
+			has_all_rev_flags(commit, num_rev)) {
 			puts(oid_to_hex(&commit->object.oid));
 			exit_status = 0;
 			commit->object.flags |= UNINTERESTING;
@@ -528,17 +598,13 @@ static int show_merge_base(const struct commit_list *seen, int num_rev)
 	return exit_status;
 }
 
-static int show_independent(struct commit **rev,
-			    int num_rev,
-			    unsigned int *rev_mask)
+static int show_independent(struct commit **rev, int num_rev)
 {
 	int i;
 
 	for (i = 0; i < num_rev; i++) {
 		struct commit *commit = rev[i];
-		unsigned int flag = rev_mask[i];
-
-		if (commit->object.flags == flag)
+		if (has_only_rev_flag_bit(commit, i))
 			puts(oid_to_hex(&commit->object.oid));
 		commit->object.flags |= UNINTERESTING;
 	}
@@ -560,8 +626,7 @@ static void append_one_rev(const char *av)
 		match_ref_slash = count_slashes(av);
 		refs_for_each_ref(get_main_ref_store(the_repository),
 				  append_matching_ref, NULL);
-		if (saved_matches == ref_name_cnt &&
-		    ref_name_cnt < MAX_REVS)
+		if (saved_matches == ref_name_cnt)
 			error(_("no matching refs with %s"), av);
 		sort_ref_range(saved_matches, ref_name_cnt);
 		return;
@@ -603,13 +668,12 @@ static int omit_in_dense(struct commit *commit, struct commit **rev, int n)
 	 * Otherwise, if it is a merge that is reachable from only one
 	 * tip, it is not that interesting.
 	 */
-	int i, flag, count;
+	int i, count;
 	for (i = 0; i < n; i++)
 		if (rev[i] == commit)
 			return 0;
-	flag = commit->object.flags;
 	for (i = count = 0; i < n; i++) {
-		if (flag & (1u << (i + REV_SHIFT)))
+		if (test_rev_flag_bit(commit, i))
 			count++;
 	}
 	if (count == 1)
@@ -644,14 +708,12 @@ int cmd_show_branch(int ac,
 		const char *prefix,
 		struct repository *repo UNUSED)
 {
-	struct commit *rev[MAX_REVS], *commit;
-	char *reflog_msg[MAX_REVS] = {0};
+	struct commit **rev = NULL, *commit;
+	char **reflog_msg = NULL;
 	struct commit_list *seen = NULL;
 	struct prio_queue queue = { compare_commits_by_commit_date };
-	unsigned int rev_mask[MAX_REVS];
 	int num_rev, i, extra = 0;
 	int all_heads = 0, all_remotes = 0;
-	int all_mask, all_revs;
 	enum rev_sort_order sort_order = REV_SORT_IN_GRAPH_ORDER;
 	char *head;
 	struct object_id head_oid;
@@ -713,8 +775,6 @@ int cmd_show_branch(int ac,
 	const char **args_copy = NULL;
 	int ret;
 
-	init_commit_name_slab(&name_slab);
-
 	repo_config(the_repository, git_show_branch_config, NULL);
 
 	/* If nothing is specified, try the default first */
@@ -778,10 +838,7 @@ int cmd_show_branch(int ac,
 		if (ac != 1)
 			die(_("--reflog option needs one branch name"));
 
-		if (MAX_REVS < reflog)
-			die(Q_("only %d entry can be shown at one time.",
-			       "only %d entries can be shown at one time.",
-			       MAX_REVS), MAX_REVS);
+
 		if (!repo_dwim_ref(the_repository, *av, strlen(*av), &oid,
 				   &ref, 0))
 			die(_("no such ref %s"), *av);
@@ -868,14 +925,17 @@ int cmd_show_branch(int ac,
 		goto out;
 	}
 
+	flags_stride = (ref_name_cnt + 63) / 64;
+	if (!flags_stride)
+		flags_stride = 1;
+	init_commit_rev_flags_with_stride(&rev_flags_slab, flags_stride);
+	CALLOC_ARRAY(rev, ref_name_cnt);
+	CALLOC_ARRAY(reflog_msg, ref_name_cnt);
+
 	for (num_rev = 0; ref_name[num_rev]; num_rev++) {
 		struct object_id revkey;
-		unsigned int flag = 1u << (num_rev + REV_SHIFT);
+		int first_seen;
 
-		if (MAX_REVS <= num_rev)
-			die(Q_("cannot handle more than %d rev.",
-			       "cannot handle more than %d revs.",
-			       MAX_REVS), MAX_REVS);
 		if (repo_get_oid(the_repository, ref_name[num_rev], &revkey))
 			die(_("'%s' is not a valid ref."), ref_name[num_rev]);
 		commit = lookup_commit_reference(the_repository, &revkey);
@@ -885,17 +945,15 @@ int cmd_show_branch(int ac,
 		repo_parse_commit(the_repository, commit);
 		mark_seen(commit, &seen);
 
-		/* rev#0 uses bit REV_SHIFT, rev#1 uses bit REV_SHIFT+1,
-		 * and so on.  REV_SHIFT bits from bit 0 are used for
-		 * internal bookkeeping.
+		/* rev#0 uses bit 0, rev#1 uses bit 1,
+		 * and so on.  All bits are available for branch tracking.
 		 */
-		commit->object.flags |= flag;
-		if (commit->object.flags == flag)
+		first_seen = !has_any_rev_flags(commit);
+		or_rev_flag_bit(commit, num_rev);
+		if (first_seen)
 			prio_queue_put(&queue, commit);
 		rev[num_rev] = commit;
 	}
-	for (i = 0; i < num_rev; i++)
-		rev_mask[i] = rev[i]->object.flags;
 
 	if (0 <= extra)
 		join_revs(&queue, &seen, num_rev, extra);
@@ -908,7 +966,7 @@ int cmd_show_branch(int ac,
 	}
 
 	if (independent) {
-		ret = show_independent(rev, num_rev, rev_mask);
+		ret = show_independent(rev, num_rev);
 		goto out;
 	}
 
@@ -958,13 +1016,9 @@ int cmd_show_branch(int ac,
 	if (!sha1_name && !no_name)
 		name_commits(seen, rev, ref_name, num_rev);
 
-	all_mask = ((1u << (REV_SHIFT + num_rev)) - 1);
-	all_revs = all_mask & ~((1u << REV_SHIFT) - 1);
-
 	for (struct commit_list *l = seen; l; l = l->next) {
 		struct commit *commit = l->item;
-		int this_flag = commit->object.flags;
-		int is_merge_point = ((this_flag & all_revs) == all_revs);
+		int is_merge_point = has_all_rev_flags(commit, num_rev);
 
 		shown_merge_point |= is_merge_point;
 
@@ -973,14 +1027,14 @@ int cmd_show_branch(int ac,
 					  commit->parents->next);
 			if (topics &&
 			    !is_merge_point &&
-			    (this_flag & (1u << REV_SHIFT)))
+			    test_rev_flag_bit(commit, 0))
 				continue;
 			if (!sparse && is_merge &&
 			    omit_in_dense(commit, rev, num_rev))
 				continue;
 			for (i = 0; i < num_rev; i++) {
 				int mark;
-				if (!(this_flag & (1u << (i + REV_SHIFT))))
+				if (!test_rev_flag_bit(commit, i))
 					mark = ' ';
 				else if (is_merge)
 					mark = '-';
@@ -1006,10 +1060,16 @@ int cmd_show_branch(int ac,
 	ret = 0;
 
 out:
-	for (size_t i = 0; i < ARRAY_SIZE(reflog_msg); i++)
+	for (i = 0; i < ref_name_cnt; i++)
 		free(reflog_msg[i]);
+	free(reflog_msg);
+	free(rev);
+	for (i = 0; i < ref_name_cnt; i++)
+		free(ref_name[i]);
+	free(ref_name);
 	commit_list_free(seen);
 	clear_prio_queue(&queue);
+	clear_commit_rev_flags(&rev_flags_slab);
 	free(args_copy);
 	free(head);
 	return ret;
diff --git a/t/t3202-show-branch.sh b/t/t3202-show-branch.sh
index a1139f79e2..d04f642998 100755
--- a/t/t3202-show-branch.sh
+++ b/t/t3202-show-branch.sh
@@ -283,4 +283,36 @@ test_expect_success '--reflog handles missing reflog' '
 	test_must_be_empty actual
 '
 
+test_expect_success 'show-branch with 30 branches succeeds' '
+	git checkout initial &&
+	for i in $(test_seq 11 30)
+	do
+		git checkout -b branch$i initial &&
+		test_commit --no-tag branch$i || return 1
+	done &&
+	git show-branch $(git for-each-ref \
+		--sort=version:refname \
+		--format="%(refname:strip=2)" \
+		"refs/heads/branch*") >actual &&
+	test_line_count -ge 30 actual
+'
+
+test_expect_success 'show-branch --independent with 30 branches' '
+	git show-branch --independent $(git for-each-ref \
+		--sort=version:refname \
+		--format="%(refname:strip=2)" \
+		"refs/heads/branch*") >actual &&
+	test_line_count -ge 30 actual
+'
+
+test_expect_success 'show-branch --merge-base with 30 branches' '
+	git rev-parse initial >expect &&
+	git show-branch --merge-base $(git for-each-ref \
+		--sort=version:refname \
+		--format="%(refname:strip=2)" \
+		"refs/heads/branch*") >actual &&
+	test_cmp expect actual
+'
+
+
 test_done
-- 
2.54.0


^ permalink raw reply related

* Re: [PATCH] wt-status: avoid quadratic insertion for untracked paths
From: Jeff King @ 2026-07-17  7:54 UTC (permalink / raw)
  To: Patrick Steinhardt; +Cc: Sahitya Chandra, git, gitster, avarab, stolee
In-Reply-To: <alnLPSnOt_Sf7cA5@pks.im>

On Fri, Jul 17, 2026 at 08:27:09AM +0200, Patrick Steinhardt wrote:

> > fill_directory() currently sorts dir.entries and dir.ignored
> > before returning, so another possible approach would be to append the
> > entries directly and rely on that order, reducing this copy step to O(n).
> > That would require relying on these arrays not containing duplicate
> > entries, though, which I have not been able to verify yet. This patch
> > takes the safer approach of preserving the existing duplicate-removal
> > behavior from `string_list_insert()` by sorting and deduplicating once
> > after appending.
> 
> Out of curiosity: is this something that you have encountered in the
> real world as inefficient, or is this rather a theoretical inefficiency?
> If the former it would be great to add a small benchmark to the commit
> message.

Yeah, I had the same question, and tried for a moment to produce an
example before realizing that it probably is theoretical. If we are
feeding the entries in pre-sorted order then the insert is always O(1).

I think it's still worth doing this, though, as it makes the result much
more obvious to analyze. I think it could even be O(n) if the sort
implementation is optimized under the hood for pre-sorted inputs.

-Peff

^ permalink raw reply

* Re: git-last-modified(1) slower than git-log(1)?
From: Jeff King @ 2026-07-17  8:02 UTC (permalink / raw)
  To: Toon Claes; +Cc: Gusted, git
In-Reply-To: <87v7afffpa.fsf@emacs.iotcl.com>

On Thu, Jul 16, 2026 at 11:26:25AM +0200, Toon Claes wrote:

> The thing is, you're testing the difference on a single file. For us at
> GitLab, it wasn't very useful to optimize that use-case, because usually
> we want to see the last commit for a bunch of files at once.
> So the use-case for git-last-modified(1) for us has been to replace
> (pseudo code):

That was my assumption at first, too, but I think the log command there
really is returning results for the whole subtree. You just have to
post-process it to pick out the files from each commit.

> $ FILES=$(git ls-tree $COMMIT $PATH)
> $ foreach $FILE in $FILES; do git log -1 $COMMIT -- $FILE; end

Yeah, that is the most horrible way to do it. It's expensive in
processes, but also in walking over the same set of history repeatedly.

The log in Gusted's example does a single walk, but it is up to the
caller to then interpret the walk results. That would add extra time,
but I think it scales independently of the time difference he's
observing. In his hyperfine results, last-modified is scaling with the
total numbers of commits in the repo, but processing the output scales
to the number of commits which actually touched the subtree in question.

So I think it really could perform better than last-modified, even with
the post-processing step (which we didn't see nor time). But we should
be able to do better in last-modified using similar top-level commit
filtering.

-Peff

^ permalink raw reply

* Re: git-last-modified(1) slower than git-log(1)?
From: Jeff King @ 2026-07-17  8:09 UTC (permalink / raw)
  To: Toon Claes; +Cc: Gusted, git, Taylor Blau
In-Reply-To: <87se5jf9f7.fsf@emacs.iotcl.com>

On Thu, Jul 16, 2026 at 01:42:04PM +0200, Toon Claes wrote:

> >   - more timing exploration; e.g., might it make things worse if
> >     doc/langref were touched in 99% of the commits? Probably not, but it
> >     might be nice to check timings against a few repo shapes and request
> >     depths.
> 
> Maybe, I tried a few things.

Yeah, I would be surprised to find a practical case where it makes
things slower. Checking one bloom key is cheap-ish, and unless the
subtree being queried is touched by almost every commit, it's going to
be a net win.

> Personally I'm not too worried any use-case would be at least equally
> fast.

So yeah, that's my gut feeling, too.

> > +/*
> > + * revision.c already has this functionality, but it is not public
> > + * and it looks up the filter itself. But probably some refactoring
> > + * could make it available at the right level?
> 
> I assume you're talking about check_maybe_different_in_bloom_filter()?

Yeah, exactly. We already do the first half (getting the commit's
filter) ourselves. And then most of the rest is just trace2 accounting,
which we don't necessarily need to do.  So we're left with just that one
bloom over the keyvecs, which is fairly trivial. Mostly it felt weird to
be looking at the innards of rev_info, and the logic for what those
keyvecs means should remain in revision.c.

> I was working on a fix to simply make it public and call it, but that's
> a very valid point you're making. I'll change my plans.

I was just thinking to split it into two (get the filter, and then check
the filter against the rev_info) and make the latter half public.

-Peff

^ permalink raw reply

* Re: [PATCH v5] show-branch: convert per-branch flags to commit-slab
From: Patrick Steinhardt @ 2026-07-17  8:51 UTC (permalink / raw)
  To: Gatla Vishweshwar Reddy; +Cc: gitster, git
In-Reply-To: <20260715120156.53025-1-gatlavishweshwarreddy26@gmail.com>

On Wed, Jul 15, 2026 at 05:31:56PM +0530, Gatla Vishweshwar Reddy wrote:
> Apologies to Patrick for not replying inline to the review before
> sending v4. For previous review threads I did reply inline; for that
> round I mistakenly folded the response into the annotation only.

Apologies but I'll ignore that advice anyway...?

Patrick

^ permalink raw reply

* Re: [PATCH v7] show-branch: convert per-branch flags to commit-slab
From: Patrick Steinhardt @ 2026-07-17  8:52 UTC (permalink / raw)
  To: Gatla Vishweshwar Reddy; +Cc: gitster, git
In-Reply-To: <20260717074208.61303-1-gatlavishweshwarreddy26@gmail.com>

On Fri, Jul 17, 2026 at 01:12:06PM +0530, Gatla Vishweshwar Reddy wrote:
> Thank you very much for the thorough and insightful review —
> the performance observation about subset checking and the
> suggestion to remove MAX_REVS entirely were both excellent
> points that significantly improve this patch.

Sorry, but are we merely chatting with an AI or is there a human
anywhere between us and the prompt? I feel like this thread is becoming
a waste of time.

Patrick

^ permalink raw reply

* Re: [PATCH 3/3] bisect: add --auto-reset to leave when done
From: Harald Nordgren @ 2026-07-17  9:16 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Harald Nordgren via GitGitGadget, git
In-Reply-To: <xmqqpl0m9pnq.fsf@gitster.g>

That's a great idea!


Harald

^ permalink raw reply

* [PATCH 0/9] object-file: move writing of loose objects into "loose" source
From: Patrick Steinhardt @ 2026-07-17  9:32 UTC (permalink / raw)
  To: git; +Cc: Justin Tobler

Hi,

this patch series refactors "object-file.c" so that we can move the
logic that writes loose objects into "odb/source-loose.c". This ensures
that the logic to read and write loose objects is mostly self-contained
within that source's implementation now.

To achieve that, this series first refactors `force_object_loose()` so
that it can work on top of the generic `odb_source_write_object()`
instead of having to call into `write_loose_object()`. This is the bulk
of the patch series.

This patch series is built on top of 44de1520f0 (Merge branch 'master'
of https://github.com/j6t/git-gui, 2026-07-16) with the following two
topics merged into it:

  - jt/receive-pack-use-odb-transaction at bdee7b3013
    (builtin/receive-pack: stage incoming objects via ODB transactions,
    2026-07-10).

  - jk/git-hash-cleanups at 9e396aa553 (hash: check ctx->active flag in
    all wrapper functions, 2026-07-07).

Note that jt/receive-pack-use-odb-transaction requires an evil merge:

diff --git a/odb/source-packed.c b/odb/source-packed.c
index 06b31dd743..cbb06da038 100644
--- a/odb/source-packed.c
+++ b/odb/source-packed.c
@@ -545,7 +545,8 @@ static int odb_source_packed_write_object_stream(struct odb_source *source UNUSE
 }

 static int odb_source_packed_begin_transaction(struct odb_source *source UNUSED,
-                                              struct odb_transaction **out UNUSED)
+                                              struct odb_transaction **out UNUSED,
+                                              enum odb_transaction_flags flags UNUSED)
 {
        return error("packed backend cannot begin transactions");
 }

Thanks!

Patrick

---
Patrick Steinhardt (9):
      odb: compute compat object ID in `odb_write_object_ext()`
      t/u-odb-inmemory: implement wrapper for writing objects
      odb: compute object hash in `odb_write_object_ext()`
      odb: lift object existence check out of the "loose" backend
      odb: support setting mtime when writing objects
      object-file: fix memory leak in `force_object_loose()`
      object-file: force objects loose via generic interface
      object-file: move `force_object_loose()`
      object-file: move logic to write loose objects

 builtin/pack-objects.c        |  48 ++++-
 object-file.c                 | 449 +++---------------------------------------
 object-file.h                 |  32 +--
 odb.c                         |  39 +++-
 odb.h                         |  10 +-
 odb/source-files.c            |  14 +-
 odb/source-inmemory.c         |  14 +-
 odb/source-loose.c            | 402 +++++++++++++++++++++++++++++++++----
 odb/source-packed.c           |  17 +-
 odb/source.h                  |  20 +-
 read-cache.c                  |   2 +-
 t/unit-tests/u-odb-inmemory.c |  53 +++--
 12 files changed, 551 insertions(+), 549 deletions(-)


---
base-commit: 8061f1ea286318fbf976f58df3985bd60237b8a7
change-id: 20260703-pks-odb-move-loose-object-writing-8bcf1f51b04d


^ permalink raw reply related

* [PATCH 1/9] odb: compute compat object ID in `odb_write_object_ext()`
From: Patrick Steinhardt @ 2026-07-17  9:32 UTC (permalink / raw)
  To: git; +Cc: Justin Tobler
In-Reply-To: <20260717-pks-odb-move-loose-object-writing-v1-0-46446a3cb5b7@pks.im>

Repositories can have a compatibility hash configured, which means that
such a repository is expected to maintain a mapping between canonical
and compatibility object hashes. Maintaining this mapping is the
responsibility of the object database sources, where we either store
them as part of the loose objects map or in packfile indices v3 (once we
gain support for this feature).

But besides storing these compatibility hashes, the sources are also
responsible for generating the compatibility hash in the first place.
This is somewhat unnecessary though, as the compatibility hash should be
computed the same no matter which source is being used. The consequence
is that we need to duplicate this functionality across the different
backends, which does not make a lot of sense.

Refactor the code so that we instead compute the compatibility hash in
`odb_write_object_ext()` and then pass the computed value to the
sources. No callers need adjustment as there are none that write objects
via the source interfaces directly.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 odb.c                 | 26 ++++++++++++++++++++++++--
 odb.h                 | 10 ++++++----
 odb/source-files.c    |  2 +-
 odb/source-inmemory.c |  2 +-
 odb/source-loose.c    | 24 +++---------------------
 odb/source-packed.c   |  2 +-
 odb/source.h          |  4 ++--
 7 files changed, 38 insertions(+), 32 deletions(-)

diff --git a/odb.c b/odb.c
index cf6e7938c0..1d6538163b 100644
--- a/odb.c
+++ b/odb.c
@@ -989,11 +989,33 @@ int odb_write_object_ext(struct object_database *odb,
 			 const void *buf, unsigned long len,
 			 enum object_type type,
 			 struct object_id *oid,
-			 struct object_id *compat_oid,
+			 const struct object_id *compat_oid_in,
 			 enum odb_write_object_flags flags)
 {
+	const struct git_hash_algo *compat = odb->repo->compat_hash_algo;
+	struct object_id compat_oid, *compat_oid_p = NULL;
+
+	if (compat) {
+		const struct git_hash_algo *algo = odb->repo->hash_algo;
+
+		if (compat_oid_in) {
+			oidcpy(&compat_oid, compat_oid_in);
+		} else if (type == OBJ_BLOB) {
+			hash_object_file(compat, buf, len, type, &compat_oid);
+		} else {
+			struct strbuf converted = STRBUF_INIT;
+			convert_object_file(odb->repo, &converted, algo, compat,
+					    buf, len, type, 0);
+			hash_object_file(compat, converted.buf, converted.len,
+					 type, &compat_oid);
+			strbuf_release(&converted);
+		}
+
+		compat_oid_p = &compat_oid;
+	}
+
 	return odb_source_write_object(odb->sources, buf, len, type,
-				       oid, compat_oid, flags);
+				       oid, compat_oid_p, flags);
 }
 
 int odb_write_object_stream(struct object_database *odb,
diff --git a/odb.h b/odb.h
index 94754643d2..066560113e 100644
--- a/odb.h
+++ b/odb.h
@@ -585,9 +585,11 @@ enum odb_write_object_flags {
 
 /*
  * Write an object into the object database. The object is being written into
- * the local alternate of the repository. If provided, the converted object ID
- * as well as the compatibility object ID are written to the respective
- * pointers.
+ * the local alternate of the repository. If provided, the object ID of the
+ * final object is written into `oid`.
+ *
+ * If the caller provides a `compat_oid`, then this compatibility object hash
+ * will be stored instead of computing the compatibility hash ad-hoc.
  *
  * Returns 0 on success, a negative error code otherwise.
  */
@@ -595,7 +597,7 @@ int odb_write_object_ext(struct object_database *odb,
 			 const void *buf, unsigned long len,
 			 enum object_type type,
 			 struct object_id *oid,
-			 struct object_id *compat_oid,
+			 const struct object_id *compat_oid,
 			 enum odb_write_object_flags flags);
 
 static inline int odb_write_object(struct object_database *odb,
diff --git a/odb/source-files.c b/odb/source-files.c
index 4138758511..3d9f5eca32 100644
--- a/odb/source-files.c
+++ b/odb/source-files.c
@@ -163,7 +163,7 @@ static int odb_source_files_write_object(struct odb_source *source,
 					 const void *buf, size_t len,
 					 enum object_type type,
 					 struct object_id *oid,
-					 struct object_id *compat_oid,
+					 const struct object_id *compat_oid,
 					 enum odb_write_object_flags flags)
 {
 	struct odb_source_files *files = odb_source_files_downcast(source);
diff --git a/odb/source-inmemory.c b/odb/source-inmemory.c
index e47bfd8fcc..e727aba427 100644
--- a/odb/source-inmemory.c
+++ b/odb/source-inmemory.c
@@ -231,7 +231,7 @@ static int odb_source_inmemory_write_object(struct odb_source *source,
 					    const void *buf, size_t len,
 					    enum object_type type,
 					    struct object_id *oid,
-					    struct object_id *compat_oid UNUSED,
+					    const struct object_id *compat_oid UNUSED,
 					    enum odb_write_object_flags flags UNUSED)
 {
 	struct odb_source_inmemory *inmemory = odb_source_inmemory_downcast(source);
diff --git a/odb/source-loose.c b/odb/source-loose.c
index 3f7d04a56e..ca223109cd 100644
--- a/odb/source-loose.c
+++ b/odb/source-loose.c
@@ -585,32 +585,14 @@ static int odb_source_loose_freshen_object(struct odb_source *source,
 static int odb_source_loose_write_object(struct odb_source *source,
 					 const void *buf, size_t len,
 					 enum object_type type, struct object_id *oid,
-					 struct object_id *compat_oid_in,
+					 const struct object_id *compat_oid,
 					 enum odb_write_object_flags flags)
 {
 	struct odb_source_loose *loose = odb_source_loose_downcast(source);
 	const struct git_hash_algo *algo = source->odb->repo->hash_algo;
-	const struct git_hash_algo *compat = source->odb->repo->compat_hash_algo;
-	struct object_id compat_oid;
 	char hdr[MAX_HEADER_LEN];
 	size_t hdrlen = sizeof(hdr);
 
-	/* Generate compat_oid */
-	if (compat) {
-		if (compat_oid_in)
-			oidcpy(&compat_oid, compat_oid_in);
-		else if (type == OBJ_BLOB)
-			hash_object_file(compat, buf, len, type, &compat_oid);
-		else {
-			struct strbuf converted = STRBUF_INIT;
-			convert_object_file(source->odb->repo, &converted, algo, compat,
-					    buf, len, type, 0);
-			hash_object_file(compat, converted.buf, converted.len,
-					 type, &compat_oid);
-			strbuf_release(&converted);
-		}
-	}
-
 	/* Normally if we have it in the pack then we do not bother writing
 	 * it out into .git/objects/??/?{38} file.
 	 */
@@ -619,8 +601,8 @@ static int odb_source_loose_write_object(struct odb_source *source,
 		return 0;
 	if (write_loose_object(loose, oid, hdr, hdrlen, buf, len, 0, flags))
 		return -1;
-	if (compat)
-		return repo_add_loose_object_map(loose, oid, &compat_oid);
+	if (compat_oid)
+		return repo_add_loose_object_map(loose, oid, compat_oid);
 	return 0;
 }
 
diff --git a/odb/source-packed.c b/odb/source-packed.c
index 8d9ce197cc..af0d533375 100644
--- a/odb/source-packed.c
+++ b/odb/source-packed.c
@@ -530,7 +530,7 @@ static int odb_source_packed_write_object(struct odb_source *source UNUSED,
 					  size_t len UNUSED,
 					  enum object_type type UNUSED,
 					  struct object_id *oid UNUSED,
-					  struct object_id *compat_oid UNUSED,
+					  const struct object_id *compat_oid UNUSED,
 					  unsigned flags UNUSED)
 {
 	return error("packed backend cannot write objects");
diff --git a/odb/source.h b/odb/source.h
index cd63dba91f..b3c1ca3a66 100644
--- a/odb/source.h
+++ b/odb/source.h
@@ -207,7 +207,7 @@ struct odb_source {
 			    const void *buf, size_t len,
 			    enum object_type type,
 			    struct object_id *oid,
-			    struct object_id *compat_oid,
+			    const struct object_id *compat_oid,
 			    enum odb_write_object_flags flags);
 
 	/*
@@ -417,7 +417,7 @@ static inline int odb_source_write_object(struct odb_source *source,
 					  const void *buf, unsigned long len,
 					  enum object_type type,
 					  struct object_id *oid,
-					  struct object_id *compat_oid,
+					  const struct object_id *compat_oid,
 					  enum odb_write_object_flags flags)
 {
 	return source->write_object(source, buf, len, type, oid,

-- 
2.55.0.407.g700c83d4f3.dirty


^ permalink raw reply related

* [PATCH 2/9] t/u-odb-inmemory: implement wrapper for writing objects
From: Patrick Steinhardt @ 2026-07-17  9:32 UTC (permalink / raw)
  To: git; +Cc: Justin Tobler
In-Reply-To: <20260717-pks-odb-move-loose-object-writing-v1-0-46446a3cb5b7@pks.im>

In the next commit we're about to change how objects are being written
into the object database source. Prepare for this refactoring by
introducing a wrapper function into our unit tests so that we don't have
to adjust all callsites.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 t/unit-tests/u-odb-inmemory.c | 48 +++++++++++++++++++++----------------------
 1 file changed, 23 insertions(+), 25 deletions(-)

diff --git a/t/unit-tests/u-odb-inmemory.c b/t/unit-tests/u-odb-inmemory.c
index 6844bfc37c..2dbc3ab1df 100644
--- a/t/unit-tests/u-odb-inmemory.c
+++ b/t/unit-tests/u-odb-inmemory.c
@@ -1,5 +1,6 @@
 #include "unit-test.h"
 #include "hex.h"
+#include "object-file.h"
 #include "odb/source-inmemory.h"
 #include "odb/streaming.h"
 #include "oidset.h"
@@ -36,6 +37,16 @@ static void cl_assert_object_info(struct odb_source_inmemory *source,
 	free(actual_content);
 }
 
+static void cl_assert_write_object(struct odb_source_inmemory *source,
+				   const char *content,
+				   enum object_type type,
+				   struct object_id *oid)
+{
+	size_t content_len = strlen(content);
+	cl_must_pass(odb_source_write_object(&source->base, content, content_len,
+					     type, oid, NULL, 0));
+}
+
 void test_odb_inmemory__initialize(void)
 {
 	odb = odb_new(&repo, "", "");
@@ -78,8 +89,7 @@ void test_odb_inmemory__read_written_object(void)
 	const char data[] = "foobar";
 	struct object_id written_oid;
 
-	cl_must_pass(odb_source_write_object(&source->base, data, strlen(data),
-					     OBJ_BLOB, &written_oid, NULL, 0));
+	cl_assert_write_object(source, data, OBJ_BLOB, &written_oid);
 	cl_assert_equal_s(oid_to_hex(&written_oid), FOOBAR_OID);
 	cl_assert_object_info(source, &written_oid, OBJ_BLOB, "foobar");
 
@@ -94,8 +104,7 @@ void test_odb_inmemory__read_stream_object(void)
 	const char data[] = "foobar";
 	char buf[3] = { 0 };
 
-	cl_must_pass(odb_source_write_object(&source->base, data, strlen(data),
-					     OBJ_BLOB, &written_oid, NULL, 0));
+	cl_assert_write_object(source, data, OBJ_BLOB, &written_oid);
 
 	cl_must_pass(odb_source_read_object_stream(&stream, &source->base,
 						   &written_oid));
@@ -141,8 +150,7 @@ void test_odb_inmemory__for_each_object(void)
 		strbuf_reset(&buf);
 		strbuf_addf(&buf, "%d", i);
 
-		cl_must_pass(odb_source_write_object(&source->base, buf.buf, buf.len,
-						     OBJ_BLOB, &written_oid, NULL, 0));
+		cl_assert_write_object(source, buf.buf, OBJ_BLOB, &written_oid);
 		cl_must_pass(oidset_insert(&expected_oids, &written_oid));
 	}
 
@@ -174,12 +182,9 @@ void test_odb_inmemory__for_each_object_can_abort_iteration(void)
 	struct object_id written_oid;
 	unsigned counter = 0;
 
-	cl_must_pass(odb_source_write_object(&source->base, "1", 1,
-					     OBJ_BLOB, &written_oid, NULL, 0));
-	cl_must_pass(odb_source_write_object(&source->base, "2", 1,
-					     OBJ_BLOB, &written_oid, NULL, 0));
-	cl_must_pass(odb_source_write_object(&source->base, "3", 1,
-					     OBJ_BLOB, &written_oid, NULL, 0));
+	cl_assert_write_object(source, "1", OBJ_BLOB, &written_oid);
+	cl_assert_write_object(source, "2", OBJ_BLOB, &written_oid);
+	cl_assert_write_object(source, "3", OBJ_BLOB, &written_oid);
 
 	cl_assert_equal_i(odb_source_for_each_object(&source->base, NULL,
 						     abort_after_two_objects,
@@ -199,12 +204,9 @@ void test_odb_inmemory__count_objects(void)
 	cl_must_pass(odb_source_count_objects(&source->base, 0, &count));
 	cl_assert_equal_u(count, 0);
 
-	cl_must_pass(odb_source_write_object(&source->base, "1", 1,
-					     OBJ_BLOB, &written_oid, NULL, 0));
-	cl_must_pass(odb_source_write_object(&source->base, "2", 1,
-					     OBJ_BLOB, &written_oid, NULL, 0));
-	cl_must_pass(odb_source_write_object(&source->base, "3", 1,
-					     OBJ_BLOB, &written_oid, NULL, 0));
+	cl_assert_write_object(source, "1", OBJ_BLOB, &written_oid);
+	cl_assert_write_object(source, "2", OBJ_BLOB, &written_oid);
+	cl_assert_write_object(source, "3", OBJ_BLOB, &written_oid);
 
 	cl_must_pass(odb_source_count_objects(&source->base, 0, &count));
 	cl_assert_equal_u(count, 3);
@@ -228,8 +230,7 @@ void test_odb_inmemory__find_abbrev_len(void)
 	 *
 	 * With only one blob written we expect a length of 4.
 	 */
-	cl_must_pass(odb_source_write_object(&source->base, "368317", strlen("368317"),
-					     OBJ_BLOB, &oid1, NULL, 0));
+	cl_assert_write_object(source, "368317", OBJ_BLOB, &oid1);
 	cl_must_pass(odb_source_find_abbrev_len(&source->base, &oid1, 4,
 						&abbrev_len));
 	cl_assert_equal_u(abbrev_len, 4);
@@ -238,8 +239,7 @@ void test_odb_inmemory__find_abbrev_len(void)
 	 * With both objects present, the shared 10-character prefix means we
 	 * need at least 11 characters to uniquely identify either object.
 	 */
-	cl_must_pass(odb_source_write_object(&source->base, "514796", strlen("514796"),
-					     OBJ_BLOB, &oid2, NULL, 0));
+	cl_assert_write_object(source, "514796", OBJ_BLOB, &oid2);
 	cl_must_pass(odb_source_find_abbrev_len(&source->base, &oid1, 4,
 						&abbrev_len));
 	cl_assert_equal_u(abbrev_len, 11);
@@ -257,9 +257,7 @@ void test_odb_inmemory__freshen_object(void)
 	cl_must_pass(parse_oid_hex_algop(RANDOM_OID, &oid, &end, repo.hash_algo));
 	cl_assert_equal_i(odb_source_freshen_object(&source->base, &oid), 0);
 
-	cl_must_pass(odb_source_write_object(&source->base, "foobar",
-					     strlen("foobar"), OBJ_BLOB,
-					     &written_oid, NULL, 0));
+	cl_assert_write_object(source, "foobar", OBJ_BLOB, &written_oid);
 	cl_assert_equal_i(odb_source_freshen_object(&source->base,
 						    &written_oid), 1);
 

-- 
2.55.0.407.g700c83d4f3.dirty


^ permalink raw reply related

* [PATCH 3/9] odb: compute object hash in `odb_write_object_ext()`
From: Patrick Steinhardt @ 2026-07-17  9:32 UTC (permalink / raw)
  To: git; +Cc: Justin Tobler
In-Reply-To: <20260717-pks-odb-move-loose-object-writing-v1-0-46446a3cb5b7@pks.im>

Same as in a preceding commit, compute the object hash in
`odb_write_object_ext()` so that we can unify this logic.

Besides unification, this change also allows us to lift the object
existence check out of the "loose" backend into the generic layer, which
will happen in the next commit.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 object-file.c                 | 35 ++++++++---------------------------
 object-file.h                 |  4 ----
 odb.c                         |  2 ++
 odb/source-files.c            |  2 +-
 odb/source-inmemory.c         |  6 +++---
 odb/source-loose.c            | 12 +++++++-----
 odb/source-packed.c           |  2 +-
 odb/source.h                  |  4 ++--
 t/unit-tests/u-odb-inmemory.c |  1 +
 9 files changed, 25 insertions(+), 43 deletions(-)

diff --git a/object-file.c b/object-file.c
index 5283292f1e..9ca14f484d 100644
--- a/object-file.c
+++ b/object-file.c
@@ -316,31 +316,6 @@ int parse_loose_header(const char *hdr, struct object_info *oi)
 	return 0;
 }
 
-static void hash_object_body(const struct git_hash_algo *algo, struct git_hash_ctx *c,
-			     const void *buf, size_t len,
-			     struct object_id *oid,
-			     char *hdr, size_t *hdrlen)
-{
-	git_hash_init(c, algo);
-	git_hash_update(c, hdr, *hdrlen);
-	git_hash_update(c, buf, len);
-	git_hash_final_oid(oid, c);
-}
-
-void write_object_file_prepare(const struct git_hash_algo *algo,
-			       const void *buf, size_t len,
-			       enum object_type type, struct object_id *oid,
-			       char *hdr, size_t *hdrlen)
-{
-	struct git_hash_ctx c;
-
-	/* Generate the header */
-	*hdrlen = format_object_header(hdr, *hdrlen, type, len);
-
-	/* Hash (function pointers) computation */
-	hash_object_body(algo, &c, buf, len, oid, hdr, hdrlen);
-}
-
 #define CHECK_COLLISION_DEST_VANISHED -2
 
 static int check_collision(const char *source, const char *dest)
@@ -476,10 +451,16 @@ void hash_object_file(const struct git_hash_algo *algo, const void *buf,
 		      size_t len, enum object_type type,
 		      struct object_id *oid)
 {
+	struct git_hash_ctx c;
 	char hdr[MAX_HEADER_LEN];
-	size_t hdrlen = sizeof(hdr);
+	int hdrlen;
+
+	hdrlen = format_object_header(hdr, sizeof(hdr), type, len);
 
-	write_object_file_prepare(algo, buf, len, type, oid, hdr, &hdrlen);
+	git_hash_init(&c, algo);
+	git_hash_update(&c, hdr, hdrlen);
+	git_hash_update(&c, buf, len);
+	git_hash_final_oid(oid, &c);
 }
 
 struct transaction_packfile {
diff --git a/object-file.h b/object-file.h
index d04ffa6493..08aafcda0d 100644
--- a/object-file.h
+++ b/object-file.h
@@ -134,10 +134,6 @@ int finalize_object_file_flags(struct repository *repo,
 void hash_object_file(const struct git_hash_algo *algo, const void *buf,
 		      size_t len, enum object_type type,
 		      struct object_id *oid);
-void write_object_file_prepare(const struct git_hash_algo *algo,
-			       const void *buf, size_t len,
-			       enum object_type type, struct object_id *oid,
-			       char *hdr, size_t *hdrlen);
 int write_loose_object(struct odb_source_loose *loose,
 		       const struct object_id *oid, char *hdr,
 		       int hdrlen, const void *buf, unsigned long len,
diff --git a/odb.c b/odb.c
index 1d6538163b..4adbdf8a64 100644
--- a/odb.c
+++ b/odb.c
@@ -995,6 +995,8 @@ int odb_write_object_ext(struct object_database *odb,
 	const struct git_hash_algo *compat = odb->repo->compat_hash_algo;
 	struct object_id compat_oid, *compat_oid_p = NULL;
 
+	hash_object_file(odb->repo->hash_algo, buf, len, type, oid);
+
 	if (compat) {
 		const struct git_hash_algo *algo = odb->repo->hash_algo;
 
diff --git a/odb/source-files.c b/odb/source-files.c
index 3d9f5eca32..06dfc8dd78 100644
--- a/odb/source-files.c
+++ b/odb/source-files.c
@@ -162,7 +162,7 @@ static int odb_source_files_freshen_object(struct odb_source *source,
 static int odb_source_files_write_object(struct odb_source *source,
 					 const void *buf, size_t len,
 					 enum object_type type,
-					 struct object_id *oid,
+					 const struct object_id *oid,
 					 const struct object_id *compat_oid,
 					 enum odb_write_object_flags flags)
 {
diff --git a/odb/source-inmemory.c b/odb/source-inmemory.c
index e727aba427..963d520317 100644
--- a/odb/source-inmemory.c
+++ b/odb/source-inmemory.c
@@ -230,15 +230,13 @@ static int odb_source_inmemory_count_objects(struct odb_source *source,
 static int odb_source_inmemory_write_object(struct odb_source *source,
 					    const void *buf, size_t len,
 					    enum object_type type,
-					    struct object_id *oid,
+					    const struct object_id *oid,
 					    const struct object_id *compat_oid UNUSED,
 					    enum odb_write_object_flags flags UNUSED)
 {
 	struct odb_source_inmemory *inmemory = odb_source_inmemory_downcast(source);
 	struct inmemory_object *object;
 
-	hash_object_file(source->odb->repo->hash_algo, buf, len, type, oid);
-
 	if (!inmemory->objects) {
 		CALLOC_ARRAY(inmemory->objects, 1);
 		oidtree_init(inmemory->objects);
@@ -285,6 +283,8 @@ static int odb_source_inmemory_write_object_stream(struct odb_source *source,
 		goto out;
 	}
 
+	hash_object_file(source->odb->repo->hash_algo, data, total_read, OBJ_BLOB, oid);
+
 	ret = odb_source_inmemory_write_object(source, data, len, OBJ_BLOB, oid,
 					       NULL, 0);
 	if (ret < 0)
diff --git a/odb/source-loose.c b/odb/source-loose.c
index ca223109cd..d4715da6d1 100644
--- a/odb/source-loose.c
+++ b/odb/source-loose.c
@@ -584,19 +584,21 @@ static int odb_source_loose_freshen_object(struct odb_source *source,
 
 static int odb_source_loose_write_object(struct odb_source *source,
 					 const void *buf, size_t len,
-					 enum object_type type, struct object_id *oid,
+					 enum object_type type,
+					 const struct object_id *oid,
 					 const struct object_id *compat_oid,
 					 enum odb_write_object_flags flags)
 {
 	struct odb_source_loose *loose = odb_source_loose_downcast(source);
-	const struct git_hash_algo *algo = source->odb->repo->hash_algo;
 	char hdr[MAX_HEADER_LEN];
-	size_t hdrlen = sizeof(hdr);
+	int hdrlen;
+
+	hdrlen = format_object_header(hdr, sizeof(hdr), type, len);
 
-	/* Normally if we have it in the pack then we do not bother writing
+	/*
+	 * Normally if we have it in the pack then we do not bother writing
 	 * it out into .git/objects/??/?{38} file.
 	 */
-	write_object_file_prepare(algo, buf, len, type, oid, hdr, &hdrlen);
 	if (odb_freshen_object(source->odb, oid))
 		return 0;
 	if (write_loose_object(loose, oid, hdr, hdrlen, buf, len, 0, flags))
diff --git a/odb/source-packed.c b/odb/source-packed.c
index af0d533375..f7f1706447 100644
--- a/odb/source-packed.c
+++ b/odb/source-packed.c
@@ -529,7 +529,7 @@ static int odb_source_packed_write_object(struct odb_source *source UNUSED,
 					  const void *buf UNUSED,
 					  size_t len UNUSED,
 					  enum object_type type UNUSED,
-					  struct object_id *oid UNUSED,
+					  const struct object_id *oid UNUSED,
 					  const struct object_id *compat_oid UNUSED,
 					  unsigned flags UNUSED)
 {
diff --git a/odb/source.h b/odb/source.h
index b3c1ca3a66..c4e94c9d0d 100644
--- a/odb/source.h
+++ b/odb/source.h
@@ -206,7 +206,7 @@ struct odb_source {
 	int (*write_object)(struct odb_source *source,
 			    const void *buf, size_t len,
 			    enum object_type type,
-			    struct object_id *oid,
+			    const struct object_id *oid,
 			    const struct object_id *compat_oid,
 			    enum odb_write_object_flags flags);
 
@@ -416,7 +416,7 @@ static inline int odb_source_freshen_object(struct odb_source *source,
 static inline int odb_source_write_object(struct odb_source *source,
 					  const void *buf, unsigned long len,
 					  enum object_type type,
-					  struct object_id *oid,
+					  const struct object_id *oid,
 					  const struct object_id *compat_oid,
 					  enum odb_write_object_flags flags)
 {
diff --git a/t/unit-tests/u-odb-inmemory.c b/t/unit-tests/u-odb-inmemory.c
index 2dbc3ab1df..28a69fc244 100644
--- a/t/unit-tests/u-odb-inmemory.c
+++ b/t/unit-tests/u-odb-inmemory.c
@@ -43,6 +43,7 @@ static void cl_assert_write_object(struct odb_source_inmemory *source,
 				   struct object_id *oid)
 {
 	size_t content_len = strlen(content);
+	hash_object_file(repo.hash_algo, content, content_len, type, oid);
 	cl_must_pass(odb_source_write_object(&source->base, content, content_len,
 					     type, oid, NULL, 0));
 }

-- 
2.55.0.407.g700c83d4f3.dirty


^ permalink raw reply related


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox