* [PATCH v2 4/9] reset: introduce dry-run mode
From: Patrick Steinhardt @ 2026-06-03 16:14 UTC (permalink / raw)
To: git; +Cc: Pablo Sabater, Junio C Hamano
In-Reply-To: <20260603-b4-pks-history-drop-v2-0-742cb5b5176d@pks.im>
In a subsequent commit we'll add add another caller to `reset_head()`
that wants to perform a dry-run check of whether it would be possible to
udpate the index and working tree when moving to a new commit. Introduce
a new flag that lets the caller perform this operation.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
reset.c | 44 +++++++++++++++++++++++++++++++++-----------
reset.h | 6 ++++++
2 files changed, 39 insertions(+), 11 deletions(-)
diff --git a/reset.c b/reset.c
index 9ff14f5ed1..a8d7eea4d6 100644
--- a/reset.c
+++ b/reset.c
@@ -92,11 +92,14 @@ int reset_head(struct repository *r, const struct reset_head_opts *opts)
unsigned reset_hard = opts->flags & RESET_HEAD_HARD;
unsigned refs_only = opts->flags & RESET_HEAD_REFS_ONLY;
unsigned update_orig_head = opts->flags & RESET_HEAD_ORIG_HEAD;
+ unsigned dry_run = opts->flags & RESET_HEAD_DRY_RUN;
struct object_id *head = NULL, head_oid;
struct tree_desc desc[2] = { { NULL }, { NULL } };
struct lock_file lock = LOCK_INIT;
struct unpack_trees_options unpack_tree_opts = { 0 };
struct tree *tree;
+ struct index_state scratch_index = INDEX_STATE_INIT(r);
+ struct index_state *istate;
const char *action;
int ret = 0, nr = 0;
@@ -109,7 +112,7 @@ int reset_head(struct repository *r, const struct reset_head_opts *opts)
if (opts->branch_msg && !opts->branch)
BUG("branch reflog message given without a branch");
- if (!refs_only && repo_hold_locked_index(r, &lock, LOCK_REPORT_ON_ERROR) < 0) {
+ if (!refs_only && !dry_run && repo_hold_locked_index(r, &lock, LOCK_REPORT_ON_ERROR) < 0) {
ret = -1;
goto leave_reset_head;
}
@@ -124,16 +127,36 @@ int reset_head(struct repository *r, const struct reset_head_opts *opts)
if (!oid)
oid = &head_oid;
- if (refs_only)
- return update_refs(r, opts, oid, head);
+ if (refs_only) {
+ if (!dry_run)
+ return update_refs(r, opts, oid, head);
+ return 0;
+ }
+
+ if (dry_run) {
+ if (read_index_from(&scratch_index, r->index_file, r->gitdir) < 0 ||
+ index_state_unmerged_to_stage0(&scratch_index) < 0) {
+ ret = error(_("could not read index"));
+ goto leave_reset_head;
+ }
+
+ istate = &scratch_index;
+ } else {
+ if (repo_read_index_unmerged(r) < 0) {
+ ret = error(_("could not read index"));
+ goto leave_reset_head;
+ }
+ istate = r->index;
+ }
action = reset_hard ? "reset" : "checkout";
setup_unpack_trees_porcelain(&unpack_tree_opts, action);
unpack_tree_opts.head_idx = 1;
- unpack_tree_opts.src_index = r->index;
- unpack_tree_opts.dst_index = r->index;
+ unpack_tree_opts.src_index = istate;
+ unpack_tree_opts.dst_index = istate;
unpack_tree_opts.fn = reset_hard ? oneway_merge : twoway_merge;
- unpack_tree_opts.update = 1;
+ unpack_tree_opts.update = !dry_run;
+ unpack_tree_opts.dry_run = dry_run;
unpack_tree_opts.merge = 1;
unpack_tree_opts.preserve_ignored = 0; /* FIXME: !overwrite_ignore */
unpack_tree_opts.skip_cache_tree_update = 1;
@@ -141,11 +164,6 @@ int reset_head(struct repository *r, const struct reset_head_opts *opts)
if (reset_hard)
unpack_tree_opts.reset = UNPACK_RESET_PROTECT_UNTRACKED;
- if (repo_read_index_unmerged(r) < 0) {
- ret = error(_("could not read index"));
- goto leave_reset_head;
- }
-
if (!reset_hard && !fill_tree_descriptor(r, &desc[nr++], &head_oid)) {
ret = error(_("failed to find tree of %s"),
oid_to_hex(&head_oid));
@@ -162,6 +180,9 @@ int reset_head(struct repository *r, const struct reset_head_opts *opts)
goto leave_reset_head;
}
+ if (dry_run)
+ goto leave_reset_head;
+
tree = repo_parse_tree_indirect(r, oid);
if (!tree) {
ret = error(_("unable to read tree (%s)"), oid_to_hex(oid));
@@ -181,6 +202,7 @@ int reset_head(struct repository *r, const struct reset_head_opts *opts)
leave_reset_head:
rollback_lock_file(&lock);
clear_unpack_trees_porcelain(&unpack_tree_opts);
+ release_index(&scratch_index);
while (nr)
free((void *)desc[--nr].buffer);
return ret;
diff --git a/reset.h b/reset.h
index 97ced2601e..9f696382c1 100644
--- a/reset.h
+++ b/reset.h
@@ -21,6 +21,12 @@ enum reset_head_flags {
/* Update ORIG_HEAD as well as HEAD */
RESET_HEAD_ORIG_HEAD = (1 << 4),
+
+ /*
+ * Perform a dry-run by performing the operation without updating
+ * any user-visible state.
+ */
+ RESET_HEAD_DRY_RUN = (1 << 5),
};
struct reset_head_opts {
--
2.54.0.1064.gd145956f57.dirty
^ permalink raw reply related
* [PATCH v2 3/9] reset: modernize flags passed to `reset_head()`
From: Patrick Steinhardt @ 2026-06-03 16:14 UTC (permalink / raw)
To: git; +Cc: Pablo Sabater, Junio C Hamano
In-Reply-To: <20260603-b4-pks-history-drop-v2-0-742cb5b5176d@pks.im>
The flags passed to `reset_head()` are declared as defines. This has
fallen a bit out of practice nowadays, where we instead prefer to use
enums.
Modernize the code accordingly.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
builtin/rebase.c | 2 +-
reset.c | 4 ++--
reset.h | 30 ++++++++++++++++++------------
sequencer.c | 2 +-
4 files changed, 22 insertions(+), 16 deletions(-)
diff --git a/builtin/rebase.c b/builtin/rebase.c
index fa4f5d9306..6351a3aa32 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -1876,7 +1876,7 @@ int cmd_rebase(int argc,
options.reflog_action, options.onto_name);
ropts.oid = &options.onto->object.oid;
ropts.orig_head = &options.orig_head->object.oid;
- ropts.flags = RESET_HEAD_DETACH | RESET_ORIG_HEAD |
+ ropts.flags = RESET_HEAD_DETACH | RESET_HEAD_ORIG_HEAD |
RESET_HEAD_RUN_POST_CHECKOUT_HOOK;
ropts.head_msg = msg.buf;
ropts.default_reflog_action = options.reflog_action;
diff --git a/reset.c b/reset.c
index 3b3cb74dab..9ff14f5ed1 100644
--- a/reset.c
+++ b/reset.c
@@ -18,7 +18,7 @@ static int update_refs(struct repository *repo,
{
unsigned detach_head = opts->flags & RESET_HEAD_DETACH;
unsigned run_hook = opts->flags & RESET_HEAD_RUN_POST_CHECKOUT_HOOK;
- unsigned update_orig_head = opts->flags & RESET_ORIG_HEAD;
+ unsigned update_orig_head = opts->flags & RESET_HEAD_ORIG_HEAD;
const struct object_id *orig_head = opts->orig_head;
const char *switch_to_branch = opts->branch;
const char *reflog_branch = opts->branch_msg;
@@ -91,7 +91,7 @@ int reset_head(struct repository *r, const struct reset_head_opts *opts)
const char *switch_to_branch = opts->branch;
unsigned reset_hard = opts->flags & RESET_HEAD_HARD;
unsigned refs_only = opts->flags & RESET_HEAD_REFS_ONLY;
- unsigned update_orig_head = opts->flags & RESET_ORIG_HEAD;
+ unsigned update_orig_head = opts->flags & RESET_HEAD_ORIG_HEAD;
struct object_id *head = NULL, head_oid;
struct tree_desc desc[2] = { { NULL }, { NULL } };
struct lock_file lock = LOCK_INIT;
diff --git a/reset.h b/reset.h
index a28f81829d..97ced2601e 100644
--- a/reset.h
+++ b/reset.h
@@ -6,16 +6,22 @@
#define GIT_REFLOG_ACTION_ENVIRONMENT "GIT_REFLOG_ACTION"
-/* Request a detached checkout */
-#define RESET_HEAD_DETACH (1<<0)
-/* Request a reset rather than a checkout */
-#define RESET_HEAD_HARD (1<<1)
-/* Run the post-checkout hook */
-#define RESET_HEAD_RUN_POST_CHECKOUT_HOOK (1<<2)
-/* Only update refs, do not touch the worktree */
-#define RESET_HEAD_REFS_ONLY (1<<3)
-/* Update ORIG_HEAD as well as HEAD */
-#define RESET_ORIG_HEAD (1<<4)
+enum reset_head_flags {
+ /* Request a detached checkout */
+ RESET_HEAD_DETACH = (1 << 0),
+
+ /* Request a reset rather than a checkout */
+ RESET_HEAD_HARD = (1 << 1),
+
+ /* Run the post-checkout hook */
+ RESET_HEAD_RUN_POST_CHECKOUT_HOOK = (1 << 2),
+
+ /* Only update refs, do not touch the worktree */
+ RESET_HEAD_REFS_ONLY = (1 << 3),
+
+ /* Update ORIG_HEAD as well as HEAD */
+ RESET_HEAD_ORIG_HEAD = (1 << 4),
+};
struct reset_head_opts {
/*
@@ -33,7 +39,7 @@ struct reset_head_opts {
/*
* Flags defined above.
*/
- unsigned flags;
+ enum reset_head_flags flags;
/*
* Optional reflog message for branch, defaults to head_msg.
*/
@@ -45,7 +51,7 @@ struct reset_head_opts {
const char *head_msg;
/*
* Optional reflog message for ORIG_HEAD, if this omitted and flags
- * contains RESET_ORIG_HEAD then default_reflog_action must be given.
+ * contains RESET_HEAD_ORIG_HEAD then default_reflog_action must be given.
*/
const char *orig_head_msg;
/*
diff --git a/sequencer.c b/sequencer.c
index 1ee4b2875b..0b89a977b0 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -4870,7 +4870,7 @@ static int checkout_onto(struct repository *r, struct replay_opts *opts,
struct reset_head_opts ropts = {
.oid = onto,
.orig_head = orig_head,
- .flags = RESET_HEAD_DETACH | RESET_ORIG_HEAD |
+ .flags = RESET_HEAD_DETACH | RESET_HEAD_ORIG_HEAD |
RESET_HEAD_RUN_POST_CHECKOUT_HOOK,
.head_msg = reflog_message(opts, "start", "checkout %s",
onto_name),
--
2.54.0.1064.gd145956f57.dirty
^ permalink raw reply related
* [PATCH v2 2/9] reset: drop `USE_THE_REPOSITORY_VARIABLE`
From: Patrick Steinhardt @ 2026-06-03 16:14 UTC (permalink / raw)
To: git; +Cc: Pablo Sabater, Junio C Hamano
In-Reply-To: <20260603-b4-pks-history-drop-v2-0-742cb5b5176d@pks.im>
In "reset.c" we still have references to `the_repository`, even though
the only entry point into the file already receives a repository as
parameter.
Update all uses of `the_repository` to instead use the passed-in repo
and drop `USE_THE_REPOSITORY_VARIABLE`.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
reset.c | 27 +++++++++++++--------------
1 file changed, 13 insertions(+), 14 deletions(-)
diff --git a/reset.c b/reset.c
index 46e30e6394..3b3cb74dab 100644
--- a/reset.c
+++ b/reset.c
@@ -1,5 +1,3 @@
-#define USE_THE_REPOSITORY_VARIABLE
-
#include "git-compat-util.h"
#include "cache-tree.h"
#include "gettext.h"
@@ -13,7 +11,8 @@
#include "unpack-trees.h"
#include "hook.h"
-static int update_refs(const struct reset_head_opts *opts,
+static int update_refs(struct repository *repo,
+ const struct reset_head_opts *opts,
const struct object_id *oid,
const struct object_id *head)
{
@@ -42,19 +41,19 @@ static int update_refs(const struct reset_head_opts *opts,
prefix_len = msg.len;
if (update_orig_head) {
- if (!repo_get_oid(the_repository, "ORIG_HEAD", &oid_old_orig))
+ if (!repo_get_oid(repo, "ORIG_HEAD", &oid_old_orig))
old_orig = &oid_old_orig;
if (head) {
if (!reflog_orig_head) {
strbuf_addstr(&msg, "updating ORIG_HEAD");
reflog_orig_head = msg.buf;
}
- refs_update_ref(get_main_ref_store(the_repository),
+ refs_update_ref(get_main_ref_store(repo),
reflog_orig_head, "ORIG_HEAD",
orig_head ? orig_head : head,
old_orig, 0, UPDATE_REFS_MSG_ON_ERR);
} else if (old_orig)
- refs_delete_ref(get_main_ref_store(the_repository),
+ refs_delete_ref(get_main_ref_store(repo),
NULL, "ORIG_HEAD", old_orig, 0);
}
@@ -64,23 +63,23 @@ static int update_refs(const struct reset_head_opts *opts,
reflog_head = msg.buf;
}
if (!switch_to_branch)
- ret = refs_update_ref(get_main_ref_store(the_repository),
+ ret = refs_update_ref(get_main_ref_store(repo),
reflog_head, "HEAD", oid, head,
detach_head ? REF_NO_DEREF : 0,
UPDATE_REFS_MSG_ON_ERR);
else {
- ret = refs_update_ref(get_main_ref_store(the_repository),
+ ret = refs_update_ref(get_main_ref_store(repo),
reflog_branch ? reflog_branch : reflog_head,
switch_to_branch, oid, NULL, 0,
UPDATE_REFS_MSG_ON_ERR);
if (!ret)
- ret = refs_update_symref(get_main_ref_store(the_repository),
+ ret = refs_update_symref(get_main_ref_store(repo),
"HEAD", switch_to_branch,
reflog_head);
}
if (!ret && run_hook)
- run_hooks_l(the_repository, "post-checkout",
- oid_to_hex(head ? head : null_oid(the_hash_algo)),
+ run_hooks_l(repo, "post-checkout",
+ oid_to_hex(head ? head : null_oid(repo->hash_algo)),
oid_to_hex(oid), "1", NULL);
strbuf_release(&msg);
return ret;
@@ -126,7 +125,7 @@ int reset_head(struct repository *r, const struct reset_head_opts *opts)
oid = &head_oid;
if (refs_only)
- return update_refs(opts, oid, head);
+ return update_refs(r, opts, oid, head);
action = reset_hard ? "reset" : "checkout";
setup_unpack_trees_porcelain(&unpack_tree_opts, action);
@@ -163,7 +162,7 @@ int reset_head(struct repository *r, const struct reset_head_opts *opts)
goto leave_reset_head;
}
- tree = repo_parse_tree_indirect(the_repository, oid);
+ tree = repo_parse_tree_indirect(r, oid);
if (!tree) {
ret = error(_("unable to read tree (%s)"), oid_to_hex(oid));
goto leave_reset_head;
@@ -177,7 +176,7 @@ int reset_head(struct repository *r, const struct reset_head_opts *opts)
}
if (oid != &head_oid || update_orig_head || switch_to_branch)
- ret = update_refs(opts, oid, head);
+ ret = update_refs(r, opts, oid, head);
leave_reset_head:
rollback_lock_file(&lock);
--
2.54.0.1064.gd145956f57.dirty
^ permalink raw reply related
* [PATCH v2 1/9] read-cache: split out function to drop unmerged entries to stage 0
From: Patrick Steinhardt @ 2026-06-03 16:14 UTC (permalink / raw)
To: git; +Cc: Pablo Sabater, Junio C Hamano
In-Reply-To: <20260603-b4-pks-history-drop-v2-0-742cb5b5176d@pks.im>
In `repo_read_index_unmerged()` we read the index and then drop any
unmerged entries to stage 0. In a subsequent commit we'll want to
perform this operation on arbitrary indexes, not only the one of the
given repository.
Prepare for this by splitting out the functionality into a new function
that can act on an arbitrary index.
While at it, fix a signedness mismatch when iterating through the index
cache entries.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
read-cache-ll.h | 1 +
read-cache.c | 12 +++++++-----
2 files changed, 8 insertions(+), 5 deletions(-)
diff --git a/read-cache-ll.h b/read-cache-ll.h
index 2c8b4b21b1..71b87615eb 100644
--- a/read-cache-ll.h
+++ b/read-cache-ll.h
@@ -309,6 +309,7 @@ int write_locked_index(struct index_state *, struct lock_file *lock, unsigned fl
void discard_index(struct index_state *);
void move_index_extensions(struct index_state *dst, struct index_state *src);
int unmerged_index(const struct index_state *);
+int index_state_unmerged_to_stage0(struct index_state *istate);
/**
* Returns 1 if istate differs from tree, 0 otherwise. If tree is NULL,
diff --git a/read-cache.c b/read-cache.c
index 21829102ae..799a5bc719 100644
--- a/read-cache.c
+++ b/read-cache.c
@@ -3403,13 +3403,15 @@ int write_locked_index(struct index_state *istate, struct lock_file *lock,
*/
int repo_read_index_unmerged(struct repository *repo)
{
- struct index_state *istate;
- int i;
+ repo_read_index(repo);
+ return index_state_unmerged_to_stage0(repo->index);
+}
+
+int index_state_unmerged_to_stage0(struct index_state *istate)
+{
int unmerged = 0;
- repo_read_index(repo);
- istate = repo->index;
- for (i = 0; i < istate->cache_nr; i++) {
+ for (unsigned int i = 0; i < istate->cache_nr; i++) {
struct cache_entry *ce = istate->cache[i];
struct cache_entry *new_ce;
int len;
--
2.54.0.1064.gd145956f57.dirty
^ permalink raw reply related
* [PATCH v2 0/9] builtin/history: introduce "drop" subcommand
From: Patrick Steinhardt @ 2026-06-03 16:13 UTC (permalink / raw)
To: git; +Cc: Pablo Sabater, Junio C Hamano
In-Reply-To: <20260601-b4-pks-history-drop-v1-0-643e32340d55@pks.im>
Hi,
this small patch series introduces the new "drop" subcommand for
git-history(1). As a reader might guess, the command does exactly that:
given a commit, it will drop that commit from the commit history and
replay descendant branches on top of it.
Changes in v2:
- Reworked `update_worktree()` to use `reset_head()`, which required a
bunch of changes to `reset_head()`.
- Consistently mention the commit that cannot be dropped as part of
error messages.
- Adapt error message to not use backticks anymore.
- Drop redundant "--graph" flag in a test helper.
- Link to v1: https://patch.msgid.link/20260601-b4-pks-history-drop-v1-0-643e32340d55@pks.im
Thanks!
Patrick
---
Patrick Steinhardt (9):
read-cache: split out function to drop unmerged entries to stage 0
reset: drop `USE_THE_REPOSITORY_VARIABLE`
reset: modernize flags passed to `reset_head()`
reset: introduce dry-run mode
reset: introduce ability to skip reference updates
reset: allow the caller to specify the current HEAD object
reset: stop assuming that the caller passes in a clean index
builtin/history: split handling of ref updates into two phases
builtin/history: implement "drop" subcommand
Documentation/git-history.adoc | 38 ++-
builtin/history.c | 289 ++++++++++++++++++++---
builtin/rebase.c | 2 +-
read-cache-ll.h | 1 +
read-cache.c | 12 +-
reset.c | 91 +++++---
reset.h | 44 +++-
sequencer.c | 2 +-
t/meson.build | 1 +
t/t3454-history-drop.sh | 513 +++++++++++++++++++++++++++++++++++++++++
10 files changed, 905 insertions(+), 88 deletions(-)
Range-diff versus v1:
-: ---------- > 1: d6e4f3193d read-cache: split out function to drop unmerged entries to stage 0
-: ---------- > 2: 2eef3d77e4 reset: drop `USE_THE_REPOSITORY_VARIABLE`
-: ---------- > 3: cbfd105ca3 reset: modernize flags passed to `reset_head()`
-: ---------- > 4: bbb7f3c61c reset: introduce dry-run mode
-: ---------- > 5: b3d036cea1 reset: introduce ability to skip reference updates
-: ---------- > 6: 7df1787049 reset: allow the caller to specify the current HEAD object
-: ---------- > 7: f58254bbb8 reset: stop assuming that the caller passes in a clean index
1: 2a4b683b8c = 8: 9dee781f0a builtin/history: split handling of ref updates into two phases
2: 02712e70d3 ! 9: 2b4e4075e6 builtin/history: implement "drop" subcommand
@@ Documentation/git-history.adoc: The staged addition of `unrelated.txt` has been
## builtin/history.c ##
@@
+ #include "read-cache.h"
+ #include "refs.h"
+ #include "replay.h"
++#include "reset.h"
+ #include "revision.h"
#include "sequencer.h"
#include "strvec.h"
#include "tree.h"
@@ builtin/history.c: static int cmd_history_split(int argc,
+ const struct commit *new_head,
+ bool dry_run)
+{
-+ struct index_state index = INDEX_STATE_INIT(repo);
-+ struct unpack_trees_options opts = { 0 };
-+ struct lock_file lock = LOCK_INIT;
-+ struct tree_desc desc[2] = { 0 };
-+ char *desc_buf[2] = { 0 };
-+ int ret;
-+
-+ if (!dry_run &&
-+ repo_hold_locked_index(repo, &lock, LOCK_REPORT_ON_ERROR) < 0)
-+ return -1;
-+
-+ if (read_index_from(&index, repo->index_file, repo->gitdir) < 0) {
-+ ret = error(_("unable to read index"));
-+ goto out;
-+ }
-+
-+ setup_unpack_trees_porcelain(&opts, "history drop");
-+ opts.head_idx = 1;
-+ opts.src_index = &index;
-+ opts.dst_index = &index;
-+ opts.fn = twoway_merge;
-+ opts.merge = 1;
-+ opts.update = !dry_run;
-+ opts.dry_run = dry_run;
-+ opts.preserve_ignored = 0;
-+ init_checkout_metadata(&opts.meta, NULL, &new_head->object.oid, NULL);
-+
-+ desc_buf[0] = fill_tree_descriptor(repo, &desc[0], &old_head->object.oid);
-+ desc_buf[1] = fill_tree_descriptor(repo, &desc[1], &new_head->object.oid);
-+
-+ if (unpack_trees(2, desc, &opts)) {
-+ ret = -1;
-+ goto out;
-+ }
-+
-+ if (!dry_run) {
-+ cache_tree_free(&index.cache_tree);
-+
-+ if (write_locked_index(&index, &lock, COMMIT_LOCK)) {
-+ ret = error(_("could not write index"));
-+ goto out;
-+ }
-+ }
-+
-+ ret = 0;
-+
-+out:
-+ clear_unpack_trees_porcelain(&opts);
-+ rollback_lock_file(&lock);
-+ release_index(&index);
-+ free(desc_buf[0]);
-+ free(desc_buf[1]);
-+ return ret;
++ struct reset_head_opts opts = {
++ .oid_from = &old_head->object.oid,
++ .oid = &new_head->object.oid,
++ .flags = RESET_HEAD_SKIP_REF_UPDATES,
++ };
++ if (dry_run)
++ opts.flags |= RESET_HEAD_DRY_RUN;
++ return reset_head(repo, &opts);
+}
+
+static int find_head_tree_change(struct repository *repo,
@@ builtin/history.c: static int cmd_history_split(int argc,
+ argv[0]);
+ goto out;
+ } else if (original->parents->next) {
-+ ret = error(_("cannot drop merge commit"));
++ ret = error(_("cannot drop merge commit: %s"), argv[0]);
+ goto out;
+ }
+
@@ builtin/history.c: static int cmd_history_split(int argc,
+ }
+
+ if (head_moves && update_worktree(repo, old_head, new_head, false) < 0) {
-+ ret = error(_("failed to update working tree; "
-+ "run `git checkout HEAD` to sync"));
++ ret = error(_("could not update working tree to new commit %s"),
++ oid_to_hex(&new_head->object.oid));
+ goto out;
+ }
+
@@ t/t3454-history-drop.sh (new)
+
+expect_graph () {
+ cat >expect &&
-+ lib_test_cmp_graph --graph --format=%s "$@"
++ lib_test_cmp_graph --format=%s "$@"
+}
+
+expect_log () {
---
base-commit: 1666c1265231b0bc5f613fbbf3f0a9896cdef76e
change-id: 20260601-b4-pks-history-drop-28f6c6399e7b
^ permalink raw reply
* [PATCH v2 4/4] doc: replay: move “default” to the right-hand side
From: kristofferhaugsbakk @ 2026-06-03 16:04 UTC (permalink / raw)
To: Junio C Hamano; +Cc: Kristoffer Haugsbakk, Siddharth Asthana, git
In-Reply-To: <V2_CV_doc_replay_config.767@msgid.xyz>
From: Kristoffer Haugsbakk <code@khaugsbakk.name>
This is now a description list (see previous commit) and parentheticals
like this do not go on the left-hand side. Moving it to the other side
makes it stand out just as much and is also more consistent with the
rest of the documentation.
Let’s also do the same for the `replay.refAction` description list.
That makes the two desc. lists identical in the first sentence. Let’s
add a comment about that for future editors.
Signed-off-by: Kristoffer Haugsbakk <code@khaugsbakk.name>
---
Notes (series):
v2:
• It’s “description list”, not “definition list”
• (Same mistake I have done for “line continuation” (it’s “list”))
• It’s e.g. “right-hand side” (drop “-side” hyphen)
• Change `replay.refAction` “default” placement
• Now that these two description lists are so similar, add an
AsciiDoc comment about it for future editors. Note that I
outright deleted this list in the previous version because I
didn’t want to keep them in synch. But we can remain aware of
these with two comments.
---
v1:
> do not go on the left-hand-side.
At least I haven’t seen it.
Documentation/config/replay.adoc | 5 ++++-
Documentation/git-replay.adoc | 5 ++++-
2 files changed, 8 insertions(+), 2 deletions(-)
diff --git a/Documentation/config/replay.adoc b/Documentation/config/replay.adoc
index 7328da9537d..40d1695782a 100644
--- a/Documentation/config/replay.adoc
+++ b/Documentation/config/replay.adoc
@@ -3,7 +3,10 @@ replay.refAction::
The value can be:
+
--
-`update`;; Update refs directly using an atomic transaction (default behavior).
+////
+These use the first sentences from the description list in git-replay(1).
+////
+`update`;; (default) Update refs directly using an atomic transaction.
`print`;; Output update-ref commands for pipeline use.
--
+
diff --git a/Documentation/git-replay.adoc b/Documentation/git-replay.adoc
index b4fe43ec687..ea4d14baddb 100644
--- a/Documentation/git-replay.adoc
+++ b/Documentation/git-replay.adoc
@@ -80,7 +80,10 @@ incompatible with `--contained` (which is a modifier for `--onto` only).
Control how references are updated. The mode can be:
+
--
-`update` (default);; Update refs directly using an atomic transaction.
+////
+Expanded description list compared to 'replay.refAction'.
+////
+`update`;; (default) Update refs directly using an atomic transaction.
All refs are updated or none are (all-or-nothing behavior).
`print`;; Output update-ref commands for pipeline use. This is the
traditional behavior where output can be piped to `git update-ref --stdin`.
--
2.54.0.22.g9e26862b904
^ permalink raw reply related
* [PATCH v2 3/4] doc: replay: use a nested description list
From: kristofferhaugsbakk @ 2026-06-03 16:04 UTC (permalink / raw)
To: Junio C Hamano; +Cc: Kristoffer Haugsbakk, Siddharth Asthana, git
In-Reply-To: <V2_CV_doc_replay_config.767@msgid.xyz>
From: Kristoffer Haugsbakk <code@khaugsbakk.name>
This bullet list for `--ref-action` introduces a term with a colon.
This is exactly what a description list is, structurally. Let’s be
sylistically consistent and use the desc. list markup construct.[1]
We can reuse the `::` delimiter since we use an open block.
But for consistency use the typical nested description list
delimiter, namely `;;`.
Also drop the harmless but unneeded indentation.
† 1: Same explanation as in the previous commit
Signed-off-by: Kristoffer Haugsbakk <code@khaugsbakk.name>
---
Notes (series):
v2:
• Msg: Mention that the explanation for the description list is the
same as in the previous commit
• Msg: It’s “description list”, not “definition list”
Documentation/git-replay.adoc | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/Documentation/git-replay.adoc b/Documentation/git-replay.adoc
index 4de85088d6c..b4fe43ec687 100644
--- a/Documentation/git-replay.adoc
+++ b/Documentation/git-replay.adoc
@@ -80,10 +80,10 @@ incompatible with `--contained` (which is a modifier for `--onto` only).
Control how references are updated. The mode can be:
+
--
- * `update` (default): Update refs directly using an atomic transaction.
- All refs are updated or none are (all-or-nothing behavior).
- * `print`: Output update-ref commands for pipeline use. This is the
- traditional behavior where output can be piped to `git update-ref --stdin`.
+`update` (default);; Update refs directly using an atomic transaction.
+ All refs are updated or none are (all-or-nothing behavior).
+`print`;; Output update-ref commands for pipeline use. This is the
+ traditional behavior where output can be piped to `git update-ref --stdin`.
--
+
The default mode can be configured via the `replay.refAction` configuration variable.
--
2.54.0.22.g9e26862b904
^ permalink raw reply related
* [PATCH v2 2/4] doc: replay: improve config description
From: kristofferhaugsbakk @ 2026-06-03 16:04 UTC (permalink / raw)
To: Junio C Hamano; +Cc: Kristoffer Haugsbakk, Siddharth Asthana, git
In-Reply-To: <V2_CV_doc_replay_config.767@msgid.xyz>
From: Kristoffer Haugsbakk <code@khaugsbakk.name>
First of all, this bullet list for `--ref-action` introduces a term with
a colon. This is exactly what a description list is, structurally. Let’s
be sylistically consistent and use the description list markup
construct. Let’s also drop the harmless but unneeded indentation.
Second, let’s replace the inline-verbatim `git replay` with a link
to git-replay(1), since we are naming the command. But make that
conditional so that we avoid a self-link inside git-replay(1).[1]
† 1: See e.g. e7b3a768 (doc: git-init: rework config item
init.templateDir, 2024-03-10) for another example of
avoiding self-linking
Signed-off-by: Kristoffer Haugsbakk <code@khaugsbakk.name>
---
Notes (series):
v2:
• Keep the description list for `replay.refAction` (Junio)
• Now rewrite the description list like in patch 1/3 (it’s
technically an unordered list)
• Msg: mention a previous commit which also avoided self-linking.
This helps establish a bit more context for why we do this.
Documentation/config/replay.adoc | 16 ++++++++++------
Documentation/git-replay.adoc | 1 +
2 files changed, 11 insertions(+), 6 deletions(-)
diff --git a/Documentation/config/replay.adoc b/Documentation/config/replay.adoc
index 7d549d2f0e5..7328da9537d 100644
--- a/Documentation/config/replay.adoc
+++ b/Documentation/config/replay.adoc
@@ -1,11 +1,15 @@
replay.refAction::
- Specifies the default mode for handling reference updates in
- `git replay`. The value can be:
+ Specifies the default mode for handling reference updates.
+ The value can be:
+
--
- * `update`: Update refs directly using an atomic transaction (default behavior).
- * `print`: Output update-ref commands for pipeline use.
+`update`;; Update refs directly using an atomic transaction (default behavior).
+`print`;; Output update-ref commands for pipeline use.
--
+
-This setting can be overridden with the `--ref-action` command-line option.
-When not configured, `git replay` defaults to `update` mode.
+ifdef::git-replay[]
+See `--ref-action`.
+endif::git-replay[]
+ifndef::git-replay[]
+See `--ref-action` for linkgit:git-replay[1] for details.
+endif::git-replay[]
diff --git a/Documentation/git-replay.adoc b/Documentation/git-replay.adoc
index f9ca2db2833..4de85088d6c 100644
--- a/Documentation/git-replay.adoc
+++ b/Documentation/git-replay.adoc
@@ -211,6 +211,7 @@ to use bare commit IDs instead of branch names.
CONFIGURATION
-------------
+:git-replay: 1
include::config/replay.adoc[]
GIT
--
2.54.0.22.g9e26862b904
^ permalink raw reply related
* [PATCH v2 1/4] doc: link to config for git-replay(1)
From: kristofferhaugsbakk @ 2026-06-03 16:04 UTC (permalink / raw)
To: Junio C Hamano; +Cc: Kristoffer Haugsbakk, Siddharth Asthana, git
In-Reply-To: <V2_CV_doc_replay_config.767@msgid.xyz>
From: Kristoffer Haugsbakk <code@khaugsbakk.name>
This config doc was added in 336ac90c (replay: add replay.refAction
config option, 2025-11-06) but never included anywhere. Include it in
git-replay(1) and git-config(1).
Signed-off-by: Kristoffer Haugsbakk <code@khaugsbakk.name>
---
Documentation/config.adoc | 2 ++
Documentation/git-replay.adoc | 4 ++++
2 files changed, 6 insertions(+)
diff --git a/Documentation/config.adoc b/Documentation/config.adoc
index 62eebe7c545..51fabecb9b0 100644
--- a/Documentation/config.adoc
+++ b/Documentation/config.adoc
@@ -511,6 +511,8 @@ include::config/remotes.adoc[]
include::config/repack.adoc[]
+include::config/replay.adoc[]
+
include::config/rerere.adoc[]
include::config/revert.adoc[]
diff --git a/Documentation/git-replay.adoc b/Documentation/git-replay.adoc
index a32f72aead3..f9ca2db2833 100644
--- a/Documentation/git-replay.adoc
+++ b/Documentation/git-replay.adoc
@@ -209,6 +209,10 @@ This replays the range `aabbcc..ddeeff` onto commit `112233` and updates
`refs/heads/mybranch` to point at the result. This can be useful when you want
to use bare commit IDs instead of branch names.
+CONFIGURATION
+-------------
+include::config/replay.adoc[]
+
GIT
---
Part of the linkgit:git[1] suite
--
2.54.0.22.g9e26862b904
^ permalink raw reply related
* [PATCH v2 0/4] doc: replay: fix config link
From: kristofferhaugsbakk @ 2026-06-03 16:04 UTC (permalink / raw)
To: Junio C Hamano; +Cc: Kristoffer Haugsbakk, Siddharth Asthana, git
In-Reply-To: <CV_doc_replay_config.709@msgid.xyz>
From: Kristoffer Haugsbakk <code@khaugsbakk.name>
Topic name (applied): kh/doc-replay-config
Topic summary: link to the config for git-replay(1) (one variable) in
git-replay(1) and git-config(1). Also improve the doc for that config
variable and `--ref-action`.
§ Changes in v2
See the notes on the patches for more points and details.
• Keep the description list for `replay.refAction` (Junio)
• Add a comment on both description lists about the fact that
the two are similar
[1/4] doc: link to config for git-replay(1)
[2/4] doc: replay: improve config description
[3/4] doc: replay: use a nested description list
[4/4] doc: replay: move “default” to the right-hand side
Documentation/config.adoc | 2 ++
Documentation/config/replay.adoc | 19 +++++++++++++------
Documentation/git-replay.adoc | 16 ++++++++++++----
3 files changed, 27 insertions(+), 10 deletions(-)
Interdiff against v1:
diff --git a/Documentation/config/replay.adoc b/Documentation/config/replay.adoc
index 42e521694d1..40d1695782a 100644
--- a/Documentation/config/replay.adoc
+++ b/Documentation/config/replay.adoc
@@ -1,5 +1,15 @@
replay.refAction::
- Specifies the default mode for handling reference updates. Either `update` or `print`.
+ Specifies the default mode for handling reference updates.
+ The value can be:
++
+--
+////
+These use the first sentences from the description list in git-replay(1).
+////
+`update`;; (default) Update refs directly using an atomic transaction.
+`print`;; Output update-ref commands for pipeline use.
+--
++
ifdef::git-replay[]
See `--ref-action`.
endif::git-replay[]
diff --git a/Documentation/git-replay.adoc b/Documentation/git-replay.adoc
index 39ecc2e1876..ea4d14baddb 100644
--- a/Documentation/git-replay.adoc
+++ b/Documentation/git-replay.adoc
@@ -80,6 +80,9 @@ incompatible with `--contained` (which is a modifier for `--onto` only).
Control how references are updated. The mode can be:
+
--
+////
+Expanded description list compared to 'replay.refAction'.
+////
`update`;; (default) Update refs directly using an atomic transaction.
All refs are updated or none are (all-or-nothing behavior).
`print`;; Output update-ref commands for pipeline use. This is the
Range-diff against v1:
1: ef8212a076a = 1: ef8212a076a doc: link to config for git-replay(1)
2: 7e915e331b5 ! 2: b60e2e02826 doc: replay: simplify replay.refAction description
@@ Metadata
Author: Kristoffer Haugsbakk <code@khaugsbakk.name>
## Commit message ##
- doc: replay: simplify replay.refAction description
+ doc: replay: improve config description
- We don’t need to list what each argument does since the documentation
- for `--ref-action` does that. So let’s simplify the `replay.refAction`
- description by referring to git-replay(1).
+ First of all, this bullet list for `--ref-action` introduces a term with
+ a colon. This is exactly what a description list is, structurally. Let’s
+ be sylistically consistent and use the description list markup
+ construct. Let’s also drop the harmless but unneeded indentation.
- Also make sure to not self-link for the git-replay(1) inclusion.
+ Second, let’s replace the inline-verbatim `git replay` with a link
+ to git-replay(1), since we are naming the command. But make that
+ conditional so that we avoid a self-link inside git-replay(1).[1]
+
+ † 1: See e.g. e7b3a768 (doc: git-init: rework config item
+ init.templateDir, 2024-03-10) for another example of
+ avoiding self-linking
Signed-off-by: Kristoffer Haugsbakk <code@khaugsbakk.name>
@@ Documentation/config/replay.adoc
replay.refAction::
- Specifies the default mode for handling reference updates in
- `git replay`. The value can be:
--+
----
++ Specifies the default mode for handling reference updates.
++ The value can be:
+ +
+ --
- * `update`: Update refs directly using an atomic transaction (default behavior).
- * `print`: Output update-ref commands for pipeline use.
----
--+
++`update`;; Update refs directly using an atomic transaction (default behavior).
++`print`;; Output update-ref commands for pipeline use.
+ --
+ +
-This setting can be overridden with the `--ref-action` command-line option.
-When not configured, `git replay` defaults to `update` mode.
-+ Specifies the default mode for handling reference updates. Either `update` or `print`.
+ifdef::git-replay[]
+See `--ref-action`.
+endif::git-replay[]
3: 30952387f35 ! 3: d13cd39cb36 doc: replay: use a nested definition list
@@ Metadata
Author: Kristoffer Haugsbakk <code@khaugsbakk.name>
## Commit message ##
- doc: replay: use a nested definition list
+ doc: replay: use a nested description list
This bullet list for `--ref-action` introduces a term with a colon.
- This is exactly what a definition list is, structurally. Let’s be
- sylistically consistent and use the definition list markup construct.
+ This is exactly what a description list is, structurally. Let’s be
+ sylistically consistent and use the desc. list markup construct.[1]
We can reuse the `::` delimiter since we use an open block.
- But for consistency use the typical nested definition list
+ But for consistency use the typical nested description list
delimiter, namely `;;`.
Also drop the harmless but unneeded indentation.
+ † 1: Same explanation as in the previous commit
+
Signed-off-by: Kristoffer Haugsbakk <code@khaugsbakk.name>
## Documentation/git-replay.adoc ##
4: 71a61bc0ed4 < -: ----------- doc: replay: move “default” to the right-hand-side
-: ----------- > 4: 17804ea7afa doc: replay: move “default” to the right-hand side
base-commit: a89346e34a937f001e5d397ee62224e3e9852040
--
2.54.0.22.g9e26862b904
^ permalink raw reply related
* Re: [PATCH] t3070: skip ls-files tests with backslash patterns on Windows
From: Junio C Hamano @ 2026-06-03 14:23 UTC (permalink / raw)
To: Kristofer Karlsson
Cc: Kristofer Karlsson via GitGitGadget, git, Johannes Schindelin
In-Reply-To: <CAL71e4MLyEEWtrHvB2K+stZUq6s+5sQUpSjmos3F9aVo3ej=Fw@mail.gmail.com>
Kristofer Karlsson <krka@spotify.com> writes:
> On Thu, 28 May 2026 at 22:26, Junio C Hamano <gitster@pobox.com> wrote:
>> Two questions.
>>
>> * Has this been broken on Windows since October, or has something
>> external change on Windows recently? I do not know. Anybody
>> knows?
>>
>> * Is this change a workaround that sweeps ugly breakage under the
>> rug, or is backslash inherently unusable as an excape character
>> when handling paths on Windows (which I am afraid would make
>> wildmatch fairly useless there)?
>>
>
> I am fairly new to the git ecosystem as a developer (not as a user),
> so I am not sure how long this has been broken. The backslash patterns
> in the ls-files test path predate 8a6d158a - patterns like 'foo\*'
> and '[\-_]' have been there since de8bada2bf (2018) - so it may
> have been failing for a while before anyone noticed.
Hmph.
> My thinking was that it would be good in general if the CI results
> were green and did not include false positives for errors that we
> know cannot work on this platform. The risk is that people stop
> looking into CI failures in detail because they start to assume it
> is the same old backslash problem.
Oh, no question about that.
> That said, there is also a risk that the real underlying issue does
> not get fixed. I am hoping it is sufficient that the BSLASHPSPEC
> prereq and the case *\\* filter make it obvious to anyone reading
> the test what we are skipping over and why.
>
>> Will queue. Thanks.
>
> Thanks! It felt a bit heavyweight to add noise to the list for trivial CI test
> changes but I suppose the process is the same even if it does not
> affect the production code.
Sure. I just found it a little disturbing to declare that there
won't be ways on Windows to quote special letters with backslash
when writing wildmatch patterns. But if some Windows folks got
motivated enough, they are capable of lifting the prereq when they
fix the underlying code as well, so it probably is not something I
should be worrying too much about.
Thanks.
^ permalink raw reply
* Re: [PATCH v2 3/3] b4: introduce configuration for the Git project
From: Toon Claes @ 2026-06-03 13:58 UTC (permalink / raw)
To: Patrick Steinhardt, git
Cc: Junio C Hamano, Tuomas Ahola, Weijie Yuan, Ramsay Jones
In-Reply-To: <20260603-pks-b4-v2-3-a8aea0aa2c23@pks.im>
Patrick Steinhardt <ps@pks.im> writes:
> We're about to extend our documentation to recommend b4 for sending
> patch series to the mailing list. Prepare for this by introducing a b4
> configuration so that the tool knows to honor our preferences. For now,
> this configuration does two things:
>
> - It configures "send-same-thread = shallow", which tells b4 to always
> send subsequent versions of the same patch series as a reply to the
> cover letter of the first version.
>
> - It configures "prep-cover-template", which tells b4 to use a custom
> template for the cover letter. The most important change compared to
> the default template is that our custom template also includes a
> range-diff.
>
> There's potentially more things that we may want to configure going
> forward, like for example auto-configuration of folks to Cc on certain
> patches. But these two tweaks feel like a good place to start.
>
> Note that these values only serve as defaults, and users may want to
> tweak those defaults based on their own preference. Luckily, users can
> do that without having to touch `.b4-config` at all, as b4 allows them
> to override values via Git configuration:
>
> ```
> $ git config set b4.prep-cover-template /does/not/exist
> $ b4 send --dry-run
> ERROR: prep-cover-template says to use x, but it does not exist
> ```
>
> So this gives users an easy way to override our defaults without having
> to touch ".b4-config", which would dirty the tree.
>
> Signed-off-by: Patrick Steinhardt <ps@pks.im>
> ---
> .b4-config | 6 ++++++
> .b4-cover-template | 11 +++++++++++
> 2 files changed, 17 insertions(+)
>
> diff --git a/.b4-config b/.b4-config
> new file mode 100644
> index 0000000000..fd4fb56b6d
> --- /dev/null
> +++ b/.b4-config
> @@ -0,0 +1,6 @@
> +# Note that these are default values that you can tweak via the typical
> +# git-config(1) machinery. You thus shouldn't ever have to change this file.
> +# See also https://b4.docs.kernel.org/en/latest/config.html.
> +[b4]
> +send-same-thread = shallow
Is it worth to note this requires v0.15 or higher?
That version was released only 2 months ago, I can imagine many distros
still ship an older version, what happens if a version doesn't support
this setting yet?
> +prep-cover-template = ./.b4-cover-template
> diff --git a/.b4-cover-template b/.b4-cover-template
> new file mode 100644
> index 0000000000..ab864933b5
> --- /dev/null
> +++ b/.b4-cover-template
> @@ -0,0 +1,11 @@
> +${cover}
> +
> +---
> +${shortlog}
> +
> +${diffstat}
> +
> +${range_diff}
> +---
> +base-commit: ${base_commit}
> +${prerequisites}
>
> --
> 2.54.0.1064.gd145956f57.dirty
>
>
--
Cheers,
Toon
^ permalink raw reply
* Re: Suggetsions for collaboration workflows in large repos
From: Toon Claes @ 2026-06-03 13:44 UTC (permalink / raw)
To: Matthew Hughes, git
In-Reply-To: <ahnUeESE1x802Z9N@desktop>
Matthew Hughes <matthewhughes934@gmail.com> writes:
> On Fri, May 29, 2026 at 05:31:17PM +0100, Matthew Hughes wrote:
>> I thought about doing something like tracking
>> `refs/heads*/some-colleague-branch` from the remote, since with the wildcard
>> `*` I at least won't the fatal error on the missing reference during fetch, but
>> that risks my config containing an ever growing list of such wildcards, or a
>> bunch of manual work occasionally cleaning up old ones (or maybe that could be
>> automated).
I feel your problem, although a lot less in the project I'm working on
lately. I have these refspecs by the way:
fetch = +refs/heads/master:refs/remotes/origin/master
fetch = +refs/heads/toon-*:refs/remotes/origin/toon-*
> I hacked some scripts to automate this. Firstly, one for fetching:
>
> 1. Fetches the branch
> 2. Adds a fetch config with wildcard hacks so `git fetch` brings in updates for
> that branch (the refspec should match _exactly_ that branch and never
> anything more)
> 3. Adds a separate ref to record that we're tracking this branch (so something
> knows to clean it up later)
>
> #!/usr/bin/env bash
>
> set -o errexit -o pipefail -o nounset
>
> # save command as e.g. git-fetch-other
> CMD_NAME="$(basename "$0" | sed 's/git-//g')"
> if [ $# -lt 1 ]
> then
> echo "usage: git $CMD_NAME branch-name [ remote-name ]" >&2
> exit 1
> fi
>
> BRANCH_NAME="$1"
> REMOTE_NAME="${2:-origin}"
> FETCH_CONFIG_NAME="remote.$REMOTE_NAME.fetch"
>
> git fetch "$REMOTE_NAME" "$BRANCH_NAME"
> git checkout -b "$BRANCH_NAME"
>
> # we want to record that we are tracking this branch, to do this create
> # a new ref whose name tells us what we're tracking, but whose value is
> # unimportant. So as a placeholder value just use the hash of an empty tree
> # taken from https://git.kernel.org/pub/scm/git/git.git/commit/?id=9c8a294a1ae1335511475db9c0eb8841c0ec9738
> EMPTY_TREE_REF="$(git hash-object -t tree /dev/null)"
>
> # refspec used to track the branch: we expect branches to be deleted from the
> # upstream when merged so tracking exactly:
> # "+refs/heads/$BRANCH_NAME:refs/remotes/$REMOTE_NAME/$BRANCH_NAME" will error
> # when we go to fetch that exact ref after its removed upstream.
> # so HACK around this: add wildcards that we still expect to only ever match
> # this exact branch (but doesn't have the issue of git complaining when it
> # tries to fetch an _exact_ ref)
> TRACKING_REFSPEC="+refs/heads*/$BRANCH_NAME:refs/remotes*/$REMOTE_NAME/$BRANCH_NAME"
>
> # record that we're tracking this branch. First check we've not already
> # recorded this, then ...
> if ! git config get --local --fixed-value --value "$TRACKING_REFSPEC" "$FETCH_CONFIG_NAME" >/dev/null
> then
> # ... set the config to track it for fetching, and ...
> git config set --comment "$CMD_NAME: tracking at $(date -I)" --local --append "$FETCH_CONFIG_NAME" "$TRACKING_REFSPEC"
> # ... record that we have special cased this tracking
> git update-ref "refs/tracked/$REMOTE_NAME/$BRANCH_NAME" "$EMPTY_TREE_REF"
> fi
It seems to be a bit more advanced than the alias I have:
cofetch = !sh -c 'git fetch $1 $2:remotes/$1/$2 && git switch -c $2 remotes/$1/$2' -
You need to pass it the remote and the branch name (in reverse order of
yours, which makes sense if you want the remote to be optional).
> And the cleanup script (needs to be run periodically):
>
> 1. Collects all the remote branches we know about
> 2. Checks all the references from step 3. above and checks if any branches
> defined there are missing remotes (I have fetch.prune=true to keep the remote
> tracking references up-to-date)
> 3. If they are, drops the tracking config for that branch
>
> #!/usr/bin/env bash
>
> set -o errexit -o pipefail -o nounset
>
> REMOTE_NAME="${1:-origin}"
> TRACKED_REF_PREFIX="refs/tracked/$REMOTE_NAME"
> REMOTE_REF_PREFIX="refs/remotes/$REMOTE_NAME"
>
> declare -A remote_branch_lookup
> while read -r remote_ref
> do
> # strip prefix, e.g. 'refs/remotes/origin/some-branch' -> 'some-branch'
> branch_name="${remote_ref#$REMOTE_REF_PREFIX/}"
> remote_branch_lookup["$branch_name"]=1
> done < <(git for-each-ref --format='%(refname)' "$REMOTE_REF_PREFIX/")
>
> while read -r tracking_info
> do
> tracked_branch="${tracking_info#$TRACKED_REF_PREFIX/}"
> if ! [[ -v "remote_branch_lookup[$tracked_branch]" ]]
> then
> echo "branch $tracked_branch has been removed from the remote, untracking it"
> git update-ref -d "$TRACKED_REF_PREFIX/$tracked_branch"
>
> tracking_refspec="+refs/heads*/$tracked_branch:refs/remotes*/$REMOTE_NAME/$tracked_branch"
> git config unset --local --fixed-value --value "$tracking_refspec" "remote.$REMOTE_NAME.fetch"
> fi
> done < <(git for-each-ref --format='%(refname)' "$TRACKED_REF_PREFIX/")
>
> So functionally I think this allows for the workflow I want, but does feel like
> a big ol' hack :>
I agree it feels hacky, but I don't really see how we can generalize it
more so it will become a standard feature in git?
I was thinking you can already pass `-c remote.origin.fetch=<refspec>`
(multiple times) to git-clone(1), but in practice it doesn't seem to
work because that config is additive, so it adds the refspec, instead of
overwriting, so you're getting:
fatal: multiple updates for ref 'refs/remotes/origin/main' not allowed
And you cannot combine it with `--single-branch`, although you could do
a single branch clone and then add additional refspecs later.
--
Cheers,
Toon
^ permalink raw reply
* Re: [PATCH 1/2] b4: introduce configuration for the Git project
From: Tuomas Ahola @ 2026-06-03 13:30 UTC (permalink / raw)
To: SZEDER Gábor; +Cc: Patrick Steinhardt, Weijie Yuan, git, Junio C Hamano
In-Reply-To: <aiAK9eLvew+mgWt+@szeder.dev>
SZEDER Gábor <szeder.dev@gmail.com> wrote:
> On Wed, Jun 03, 2026 at 08:55:04AM +0200, Patrick Steinhardt wrote:
> > On Wed, Jun 03, 2026 at 10:12:22AM +0800, Weijie Yuan wrote:
> > > On Tue, Jun 02, 2026 at 08:09:55PM +0300, Tuomas Ahola wrote:
> > > > Huh? Doesn't MyFirstContribution speak *against* shallow threading?
> > > >
> > > > [...] make sure to replace it with the correct Message-ID for your
> > > > **previous cover letter** - that is, if you're sending v2, use the Message-ID
> > > > from v1; if you're sending v3, use the Message-ID from v2.
> > >
> > > I don't get it. Doesn't shallow threading means every following patches
> > > are replying to the cover letter? Replying to the previous one is
> > > --chain-reply-to, if I'm not mistaken.
> >
> > Shallow threading basically means that all patches are sent as a
> > response to the current cover letter, and the current cover letter is
> > always attached to the cover letter of the _first_ version.
>
> No, in Git shallow threading means that all patches are sent as a
> respose to the current cover letter, period. It has nothing to do
> with whether the current cover letter is sent as a reply to the cover
> letter of the first or the previous version.
>
That seems to be the established meaning of shallow threading, e.g. in
`git format-patch --thread=shallow`. Unfortunately there is a slight
terminology clash here.
Indeed, in B4 the config option `b4.send-same-thread = shallow` *is*
about whether the cover letter is a reply to v1 or v(n-1).
> > So this quote is definitely at odds with the configuration I have
> > proposed. It's actually quite surprising to me that we recommend deep
> > threading -- I personally find it extremely hard to navigate as the
> > nesting eventually gets way too deep.
>
> Deep threading means that every mail is a reply to the previous one.
> Again, it has nothing to do with the relation of the current cover
> letter and the previous cover letters.
>
> Therefore, we do not recommend deep threading.
>
In the usual meaning of the word that is the case. Most certainly
we don't recommend that kind of deep threading, but that wasn't the
question we were discussing here.
^ permalink raw reply
* Re: git hook question
From: Adrian Ratiu @ 2026-06-03 13:07 UTC (permalink / raw)
To: Jeff King, Wesley Schwengle; +Cc: git
In-Reply-To: <20260529210049.GC2628906@coredump.intra.peff.net>
On Fri, 29 May 2026, Jeff King <peff@peff.net> wrote:
> [re-adding list cc; let's let everyone benefit from the discussion]
>
> On Fri, May 29, 2026 at 04:14:33PM -0400, Wesley Schwengle wrote:
>
>> > I don't think the hooks themselves should need to be aware. If somebody
>> > is calling "git hook run pre-push" without providing arguments, they are
>> > breaking the contract to the hooks. You can get away with it if you know
>> > your particular hooks do not care about those arguments, but in the
>> > general case, what should a pre-push hook that _does_ care about the
>> > remote name do when it doesn't get any arguments? It's an error.
>>
>> Are they? The manual says this:
>>
>> git hook run has been designed to make it easy for tools which wrap Git to
>> configure and execute hooks using the Git hook infrastructure. It is
>> possible to provide arguments and stdin via the command line, as well as
>> specifying parallel or series execution if the user has provided multiple
>> hooks.
>>
>> Assuming your wrapper wants to support a hook named
>> "mywrapper-start-tests", you can have your users specify their hooks like
>> so:
>>
>> [hook "setup-test-dashboard"]
>> event = mywrapper-start-tests
>> command = ~/mywrapper/setup-dashboard.py --tap
>>
>> Then, in your mywrapper tool, you can invoke any users' configured
>> hooks by running:
>>
>> git hook run --allow-unknown-hook-name mywrapper-start-tests \
>> # providing something to stdin
>> --stdin some-tempfile-123 \
>> # execute multiple hooks in parallel
>> --jobs 3 \
>> # plus some arguments of your own...
>> -- \
>> --testname bar \
>> baz
>>
>> There is nothing about the contract of the hook, in fact, the way it is
>> written there isn't really a contract.
>
> This is a made-up hook, so it is up to the person defining
> mywrapper-start-tests to define that contract. And in this example,
> implicitly it takes whatever is in some-tempfile-123 on stdin, and
> --testname as an argument. What those mean would need to be communicated
> between the script invoking "git hook" and whoever is configuring hooks.
>
> I agree that is not made very clear in the documentation, though.
>
>> > So whether you are getting input as arguments or over stdin, it's
>> > probably something the hook needs to deal with (or at least think
>> > about).
>>
>> Right. I see where this is going. That means I think the examples in the
>> manual are incorrect, no, that's harsh, it could be stated more clearly in
>> git-hook(1).
>>
>> Examples like this:
>>
>> > [hook "linter"]
>> > event = pre-commit
>> > command = ~/bin/linter --cpp20
>>
>> seem to indicate: Any script can be run as a hook, the fact it needs to
>> respect the native hook structure isn't mentioned. This is mentioned:
>
> That example is OK-ish, in the sense that pre-commit does not take any
> arguments or receive anything on stdin. So you really can invoke
> whatever program you like (though it needs to understand how to use Git
> commands to look at what is staged in the index). So the details of
> "~/bin/linter" are doing a lot of the heavy lifting here, which is left
> unsaid.
>
> But the later example that adds "event = pre-push" is actively
> misleading. How does the ~/bin/linter script even know in which context
> it's being run? In the real world you are more likely to invoke a script
> that is aware it is a Git hook and can react accordingly.
>
> So I suspect there is a lot of room for expanding the documentation and
> explaining some of these gotchas. +cc Adrian, who wrote these docs, for
> visibility.
Yes, there is a lot of room for improvements everywhere, especially in
the documentation.
Patches are very much welcome to expand on or correct hook-related
issues. :)
BTW the git hook command is also just a very basic tool for testing, it
needs much attention and more additions. It is obviously not
feature-complete or bug-free.
Some historical context for the curious:
This area of work was blocked for almost a decade because people tried to
find a perfect/complete solution in one go, with complex patch series
reaching even 36-38 review iterations for a single series which went
nowhere, was regressing, was hard to review, you get the idea.
So I tried to enable a simplified incremental development approach,
reusing existing APIs & mechanisms, to allow more people to contribute
smaller patches which are also easier to review, test and so on.
P.S:
This also reminds me, I don't think it's documented anywhere that the
proc-receive hook is not using hook.[ch], so it cannot be specified via
configs yet like pre-receive and other similar server hooks.
I actually have a collegue at Collabora working on converting
proc-receive so we can remove some deprecated APIs and also clean up
some external hook_exists() calls which are now redundant because they
are handled by the unified hook.c implementation.
^ permalink raw reply
* Re: [PATCH v2 01/18] odb/source-loose: move loose source into "odb/" subsystem
From: Karthik Nayak @ 2026-06-03 13:07 UTC (permalink / raw)
To: Patrick Steinhardt, git; +Cc: Junio C Hamano
In-Reply-To: <20260601-b4-pks-odb-source-loose-v2-1-90ff159430af@pks.im>
[-- Attachment #1: Type: text/plain, Size: 501 bytes --]
Patrick Steinhardt <ps@pks.im> writes:
> In subsequent patches we'll be turning `struct odb_source_loose` into a
> proper `struct odb_source`. As a first step towards this goal, move its
s/its/this?
> struct out of "object-file.c" and into "odb/source-loose.c".
>
> This detaches the implementation of the loose object source from the
> generic object file code, following the same convention already used by
> the "files" and "in-memory" sources.
>
> No functional changes are intended.
>
[snip]
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]
^ permalink raw reply
* Re: [PATCH v2 0/8] setup: centralize object database creation
From: Karthik Nayak @ 2026-06-03 13:04 UTC (permalink / raw)
To: Patrick Steinhardt, git; +Cc: Kristoffer Haugsbakk, Junio C Hamano
In-Reply-To: <20260526-b4-pks-setup-centralize-odb-creation-v2-0-2fa5b385c13e@pks.im>
[-- Attachment #1: Type: text/plain, Size: 1215 bytes --]
Patrick Steinhardt <ps@pks.im> writes:
> Hi,
>
> this small patch series refactors the logic for how we discover and
> configure repositories. Most importantly, this involves the following
> two steps:
>
> 1. We unify the logic to apply the repository format, which is
> currently open-coded across multiple sites. These sites have
> already diverged, where some repository extensions are not
> consistently applied.
>
> 2. We then centralize creation of the object database to happen at the
> same time we apply the repository format.
>
> The end result is that we apply the repository format exactly once, and
> that's also the point in time where we can finalize the setup of the
> repo's data structures as we know about all details of the repo at that
> time. Ultimately, this makes it trivial to introduce the "objectStorage"
> extension, even though that's not part of this patch series.
>
> The series is built on top of aec3f58750 (Sync with 'maint', 2026-05-21)
> with ps/setup-wo-the-repository at df69f40c34 (setup: stop using
> `the_repository` in `init_db()`, 2026-05-19) merged into it.
>
Apart from some questions/comments, the series looks good. Thanks
- Karthik
[snip]
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]
^ permalink raw reply
* Re: [PATCH] git-gui: silence install recipes under "make -s"
From: Johannes Sixt @ 2026-06-03 12:58 UTC (permalink / raw)
To: Harald Nordgren; +Cc: git, Harald Nordgren via GitGitGadget
In-Reply-To: <pull.2318.git.git.1780477489662.gitgitgadget@gmail.com>
Am 03.06.26 um 11:04 schrieb Harald Nordgren via GitGitGadget:
> From: Harald Nordgren <haraldnordgren@gmail.com>
>
> The split install/uninstall recipes embed "echo" calls that fire
> even under "make -s", so install still prints "DEST /path" and
> "INSTALL 644 about.tcl" banners. The existing "-s" block only
> clears QUIET_GEN.
Good catch.
> Wrap the whole "ifndef V" block in the canonical "-s" guard from
> shared.mak, and drop the now-redundant narrow block.
Can we please mention shared.mak in a way that doesn't assume that this
patch was made in the Git repository?
> +ifneq ($(findstring s,$(firstword -$(MAKEFLAGS))),s)
> ifndef V
> QUIET = @
> QUIET_GEN = $(QUIET)echo ' ' GEN '$@' &&
> @@ -89,6 +90,7 @@ ifndef V
> REMOVE_F0 = dst=
> REMOVE_F1 = && echo ' ' REMOVE `basename "$$dst"` && $(RM_RF) "$$dst"
> endif
> +endif
> -ifeq ($(findstring $(firstword -$(MAKEFLAGS)),s),s)
I would have expected that the old and the new condition expressions
only differ in the ifeq vs. ifneq, but they are different in more than
that. Assuming that the new expression is correct, was the old one
incorrect?
> -QUIET_GEN =
> -endif
-- Hannes
^ permalink raw reply
* Re: [PATCH v2 5/8] setup: stop creating the object database in `setup_git_env()`
From: Karthik Nayak @ 2026-06-03 12:57 UTC (permalink / raw)
To: Patrick Steinhardt, git; +Cc: Kristoffer Haugsbakk, Junio C Hamano
In-Reply-To: <20260526-b4-pks-setup-centralize-odb-creation-v2-5-2fa5b385c13e@pks.im>
[-- Attachment #1: Type: text/plain, Size: 5708 bytes --]
Patrick Steinhardt <ps@pks.im> writes:
> In the preceding commit we have stopped creating the object database in
> `repo_set_gitdir()`. But the logic is still somewhat confusing as we
> still end up creating it conditionally in `setup_git_dir()`, which is
> called multiple times.
>
> Drop the conditional logic and instead create the object database in all
> places where we have discovered and configured a repository.
>
> This leads to even more duplication than we already had in the preceding
> commit, but an alert reader may notice that we now (almost) always call
> `odb_new()` directly before having called `apply_repository_format()`.
> The only exception to this is `setup_git_directory_gently()`, where we
> also call the function when _not_ applying the repository format. This
> will be fixed in the next commit, and once that's done we can then unify
> creation of the object database into `apply_repository_format()`.
>
> Signed-off-by: Patrick Steinhardt <ps@pks.im>
> ---
> setup.c | 37 ++++++++++++++++++++++++++-----------
> 1 file changed, 26 insertions(+), 11 deletions(-)
>
> diff --git a/setup.c b/setup.c
> index 3bd3f6c592..0dc9fe4565 100644
> --- a/setup.c
> +++ b/setup.c
> @@ -1035,8 +1035,7 @@ const char *read_gitfile_gently(const char *path, int *return_error_code)
> }
>
> static void setup_git_env_internal(struct repository *repo,
> - const char *git_dir,
> - bool skip_initializing_odb)
> + const char *git_dir)
> {
> char *git_replace_ref_base;
> const char *shallow_file;
> @@ -1053,10 +1052,6 @@ static void setup_git_env_internal(struct repository *repo,
> repo_set_gitdir(repo, git_dir, &args);
> strvec_clear(&to_free);
>
> - if (!skip_initializing_odb)
> - repo->objects = odb_new(repo, getenv_safe(&to_free, DB_ENVIRONMENT),
> - getenv_safe(&to_free, ALTERNATE_DB_ENVIRONMENT));
> -
> if (getenv(NO_REPLACE_OBJECTS_ENVIRONMENT))
> disable_replace_refs();
> replace_ref_base = getenv(GIT_REPLACE_REF_BASE_ENVIRONMENT);
> @@ -1072,10 +1067,10 @@ static void setup_git_env_internal(struct repository *repo,
> fetch_if_missing = 0;
> }
>
> -static void set_git_dir_1(struct repository *repo, const char *path, bool skip_initializing_odb)
> +static void set_git_dir_1(struct repository *repo, const char *path)
> {
> xsetenv(GIT_DIR_ENVIRONMENT, path, 1);
> - setup_git_env_internal(repo, path, skip_initializing_odb);
> + setup_git_env_internal(repo, path);
> }
>
> static void update_relative_gitdir(const char *name UNUSED,
> @@ -1089,7 +1084,7 @@ static void update_relative_gitdir(const char *name UNUSED,
> trace_printf_key(&trace_setup_key,
> "setup: move $GIT_DIR to '%s'",
> path);
> - set_git_dir_1(repo, path, true);
> + set_git_dir_1(repo, path);
Since we were providing `true`, we didn't have to initialize the odb
here. Makes sense.
> free(path);
> }
>
> @@ -1102,7 +1097,7 @@ static void set_git_dir(struct repository *repo, const char *path, int make_real
> path = realpath.buf;
> }
>
> - set_git_dir_1(repo, path, false);
> + set_git_dir_1(repo, path);
>
Huh. I was expecting the odb to be setup here.
> if (!is_absolute_path(path))
> chdir_notify_register(NULL, update_relative_gitdir, repo);
>
> @@ -1879,8 +1874,15 @@ const char *enter_repo(struct repository *repo, const char *path, unsigned flags
> }
>
> if (is_git_directory(".")) {
> + struct strvec to_free = STRVEC_INIT;
> +
> set_git_dir(repo, ".", 0);
> + repo->objects = odb_new(repo,
> + getenv_safe(&to_free, DB_ENVIRONMENT),
> + getenv_safe(&to_free, ALTERNATE_DB_ENVIRONMENT));
> check_and_apply_repository_format(repo, NULL);
> +
> + strvec_clear(&to_free);
> return path;
> }
>
> @@ -2032,13 +2034,19 @@ const char *setup_git_directory_gently(struct repository *repo, int *nongit_ok)
> startup_info->have_repository ||
> /* GIT_DIR_EXPLICIT */
> getenv(GIT_DIR_ENVIRONMENT)) {
> + struct strvec to_free = STRVEC_INIT;
> +
> if (!repo->gitdir) {
> const char *gitdir = getenv(GIT_DIR_ENVIRONMENT);
> if (!gitdir)
> gitdir = DEFAULT_GIT_DIR_ENVIRONMENT;
> - setup_git_env_internal(repo, gitdir, false);
> + setup_git_env_internal(repo, gitdir);
> }
>
> + repo->objects = odb_new(repo,
> + getenv_safe(&to_free, DB_ENVIRONMENT),
> + getenv_safe(&to_free, ALTERNATE_DB_ENVIRONMENT));
> +
>
Okay, now it makes sense. we move the ODB creations to layers above.
> if (startup_info->have_repository) {
> struct strbuf err = STRBUF_INIT;
>
> @@ -2048,6 +2056,8 @@ const char *setup_git_directory_gently(struct repository *repo, int *nongit_ok)
> clear_repository_format(&repo_fmt);
> strbuf_release(&err);
> }
> +
> + strvec_clear(&to_free);
> }
> /*
> * Since precompose_string_if_needed() needs to look at
> @@ -2796,6 +2806,7 @@ int init_db(struct repository *repo,
> int exist_ok = flags & INIT_DB_EXIST_OK;
> char *original_git_dir = real_pathdup(git_dir, 1);
> struct repository_format repo_fmt = REPOSITORY_FORMAT_INIT;
> + struct strvec to_free = STRVEC_INIT;
>
> if (real_git_dir) {
> struct stat st;
> @@ -2816,6 +2827,9 @@ int init_db(struct repository *repo,
> }
> startup_info->have_repository = 1;
>
> + repo->objects = odb_new(repo, getenv_safe(&to_free, DB_ENVIRONMENT),
> + getenv_safe(&to_free, ALTERNATE_DB_ENVIRONMENT));
> +
> /*
> * Check to see if the repository version is right.
> * Note that a newly created repository does not have
> @@ -2879,6 +2893,7 @@ int init_db(struct repository *repo,
> }
>
> clear_repository_format(&repo_fmt);
> + strvec_clear(&to_free);
> free(original_git_dir);
> return 0;
> }
>
> --
> 2.54.0.926.g75ba10bac6.dirty
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]
^ permalink raw reply
* Re: [PATCH v2 4/8] repository: stop initializing the object database in `repo_set_gitdir()`
From: Karthik Nayak @ 2026-06-03 12:49 UTC (permalink / raw)
To: Patrick Steinhardt, git; +Cc: Kristoffer Haugsbakk, Junio C Hamano
In-Reply-To: <20260526-b4-pks-setup-centralize-odb-creation-v2-4-2fa5b385c13e@pks.im>
[-- Attachment #1: Type: text/plain, Size: 2655 bytes --]
Patrick Steinhardt <ps@pks.im> writes:
[snip]
> diff --git a/repository.c b/repository.c
> index 58a13f7c4f..2c2395105f 100644
> --- a/repository.c
> +++ b/repository.c
> @@ -181,12 +181,6 @@ void repo_set_gitdir(struct repository *repo,
> free(old_gitdir);
>
> repo_set_commondir(repo, o->commondir);
> -
> - if (!repo->objects)
> - repo->objects = odb_new(repo, o->object_dir, o->alternate_db);
> - else if (!o->skip_initializing_odb)
> - BUG("cannot reinitialize an already-initialized object directory");
> -
This always confuses me, so we were creating the odb even if
`o->skip_initializing_odb` was set to true, if `repo->objects` didn't
exist. Weird.
> repo->disable_ref_updates = o->disable_ref_updates;
>
> expand_base_dir(&repo->graft_file, o->graft_file,
> @@ -302,6 +296,8 @@ int repo_init(struct repository *repo,
> goto error;
> }
>
> + repo->objects = odb_new(repo, NULL, NULL);
> +
> if (worktree)
> repo_set_worktree(repo, worktree);
>
> diff --git a/repository.h b/repository.h
> index c3ec0f4b79..36e2db2633 100644
> --- a/repository.h
> +++ b/repository.h
> @@ -221,12 +221,9 @@ const char *repo_get_work_tree(struct repository *repo);
> */
> struct set_gitdir_args {
> const char *commondir;
> - const char *object_dir;
> const char *graft_file;
> const char *index_file;
> - const char *alternate_db;
> bool disable_ref_updates;
> - bool skip_initializing_odb;
> };
>
> void repo_set_gitdir(struct repository *repo, const char *root,
> diff --git a/setup.c b/setup.c
> index c5015923f1..3bd3f6c592 100644
> --- a/setup.c
> +++ b/setup.c
> @@ -1045,17 +1045,18 @@ static void setup_git_env_internal(struct repository *repo,
> struct strvec to_free = STRVEC_INIT;
>
> args.commondir = getenv_safe(&to_free, GIT_COMMON_DIR_ENVIRONMENT);
> - args.object_dir = getenv_safe(&to_free, DB_ENVIRONMENT);
> args.graft_file = getenv_safe(&to_free, GRAFT_ENVIRONMENT);
> args.index_file = getenv_safe(&to_free, INDEX_ENVIRONMENT);
> - args.alternate_db = getenv_safe(&to_free, ALTERNATE_DB_ENVIRONMENT);
> if (getenv(GIT_QUARANTINE_ENVIRONMENT))
> args.disable_ref_updates = true;
> - args.skip_initializing_odb = skip_initializing_odb;
>
> repo_set_gitdir(repo, git_dir, &args);
> strvec_clear(&to_free);
>
> + if (!skip_initializing_odb)
> + repo->objects = odb_new(repo, getenv_safe(&to_free, DB_ENVIRONMENT),
> + getenv_safe(&to_free, ALTERNATE_DB_ENVIRONMENT));
> +
Now this makes a lot more sense.
> if (getenv(NO_REPLACE_OBJECTS_ENVIRONMENT))
> disable_replace_refs();
> replace_ref_base = getenv(GIT_REPLACE_REF_BASE_ENVIRONMENT);
>
> --
> 2.54.0.926.g75ba10bac6.dirty
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]
^ permalink raw reply
* Re: [PATCH v2 3/8] setup: deduplicate logic to apply repository format
From: Karthik Nayak @ 2026-06-03 12:43 UTC (permalink / raw)
To: Patrick Steinhardt, git; +Cc: Kristoffer Haugsbakk, Junio C Hamano
In-Reply-To: <20260526-b4-pks-setup-centralize-odb-creation-v2-3-2fa5b385c13e@pks.im>
[-- Attachment #1: Type: text/plain, Size: 4439 bytes --]
Patrick Steinhardt <ps@pks.im> writes:
> After having discovered the repository format we then apply it to the
> repository so that it knows to use the proper repository extensions. The
> logic to apply the format is duplicated across three callsites, which
> makes it rather painfull to add new extensions.
>
> Introduce a new function `apply_repository_format()` that takes a repo
> and applies a given format to it and adapt all callsites to use it.
> While at it, rename `check_repository_format()` to clarify that it
> doesn't only _check_ the format, but that it also applies it.
>
> Signed-off-by: Patrick Steinhardt <ps@pks.im>
> ---
> repository.c | 31 +++++++-------------
> setup.c | 93 ++++++++++++++++++++++++++++++++----------------------------
> setup.h | 9 ++++++
> 3 files changed, 70 insertions(+), 63 deletions(-)
>
> diff --git a/repository.c b/repository.c
> index db57b8308b..58a13f7c4f 100644
> --- a/repository.c
> +++ b/repository.c
> @@ -262,8 +262,8 @@ void repo_set_worktree(struct repository *repo, const char *path)
> trace2_def_repo(repo);
> }
>
> -static int read_and_verify_repository_format(struct repository_format *format,
> - const char *commondir)
> +static int read_repository_format_from_commondir(struct repository_format *format,
> + const char *commondir)
Nit: The commit explicitly calls out one rename, but this one wasn't.
> {
> int ret = 0;
> struct strbuf sb = STRBUF_INIT;
> @@ -272,11 +272,6 @@ static int read_and_verify_repository_format(struct repository_format *format,
> read_repository_format(format, sb.buf);
> strbuf_reset(&sb);
>
> - if (verify_repository_format(format, &sb) < 0) {
> - warning("%s", sb.buf);
> - ret = -1;
> - }
> -
So we remove this, so that the callee would independently verify the
format I assume.
Edit: seems like we call verify_repository_format() within
apply_repository_format() and the latter is called by the callee.
> strbuf_release(&sb);
> return ret;
> }
> @@ -290,6 +285,8 @@ int repo_init(struct repository *repo,
> const char *worktree)
> {
> struct repository_format format = REPOSITORY_FORMAT_INIT;
> + struct strbuf err = STRBUF_INIT;
> +
> memset(repo, 0, sizeof(*repo));
>
> initialize_repository(repo);
> @@ -297,21 +294,13 @@ int repo_init(struct repository *repo,
> if (repo_init_gitdir(repo, gitdir))
> goto error;
>
> - if (read_and_verify_repository_format(&format, repo->commondir))
> + if (read_repository_format_from_commondir(&format, repo->commondir))
> goto error;
>
> - repo_set_hash_algo(repo, format.hash_algo);
> - repo_set_compat_hash_algo(repo, format.compat_hash_algo);
> - repo_set_ref_storage_format(repo, format.ref_storage_format,
> - format.ref_storage_payload);
> - repo->repository_format_worktree_config = format.worktree_config;
> - repo->repository_format_relative_worktrees = format.relative_worktrees;
> - repo->repository_format_precious_objects = format.precious_objects;
> - repo->repository_format_submodule_path_cfg = format.submodule_path_cfg;
> -
> - /* take ownership of format.partial_clone */
I see that we now do an xstrdup for format.partial_clone, meaning we
have our own memory segment to care about. Do we have to worry about
format.partial_clone not being free'd?
> - repo->repository_format_partial_clone = format.partial_clone;
> - format.partial_clone = NULL;
> + if (apply_repository_format(repo, &format, &err) < 0) {
> + warning("%s", err.buf);
> + goto error;
> + }
>
> if (worktree)
> repo_set_worktree(repo, worktree);
[snip]
> diff --git a/setup.h b/setup.h
> index 9409326fe4..5ed92f53fa 100644
> --- a/setup.h
> +++ b/setup.h
> @@ -221,6 +221,15 @@ void clear_repository_format(struct repository_format *format);
> int verify_repository_format(const struct repository_format *format,
> struct strbuf *err);
>
> +/*
> + * Apply the given repository format to the repo. This initializes extensions
> + * and basic data structures required for normal operation. Returns 0 on
> + * success, a negative error code otherwise.
> + */
Nit: perhaps we should also mention that we verify the format?
> +int apply_repository_format(struct repository *repo,
> + const struct repository_format *format,
> + struct strbuf *err);
> +
> const char *get_template_dir(const char *option_template);
>
> #define INIT_DB_QUIET (1 << 0)
>
> --
> 2.54.0.926.g75ba10bac6.dirty
p
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]
^ permalink raw reply
* Re: Git for Windows Failing to Clone
From: Johannes Schindelin @ 2026-06-03 12:42 UTC (permalink / raw)
To: Dylan Carlyle; +Cc: git
In-Reply-To: <CAJKusd6WJUUVhbyN_-XHkGWVYeNe_=K2U3tZoezPWFG3+OG_zw@mail.gmail.com>
Hi Dylan,
On Wed, 3 Jun 2026, Dylan Carlyle wrote:
> Thank you for filling out a Git bug report!
> Please answer the following questions to help us understand your issue.
>
> What did you do before the bug happened? (Steps to reproduce your issue):
>
> Ran git clone user@ip_addres:repo
>
> What did you expect to happen? (Expected behavior):
>
> The repo to be cloned
>
> What happened instead? (Actual behavior):
>
> remote: Enumerating objects: 57873, done.
> remote: Counting objects: 100% (57873/57873), done.
> remote: Compressing objects: 100% (32002/32002), done.
> fatal: pack has bad object at offset 460179591: inflate returned 1
> fatal: fetch-pack: invalid index-pack output
>
> What's different between what you expected and what actually happened?
>
> The clone never finishes on Windows.
>
> Anything else you want to add:
>
> Git version on the remote server is 2.47.3
> This works fine from Linux but fails on Windows.
I guess that this is virtually identical to
https://github.com/git-for-windows/git/issues/6265
Since it works from Linux, but not from Windows, I strongly suspect the
problem to be related to that vexing 2GB/4GB problem induced by Git's
continued use of `unsigned long` instead of `size_t`, which I am slowly
(_very_ slowly) trying to address.
You can find out whether that effort might help you, by running `git repo
structure` on the original repository (I'd expect a blob whose unpacked
size is larger than 4GB).
Ciao,
Johannes
>
> [System Info]
> git version:
> git version 2.54.0.windows.1
> cpu: x86_64
> built from commit: 2b8a3ab140826ac423c2845ef81d4c6ac4f7bf3c
> sizeof-long: 4
> sizeof-size_t: 8
> shell-path: D:/git-sdk-64-build-installers/usr/bin/sh
> rust: disabled
> feature: fsmonitor--daemon
>
> --
> Kind Regards,
>
> Dylan Carlyle
> REFTEK Systems, Inc.
> Systems Administrator
>
>
^ permalink raw reply
* Re: [PATCH v3] index-pack: retain child bases in delta cache
From: Derrick Stolee @ 2026-06-03 12:24 UTC (permalink / raw)
To: Arijit Banerjee via GitGitGadget, git
Cc: Ævar Arnfjörð Bjarmason, Junio C Hamano, Jeff King,
Arijit Banerjee, Arijit Banerjee
In-Reply-To: <pull.2131.v3.git.1780445118653.gitgitgadget@gmail.com>
On 6/2/26 8:05 PM, Arijit Banerjee via GitGitGadget wrote:
> Changes since v2:
>
> * Addressed Jeff King's review question by releasing cached base data
> after all direct children have been dispatched, while keeping the
> existing subtree bookkeeping intact.
> * Re-ran t/t5302-pack-index.sh, p5302-pack-index.sh, and end-to-end
> full clone spot checks with the precise-release version.
...
> +static int base_data_has_remaining_direct_children(struct base_data *c)
> +{
> + return c->ref_first <= c->ref_last ||
> + c->ofs_first <= c->ofs_last;
> +}
> +
I'm glad you were able to find some bookkeeping that already exists to
help with this decision.
> static void prune_base_data(struct base_data *retain)
> {
> struct list_head *pos;
> @@ -1201,8 +1207,12 @@ static void *threaded_second_pass(void *data)
> }
>
> work_lock();
> - if (parent)
> + if (parent) {
> parent->retain_data--;
> + if (!parent->retain_data &&
> + !base_data_has_remaining_direct_children(parent))
> + free_base_data(parent);
> + }
This appears like the correct place to do this.
> if (child && child->data) {
> /*
> @@ -1212,7 +1222,6 @@ static void *threaded_second_pass(void *data)
> list_add(&child->list, &work_head);
> base_cache_used += child->size;
> prune_base_data(NULL);
> - free_base_data(child);
And still we don't want this universal free.
Thanks for re-running your performance numbers after this change. I didn't
see any significant difference in the relative changes.
I don't think we have a way of measuring "memory pressure" during the
performance test suite. Did you see any evidence that this change has the
intended effect of reducing process memory proactively instead of relying
on the cache evictions?
Thanks,
-Stolee
^ permalink raw reply
* Re: [PATCH 1/2] b4: introduce configuration for the Git project
From: Weijie Yuan @ 2026-06-03 12:23 UTC (permalink / raw)
To: SZEDER Gábor; +Cc: Patrick Steinhardt, Tuomas Ahola, git, Junio C Hamano
In-Reply-To: <aiAK9eLvew+mgWt+@szeder.dev>
On Wed, Jun 03, 2026 at 01:07:33PM +0200, SZEDER Gábor wrote:
> No, in Git shallow threading means that all patches are sent as a
> respose to the current cover letter, period. It has nothing to do
> with whether the current cover letter is sent as a reply to the cover
> letter of the first or the previous version.
Thanks, agree
> Deep threading means that every mail is a reply to the previous one.
> Again, it has nothing to do with the relation of the current cover
> letter and the previous cover letters.
>
> Therefore, we do not recommend deep threading.
So the same reason with Patrick?
Thanks
^ permalink raw reply
* Re: [PATCH v2 1/8] t0001: plug test gaps for git-init(1) with GIT_OBJECT_DIRECTORY
From: Karthik Nayak @ 2026-06-03 12:22 UTC (permalink / raw)
To: Patrick Steinhardt, git; +Cc: Kristoffer Haugsbakk, Junio C Hamano
In-Reply-To: <20260526-b4-pks-setup-centralize-odb-creation-v2-1-2fa5b385c13e@pks.im>
[-- Attachment #1: Type: text/plain, Size: 1861 bytes --]
Patrick Steinhardt <ps@pks.im> writes:
> In subsequent commits we'll rework how we set up the repository. This is
> a somewhat intricate and thus fragile sequence; there's many things that
> can go subtly wrong, and there are lots of interesting interactions that
> one can discover.
>
> One such discovered edge case was the interaction between git-init(1)
> and the "GIT_OBJECT_DIRECTORY" environment variable. When set, the
> behaviour is that the object directory should be created at the path
> that the variable points to. This behaviour is documented as such in
> its man page:
>
> If the object storage directory is specified via the
> GIT_OBJECT_DIRECTORY environment variable then the sha1 directories
> are created underneath; otherwise, the default $GIT_DIR/objects
> directory is used.
>
> Curiously enough though we don't seem to have any tests that exercise
> this directly, and thus a subsequent commit inadvertently would have
> broken this expectation.
>
> Plug this test gap.
>
> Signed-off-by: Patrick Steinhardt <ps@pks.im>
> ---
> t/t0001-init.sh | 10 ++++++++++
> 1 file changed, 10 insertions(+)
>
> diff --git a/t/t0001-init.sh b/t/t0001-init.sh
> index e4d32bb4d2..e89feca544 100755
> --- a/t/t0001-init.sh
> +++ b/t/t0001-init.sh
> @@ -980,4 +980,14 @@ test_expect_success 're-init reads matching includeIf.onbranch' '
> test_cmp expect err
> '
>
> +test_expect_success 'init honors GIT_OBJECT_DIRECTORY' '
> + test_when_finished "rm -rf init-objdir custom-odb" &&
> + mkdir custom-odb &&
> + env GIT_OBJECT_DIRECTORY="$(pwd)/custom-odb" \
> + git init init-objdir &&
> + test_path_is_missing init-objdir/.git/objects/pack &&
> + test_path_is_dir custom-odb/pack &&
> + test_path_is_dir custom-odb/info
> +'
> +
I was surprised to find that such a small number of tests ever use
GIT_OBJECT_DIRECTORY. This looks good.
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]
^ 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