* [PATCH v2] submodule: resolve insteadOf aliases when matching remote
From: Éric NICOLAS @ 2026-07-23 0:21 UTC (permalink / raw)
To: git; +Cc: gitster, jacob.keller, Éric NICOLAS
In-Reply-To: <20260721213042.3357346-1-ccjmne@gmail.com>
When ca62f524c1 (submodule: look up remotes by URL first, 2025-06-23)
introduced a mechanism to identify which remote is to be used by a
submodule, it compared the URL stored in the .gitmodules inventory to
that of each available remote.
The URLs of remotes are rewritten according to url.<base>.insteadOf,
whereas those stored in the .gitmodules aren't. When such aliasing
applies, no match can be made between the two corresponding sides, and
the procedure degrades to its fallback logic electing either the only
configured remote if there is only one, or "origin" otherwise.
That behaviour is unfortunate when no remote is called "origin",
because its last resort will have a submodule update command look for a
non-existent remote-tracking reference and fail to proceed, instead of
using the remote whose rewritten URL matches.
Resolve the alias in the URL inventoried in .gitmodules before comparing
it against those of the corresponding submodule's configured remotes.
Signed-off-by: Éric NICOLAS <ccjmne@gmail.com>
---
Thank you for your guidance.
Changes in v2:
- Reword the commit message more purposefully
- Adjust the implementation as suggested, avoiding a superfluous
variable
- Tidy up the integration test
remote.c | 14 +++++++++++---
t/t7406-submodule-update.sh | 19 +++++++++++++++++++
2 files changed, 30 insertions(+), 3 deletions(-)
diff --git a/remote.c b/remote.c
index b17648d6ef..b1fed58e79 100644
--- a/remote.c
+++ b/remote.c
@@ -1821,17 +1821,25 @@ const char *repo_default_remote(struct repository *repo)
const char *repo_remote_from_url(struct repository *repo, const char *url)
{
+ char *rewritten_url;
+ const char *remote_name = NULL;
+
read_config(repo, 0);
+ if ((rewritten_url = alias_url(url, &repo->remote_state->rewrites)))
+ url = rewritten_url;
for (int i = 0; i < repo->remote_state->remotes_nr; i++) {
struct remote *remote = repo->remote_state->remotes[i];
if (!remote)
continue;
- if (remote_has_url(remote, url))
- return remote->name;
+ if (remote_has_url(remote, url)) {
+ remote_name = remote->name;
+ break;
+ }
}
- return NULL;
+ free(rewritten_url);
+ return remote_name;
}
int branch_has_merge_config(struct branch *branch)
diff --git a/t/t7406-submodule-update.sh b/t/t7406-submodule-update.sh
index 9554720152..10adeabf0f 100755
--- a/t/t7406-submodule-update.sh
+++ b/t/t7406-submodule-update.sh
@@ -256,6 +256,25 @@ test_expect_success 'submodule update --remote should fetch upstream changes' '
)
'
+test_expect_success 'submodule update --remote resolves URL rewrites' '
+ test_config_global "url.$(pwd)/.insteadOf" local: &&
+ mkdir alias-super alias-submodule &&
+ (
+ cd alias-submodule &&
+ git init &&
+ git commit --allow-empty --message "Initial commit"
+ ) &&
+ (
+ cd alias-super &&
+ git init &&
+ git submodule add local:alias-submodule submodule &&
+ git submodule update --force &&
+ git -C submodule remote rename origin upstream &&
+ git -C submodule remote add fork user@host &&
+ git submodule update --remote
+ )
+'
+
test_expect_success 'submodule update --remote should fetch upstream changes with .' '
(
cd super &&
Range-diff against v1:
1: ed507998b3 ! 1: 4363eb3cb1 submodule: resolve insteadof-aliases when matching remote
@@ Metadata
Author: Éric NICOLAS <ccjmne@gmail.com>
## Commit message ##
- submodule: resolve insteadof-aliases when matching remote
+ submodule: resolve insteadOf aliases when matching remote
- When ca62f524c1 introduced a mechanism to identify which remote is to be
- used by a submodule, we had it compare the URL stored in the .gitmodules
- inventory to those of each available remote.
+ When ca62f524c1 (submodule: look up remotes by URL first, 2025-06-23)
+ introduced a mechanism to identify which remote is to be used by a
+ submodule, it compared the URL stored in the .gitmodules inventory to
+ that of each available remote.
- However, when using URL aliasing via url.<base>.insteadOf, we store
- in .gitmodules the URL pre-resolution of the alias, whereas the
- corresponding remote set up in the submodule reports using the
- *resolved* URL. This mechanism therefore fails to find a match then,
- and resorts to the fallback logic, which does use either the only
- configured remote if there is only one, or attempts using "origin"
- otherwise.
+ The URLs of remotes are rewritten according to url.<base>.insteadOf,
+ whereas those stored in the .gitmodules aren't. When such aliasing
+ applies, no match can be made between the two corresponding sides, and
+ the procedure degrades to its fallback logic electing either the only
+ configured remote if there is only one, or "origin" otherwise.
+
+ That behaviour is unfortunate when no remote is called "origin",
+ because its last resort will have a submodule update command look for a
+ non-existent remote-tracking reference and fail to proceed, instead of
+ using the remote whose rewritten URL matches.
Resolve the alias in the URL inventoried in .gitmodules before comparing
it against those of the corresponding submodule's configured remotes.
@@ remote.c: const char *repo_default_remote(struct repository *repo)
const char *repo_remote_from_url(struct repository *repo, const char *url)
{
+ char *rewritten_url;
-+ const char *url_to_match;
+ const char *remote_name = NULL;
+
read_config(repo, 0);
-+ rewritten_url = alias_url(url, &repo->remote_state->rewrites);
-+ url_to_match = rewritten_url ? rewritten_url : url;
++ if ((rewritten_url = alias_url(url, &repo->remote_state->rewrites)))
++ url = rewritten_url;
for (int i = 0; i < repo->remote_state->remotes_nr; i++) {
struct remote *remote = repo->remote_state->remotes[i];
@@ remote.c: const char *repo_default_remote(struct repository *repo)
- if (remote_has_url(remote, url))
- return remote->name;
-+ if (remote_has_url(remote, url_to_match)) {
++ if (remote_has_url(remote, url)) {
+ remote_name = remote->name;
+ break;
+ }
@@ t/t7406-submodule-update.sh: test_expect_success 'submodule update --remote shou
+test_expect_success 'submodule update --remote resolves URL rewrites' '
+ test_config_global "url.$(pwd)/.insteadOf" local: &&
-+ mkdir aliased-super aliased-submodule &&
++ mkdir alias-super alias-submodule &&
+ (
-+ cd aliased-submodule &&
++ cd alias-submodule &&
+ git init &&
-+ echo line >file &&
-+ git add file &&
-+ git commit -m "Initial commit"
++ git commit --allow-empty --message "Initial commit"
+ ) &&
+ (
-+ cd aliased-super &&
++ cd alias-super &&
+ git init &&
-+ git submodule add local:aliased-submodule submodule &&
-+ git submodule update --force submodule &&
++ git submodule add local:alias-submodule submodule &&
++ git submodule update --force &&
+ git -C submodule remote rename origin upstream &&
+ git -C submodule remote add fork user@host &&
-+ git submodule update --remote submodule
++ git submodule update --remote
+ )
+'
+
--
2.55.0
^ permalink raw reply related
* Re: [PATCH v4 0/2] remote: url-based pushRemote with renamed remotes
From: Junio C Hamano @ 2026-07-23 0:52 UTC (permalink / raw)
To: Harald Nordgren via GitGitGadget; +Cc: git, D. Ben Knoble, Harald Nordgren
In-Reply-To: <pull.2358.v4.git.git.1784743738.gitgitgadget@gmail.com>
"Harald Nordgren via GitGitGadget" <gitgitgadget@gmail.com> writes:
> Fix git status not showing the push branch after remotes are renamed, when
> branch.<name>.pushRemote is a URL matching exactly one configured remote.
>
> Changes in v4:
>
> * Match configured remotes by effective push URL, preferring pushurl over
> url.
> * Update the documentation and rationale to describe where the remote would
> push.
My cursory review did not spot anything obviously wrong anymore.
As it somehow seems to be a slow week, I do not expect to see many
eyeballs from others helping to review the topics in flight as
quickly as we would have liked to move them forward, though.
Thanks.
^ permalink raw reply
* What's cooking in git.git (Jul 2026, #10)
From: Junio C Hamano @ 2026-07-23 2:38 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 seventh 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']
* 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.
Graduated to 'master'.
cf. <alX5Nl8uX4ctVqo3@pks.im>
cf. <xmqq4ii228dd.fsf@gitster.g>
source: <20260713124153.245268-1-christian.couder@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.
Graduated to 'master'.
source: <20260710-add-change-id-to-b4-template-v1-1-1bd37a25064e@black-desk.cn>
* 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.
Graduated to 'master'.
cf. <alTJCTKR9jOWfgbk@pks.im>
source: <20260710-includeif-worktree-v8-0-04686d8a616c@black-desk.cn>
* 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.
Graduated to 'master'.
cf. <xmqq8q7ltf51.fsf@gitster.g>
source: <20260708-submodule-init-v1-1-719456077262@atmark-techno.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
(this branch is used by ps/odb-move-loose-object-writing.)
'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.
Graduated to 'master'.
cf. <alR1P-RGZNmjyiUE@pks.im>
source: <20260710163722.2962278-1-jltobler@gmail.com>
* kk/no-walk-pathspec-fix (2026-07-16) 2 commits
(merged to 'next' on 2026-07-16 at 4dd6fb0e7e)
+ 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 has been corrected to restore
pathspec filtering, which was lost when the streaming walk was
refactored.
Graduated to 'master'.
source: <pull.2181.git.1784198879711.gitgitgadget@gmail.com>
* 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.
Graduated to 'master'.
cf. <alTHrUEh4_O5ROeu@pks.im>
source: <20260711160447.99708-1-marcelomlage@usp.br>
* 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.
Graduated to 'master'.
cf. <874ii0h2uf.fsf@emacs.iotcl.com>
source: <20260715-pks-odb-for-each-object-filter-v4-0-616d7adf7fb7@pks.im>
* 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.
Graduated to 'master'.
source: <20260710-pks-odb-stream-double-close-v1-1-d5fa233a37c7@pks.im>
* 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.
Graduated to 'master'.
cf. <xmqq8q7dv82b.fsf@gitster.g>
cf. <20260714214941.GB4095533@coredump.intra.peff.net>
source: <d4ffe7fb-f782-4f06-9e3b-f72729d1e225@web.de>
* 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.
Graduated to 'master'.
cf. <xmqq4ii1v7x0.fsf@gitster.g>
source: <20260714122033.61947-1-diy2903@gmail.com>
* 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.
Graduated to 'master'.
cf. <xmqq1pd4m4ea.fsf@gitster.g>
source: <20260715113344.3490-1-diy2903@gmail.com>
--------------------------------------------------
[New Topics]
* hs/rebase-continue-edit (2026-07-21) 1 commit
- rebase: add --[no-]edit to --continue
Support for skipping the editor when continuing a rebase after
conflict resolution has been added with the '--no-edit' option, and
forcing it with '--edit'. A new configuration variable
'rebase.noEdit' can be used to set the default behavior.
Needs review.
source: <20260721140443.1809379-2-hugo@hsal.es>
* tn/stash-avoid-sparse-index-expansion (2026-07-20) 2 commits
- stash: avoid sparse-index expansion for in-cone paths
- pathspec: use match for sparse-index expansion checks
The 'git stash push' command has been optimized to avoid unnecessary
sparse index expansion when pathspecs are wholly inside the
sparse-checkout cone. Also, a potential out-of-bounds read in the
sparse-index expansion check helper pathspec_needs_expanded_index()
has been fixed by consistently using the parsed, prefixed path.
Will merge to 'next'.
cf. <al61UTM0aK9j9eiP@com-79390>
cf. <al61ERa3fS2MerHp@com-79390>
source: <20260720223118.62821-4-tnyman@openai.com>
* en/submodule-insteadof-remote-match (2026-07-21) 1 commit
- submodule: resolve insteadof-aliases when matching remote
The remote-matching logic for submodules has been corrected to
resolve 'url.*.insteadOf' aliases before comparing the inventoried
URL from '.gitmodules' with the URLs of configured remotes.
Needs review.
source: <20260721213042.3357346-1-ccjmne@gmail.com>
* td/fsmonitor-darwin-cookie-flush (2026-07-21) 1 commit
- fsmonitor: flush pending FSEvents before cookie wait
The 'fsmonitor' daemon on macOS has been updated to flush pending
FSEvents before waiting for the cookie file, to avoid premature
timeouts on busy systems.
Needs review.
source: <20260721-fsmonitor-darwin-cookie-flush-v1-1-357dc5e32040@gmail.com>
* jc/exclude-first-parent-seen (2026-07-22) 1 commit
- revision: honor --exclude-first-parent-only with SEEN first parent
Traversals with '--exclude-first-parent-only' have been corrected to
properly stop after the first parent even when it has already been
marked as SEEN.
Needs review.
source: <xmqqbjbzq7n2.fsf@gitster.g>
--------------------------------------------------
[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>
cf. <xmqqcxxyt4op.fsf@gitster.g>
source: <V3_CV_doc_int-tr_key_format.8a3@msgid.xyz>
* 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]
* sn/rebase-update-refs-symrefs (2026-07-22) 2 commits
- rebase: guard non-branch symref targets
- 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.
Needs review.
source: <pull.2126.v3.git.1784708107.gitgitgadget@gmail.com>
* bc/rust-hash-cleanups (2026-07-18) 2 commits
(merged to 'next' on 2026-07-21 at 51627468a0)
+ rust: discard hash context when finished
+ hash: initialize context before cloning
A few memory problems in the Rust interface to C hash functions have
been corrected. The 'Clone' implementation of 'CryptoHasher' now
properly initializes the context before cloning, and its 'Drop'
implementation now discards the context to prevent leaks.
Will merge to 'master'.
cf. <20260719080754.GA429688@coredump.intra.peff.net>
source: <20260719010842.17991-1-sandals@crustytoothpaste.net>
* ja/doc-synopsis-style-yet-more (2026-07-19) 4 commits
- doc: convert git-request-pull synopsis and options to new style
- doc: convert git-send-email synopsis and options to new style
- doc: convert git-format-patch synopsis and options to new style
- doc: convert git-imap-send synopsis and options to new style
Synopsis and options in the documentation for 'git format-patch',
'git imap-send', 'git send-email', and 'git request-pull' have been
updated to the modern style.
Expecting a reroll.
cf. <23179740.EfDdHjke4D@piment-oiseau>
cf. <2418232.ElGaqSPkdT@piment-oiseau>
source: <pull.2185.git.1784490878.gitgitgadget@gmail.com>
* hn/url-push-tracking (2026-07-22) 2 commits
- remote: find tracking branches for URL push destinations
- remote: pass repository to push tracking helper
When the push remote is specified as a URL, the fetch refspec of a
uniquely matching configured remote is now used to find and update
the remote-tracking branch (e.g., '@{push}').
Will merge to 'next'?
cf. <xmqqpl0eoniz.fsf@gitster.g>
cf. <3FE12592-B663-438C-B33E-A251AE08E146@gmail.com>
source: <pull.2358.v4.git.git.1784743738.gitgitgadget@gmail.com>
* tl/gitweb-shorten-hashes-with-modes (2026-07-17) 1 commit
- gitweb: shorten index hashes with trailing file modes
The object ID shortening and linking in the 'commitdiff' view of
'gitweb' has been corrected to work even when the index line carries
a trailing file mode.
Needs review.
source: <SA1PR10MB9977150C823C0751E53B150D5AF1C62@SA1PR10MB997715.namprd10.prod.outlook.com>
* kj/repo-info-more-path-keys (2026-07-17) 7 commits
- repo: add path.git-prefix path key
- repo: add path.grafts with absolute and relative suffix formatting
- repo: add path.index with absolute and relative suffix formatting
- repo: add path.hooks with absolute and relative suffix formatting
- repo: add path.objects with absolute and relative suffix formatting
- repo: add path.superproject-working-tree with absolute and relative suffixes
- repo: add path.toplevel with absolute and relative suffix formatting
The 'git repo info' command has been taught more keys to output
paths of various repository components (such as the working tree
root, superproject working tree, object database, etc.), supporting
both absolute and relative path formats.
Waiting for response.
cf. <845D6852-98F5-4168-82CD-90B3B476BCF5@gmail.com>
source: <20260717133015.32040-1-jayatheerthkulkarni2005@gmail.com>
* sk/userdiff-swift (2026-07-20) 1 commit
- userdiff: add support for Swift
Userdiff patterns for Swift have been added, with support for
Swift-specific constructs such as attributes, modifiers, failable
initializers, and generics.
Will merge to 'next'?
cf. <7b541cd5-bd66-4675-818d-8e23eb1c9530@kdbg.org>
source: <20260721065736.8747-1-diy2903@gmail.com>
* ps/odb-move-loose-object-writing (2026-07-17) 10 commits
- object-file: move logic to write loose objects
- object-file: move `force_object_loose()`
- object-file: force objects loose via generic interface
- object-file: fix memory leak in `force_object_loose()`
- odb: support setting mtime when writing objects
- odb: lift object existence check out of the "loose" backend
- odb: compute object hash in `odb_write_object_ext()`
- t/u-odb-inmemory: implement wrapper for writing objects
- odb: compute compat object ID in `odb_write_object_ext()`
- Merge branch 'jt/receive-pack-use-odb-transactions' into HEAD
The logic to write loose objects has been refactored and moved from
'object-file.c' to the loose backend source file 'odb/source-loose.c',
making the loose backend more self-contained. This is achieved by
first refactoring force_object_loose() to use generic ODB write
interfaces instead of loose-backend internals.
Needs review.
source: <20260717-pks-odb-move-loose-object-writing-v1-0-46446a3cb5b7@pks.im>
* pw/rebase-fixup-fixes (2026-07-17) 2 commits
- rebase: remember fixup -c after skipping fixup/squash
- rebase -i: fix counting of fixups after rebase --skip
Two bugs in how 'git rebase' handles skipped 'fixup' and 'squash'
commands have been fixed. One bug caused an incorrect commit count to
be shown in the template message when multiple commands were skipped,
and another prevented the editor from opening when the final command
in a chain containing 'fixup -c' was skipped.
Needs review.
source: <cover.1784304378.git.phillip.wood@dunelm.org.uk>
* tc/last-modified-bloom (2026-07-17) 4 commits
- last-modified: keep per-path Bloom filters for wildcard pathspecs
- last-modified: check pathspec against Bloom filter first
- revision: expose check for paths maybe changed in Bloom filter
- revision: move bloom keyvec precondition into function
The 'git last-modified' command has been optimized by using Bloom
filters. It now reuses revision walk filtering logic from 'git log'
to pre-filter commits, and maintains per-path Bloom filters even when
wildcard pathspecs are used.
Expecting a reroll.
cf. <87cxwl1lb4.fsf@emacs.iotcl.com>
source: <20260717-toon-speed-up-last-modified-v1-0-410418f18614@iotcl.com>
* hn/bisect-reset-when-found (2026-07-20) 2 commits
(merged to 'next' on 2026-07-22 at 1dc394ad9b)
+ bisect: add --reset-when-found to leave when done
+ bisect: let bisect_reset() optionally check out quietly
The 'git bisect' command has been taught a
'--reset-when-found[=<where>]' option that tells the command to
automatically run 'git bisect reset' to jump back to the original
state or to the found culprit.
Will merge to 'master'.
cf. <xmqqldb5d1d9.fsf@gitster.g>
source: <pull.2335.v3.git.git.1784538619.gitgitgadget@gmail.com>
* 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
(merged to 'next' on 2026-07-19 at b12d5d76f5)
+ submodule--helper: avoid use of %zu for now
An accidental use of the '%zu' format specifier in 'git
submodule--helper' has been corrected to use 'PRIuMAX' and cast the
value to 'uintmax_t' to avoid portability issues.
Will merge to 'master'.
source: <xmqq4ii0ko9t.fsf@gitster.g>
* 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.
Waiting for response.
cf. <xmqqzezlhgyo.fsf@gitster.g>
cf. <al4yrXXoZiHLwSvE@com-79390>
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
(merged to 'next' on 2026-07-19 at ff1b5528ba)
+ 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 'master'.
source: <935883f3-3be4-4c51-9711-5208b9ef9ca1@web.de>
* 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.
Waiting for response.
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
(merged to 'next' on 2026-07-20 at 9e38da0efc)
+ 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'.
Will merge to 'master'.
cf. <b0df688a-3b26-48f6-8b1c-98530483885e@gmail.com>
cf. <xmqqo6g54k7m.fsf@gitster.g>
source: <20260716-pks-copy-wo-the-repository-v2-1-8f5e32942929@pks.im>
* ps/refspec-wo-the-repository (2026-07-16) 3 commits
(merged to 'next' on 2026-07-20 at 31044c3fc9)
+ 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'.
Will merge to 'master'.
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.
Waiting for response.
cf. <f8050598-392f-44c9-8d66-0454740a7a12@kdbg.org>
cf. <a2676ec6-39d5-4220-8549-10a17daec668@hogyros.de>
cf. <xmqqfr1ig0hv.fsf@gitster.g>
source: <20260716-pks-reintroduce-writev-v1-0-ea9038c884bc@pks.im>
* sc/wt-status-avoid-quadratic-insertion (2026-07-18) 1 commit
(merged to 'next' on 2026-07-20 at 9330d42a4a)
+ wt-status: avoid repeated insertion for untracked paths
The enumeration of untracked and ignored files in 'git status' has
been optimized by avoiding quadratic complexity when inserting into
string lists, reducing the construction cost from O(n^2) to O(n log
n).
Will merge to 'master'.
cf. <20260718083828.GE22588@coredump.intra.peff.net>
source: <20260718081449.26747-1-sahityajb@gmail.com>
* 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>
* tn/packfile-uri-concurrency (2026-07-21) 3 commits
- fetch-pack: accept "pack" output for packfile URIs
- http: avoid concurrent appends to partial packs
- http-fetch: correct --index-pack-arg documentation
Concurrent downloads of packfiles via packfile URIs and dumb HTTP have
been made safer by avoiding concurrent appends to the staging file.
Opening the file in read-write mode and maintaining separate file
offsets prevents corruption while preserving resumability. The
'fetch-pack' command has also been updated to tolerate pre-existing
'.keep' files.
Needs review.
source: <cover.1784676106.git.tnyman@openai.com>
* rs/tempfile-wo-the-repository (2026-07-14) 5 commits
(merged to 'next' on 2026-07-22 at 968a116891)
+ 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.
Will merge to 'master'.
cf. <xmqq8q7ds3ld.fsf@gitster.g>
cf. <xmqqmrvmn6a5.fsf@gitster.g>
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>
* 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.
Ejected due to conflicts with 'pw/rebase-drop-notes-with-commit'.
Waiting for response.
cf. <690b965e-5f07-4aa4-a64c-96e60a86d73b@gmail.com>
source: <20260711-fz-autosquash-empty-v3-1-d227b63eb511@gmail.com>
* 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>
* 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>
* ps/refs-wo-the-repository (2026-07-15) 7 commits
(merged to 'next' on 2026-07-19 at 12685f410c)
+ 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 'master'.
source: <20260716-pks-refs-wo-the-repository-v3-0-db0a804e0224@pks.im>
* 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>
* 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.
cf. <xmqq33xcz2i7.fsf@gitster.g>
source: <pull.2168.git.1783359242130.gitgitgadget@gmail.com>
* 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>
* 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>
* pw/rebase-drop-notes-with-commit (2026-07-15) 9 commits
(merged to 'next' on 2026-07-20 at 5475c9f935)
+ 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.
Will merge to 'master'.
cf. <xmqqy0f5d25g.fsf@gitster.g>
source: <cover.1784128921.git.phillip.wood@dunelm.org.uk>
* 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. <xmqqpl1d56dd.fsf@gitster.g>
cf. <xmqqwlvl56vh.fsf@gitster.g>
source: <cover.1782500507.git.me@ttaylorr.com>
* 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.
Will merge to 'next'.
cf. <xmqqa4ruyhbh.fsf@gitster.g>
source: <20260714032525.1611141-1-cat@malon.dev>
* 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.
Needs review.
cf. <alR9GDNTbdjWB4dq@szeder.dev>
cf. <2d455ecf-972e-e3ce-54bc-683050c04282@gmx.de>
source: <20260713-pks-libgit-in-subdir-v4-0-696240876eb1@pks.im>
* 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-20) 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.
Will merge to 'next'.
cf. <DK1KIF2OI8IF.11188A3YEQV1C@lfurio.us>
cf. <DK1KIH6CXW0X.1U2V3GU8L6HB7@lfurio.us>
source: <pull.2337.v10.git.git.1784536024.gitgitgadget@gmail.com>
* 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>
* td/ref-filter-memoize-contains (2026-06-12) 3 commits
(merged to 'next' on 2026-07-19 at 5b640e33a1)
+ 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 'master'.
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'.
On hold, waiting for response from the author.
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-18) 13 commits
- cat-file: make remote-object-info allow-list adapt to the server
- cat-file: add remote-object-info to batch-command
- transport: add client support for object-info
- serve: advertise object-info feature
- protocol-caps: check object existence regardless of the attributes requested
- 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: use unsigned int for hash_algo variable
- fetch-pack: drop the static advertise_sid variable
- t1006: extract helper 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.
Needs review.
source: <20260718-ps-eric-work-rebase-v20-0-0c13962ac532@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 it 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-20) 4 commits
- environment: move has_symlinks into repo_config_values
- environment: move trust_executable_bit into repo_config_values
- read-cache: pass 'repo' to 'ce_mode_from_stat()'
- read-cache: remove redundant extern declarations
The 'trust_executable_bit' (coming from the 'core.filemode'
configuration) has been migrated into 'struct repo_config_values' to
tie it to a specific repository instance.
Will merge to 'next'.
cf. <alvNq8rXF/jofqUc@szeder.dev>
cf. <xmqq8q7961xe.fsf@gitster.g>
source: <20260720105335.3202013-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>
cf. <xmqq1pe0g08t.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-22) 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 into their tracked
remote-tracking branches.
Needs review.
cf. <9b9b9a2c-dd0f-44f8-b80e-565eed9a55a8@gmail.com>
cf. <xmqqik6an5t3.fsf@gitster.g>
source: <pull.2285.v20.git.git.1784704238.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 that the new branch will work with.
Waiting for response for too long, stalled.
cf. <xmqq5x37h6fj.fsf@gitster.g>
cf. <CAL71e4MiijEiM26TKJcOYT7L4pfQeMM_F2oT3U3igP-wOZm2Ag@mail.gmail.com>
source: <pull.2281.v15.git.git.1782338098.gitgitgadget@gmail.com>
* ps/shift-root-in-graph (2026-07-14) 7 commits
(merged to 'next' on 2026-07-19 at bebf13a239)
+ 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 'master'.
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
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.
cf. <xmqqse5en8wz.fsf@gitster.g>
cf. <xmqqv7al9rbj.fsf@gitster.g>
source: <pull.2149.v6.git.1783776466.gitgitgadget@gmail.com>
^ permalink raw reply
* Re: [PATCH 1/1] Extract only the message body from git commit.
From: Hardik Kumar @ 2026-07-23 7:54 UTC (permalink / raw)
To: Junio C Hamano, hardikxk; +Cc: git
In-Reply-To: <xmqqtspqn3v1.fsf@gitster.g>
Thanks for review. I will take care of the conventions moving forward.
Also if possible could you clarify the fixme (or remove it if it no
longer seems to be valid).
^ permalink raw reply
* Re: [PATCH v2] userdiff: add support for Swift
From: Johannes Sixt @ 2026-07-23 8:00 UTC (permalink / raw)
To: Junio C Hamano, Shlok Kulshreshtha
Cc: git, D . Ben Knoble, René Scharfe, Eric Sunshine,
Scott L . Burson
In-Reply-To: <xmqqmrvkw31z.fsf@gitster.g>
Am 21.07.26 um 21:33 schrieb Junio C Hamano:
> Shlok Kulshreshtha <diy2903@gmail.com> writes:
>
>> Add a built-in userdiff driver for the Swift programming language so that
>> diff hunk headers and word diffs work out of the box for ".swift" files.
>>
>> The funcname pattern is built for Swift's own declaration grammar: an
>> optional run of attributes ("@objc", "@available(iOS 13, *)", ...),
>> followed by an optional run of lowercase modifiers ("public", "static",
>> "final", ...), followed by a declaration keyword (func, class, struct,
>> enum, protocol, extension, actor, init, deinit, subscript). The keyword
>> is followed by a boundary that allows whitespace, "(" (init/subscript),
>> "?" or "!" (failable init), or "<" (generics), while still acting as a
>> word boundary so e.g. "initialize(" does not match.
>>
>> The word regex recognizes Swift identifiers, hexadecimal, octal, binary,
>> integer and floating-point literals, and the language's operators.
>>
>> Signed-off-by: Shlok Kulshreshtha <diy2903@gmail.com>
>> ---
>> v2, addressing Johannes Sixt's review of v1
>> (<2a3a73c5-5e90-44a3-bf6a-6e98ce5e5a59@kdbg.org>). Changes since v1:
>>
>> - t4018/swift-{init,failable-init,generic-subscript}: "RIGHT" now
>> appears only once, on the declaration line, so the expected header is
>> unambiguous.
>> - word regex: dropped the redundant "?" after the single-character
>> operator class. Single characters are already covered by the
>> "|[^[:space:]]" fallback that the PATTERNS macro appends, so only the
>> two-character forms need to be spelled out.
>>
>> (A couple of Hannes's other suggestions I kept as-is; I have explained
>> the reasoning in a reply to his review.)
>
> Thanks for an update.
>
> Let's wait for a few days to see if we hear more comments and
> otherwise mark the topic for 'next'.
This round looks good to me.
Acked-by: Johannes Sixt <j6t@kdbg.org>
-- Hannes
^ permalink raw reply
* Re: [PATCH v3 2/2] bisect: add --reset-when-found to leave when done
From: Johannes Sixt @ 2026-07-23 9:17 UTC (permalink / raw)
To: Harald Nordgren; +Cc: Harald Nordgren via GitGitGadget, git
In-Reply-To: <542f4b2c8065818b887437add90130d2090fa0f2.1784538619.git.gitgitgadget@gmail.com>
Am 20.07.26 um 11:10 schrieb Harald Nordgren via GitGitGadget:
> @@ -784,6 +859,10 @@ static enum bisect_error bisect_start(struct bisect_terms *terms, int argc,
> break;
> }
> }
> + if (reset_when_found != RESET_WHEN_FOUND_NONE && no_checkout) {
> + res = error(_("'--reset-when-found' cannot be used with '--no-checkout'"));
We have a boilerplate text for this kind of error that saves a translation:
res = error(_("options '%s' and '%s' cannot be used together"),
"--reset-when-found", "--no-checkout");
> + goto finish;
> + }
> pathspec_pos = i;
>
> /*
> @@ -1246,6 +1331,23 @@ static int bisect_run(struct bisect_terms *terms, int argc, const char **argv)
> if (bisect_next_check(terms, NULL))
> return BISECT_FAILED;
>
> + if (argc && !strcmp(argv[0], "--reset-when-found"))
> + reset_when_found = RESET_WHEN_FOUND_TO_ORIGINAL;
> + else if (argc && skip_prefix(argv[0], "--reset-when-found=",
> + &reset_when_found_arg)) {
> + if (parse_reset_when_found(reset_when_found_arg, &reset_when_found))
> + return BISECT_FAILED;
> + }
> +
> + if (reset_when_found != RESET_WHEN_FOUND_NONE) {
> + if (refs_ref_exists(get_main_ref_store(the_repository), "BISECT_HEAD"))
> + return error(_("'--reset-when-found' cannot be used with '--no-checkout'"));
Ditto.
> + write_file(git_path_bisect_reset_when_found(), "%s\n",
> + reset_when_found_mode_name(reset_when_found));
> + argc--;
> + argv++;
> + }
> +
> if (!argc) {
> error(_("bisect run failed: no command provided."));
> return BISECT_FAILED;
> diff --git a/t/t6030-bisect-porcelain.sh b/t/t6030-bisect-porcelain.sh
> index 081116220a..7dfb871ab9 100755
> --- a/t/t6030-bisect-porcelain.sh
> +++ b/t/t6030-bisect-porcelain.sh
> @@ -43,6 +43,38 @@ test_bisect_usage () {
> test_cmp expect actual
> }
>
> +test_bisect_state_file () {
> + test_path_is_file "$(git rev-parse --git-path "$1")"
> +}
> +
> +test_bisect_state_missing () {
> + test_path_is_missing "$(git rev-parse --git-path "$1")"
> +}
These should not use `git` in a $( ) subshell to avoid a case of "ignore
failure in upstream of pipe". Note that
local file=$(git rev-parse ...) &&
test_path...
would be wrong, too, for the same reason. But
local file
file=$(git rev-parse ...) &&
test_path...
works as desired.
> +test_expect_success '"git bisect start --reset-when-found" defaults to original' '
> + test_when_finished "git bisect reset; git checkout main" &&
Looking at other cases where more than one git command is invoked by
test_when_finished, it seems that they are chained with '&&'. `git grepc
"&& git checkout main"` does find a few hits.
> + git checkout main &&
> + bisect_start_and_finish --reset-when-found &&
> + test "$HASH4" = "$(git rev-parse HEAD)" &&
> + test main = "$(git branch --show-current)" &&
> + test_bisect_state_missing BISECT_START &&
> +
> + bisect_start_and_finish --reset-when-found=original &&
> + test "$HASH4" = "$(git rev-parse HEAD)" &&
> + test main = "$(git branch --show-current)" &&
> + test_bisect_state_missing BISECT_START
> +'
More cases of `git` in a subshell above and below. I notice that you are
mimicking existing practice in this file. I'm torn whether to change
this or not. After all, there are also a lot of cases in the file that
uses the correct pattern where the subshell is in a variable assignment.
> +
> +test_expect_success '"git bisect start --reset-when-found=found" leaves first bad checked out' '
> + test_when_finished "git bisect reset; git checkout main" &&
> + bisect_start_and_finish --reset-when-found=found &&
> + test "$HASH3" = "$(git rev-parse HEAD)" &&
> + test_bisect_state_missing BISECT_START
> +'
> +
> +test_expect_success '"git bisect run --reset-when-found" defaults to original' '
> + test_when_finished "git bisect reset; git checkout main" &&
> + bisect_run_reset_when_found --reset-when-found &&
> + test "$HASH4" = "$(git rev-parse HEAD)" &&
> + test main = "$(git branch --show-current)" &&
> + test_bisect_state_missing BISECT_START
> +'
> +
> +test_expect_success '"git bisect run --reset-when-found=found" leaves first bad checked out' '
> + test_when_finished "git bisect reset; git checkout main" &&
> + bisect_run_reset_when_found --reset-when-found=found &&
> + test "$HASH3" = "$(git rev-parse HEAD)" &&
> + test_bisect_state_missing BISECT_START
> +'
-- Hannes
^ permalink raw reply
* [PATCH v2 0/2] mv: report missing destination leading directory
From: Lucas Zamboni Orioli via GitGitGadget @ 2026-07-23 13:13 UTC (permalink / raw)
To: git; +Cc: Ben Knoble, Lucas Zamboni Orioli
In-Reply-To: <pull.2356.git.git.1784125963694.gitgitgadget@gmail.com>
Changes since v1:
* altered the error message to include both source and destination as
suggested by Ben Knoble
Lucas Zamboni Orioli (2):
mv: name both source and destination when rename fails
mv: check for missing destination directory before renaming
builtin/mv.c | 23 ++++++++++++++++++++++-
t/t7001-mv.sh | 14 ++++++++++++++
2 files changed, 36 insertions(+), 1 deletion(-)
base-commit: 9a0c4701dcd5725c4184599322b52933ff5005ca
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2356%2FZamboniL%2Fmv-detect-non-existing-target-folder-v2
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2356/ZamboniL/mv-detect-non-existing-target-folder-v2
Pull-Request: https://github.com/git/git/pull/2356
Range-diff vs v1:
-: ---------- > 1: 0d67da588b mv: name both source and destination when rename fails
1: 692f44456f ! 2: 1a790e0016 mv: report missing destination leading directory
@@ Metadata
Author: Lucas Zamboni Orioli <lucaszam0@gmail.com>
## Commit message ##
- mv: report missing destination leading directory
+ mv: check for missing destination directory before renaming
- When moving a file to a destination whose leading directory does not
- exist, "git mv" fails at the rename(2) syscall with ENOENT. Because
- the error is reported via die_errno() using only the source path:
+ Moving a file into a directory that does not exist fails at rename(2)
+ with ENOENT. The checking phase already rejects a missing destination
+ directory when the destination ends in a slash, but a destination that
+ names a file inside a non-existent directory is not caught and only
+ fails later at the syscall. As a consequence "git mv -n" does not
+ detect the problem either: the dry run never reaches rename(2) and
+ reports a move that would not actually succeed.
- fatal: renaming 'src' failed: No such file or directory
+ Detect this during the checking phase. For entries that will be renamed
+ on disk, stat the destination's leading directory and, if it is
+ missing, fail with the existing "destination directory does not exist"
+ message. Guard the check with the same condition under which rename(2)
+ is invoked, so that directory moves, whose child entries are expanded
+ to paths under a not-yet-created directory, and sparse or out-of-cone
+ destinations, which are not written to the worktree, are not flagged
+ incorrectly.
- the message misleadingly blames the source, even though it is the
- destination's parent directory that is missing. A user who runs
+ This is a best-effort diagnostic rather than a guarantee: the
+ destination directory can still disappear between the check and the
+ rename(2). It fixes the common case and, unlike the syscall path,
+ lets "git mv -n" report the failure.
- git mv a/file b/does-not-exist/file
-
- is told the problem is with 'a/file', which exists, giving no hint
- that 'b/does-not-exist/' needs to be created first.
-
- The checking phase already rejects a missing destination directory
- when the destination ends in a slash, but a destination that names a
- file inside a non-existent directory is not caught and only fails
- later at rename(2). As a result "git mv -n" also fails to detect the
- problem, since the dry run never reaches the syscall and reports a
- move that would not actually succeed.
-
- Detect this during the checking phase instead: for entries that will
- be renamed on disk, stat the destination's leading directory and, if
- it is missing, fail with the existing "destination directory does not
- exist" message. Guard the check with the same condition under which
- rename(2) is invoked so that directory moves, whose child entries are
- expanded to paths under a not-yet-created directory, and sparse or
- out-of-cone destinations, which are not written to the worktree, are
- not flagged incorrectly.
-
- This gives a clear message and lets "git mv -n" report the failure.
+ Add tests covering both the error path and the dry-run detection.
Signed-off-by: Lucas Zamboni Orioli <lucaszam0@gmail.com>
--
gitgitgadget
^ permalink raw reply
* [PATCH v2 1/2] mv: name both source and destination when rename fails
From: Lucas Zamboni Orioli via GitGitGadget @ 2026-07-23 13:13 UTC (permalink / raw)
To: git; +Cc: Ben Knoble, Lucas Zamboni Orioli, Lucas Zamboni Orioli
In-Reply-To: <pull.2356.v2.git.git.1784812390.gitgitgadget@gmail.com>
From: Lucas Zamboni Orioli <lucaszam0@gmail.com>
When "git mv" fails at the rename(2) syscall, the error is reported
with die_errno() using only the source path:
fatal: renaming 'src' failed: No such file or directory
rename(2) returns ENOENT both when the source does not exist and when
a directory component of the destination does not exist, and errno
does not distinguish the two. Reporting only the source therefore
misleads the user in the latter case: for
git mv a/file b/no-such-dir/file
the message blames 'a/file', which exists, and gives no hint that
'b/no-such-dir/' is the missing part.
Inspecting the paths again after the failure to determine which one is
at fault would be racy, since either could appear or disappear between
the rename(2) and the follow-up check. Instead, simply name both the
source and the destination in the message and let the reader see which
one is wrong:
fatal: renaming 'a/file' to 'b/no-such-dir/file' failed:
No such file or directory
Signed-off-by: Lucas Zamboni Orioli <lucaszam0@gmail.com>
---
builtin/mv.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/builtin/mv.c b/builtin/mv.c
index a82fc97a19..35e504484a 100644
--- a/builtin/mv.c
+++ b/builtin/mv.c
@@ -549,7 +549,7 @@ remove_entry:
rename(src, dst) < 0) {
if (ignore_errors)
continue;
- die_errno(_("renaming '%s' failed"), src);
+ die_errno(_("renaming '%s' to '%s' failed"), src, dst);
}
if (submodule_gitfiles[i]) {
if (!update_path_in_gitmodules(src, dst))
--
gitgitgadget
^ permalink raw reply related
* [PATCH v2 2/2] mv: check for missing destination directory before renaming
From: Lucas Zamboni Orioli via GitGitGadget @ 2026-07-23 13:13 UTC (permalink / raw)
To: git; +Cc: Ben Knoble, Lucas Zamboni Orioli, Lucas Zamboni Orioli
In-Reply-To: <pull.2356.v2.git.git.1784812390.gitgitgadget@gmail.com>
From: Lucas Zamboni Orioli <lucaszam0@gmail.com>
Moving a file into a directory that does not exist fails at rename(2)
with ENOENT. The checking phase already rejects a missing destination
directory when the destination ends in a slash, but a destination that
names a file inside a non-existent directory is not caught and only
fails later at the syscall. As a consequence "git mv -n" does not
detect the problem either: the dry run never reaches rename(2) and
reports a move that would not actually succeed.
Detect this during the checking phase. For entries that will be renamed
on disk, stat the destination's leading directory and, if it is
missing, fail with the existing "destination directory does not exist"
message. Guard the check with the same condition under which rename(2)
is invoked, so that directory moves, whose child entries are expanded
to paths under a not-yet-created directory, and sparse or out-of-cone
destinations, which are not written to the worktree, are not flagged
incorrectly.
This is a best-effort diagnostic rather than a guarantee: the
destination directory can still disappear between the check and the
rename(2). It fixes the common case and, unlike the syscall path,
lets "git mv -n" report the failure.
Add tests covering both the error path and the dry-run detection.
Signed-off-by: Lucas Zamboni Orioli <lucaszam0@gmail.com>
---
builtin/mv.c | 21 +++++++++++++++++++++
t/t7001-mv.sh | 14 ++++++++++++++
2 files changed, 35 insertions(+)
diff --git a/builtin/mv.c b/builtin/mv.c
index 35e504484a..eb59fe0f31 100644
--- a/builtin/mv.c
+++ b/builtin/mv.c
@@ -444,6 +444,27 @@ dir_check:
goto act_on_entry;
}
+ /*
+ * If we are going to move SRC to DST on disk, DST's leading
+ * directories must already exist.
+ */
+ if (!(modes[i] & (INDEX | SPARSE | SKIP_WORKTREE_DIR)) &&
+ !(dst_mode & (SKIP_WORKTREE_DIR | SPARSE))) {
+ char *dst_dir = xstrdup(dst);
+ char *slash = strrchr(dst_dir, '/');
+
+ if (slash) {
+ struct stat dir_st;
+ *slash = '\0';
+ if (lstat(dst_dir, &dir_st) < 0 && errno == ENOENT) {
+ free(dst_dir);
+ bad = _("destination directory does not exist");
+ goto act_on_entry;
+ }
+ }
+ free(dst_dir);
+ }
+
if (ignore_sparse &&
(dst_mode & (SKIP_WORKTREE_DIR | SPARSE)) &&
index_entry_exists(the_repository->index, dst, strlen(dst))) {
diff --git a/t/t7001-mv.sh b/t/t7001-mv.sh
index 7cf4aa5ba1..2d8a98d8b0 100755
--- a/t/t7001-mv.sh
+++ b/t/t7001-mv.sh
@@ -114,6 +114,20 @@ test_expect_success 'clean up' '
git reset --hard
'
+test_expect_success 'moving to non-existent destination parent directory' '
+ git reset --hard &&
+ mkdir -p from &&
+ echo content >from/file &&
+ git add from/file &&
+ test_must_fail git mv from/file no-such-dir/file 2>actual &&
+ test_grep "destination directory does not exist" actual
+'
+
+test_expect_success 'mv --dry-run detects non-existent destination parent directory' '
+ test_must_fail git mv -n from/file no-such-dir/file 2>actual &&
+ test_grep "destination directory does not exist" actual
+'
+
test_expect_success 'moving to existing untracked target with trailing slash' '
mkdir path1 &&
git mv path0/ path1/ &&
--
gitgitgadget
^ permalink raw reply related
* Re: [PATCH v3 2/2] bisect: add --reset-when-found to leave when done
From: Junio C Hamano @ 2026-07-23 14:27 UTC (permalink / raw)
To: Johannes Sixt; +Cc: Harald Nordgren, Harald Nordgren via GitGitGadget, git
In-Reply-To: <faa22968-54ac-4e4f-8324-3326ffb00c5b@kdbg.org>
Johannes Sixt <j6t@kdbg.org> writes:
> Am 20.07.26 um 11:10 schrieb Harald Nordgren via GitGitGadget:
>> @@ -784,6 +859,10 @@ static enum bisect_error bisect_start(struct bisect_terms *terms, int argc,
>> break;
>> }
>> }
>> + if (reset_when_found != RESET_WHEN_FOUND_NONE && no_checkout) {
>> + res = error(_("'--reset-when-found' cannot be used with '--no-checkout'"));
>
> We have a boilerplate text for this kind of error that saves a translation:
>
> res = error(_("options '%s' and '%s' cannot be used together"),
> "--reset-when-found", "--no-checkout");
I wonder if we should add a set of helper functions
that return an error instead of dying, to complement
the die_for_incompatible_optX() family of functions.
Are there many other places that would benefit from this?
Thanks.
^ permalink raw reply
* [PATCH resend] builtin/clone: fix segfault when using --revision on some servers
From: Adrian Friedli @ 2026-07-23 14:43 UTC (permalink / raw)
To: git; +Cc: Adrian Friedli
Fix a segfault when a server advertises more refs than requested when
using the --revision argument.
Signed-off-by: Adrian Friedli <adrian.friedli@mt.com>
---
The segfault can be reproduced by e.g.
git clone --revision=refs/heads/main \
https://dev.azure.com/public-git/sample/_git/sample
In the good case the server respects
`transport_ls_refs_options.ref_prefixes` and in `cmd_clone()` the linked
list `refs` returned by `transport_get_remote_refs()` only contains a
single item, which is the ref requested with the --revision argument.
Both `remote_head` returned by `find_ref_by_name()` and
`remote_head_points_at` returned by `guess_remote_head()` are NULL. The
guard in `update_remote_refs()` skips a the affected code because
`remote_head_points_at` is NULL.
In the bad case the server ignores
`transport_ls_refs_options.ref_prefixes` and in `cmd_clone()` the linked
list `refs` returned by `transport_get_remote_refs()` contains many
items, amongst others "HEAD". `remote_head` returned by
`find_ref_by_name()` is not NULL and `remote_head_points_at` returned by
`guess_remote_head()` is not NULL but its field `peer_ref` is NULL.
Because `remote_head_points_at` is not NULL the guard in
`update_remote_refs()` does not skip the affected code and
`remote_head_points_at->peer_ref->name` is accessed, which causes a
segfault later on.
builtin/clone.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/builtin/clone.c b/builtin/clone.c
index 9d08cd8722..bd0c6f5d56 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -557,7 +557,7 @@ static void update_remote_refs(const struct ref *refs,
write_followtags(refs, msg);
}
- if (remote_head_points_at && !option_bare) {
+ if (remote_head_points_at && remote_head_points_at->peer_ref && !option_bare) {
struct strbuf head_ref = STRBUF_INIT;
strbuf_addstr(&head_ref, branch_top);
strbuf_addstr(&head_ref, "HEAD");
--
2.55.0.379.g54b6532b97
^ permalink raw reply related
* Re: [PATCH resend] builtin/clone: fix segfault when using --revision on some servers
From: Junio C Hamano @ 2026-07-23 15:43 UTC (permalink / raw)
To: Adrian Friedli; +Cc: git
In-Reply-To: <20260723144318.69007-1-adrian.friedli@mt.com>
Adrian Friedli <adrian.friedli@mt.com> writes:
> Fix a segfault when a server advertises more refs than requested when
> using the --revision argument.
>
> Signed-off-by: Adrian Friedli <adrian.friedli@mt.com>
> ---
> The segfault can be reproduced by e.g.
>
> git clone --revision=refs/heads/main \
> https://dev.azure.com/public-git/sample/_git/sample
The following two paragraphs' worth of explanation deserves to be in
the log message:
> In the good case the server respects
> `transport_ls_refs_options.ref_prefixes` and in `cmd_clone()` the linked
> list `refs` returned by `transport_get_remote_refs()` only contains a
> single item, which is the ref requested with the --revision argument.
> Both `remote_head` returned by `find_ref_by_name()` and
> `remote_head_points_at` returned by `guess_remote_head()` are NULL. The
> guard in `update_remote_refs()` skips a the affected code because
> `remote_head_points_at` is NULL.
>
> In the bad case the server ignores
> `transport_ls_refs_options.ref_prefixes` and in `cmd_clone()` the linked
> list `refs` returned by `transport_get_remote_refs()` contains many
> items, amongst others "HEAD". `remote_head` returned by
> `find_ref_by_name()` is not NULL and `remote_head_points_at` returned by
> `guess_remote_head()` is not NULL but its field `peer_ref` is NULL.
> Because `remote_head_points_at` is not NULL the guard in
> `update_remote_refs()` does not skip the affected code and
> `remote_head_points_at->peer_ref->name` is accessed, which causes a
> segfault later on.
Usually, our commit log message begins with an observation of the
current behavior. We would probably start the log message like
this:
Servers are expected to refrain from advertising excess refs,
honoring transport_ls_refs_options.ref_prefixes, when
$ git clone --revision=refs/heads/main $URL
contacts them, but when talking to a server that does not (e.g.,
<<the URL of the problematic repository goes here>>), the client
segfaults.
and the above two paragraphs would flow perfectly after such an
introduction. They clearly explain how the client gets confused by
unusual server behavior.
The above write-up makes me wonder if there is a valid case where
guess_remote_head() should return a non-NULL 'struct ref *' whose
'.peer_ref' member is NULL. Unless a non-NULL head that is a symref
is given, in which case we firmly know where their 'HEAD' points,
the function seems to pick a randomly guessed ref out of the given
list of refs (supplied to its second parameter) and return a copy of
it. However, there does not seem to be any check to ensure that it
picks a ref with its '.peer_ref' member set. This may break other
code paths that consume the value returned from guess_remote_head()
in the exact same way, no?
I do not know offhand if that is the case, but if it is always wrong
for guess_remote_head() to return a guessed ref with NULL in its
'.peer_ref' member, perhaps that would be a better location to make
this fix. What do you think?
In any case, can we also add a test to prevent this fix from
regressing in the future?
Thanks.
> builtin/clone.c | 2 +-
> 1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/builtin/clone.c b/builtin/clone.c
> index 9d08cd8722..bd0c6f5d56 100644
> --- a/builtin/clone.c
> +++ b/builtin/clone.c
> @@ -557,7 +557,7 @@ static void update_remote_refs(const struct ref *refs,
> write_followtags(refs, msg);
> }
>
> - if (remote_head_points_at && !option_bare) {
> + if (remote_head_points_at && remote_head_points_at->peer_ref && !option_bare) {
> struct strbuf head_ref = STRBUF_INIT;
> strbuf_addstr(&head_ref, branch_top);
> strbuf_addstr(&head_ref, "HEAD");
^ permalink raw reply
* Re: [PATCH resend] builtin/clone: fix segfault when using --revision on some servers
From: Junio C Hamano @ 2026-07-23 16:10 UTC (permalink / raw)
To: Adrian Friedli; +Cc: git
In-Reply-To: <xmqqmrvhlnjv.fsf@gitster.g>
Junio C Hamano <gitster@pobox.com> writes:
> I do not know offhand if that is the case, but if it is always wrong
> for guess_remote_head() to return a guessed ref with NULL in its
> '.peer_ref' member, perhaps that would be a better location to make
> this fix. What do you think?
Never mind, scratch that part. If we are fetching without storing the
result in any remote-tracking ref, '.peer_ref' is legitimately NULL,
and if we are storing, '.peer_ref' names the local ref where we store
the result. This should not affect our guess as to which of their
branches may be pointed to by their 'HEAD'.
So this patch fixes the issue in the right place. It would still be
nice to have a new test to prevent future regressions, though.
Thanks.
^ permalink raw reply
* [GSoC] [Blog] week 8: Improving the new git repo command
From: K Jayatheerth @ 2026-07-23 16:13 UTC (permalink / raw)
To: GIT Mailing-list, Justin Tobler, Lucas Seiki Oshiro
In-Reply-To: <CA+rGoLdMnLfVF91hP3c5bdLnAv9ViW2r9pR0yejK2kQNNWSBUw@mail.gmail.com>
Hi!
My Week 8 GSoC blog is live!
https://jayatheerth.com/#/blogs/gsoc/week-8
Feel free to give it a read and share any feedback ; )
Regards,
- K Jayatheerth
^ permalink raw reply
* Re: [PATCH] http: add a config to limit the connection time
From: Junio C Hamano @ 2026-07-23 16:47 UTC (permalink / raw)
To: GalaxySnail via GitGitGadget; +Cc: git, GalaxySnail
In-Reply-To: <pull.2362.git.git.1784798733557.gitgitgadget@gmail.com>
"GalaxySnail via GitGitGadget" <gitgitgadget@gmail.com> writes:
> From: GalaxySnail <me@glxys.nl>
>
> By default, libcurl uses a 300 seconds timeout for the connection phase,
> which is too long for some use cases.
Can you elaborate a bit more on the use cases in which you want to
try connecting to an unreachable host yet want to give up on it very
fast?
> Add http.connecttimeoutms and GIT_HTTP_CONNECT_TIMEOUT_MS to specify
> timeout in milliseconds for the connection phase. Both of them call
> CURLOPT_CONNECTTIMEOUT_MS internally.
>
> Signed-off-by: GalaxySnail <me@glxys.nl>
Documentation/SubmittingPatches:[[real-name]] applies here.
> Documentation/config/http.adoc | 7 ++++
> http.c | 11 ++++++
> t/meson.build | 1 +
> t/t5585-http-connect-timeout.sh | 60 +++++++++++++++++++++++++++++++++
> 4 files changed, 79 insertions(+)
> create mode 100755 t/t5585-http-connect-timeout.sh
>
> diff --git a/Documentation/config/http.adoc b/Documentation/config/http.adoc
> index 792a71b413..a4f7afa61e 100644
> --- a/Documentation/config/http.adoc
> +++ b/Documentation/config/http.adoc
> @@ -300,6 +300,13 @@ for most push problems, but can increase memory consumption
> significantly since the entire buffer is allocated even for small
> pushes.
>
> +http.connectTimeoutMS::
> + Maximum time in milliseconds that you allow the connection phase
> + to take. The connection phase includes DNS lookup and subsequent
> + TCP, TLS or QUIC handshakes.
> + Can be overridden by the `GIT_HTTP_CONNECT_TIMEOUT_MS`
> + environment variable.
Once a knob is provided, users will want to know what value is
used when unspecified, so they can gauge what a reasonable value to
set would be.
> diff --git a/http.c b/http.c
> index caccf2108e..befe9ea8a0 100644
> --- a/http.c
> +++ b/http.c
> @@ -68,6 +68,7 @@ static char *ssl_capath;
> static char *curl_no_proxy;
> static char *ssl_pinnedkey;
> static char *ssl_cainfo;
> +static long curl_connect_timeout_ms = -1;
> static long curl_low_speed_limit = -1;
> static long curl_low_speed_time = -1;
> static int curl_ftp_no_epsv;
> @@ -450,6 +451,10 @@ static int http_options(const char *var, const char *value,
> max_requests = git_config_int(var, value, ctx->kvi);
> return 0;
> }
> + if (!strcmp("http.connecttimeoutms", var)) {
> + curl_connect_timeout_ms = git_config_int(var, value, ctx->kvi);
> + return 0;
> + }
We could set it to -1 if we wanted to, and behave as if no
configuration variable were given. That may be reasonable, but it
should be documented.
> @@ -1215,6 +1220,10 @@ static CURL *get_curl_handle(void)
> curl_easy_setopt(result, CURLOPT_PROXY_CAINFO, http_proxy_ssl_ca_info);
> }
>
> + if (curl_connect_timeout_ms > 0)
> + curl_easy_setopt(result, CURLOPT_CONNECTTIMEOUT_MS,
> + curl_connect_timeout_ms);
This code silently ignores setting the configuration variable to 0.
To the cURL library, however, passing a value of 0 to
CURLOPT_CONNECTTIMEOUT_MS signals that it should use the default
value (300s).
Perhaps we should tweak the above to
if (0 <= curlopt_connecttimeout_ms)
curl_easy_setopt(result, CURLOPT_CONNECTTIMEOUT_MS,
curl_connect_timeout_ms);
and then document what 0 means.
> @@ -1474,6 +1483,8 @@ void http_init(struct remote *remote, const char *url, int proactive_auth)
>
> set_from_env(&user_agent, "GIT_HTTP_USER_AGENT");
>
> + set_long_from_env(&curl_connect_timeout_ms, "GIT_HTTP_CONNECT_TIMEOUT_MS");
> +
> set_long_from_env(&curl_low_speed_limit, "GIT_HTTP_LOW_SPEED_LIMIT");
> set_long_from_env(&curl_low_speed_time, "GIT_HTTP_LOW_SPEED_TIME");
This, along with other environment variables, is processed after
repo_config() collects configured values by triggering the
http_options() callback, so the environment overrides the configured
value, as expected.
> diff --git a/t/t5585-http-connect-timeout.sh b/t/t5585-http-connect-timeout.sh
> new file mode 100755
> index 0000000000..7363e23bfe
> --- /dev/null
> +++ b/t/t5585-http-connect-timeout.sh
> @@ -0,0 +1,60 @@
> +#!/bin/sh
> +
> +test_description='test http.connecttimeoutms and GIT_HTTP_CONNECT_TIMEOUT_MS'
> +
> +. ./test-lib.sh
> +. "$TEST_DIRECTORY"/lib-httpd.sh
> +start_httpd
What are we testing with this new script, really?
As far as I can see, nobody is sitting next to the running test
with a stopwatch to ensure that the client times out as specified. Should
we really consume a limited shared resource, the four-digit test
number, for this instead of adding a few "not a number (should fail
to parse)" tests to existing http tests?
Thanks.
^ permalink raw reply
* Re: [PATCH v2 1/2] mv: name both source and destination when rename fails
From: Junio C Hamano @ 2026-07-23 17:36 UTC (permalink / raw)
To: Lucas Zamboni Orioli via GitGitGadget
Cc: git, Ben Knoble, Lucas Zamboni Orioli
In-Reply-To: <0d67da588bc86c5257ce366903ae58e171159b8b.1784812390.git.gitgitgadget@gmail.com>
"Lucas Zamboni Orioli via GitGitGadget" <gitgitgadget@gmail.com>
writes:
> From: Lucas Zamboni Orioli <lucaszam0@gmail.com>
>
> When "git mv" fails at the rename(2) syscall, the error is reported
> with die_errno() using only the source path:
>
> fatal: renaming 'src' failed: No such file or directory
>
> rename(2) returns ENOENT both when the source does not exist and when
> a directory component of the destination does not exist, and errno
> does not distinguish the two. Reporting only the source therefore
> misleads the user in the latter case: for
>
> git mv a/file b/no-such-dir/file
>
> the message blames 'a/file', which exists, and gives no hint that
> 'b/no-such-dir/' is the missing part.
>
> Inspecting the paths again after the failure to determine which one is
> at fault would be racy, since either could appear or disappear between
> the rename(2) and the follow-up check. Instead, simply name both the
> source and the destination in the message and let the reader see which
> one is wrong:
>
> fatal: renaming 'a/file' to 'b/no-such-dir/file' failed:
> No such file or directory
>
> Signed-off-by: Lucas Zamboni Orioli <lucaszam0@gmail.com>
> ---
> builtin/mv.c | 2 +-
> 1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/builtin/mv.c b/builtin/mv.c
> index a82fc97a19..35e504484a 100644
> --- a/builtin/mv.c
> +++ b/builtin/mv.c
> @@ -549,7 +549,7 @@ remove_entry:
> rename(src, dst) < 0) {
> if (ignore_errors)
> continue;
> - die_errno(_("renaming '%s' failed"), src);
> + die_errno(_("renaming '%s' to '%s' failed"), src, dst);
> }
> if (submodule_gitfiles[i]) {
> if (!update_path_in_gitmodules(src, dst))
Makes sense.
^ permalink raw reply
* Re: [PATCH v2 2/2] mv: check for missing destination directory before renaming
From: Junio C Hamano @ 2026-07-23 17:42 UTC (permalink / raw)
To: Lucas Zamboni Orioli via GitGitGadget
Cc: git, Ben Knoble, Lucas Zamboni Orioli
In-Reply-To: <1a790e001610d3324ec45d86ac67ca5720678cb8.1784812390.git.gitgitgadget@gmail.com>
"Lucas Zamboni Orioli via GitGitGadget" <gitgitgadget@gmail.com>
writes:
> This is a best-effort diagnostic rather than a guarantee: the
> destination directory can still disappear between the check and the
> rename(2). It fixes the common case and, unlike the syscall path,
> lets "git mv -n" report the failure.
If "can still disappear" is because we are not taking into account a
move that we are scheduled to make, then that is not very nice, but
as long as it is *not* our making (in other words, somebody else may
actively interferring with the mv we are trying to perform), I think
this is OK. It is the best we can do.
> Add tests covering both the error path and the dry-run detection.
>
> Signed-off-by: Lucas Zamboni Orioli <lucaszam0@gmail.com>
> ---
> builtin/mv.c | 21 +++++++++++++++++++++
> t/t7001-mv.sh | 14 ++++++++++++++
> 2 files changed, 35 insertions(+)
>
> diff --git a/builtin/mv.c b/builtin/mv.c
> index 35e504484a..eb59fe0f31 100644
> --- a/builtin/mv.c
> +++ b/builtin/mv.c
> @@ -444,6 +444,27 @@ dir_check:
> goto act_on_entry;
> }
>
> + /*
> + * If we are going to move SRC to DST on disk, DST's leading
> + * directories must already exist.
> + */
> + if (!(modes[i] & (INDEX | SPARSE | SKIP_WORKTREE_DIR)) &&
> + !(dst_mode & (SKIP_WORKTREE_DIR | SPARSE))) {
> + char *dst_dir = xstrdup(dst);
> + char *slash = strrchr(dst_dir, '/');
> +
> + if (slash) {
> + struct stat dir_st;
> + *slash = '\0';
> + if (lstat(dst_dir, &dir_st) < 0 && errno == ENOENT) {
> + free(dst_dir);
> + bad = _("destination directory does not exist");
> + goto act_on_entry;
> + }
> + }
> + free(dst_dir);
> + }
Horrible. Please fix this overly deep indentation.
> diff --git a/t/t7001-mv.sh b/t/t7001-mv.sh
> index 7cf4aa5ba1..2d8a98d8b0 100755
> --- a/t/t7001-mv.sh
> +++ b/t/t7001-mv.sh
> @@ -114,6 +114,20 @@ test_expect_success 'clean up' '
> git reset --hard
> '
>
> +test_expect_success 'moving to non-existent destination parent directory' '
> + git reset --hard &&
> + mkdir -p from &&
> + echo content >from/file &&
> + git add from/file &&
> + test_must_fail git mv from/file no-such-dir/file 2>actual &&
> + test_grep "destination directory does not exist" actual
> +'
> +
> +test_expect_success 'mv --dry-run detects non-existent destination parent directory' '
> + test_must_fail git mv -n from/file no-such-dir/file 2>actual &&
> + test_grep "destination directory does not exist" actual
> +'
> +
> test_expect_success 'moving to existing untracked target with trailing slash' '
> mkdir path1 &&
> git mv path0/ path1/ &&
^ permalink raw reply
* Re: [PATCH v2 2/2] mv: check for missing destination directory before renaming
From: Junio C Hamano @ 2026-07-23 18:30 UTC (permalink / raw)
To: Lucas Zamboni Orioli via GitGitGadget
Cc: git, Ben Knoble, Lucas Zamboni Orioli
In-Reply-To: <1a790e001610d3324ec45d86ac67ca5720678cb8.1784812390.git.gitgitgadget@gmail.com>
"Lucas Zamboni Orioli via GitGitGadget" <gitgitgadget@gmail.com>
writes:
> + /*
> + * If we are going to move SRC to DST on disk, DST's leading
> + * directories must already exist.
> + */
/*
* Our multi-line comment is formatted like this. The
* asterisks align vertically.
*/
> + if (!(modes[i] & (INDEX | SPARSE | SKIP_WORKTREE_DIR)) &&
> + !(dst_mode & (SKIP_WORKTREE_DIR | SPARSE))) {
> + char *dst_dir = xstrdup(dst);
> + char *slash = strrchr(dst_dir, '/');
> +
> + if (slash) {
> + struct stat dir_st;
> + *slash = '\0';
> + if (lstat(dst_dir, &dir_st) < 0 && errno == ENOENT) {
> + free(dst_dir);
> + bad = _("destination directory does not exist");
> + goto act_on_entry;
> + }
> + }
> + free(dst_dir);
> + }
lstat() can succeed and 'dir_st' may indicate something other than a
directory (for example, a symbolic link or a regular file).
Alternatively, it can fail with ENOTDIR when, for example, 'dst_dir'
is 'a/b/c' and 'a/b' is a file rather than a directory.
Both cases will cause 'git mv' into a path assumed to be a directory
to fail. Shouldn't we handle these conditions as well?
^ permalink raw reply
* Re: [PATCH v3 1/2] rebase: skip branch symref aliases
From: Phillip Wood @ 2026-07-23 18:58 UTC (permalink / raw)
To: Son Luong Ngoc via GitGitGadget, git; +Cc: Kristoffer Haugsbakk, Son Luong Ngoc
In-Reply-To: <b9a01e9141d580606527cb1a658c7c72710fb013.1784708107.git.gitgitgadget@gmail.com>
On 22/07/2026 09:15, Son Luong Ngoc via GitGitGadget wrote:
> From: Son Luong Ngoc <sluongng@gmail.com>
>
> git rebase --update-refs can finish rewriting the current branch and
> then fail while updating a local branch that is a symbolic ref. This can
> happen during a default-branch rename where refs/heads/main points at
> refs/heads/master while users migrate.
>
> The problem is a partially applied ref update: the main rebase has
> already succeeded when the later ref update fails.
>
> The sequencer queues updates from local branch decorations. Commit
> 106b6885c7 (rebase: ignore non-branch update-refs) filters out
> decorations such as HEAD and tags. A branch symref is still a local
> branch decoration, but refs_update_ref() dereferences it, so an alias to
> another branch duplicates the concrete branch update.
>
> Resolve local branch decorations before queuing them. Skip symrefs whose
> targets are under refs/heads/ so that only the concrete branch update is
> queued. Keep an owned copy of the resolved HEAD and skip the current
> branch before checked-out handling so later ref resolution cannot change
> the comparison.
>
> This prevents a successful rebase from being followed by a failed,
> partially applied ref update while preserving each alias as a symref.
Thanks for re-rolling I'm pretty sure the logic is sound now but I'm a
bit confused by a couple of things - see my comments below.
> Signed-off-by: Son Luong Ngoc <sluongng@gmail.com>
> ---
> sequencer.c | 44 +++++++++++++++++++++++++----------
> t/t3400-rebase.sh | 2 +-
> t/t3404-rebase-interactive.sh | 16 +++++++++++++
> 3 files changed, 49 insertions(+), 13 deletions(-)
>
> diff --git a/sequencer.c b/sequencer.c
> index 1355a99a09..63aba60a08 100644
> --- a/sequencer.c
> +++ b/sequencer.c
> @@ -6465,32 +6465,50 @@ static int add_decorations_to_list(const struct commit *commit,
> struct todo_add_branch_context *ctx)
> {
> const struct name_decoration *decoration = get_name_decoration(&commit->object);
> - const char *head_ref = refs_resolve_ref_unsafe(get_main_ref_store(the_repository),
> - "HEAD",
> - RESOLVE_REF_READING,
> - NULL,
> - NULL);
> + struct ref_store *refs = get_main_ref_store(the_repository);
> + char *head_ref = refs_resolve_refdup(refs, "HEAD",
> + RESOLVE_REF_READING,
> + NULL, NULL);
>
> while (decoration) {
> struct todo_item *item;
> const char *path;
> + char *resolved_ref;
> + int flags = 0;
> size_t base_offset = ctx->buf->len;
>
> /*
> - * If the branch is the current HEAD, then it will be
> - * updated by the default rebase behavior.
> - * Exclude it from the list of refs to update,
> - * as well as any non-branch decorations.
> * Non-branch decorations may be present if the pretty format
> * includes "%d", which would have loaded all refs
> * into the global decoration table.
> */
> - if ((head_ref && !strcmp(head_ref, decoration->name)) ||
> - (decoration->type != DECORATION_REF_LOCAL)) {
> + if (decoration->type != DECORATION_REF_LOCAL) {
> + decoration = decoration->next;
> + continue;
> + }
It would be nice to have a comment here explaining what we're doing.
Also I don't think we need to copy the refname so it would be more
efficient to use refs_resolve_ref_unsafe().
> + resolved_ref = refs_resolve_refdup(refs, decoration->name,
> + RESOLVE_REF_READING,
> + NULL, &flags);
> + if (resolved_ref && (flags & REF_ISSYMREF) &&
> + starts_with(resolved_ref, "refs/heads/")) {
> + free(resolved_ref);
> + decoration = decoration->next;
> + continue;
> + }
We skip any symbolic refs that point to another branch which is good.
> + /*
> + * If the branch is the current HEAD, then it will be
> + * updated by the default rebase behavior.
> + */
> + if (head_ref && !strcmp(head_ref, decoration->name)) {
> + free(resolved_ref);
> decoration = decoration->next;
> continue;
> }
Then we check to see if the decoration matches HEAD which we used to do
above - I'm not clear why we have moved this check.
> + path = branch_checked_out(decoration->name);
> +
This belongs in the next patch I think.
> diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh
> index e62e07b894..1a02f6546b 100755
> --- a/t/t3400-rebase.sh
> +++ b/t/t3400-rebase.sh
> @@ -471,7 +471,7 @@ test_expect_success 'git rebase --update-ref with core.commentChar and branch on
Adding an extra context line shows
git checkout topic2> GIT_SEQUENCE_EDITOR="cat >actual" git -c
core.commentChar=% \
> rebase -i --update-refs base &&
> test_grep "% Ref refs/heads/wt-topic checked out at" actual &&
> - test_grep "% Ref refs/heads/topic2 checked out at" actual
> + test_grep ! "% Ref refs/heads/topic2 checked out at" actual
As topic2 is checked out in the worktree where the rebase is running why
did this line appear before?
> diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh
> index e64816770a..11afa8be56 100755
> --- a/t/t3404-rebase-interactive.sh
> +++ b/t/t3404-rebase-interactive.sh
> @@ -1975,15 +1975,23 @@ test_expect_success '--update-refs ignores non-branch decorations' '
> ) &&
> grep ^update-ref todo >actual &&
> test_write_lines "update-ref refs/heads/no-conflict-branch" >expect &&
> + test_grep ! "^# Ref refs/heads/update-refs checked out" todo &&
Lets move this line below test_cmp so we keep that line next to the ones
that create the files that are being compared. Is this another case
where we used to add this comment and no longer do so?
> test_cmp expect actual
> '
>
> test_expect_success '--update-refs updates refs correctly' '
> + test_when_finished "
> + test_might_fail git symbolic-ref -d refs/heads/no-conflict-branch-alias &&
> + test_might_fail git symbolic-ref -d refs/heads/second-alias
> + " &&
> git checkout -B update-refs no-conflict-branch &&
> git branch -f base HEAD~4 &&
> git branch -f first HEAD~3 &&
> git branch -f second HEAD~3 &&
> git branch -f third HEAD~1 &&
> + git symbolic-ref refs/heads/no-conflict-branch-alias \
> + refs/heads/no-conflict-branch &&
> + git symbolic-ref refs/heads/second-alias refs/heads/second &&
> test_commit extra2 fileX &&
> git commit --amend --fixup=L &&
>
> @@ -1991,8 +1999,16 @@ test_expect_success '--update-refs updates refs correctly' '
>
> test_cmp_rev HEAD~3 refs/heads/first &&
> test_cmp_rev HEAD~3 refs/heads/second &&
> + test_cmp_rev HEAD~3 refs/heads/second-alias &&
> test_cmp_rev HEAD~1 refs/heads/third &&
> test_cmp_rev HEAD refs/heads/no-conflict-branch &&
> + test_cmp_rev HEAD refs/heads/no-conflict-branch-alias &&
> + test_write_lines refs/heads/no-conflict-branch >expect &&
> + git symbolic-ref refs/heads/no-conflict-branch-alias >actual &&
> + test_cmp expect actual &&
> + test_write_lines refs/heads/second >expect &&
> + git symbolic-ref refs/heads/second-alias >actual &&
> + test_cmp expect actual &&
This looks good - we check that "rebase --update-refs" succeeds withh
branches that are symrefs and also that those refs are untouched by the
rebase.
Thanks
Phillip
> q_to_tab >expect <<-\EOF &&
> Successfully rebased and updated refs/heads/update-refs.
^ permalink raw reply
* Re: [RFC PATCH 0/7] repack: add --drop-filtered to reclaim space in partial clones
From: Siddharth Asthana @ 2026-07-23 19:26 UTC (permalink / raw)
To: Siddharth Shrimali, git
Cc: gitster, christian.couder, me, ps, johannes.schindelin, l.s.r
In-Reply-To: <20260716132848.95982-1-r.siddharth.shrimali@gmail.com>
On 16/07/26 18:58, Siddharth Shrimali wrote:
> This is an RFC series seeking feedback on the design and approach.
> Several pieces are still missing (noted below) and the commit
> organization needs cleanup.
>
> Partial clones let you work with large repositories without downloading
> every blob up front and the missing blobs are lazily fetched from the promisor
> remote on demand. Over time, though, these lazily-fetched blobs
> accumulate locally and there is currently no safe, built-in way to
> reclaim that disk space instead of re-cloning.
>
> This series adds a "git repack --drop-filtered --filter=<spec>" command
> that removes large, locally-held promisor blobs that are recoverable
> from the promisor remote. The dropped blobs become absent locally but
> remain lazily re-fetchable, making the partial-clone still reversible.
>
> How it works:
> * Enumerate promisor objects directly (ODB_FOR_EACH_OBJECT_PROMISOR_ONLY)
> and select the blobs exceeding the filter threshold. Because every
> enumerated object is a promisor object, it is guaranteed recoverable and
> locally-created objects are never candidates.
This looks like the right approach to me. Going through the promisor
repack path instead of write_filtered_pack() matches how repack already
splits promisor objects out.
>
> * Rebuild the promisor pack without the selected blobs, reusing the
> existing repack machinery, so the drop is crash-safe.
>
> * Record each dropped object in a drop log
> ($GIT_DIR/objects/info/promisor-dropped) so a later change can
> explain a failed lazy fetch (when it was dropped, which filter
> matched, which remotes) instead of a bare "could not fetch" error.
>
> * --dry-run lists the candidates and changes nothing.
>
> Planned follow-ups:
> * Safety guards: refuse to run while a merge/rebase/cherry-pick is in
> progress, and refuse to drop blobs referenced by the current index.
I think these matter before we present this as a real space-reclaim
tool. Without the index guard especially, users may drop blobs and then
immediately fetch them back on the next command that needs the worktree.
The drop log and remote-object-info can wait. I would not block the
next RFC round on them.
On the UI, I am fine with a separate --dry-run for now (same as
Christian). We can revisit a --drop-filtered=<mode> form later if we
grow more drop-specific options.
Thanks.
Siddharth
>
> * Authoritative remote verification: the drop log currently lists all
> configured promisor remotes rather than the exact remote each object
> is recoverable from, because there is no client-side way to query a
> remote for object availability yet. A "remote-object-info" command
> is being added to the "git cat-file --batch" protocol for this. Once
> available, the exact remote can be recorded.
>
> Known issues to address in v2:
> * There is churn between "enumerate promisor blobs" and "actually drop
> filtered promisor blobs". The former introduces
> enumerate_promisor_blobs() with an interim signature that the latter
> rewrites. These will be reorganized so the function is introduced
> in its final form.
>
> * The tests are in a standalone commit. They will instead be
> distributed into the commits that introduce the behavior they test.
>
> Siddharth Shrimali (7):
> builtin/repack.c: add --drop-filtered and --dry-run options
> list-objects-filter: add list_objects_filter__filter_oidset()
> repack-promisor: allow excluding objects from the rebuilt promisor
> pack
> builtin/repack: enumerate promisor blobs for --drop-filtered
> t7706: test --drop-filtered enumeration and validation
> builtin/repack: actually drop filtered promisor blobs
> repack-promisor: record dropped objects in a drop log
>
> builtin/repack.c | 76 ++++++++++++++++-
> list-objects-filter.c | 45 ++++++++++
> list-objects-filter.h | 16 ++++
> repack-filtered.c | 81 ++++++++++++++++++
> repack-promisor.c | 106 ++++++++++++++++++++++-
> repack.h | 12 ++-
> t/meson.build | 1 +
> t/t7706-repack-drop-filtered.sh | 145 ++++++++++++++++++++++++++++++++
> 8 files changed, 478 insertions(+), 4 deletions(-)
> create mode 100755 t/t7706-repack-drop-filtered.sh
>
^ permalink raw reply
* Re: [RFC PATCH 1/7] builtin/repack.c: add --drop-filtered and --dry-run options
From: Siddharth Asthana @ 2026-07-23 19:31 UTC (permalink / raw)
To: Junio C Hamano, Siddharth Shrimali
Cc: git, christian.couder, me, ps, johannes.schindelin, l.s.r
In-Reply-To: <xmqqh5lyej6f.fsf@gitster.g>
On 17/07/26 02:38, Junio C Hamano wrote:
> Siddharth Shrimali <r.siddharth.shrimali@gmail.com> writes:
>
>> --drop-filtered is incompatible with bitmap writing: filtering breaks
>> the "all objects in one pack" closure that bitmaps require. An explicit
>> -b is rejected with a clear error and a default-on bitmap configuration is
>> silently disabled for the duration of the command.
>
> That is very well intentioned.
>
>> @@ -231,6 +234,10 @@ int cmd_repack(int argc,
>> N_("pack prefix to store a pack containing pruned objects")),
>> OPT_STRING(0, "filter-to", &filter_to, N_("dir"),
>> N_("pack prefix to store a pack containing filtered out objects")),
>> + OPT_BOOL(0, "drop-filtered", &drop_filtered,
>> + N_("delete filtered out objects (requires --filter)")),
>> + OPT_BOOL(0, "dry-run", &dry_run,
>> + N_("only show which objects would be dropped")),
>> OPT_END()
>> };
>>
>> @@ -252,6 +259,43 @@ int cmd_repack(int argc,
>> po_args.depth = xstrdup_or_null(opt_depth);
>> po_args.threads = xstrdup_or_null(opt_threads);
>>
>> + die_for_incompatible_opt2(drop_filtered, "--drop-filtered",
>> + !!filter_to, "--filter-to");
>> +
>> + die_for_incompatible_opt2(drop_filtered, "--drop-filtered",
>> + write_bitmaps > 0, "--write-bitmap-index");
>
> Hmph. Since this step does not change the parsing or configuration
> for write_bitmaps, we cannot tell if (write_bitmaps == 1) at this
> point in the execution came from the command line (e.g., an earlier
> call to parse_options() around line 247 of builtin/repack.c) or from
> the configuration files (e.g., a call to repo_config() around
> line 245). In other words, wouldn't it be ...
>
>> + if (dry_run && !drop_filtered)
>> + die(_("--dry-run only takes effect with --drop-filtered"));
>> +
>> + if (drop_filtered) {
>> + if (!dry_run)
>> + die(_("--drop-filtered doesn't work without --dry-run yet"));
>> +
>> + if (!po_args.filter_options.choice)
>> + die(_("--drop-filtered requires --filter"));
>> +
>> + if (!(pack_everything & ALL_INTO_ONE))
>> + die(_("--drop-filtered requires -a"));
>> +
>> + /*
>> + * Only blob:limit=<n> is supported for now. Reject other
>> + * filter choices early, before walking the object database.
>> + */
>> + if (po_args.filter_options.choice != LOFC_BLOB_LIMIT)
>> + die(_("--drop-filtered only supports --filter=blob:limit=<n> for now"));
>> +
>> + /*
>> + * Without a promisor remote there is nowhere to re-fetch the
>> + * dropped objects from, so dropping them would be permanent
>> + * data loss.
>> + */
>> + if (!repo_has_promisor_remote(repo))
>> + die(_("--drop-filtered requires a promisor remote"));
>> +
>> + write_bitmaps = 0;
>
> ... way too late to drop the flag here?
Yes, I agree. At that point write_bitmaps > 0 can come from either
-b/--write-bitmap-index or repack.writeBitmaps, so we cannot both
error on an explicit -b and silently clear a config default with the
same check.
For v2 it would be nice to treat those two cases differently.
Thanks.
Siddharth
>
>> + }
>> +
>> if (delete_redundant && repo->repository_format_precious_objects)
>> die(_("cannot delete packs in a precious-objects repo"));
^ permalink raw reply
* Re: [RFC PATCH 7/7] repack-promisor: record dropped objects in a drop log
From: Siddharth Asthana @ 2026-07-23 19:41 UTC (permalink / raw)
To: Siddharth Shrimali, git
Cc: gitster, christian.couder, me, ps, johannes.schindelin, l.s.r
In-Reply-To: <20260716132848.95982-8-r.siddharth.shrimali@gmail.com>
On 16/07/26 18:58, Siddharth Shrimali wrote:
> After --drop-filtered removes promisor blobs, append a record of each
> dropped object to $GIT_DIR/objects/info/promisor-dropped. Each line
> records the object ID, a reflog-style timestamp (Unix seconds and
> timezone), the filter spec, and the promisor remote it was attested
> recoverable from like the following:
>
> <oid> <time> <tz> filter=<spec> remote=<name>
>
> If a dropped object later becomes unrecoverable (for example, the
> branch holding it is deleted on the promisor remote), a lazy fetch
> fails with a generic error. This persistent record lets a later change
> explain that the object was dropped deliberately, when, under which
> filter, and from which remote it was expected to be recoverable.
I like the idea of better errors when a later lazy fetch fails.
An alternative would be to wait until we actually have that error-path
change in the same series, so we do not grow an on-disk format that
nothing reads yet. I think keeping the log in the RFC is fine though
if you find it useful while developing; I would not treat it as
required for the first mergeable version.
>
> The remote field lists all configured promisor remotes rather than the
> specific one each dropped object is recoverable from. Determining the
> exact remote would require asking the remote whether it has the object.
> A "remote-object-info" command is being added to the "git cat-file
> --batch" protocol for this kind of query, but it is not available yet.
> A NEEDSWORK marks this for a follow-up.
>
> The log is written only on a real run, i.e. --dry-run changes nothing.
>
> Mentored-by: Christian Couder <christian.couder@gmail.com>
> Mentored-by: Siddharth Asthana <siddharthasthana31@gmail.com>
> Signed-off-by: Siddharth Shrimali <r.siddharth.shrimali@gmail.com>
> ---
> builtin/repack.c | 4 +++
> repack-promisor.c | 91 +++++++++++++++++++++++++++++++++++++++++++++++
> repack.h | 4 +++
> 3 files changed, 99 insertions(+)
>
> diff --git a/builtin/repack.c b/builtin/repack.c
> index aa3257a98a..49dcbbc567 100644
> --- a/builtin/repack.c
> +++ b/builtin/repack.c
> @@ -702,6 +702,10 @@ int cmd_repack(int argc,
> write_midx_file(files->packed, NULL, NULL, flags);
> }
>
> + if (drop_filtered && !dry_run)
> + append_drop_log(repo, &drop_oids,
> + expand_list_objects_filter_spec(&po_args.filter_options));
> +
> cleanup:
> string_list_clear(&keep_pack_list, 0);
> string_list_clear(&names, 1);
> diff --git a/repack-promisor.c b/repack-promisor.c
> index fabfdc168a..60913a5150 100644
> --- a/repack-promisor.c
> +++ b/repack-promisor.c
> @@ -7,6 +7,97 @@
> #include "repository.h"
> #include "run-command.h"
> #include "oidset.h"
> +#include "date.h"
> +#include "promisor-remote.h"
> +#include "strbuf.h"
> +
> +/*
> + * Append the drop-log entries to the already-computed path.
> + * Returns -1 on any I/O failure so the caller can warn once.
> + * Keeping this in a separate helper avoids goto-based cleanup
> + * in append_drop_log();
> + */
> +static int write_to_drop_log(struct repository *repo,
> + const char *path,
> + const struct oidset *dropped,
> + const char *stamp,
> + const char *filter_spec,
> + const char *remotes)
> +{
> + struct oidset_iter iter;
> + const struct object_id *oid;
> + FILE *fp;
> +
> + if (safe_create_leading_directories(repo, (char *)path)) {
> + warning(_("could not create leading directories for '%s'"), path);
> + return -1;
> + }
> +
> + fp = fopen(path, "a");
> + if (!fp) {
> + warning_errno(_("could not open '%s'"), path);
> + return -1;
> + }
> +
> + oidset_iter_init(dropped, &iter);
> + while ((oid = oidset_iter_next(&iter))) {
> + if (fprintf(fp, "%s %s filter=%s remote=%s\n",
> + oid_to_hex(oid), stamp,
> + filter_spec ? filter_spec : "",
> + remotes) < 0) {
> + warning(_("could not write to '%s'"), path);
> + fclose(fp);
> + return -1;
> + }
> + }
> +
> + if (fclose(fp)) {
> + warning_errno(_("could not close '%s'"), path);
> + return -1;
> + }
> +
> + return 0;
> +}
> +
> +void append_drop_log(struct repository *repo,
> + const struct oidset *dropped,
> + const char *filter_spec)
> +{
> + char *path;
> + struct strbuf stamp = STRBUF_INIT;
> + struct strbuf remotes = STRBUF_INIT;
> + struct promisor_remote *pr;
> +
> + if (!oidset_size(dropped))
> + return;
> +
> + datestamp(&stamp);
> +
> + /*
> + * NEEDSWORK: we temporarily record all configured promisor remotes rather
> + * than the specific one a given object is recoverable from because there
Recording all promisor remotes for now looks OK to me with that
NEEDSWORK. I would not block this on remote-object-info.
> + * is currently no way to determine that locally. it would require
> + * asking the remote whether it has the object. A "remote-object-info"
> + * command is being added to the "git cat-file --batch" protocol for
> + * this kind of query. Once it is merged in the codebase, this should
> + * record the exact promisor remote that has each dropped object.
> + */
> + for (pr = repo_promisor_remote_find(repo, NULL); pr; pr = pr->next) {
> + if (remotes.len)
> + strbuf_addch(&remotes, ',');
> + strbuf_addstr(&remotes, pr->name);
> + }
> +
> + path = repo_git_path(repo, "objects/info/promisor-dropped");
If we keep it, it would be nice to document this path (for example in
gitrepository-layout) and to have a small test that a real drop appends
a line.
Thanks
Siddharth
> +
> + if (write_to_drop_log(repo, path, dropped, stamp.buf,
> + filter_spec, remotes.buf))
> + warning(_("could not record all dropped objects in the drop log"));
> +
> + strbuf_release(&stamp);
> + strbuf_release(&remotes);
> + free(path);
> +}
>
> struct write_oid_context {
> struct child_process *cmd;
> diff --git a/repack.h b/repack.h
> index 61e554e4ed..33309548ce 100644
> --- a/repack.h
> +++ b/repack.h
> @@ -171,6 +171,10 @@ int enumerate_promisor_blobs(struct repository *repo,
> const struct list_objects_filter_options *filter,
> struct oidset *to_drop);
>
> +void append_drop_log(struct repository *repo,
> + const struct oidset *dropped,
> + const char *filter_spec);
> +
> int write_cruft_pack(const struct write_pack_opts *opts,
> const char *cruft_expiration,
> unsigned long combine_cruft_below_size,
^ permalink raw reply
* Re: [RFC PATCH 6/7] builtin/repack: actually drop filtered promisor blobs
From: Siddharth Asthana @ 2026-07-23 19:42 UTC (permalink / raw)
To: Siddharth Shrimali, git
Cc: gitster, christian.couder, me, ps, johannes.schindelin, l.s.r
In-Reply-To: <20260716132848.95982-7-r.siddharth.shrimali@gmail.com>
On 16/07/26 18:58, Siddharth Shrimali wrote:
> Make --drop-filtered remove the enumerated promisor blobs instead of
> only listing them.
>
> The drop set is computed before repack_promisor_objects() runs, and on
> a real run it is passed in so the rebuilt promisor pack omits those
> blobs. --drop-filtered implies -d so the old promisor packs, which
> still contain the dropped blobs, are removed. Without this the blobs
> would survive in the redundant packs. The existing repack machinery
> performs the write-before-delete and fsync, so the drop is crash-safe.
>
> The dropped blobs become absent locally but remain recoverable from the
> promisor remote, so a later access lazy-fetches them back
> transparently. --dry-run keeps its previous behavior, i.e. it lists the
> candidates and changes nothing.
>
> Mentored-by: Christian Couder <christian.couder@gmail.com>
> Mentored-by: Siddharth Asthana <siddharthasthana31@gmail.com>
> Signed-off-by: Siddharth Shrimali <r.siddharth.shrimali@gmail.com>
> ---
> builtin/repack.c | 75 ++++++++++++++++++---------------
> repack-filtered.c | 17 ++------
> repack.h | 4 +-
> t/t7706-repack-drop-filtered.sh | 18 +++++---
> 4 files changed, 59 insertions(+), 55 deletions(-)
>
> diff --git a/builtin/repack.c b/builtin/repack.c
> index c2b07477d2..aa3257a98a 100644
> --- a/builtin/repack.c
> +++ b/builtin/repack.c
> @@ -15,6 +15,8 @@
> #include "repack.h"
> #include "shallow.h"
> #include "list-objects-filter-options.h"
> +#include "oidset.h"
> +#include "hex.h"
>
> #define ALL_INTO_ONE 1
> #define LOOSEN_UNREACHABLE 2
> @@ -143,6 +145,7 @@ int cmd_repack(int argc,
> struct string_list_item *item;
> struct string_list names = STRING_LIST_INIT_DUP;
> struct existing_packs existing = EXISTING_PACKS_INIT;
> + struct oidset drop_oids = OIDSET_INIT;
> struct pack_geometry geometry = { 0 };
> struct tempfile *refs_snapshot = NULL;
> int i, ret;
> @@ -269,9 +272,6 @@ int cmd_repack(int argc,
> die(_("--dry-run only takes effect with --drop-filtered"));
>
> if (drop_filtered) {
> - if (!dry_run)
> - die(_("--drop-filtered doesn't work without --dry-run yet"));
> -
> if (!po_args.filter_options.choice)
> die(_("--drop-filtered requires --filter"));
>
> @@ -294,6 +294,28 @@ int cmd_repack(int argc,
> die(_("--drop-filtered requires a promisor remote"));
>
> write_bitmaps = 0;
> +
> + /*
> + * Dropping objects means rebuilding the promisor packs
> + * without them and then removing the old packs, so the
> + * redundant packs must be deleted. Imply -d on a real run.
> + */
> + if (!dry_run)
> + delete_redundant = 1;
Yes, without that the drop would not actually reclaim space.
It would be nice if the documentation mentioned that a real
--drop-filtered run implies -d.
Thanks
> +
> + ret = enumerate_promisor_blobs(repo, &po_args.filter_options, &drop_oids);
> +
> + if (ret)
> + goto cleanup;
> +
> + if (dry_run) {
> + struct oidset_iter iter;
> + const struct object_id *oid;
> +
> + oidset_iter_init(&drop_oids, &iter);
> + while ((oid = oidset_iter_next(&iter)))
> + printf("%s\n", oid_to_hex(oid));
> + }
> }
>
> if (delete_redundant && repo->repository_format_precious_objects)
> @@ -406,7 +428,8 @@ int cmd_repack(int argc,
> strvec_push(&cmd.args, "--delta-islands");
>
> if (pack_everything & ALL_INTO_ONE) {
> - repack_promisor_objects(repo, &po_args, &names, packtmp, NULL);
> + repack_promisor_objects(repo, &po_args, &names, packtmp,
> + (drop_filtered && !dry_run) ? &drop_oids : NULL);
>
> if (existing_packs_has_non_kept(&existing) &&
> delete_redundant &&
> @@ -589,35 +612,20 @@ int cmd_repack(int argc,
> }
> }
>
> - if (po_args.filter_options.choice) {
> - if (drop_filtered) {
> - /*
> - * Enumerate promisor objects directly rather than
> - * going through write_filtered_pack(). The filter
> - * machinery cannot see promisor objects because
> - * repack_promisor_objects() handles them separately
> - * before the filter runs.
> - */
> - ret = enumerate_promisor_blobs(repo,
> - &po_args.filter_options,
> - dry_run);
> - if (ret)
> - goto cleanup;
> - } else {
> - struct write_pack_opts opts = {
> - .po_args = &po_args,
> - .destination = filter_to,
> - .packdir = packdir,
> - .packtmp = packtmp,
> - };
> -
> - if (!opts.destination)
> - opts.destination = packtmp;
> -
> - ret = write_filtered_pack(&opts, &existing, &names);
> - if (ret)
> - goto cleanup;
> - }
> + if (po_args.filter_options.choice && !drop_filtered) {
> + struct write_pack_opts opts = {
> + .po_args = &po_args,
> + .destination = filter_to,
> + .packdir = packdir,
> + .packtmp = packtmp,
> + };
> +
> + if (!opts.destination)
> + opts.destination = packtmp;
> +
> + ret = write_filtered_pack(&opts, &existing, &names);
> + if (ret)
> + goto cleanup;
> }
>
> string_list_sort(&names);
> @@ -697,6 +705,7 @@ int cmd_repack(int argc,
> cleanup:
> string_list_clear(&keep_pack_list, 0);
> string_list_clear(&names, 1);
> + oidset_clear(&drop_oids);
> existing_packs_release(&existing);
> pack_geometry_release(&geometry);
> pack_objects_args_release(&po_args);
> diff --git a/repack-filtered.c b/repack-filtered.c
> index f5a1dae5b1..6f0cecca9b 100644
> --- a/repack-filtered.c
> +++ b/repack-filtered.c
> @@ -87,16 +87,13 @@ static int collect_promisor_blob(const struct object_id *oid,
>
> int enumerate_promisor_blobs(struct repository *repo,
> const struct list_objects_filter_options *filter,
> - int dry_run)
> + struct oidset *to_drop)
> {
> struct oidset all_promisor_blobs = OIDSET_INIT;
> - struct oidset to_drop = OIDSET_INIT;
> struct collect_cb_data cb = {
> .repo = repo,
> .set = &all_promisor_blobs
> };
> - struct oidset_iter iter;
> - const struct object_id *oid;
> int ret = 0;
>
> /*
> @@ -122,22 +119,14 @@ int enumerate_promisor_blobs(struct repository *repo,
>
> /*
> * Apply the filter to find which blobs exceed the threshold.
> + * The caller has to_drop and is responsible for clearing it.
> */
> ret = list_objects_filter__filter_oidset(repo,
> (struct list_objects_filter_options *)filter,
> &all_promisor_blobs,
> - &to_drop);
> - if (ret)
> - goto cleanup;
> -
> - if (dry_run) {
> - oidset_iter_init(&to_drop, &iter);
> - while ((oid = oidset_iter_next(&iter)))
> - printf("%s\n", oid_to_hex(oid));
> - }
> + to_drop);
>
> cleanup:
> oidset_clear(&all_promisor_blobs);
> - oidset_clear(&to_drop);
> return ret;
> }
> diff --git a/repack.h b/repack.h
> index d08e25b852..61e554e4ed 100644
> --- a/repack.h
> +++ b/repack.h
> @@ -168,8 +168,8 @@ int write_filtered_pack(const struct write_pack_opts *opts,
> struct string_list *names);
>
> int enumerate_promisor_blobs(struct repository *repo,
> - const struct list_objects_filter_options *filter,
> - int dry_run);
> + const struct list_objects_filter_options *filter,
> + struct oidset *to_drop);
>
> int write_cruft_pack(const struct write_pack_opts *opts,
> const char *cruft_expiration,
> diff --git a/t/t7706-repack-drop-filtered.sh b/t/t7706-repack-drop-filtered.sh
> index b558807847..41e7941799 100755
> --- a/t/t7706-repack-drop-filtered.sh
> +++ b/t/t7706-repack-drop-filtered.sh
> @@ -56,12 +56,6 @@ test_expect_success '--dry-run only takes effect with --drop-filtered' '
> test_grep "dry-run only takes effect with --drop-filtered" err
> '
>
> -test_expect_success '--drop-filtered without --dry-run is rejected' '
> - test_must_fail git -C plain.git repack --drop-filtered \
> - --filter=blob:limit=1k -a 2>err &&
> - test_grep "drop-filtered doesn.t work without --dry-run yet" err
> -'
> -
> test_expect_success '--drop-filtered requires -a' '
> test_must_fail git -C plain.git repack --drop-filtered \
> --filter=blob:limit=1k --dry-run 2>err &&
> @@ -136,4 +130,16 @@ test_expect_success '--dry-run does not remove the filtered objects' '
> git -C repo cat-file -e "$BIG"
> '
>
> +test_expect_success '--drop-filtered removes the promisor blob locally' '
> + BIG=$(cat big_oid) &&
> + SMALL=$(cat small_oid) &&
> +
> + git -C repo -c repack.writeBitmaps=false \
> + repack --drop-filtered --filter=blob:limit=1k -a &&
> +
> + git -C repo cat-file --batch-all-objects --batch-check="%(objectname)" >present &&
> + ! grep -q "$BIG" present &&
> + grep -q "$SMALL" present
> +'
> +
> test_done
^ permalink raw reply
* Re: [PATCH v2] config: retry acquiring config.lock, configurable via core.configLockTimeout
From: Junio C Hamano @ 2026-07-23 19:54 UTC (permalink / raw)
To: Joerg Thalheim; +Cc: git, Patrick Steinhardt, Johannes Schindelin
In-Reply-To: <20260517132111.1014901-1-joerg@thalheim.io>
Joerg Thalheim <joerg@thalheim.io> writes:
> From: Jörg Thalheim <joerg@thalheim.io>
>
> Concurrent config writers race for the ".lock" file, which is taken
> with open(O_EXCL) and no retry, so the losers fail right away with
> "could not lock config file".
>
> This shows up with parallel "git worktree add -b" against the same
> repository: each one writes a couple of branch.* keys and the losers
> fail at random. Worse, "git worktree add" doesn't propagate that
> failure to its exit code, so the tracking config is silently dropped.
> (The swallowed error is a separate bug.)
>
> Retry instead of giving up on the first EEXIST. The lock is only held
> while rewriting a small file, so the loser only has to wait out the
> other writers. Same approach as 4ff0f01cb7 (refs: retry acquiring
> reference locks for 100ms, 2017-08-21).
>
> On the semantics: the on-disk config is read only after the lock is
> taken, so writers touching different keys can't lose each other's
> change. Writers touching the same key still get last-writer-wins, but
> that is already the case today and would need a compare-and-swap config
> API to fix. The retry only turns hard failures into successes.
>
> Default to 1000ms, like core.packedRefsTimeout: same shape of problem,
> one shared file everyone serializes through. A larger timeout only
> costs anything when a stale lock is left behind by a crash, which is
> rare; a smaller one fails spuriously on slow filesystems (NTFS has
> been seen needing more than 100ms). Make it configurable as
> core.configLockTimeout. There is no chicken-and-egg problem: we read
> the config before we lock it.
>
> microsoft/git carries a similar patch (core.configWriteLockTimeoutMS,
> default off) for Scalar's tests. Defaulting to non-zero here because
> the worktree case fails silently.
>
> Helped-by: Patrick Steinhardt <ps@pks.im>
> Helped-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>
> Signed-off-by: Jörg Thalheim <joerg@thalheim.io>
> ---
> Thanks for the review and for poking me, this had fallen off my radar.
>
> v1 -> v2:
>
> - added core.configLockTimeout. Johannes is right that there is no
> chicken-and-egg problem (config is read before the lock), so no env
> var needed.
> - default bumped to 1000ms; packed-refs is the closer precedent and it
> keeps NTFS out of trouble.
> - commit message now covers the read-after-lock / last-writer-wins
> semantics Patrick asked about.
> - added tests; existing stale-lock tests in t3200/t5505 now pass
> -c core.configLockTimeout=0 so they still fail fast.
>
> I matched the core.filesRefLockTimeout naming rather than reusing
> microsoft/git's core.configWriteLockTimeoutMS, but can switch if the
> downstream compat matters more.
I was reviewing the whats-cooking and noticed there are a handful of
stalled topics that are not going anywhere, and this is one of them.
This time it had fallen off my radar, sorry about that. All the
outstanding issues seem to have been resolved, so let's merge it
down to 'next'.
Thanks.
^ permalink raw reply
* Re: [PATCH v10] show-branch: convert per-branch flags to commit-slab
From: Junio C Hamano @ 2026-07-23 20:44 UTC (permalink / raw)
To: Gatla Vishweshwar Reddy; +Cc: git
In-Reply-To: <20260721203025.85044-1-gatlavishweshwarreddy26@gmail.com>
Gatla Vishweshwar Reddy <gatlavishweshwarreddy26@gmail.com> writes:
> show-branch uses commit->object.flags to store per-branch
> ...
> Signed-off-by: Gatla Vishweshwar Reddy <gatlavishweshwarreddy26@gmail.com>
> ---
>
>> Hmph. I hate to say this, but I am finding it difficult to trust
>> your "carefully" at this point.
>>
>> $ make
>> $ ./git show-branch master next
>> Floating point exception (core dumped).
>
> You are right to not trust it. I missed this completely. I ran the
> full test suite but did not run the binary manually before sending.
> That was the wrong approach. I have now run every mode manually
> before sending this version.
> ...
> All tests pass. No crashes in any mode.
>
> ---
> Changes in v10:
> - Restore init_commit_name_slab(&name_slab) before repo_config()
> that was accidentally dropped in v7. Without it, name_slab.slab_size
> is 0 causing division by zero on first commit lookup.
I will not read the contents of v10, but I think it is worth setting
some expectations first. I am usually pretty patient, but even my
patience has its limits.
First and foremost, this development community is built on humans
collaborating with other humans. An author posts a patch, a
reviewer responds with suggestions or critiques, and the author
replies to that e-mail. In their own words, the author might:
- build on the suggestion, rephrasing it and proposing further
improvements;
- disagree and offer a counter-proposal;
- concede the patch's shortcomings and outline how they plan to fix
them; or
- defend their original design to give the reviewer a chance to
reconsider.
Doing this in your own words helps reviewers see how close we are to
an agreement. This kind of discussion often needs a few rounds of
back-and-forth. It should also welcome folks watching from the
sidelines, which means letting the globe spin at least once so
developers in other timezones can chime in before we declare a rough
consensus.
Firing off a new iteration before there is a rough consensus on what
it should look like is a total waste of everyone's time.
Finally, the space below the three-dash line is absolutely not the
place to conduct a discussion. Those debates belong in separate,
threaded e-mail replies. Use the space to remind readers that this
work is based on a consensus achieved in an earlier thread [*].
Also, to be clear, I didn't bring up the core dump because I was
upset about a lack of testing [**]. We are all error-prone humans,
and mistakes (like dropping an unrelated line) happen to the best of
us. Maybe a cat distracts you, and while your head is turned, you
accidentally hit dd (or C-k for the Emacs crowd) and delete a line
without realizing it.
No, the real issue was that this deletion should have leaped out at
anyone reading the patch, immediately prompting some questions:
We are removing this initialization. Why? Have we changed the
API to make BSS initialization sufficient? Does the updated
code no longer use this structure? Do we initialize it
somewhere else now?
And until those questions are answered, no one can honestly claim
to have 'reviewed the patch carefully.'
It is perfectly fine to have some fun letting AI assistants write
code for you. However, please make sure you are prepared to explain
every single change in the patch when asked. It is already a bit of
a philosophical stretch to call a patch 'yours' when an AI did the
heavy lifting, but it definitely is not yours if you cannot explain
it in your own words. If you are not yet familiar with the
codebase, it is OK if you do not have all the answers right away.
Just hold off on sending the patch until you do.
A suggestion I can give users of AI assistants is to have your AI
assistant actually help you. And by that, I do not mean tossing it
a lazy, one-line prompt like 'please explain every line in this
patch.' Instead, read through its output yourself, line by line and
hunk by hunk, and ask yourself if you can explain why each change
exists. If you can't, ask the AI. If you don't understand its
answer, grill it further in your own words, using the actual
questions that pop into your head.
Here is a fun little exercise you might enjoy. If you can resurrect
and continue the chat session with the AI agent that spawned the v9
patch, ask it why it decided to delete that init_commit_name_slab()
call, and what it thought the ramifications of doing so would be.
I actually spotted a few more issues in the previous round, but I
left them out of my review. Why? Because I expected you would just
feed my feedback straight to your AI assistant, tell it to 'compose
a response and update the patch,' and call it a day. And as Patrick
pointed out earlier, none of us want to waste our brain cycles
playing telephone with a human middleman who is just copy-pasting
between an AI generator and the mailing list.
So, there.
[Footnotes]
* This is a total tangent, but as I am ranting here, this is
exactly why I hate seeing 'X requested this change' below the
three-dash line. Sure, the critique or suggestion might have
originated with a reviewer, but by the time the author writes an
updated iteration, it has become something both of them agree on.
At that point, it is no longer a mere 'request' because the
author is now just as much on board and backing the change as the
reviewer.
** If anything, this episode exposed a massive gap in our test
coverage, since the test suite completely missed a breakage in
such a basic use of the command. We may need to extend our test
coverage before making further changes.
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox