* Re: [PATCH v2 4/8] refs: unregister reference stores from "chdir_notify"
From: Patrick Steinhardt @ 2026-06-18 6:54 UTC (permalink / raw)
To: Justin Tobler; +Cc: git, Karthik Nayak, Jeff King
In-Reply-To: <ajLdIY_fxkKDTBaW@denethor>
On Wed, Jun 17, 2026 at 01:02:23PM -0500, Justin Tobler wrote:
> On 26/06/15 03:56PM, Patrick Steinhardt wrote:
> > When creating reference stores we register them with the "chdir_notify"
> > subsystem. This is required because some of the paths we track may be
> > relative paths, so we have to reparent them in case the current working
> > directory changes.
> >
> > But while we register the reference stores, we never unregister them.
> > This can have multiple outcomes:
> >
> > - For a repository's main reference database we essentially keep the
> > pointer alive. We never free that database, either, and our leak
> > checker doesn't notice because it's still registered.
> >
> > - For submodule and worktree reference databases we do eventually free
> > them in `repo_clear()`, so we may keep pointers to free'd memory
> > registered. We never notice though as we don't tend to chdir around
> > in the middle of the process.
> >
> > We never noticed either of these symptoms, but they are obviously bad.
> >
> > Partially fix those issues by unregistering the reference stores when
> > releasing them. The leak of the main reference database will be fixed in
> > a subsequent commit.
> >
> > Note that this requires us to use `chdir_notify_register()` instead of
> > `chdir_notify_reparent()`, as there is no infrastructure to unregister the
> > latter. It ultimately doesn't matter much though: in a subsequent commit
> > we'll drop this infrastructure completely. We merely require this step
> > here so that we can fix the memory leaks ahead of time.
>
> Since this version of the series dropped the last patch which stopped
> using `chdir_notify_reparent()`, does the log message here need to be
> updated?
True, will do.
Patrick
^ permalink raw reply
* [PATCH v3 2/8] setup: stop applying repository format twice
From: Patrick Steinhardt @ 2026-06-18 6:54 UTC (permalink / raw)
To: git; +Cc: Karthik Nayak, Jeff King, Justin Tobler
In-Reply-To: <20260618-b4-pks-refs-avoid-chdir-notify-reparent-v3-0-2a5669e8f486@pks.im>
When discovering the repository in "setup.c" we apply the final
repository format multiple times:
- Once via `repository_format_configure()`, where we apply the hash
algorithm and ref storage format to both `struct repository_format`
and `struct repository`.
- And once via `apply_repository_format()`, where we apply these two
settings from `struct repository_format` to `struct repository`.
With the current flow both of these are in fact necessary. But this is
only because we call `repository_format_configure()` after we have
called `apply_repository_format()`. Consequently, if we only changed the
repository format in `repository_format_configure()` it would never
propagate to the repository.
Refactor the code so that we first configure the repository format
before applying it to the repository so that we can stop setting the
hash and reference storage format multiple times.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
setup.c | 9 ++-------
1 file changed, 2 insertions(+), 7 deletions(-)
diff --git a/setup.c b/setup.c
index a9db1f2c23..2748155964 100644
--- a/setup.c
+++ b/setup.c
@@ -2710,8 +2710,7 @@ static int read_default_format_config(const char *key, const char *value,
return ret;
}
-static void repository_format_configure(struct repository *repo,
- struct repository_format *repo_fmt,
+static void repository_format_configure(struct repository_format *repo_fmt,
int hash, enum ref_storage_format ref_format)
{
struct default_format_config cfg = {
@@ -2748,7 +2747,6 @@ static void repository_format_configure(struct repository *repo,
} else if (cfg.hash != GIT_HASH_UNKNOWN) {
repo_fmt->hash_algo = cfg.hash;
}
- repo_set_hash_algo(repo, repo_fmt->hash_algo);
env = getenv("GIT_DEFAULT_REF_FORMAT");
if (repo_fmt->version >= 0 &&
@@ -2786,9 +2784,6 @@ static void repository_format_configure(struct repository *repo,
free(backend);
}
-
- repo_set_ref_storage_format(repo, repo_fmt->ref_storage_format,
- repo_fmt->ref_storage_payload);
}
int init_db(struct repository *repo,
@@ -2830,10 +2825,10 @@ int init_db(struct repository *repo,
* is an attempt to reinitialize new repository with an old tool.
*/
check_repository_format_gently(repo_get_git_dir(repo), &repo_fmt, NULL);
+ repository_format_configure(&repo_fmt, hash, ref_storage_format);
if (apply_repository_format(repo, &repo_fmt, APPLY_REPOSITORY_FORMAT_HONOR_ENV, &err) < 0)
die("%s", err.buf);
startup_info->have_repository = 1;
- repository_format_configure(repo, &repo_fmt, hash, ref_storage_format);
/*
* Ensure `core.hidedotfiles` is processed. This must happen after we
--
2.55.0.rc0.786.g65d90a0328.dirty
^ permalink raw reply related
* [PATCH v3 3/8] setup: don't apply "GIT_REFERENCE_BACKEND" without a repository
From: Patrick Steinhardt @ 2026-06-18 6:54 UTC (permalink / raw)
To: git; +Cc: Karthik Nayak, Jeff King, Justin Tobler
In-Reply-To: <20260618-b4-pks-refs-avoid-chdir-notify-reparent-v3-0-2a5669e8f486@pks.im>
When discovering a repository we eventually also apply the
"GIT_REFERENCE_BACKEND" environment variable to the repository. There's
two problems with that:
- We do this unconditionally, which is rather pointless: we really
only have to configure the repository when we have found one.
- We have already applied the repository format at that point in time,
so we need to manually reapply it.
Move the logic around so that we only apply the environment variable
when a repository was discovered. This also allows us to drop the
explcit call to `repo_set_ref_storage_format()` because we now adjust
the format before we apply it via `apply_repository_format()`.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
setup.c | 39 +++++++++++++++++++--------------------
1 file changed, 19 insertions(+), 20 deletions(-)
diff --git a/setup.c b/setup.c
index 2748155964..79125db565 100644
--- a/setup.c
+++ b/setup.c
@@ -1906,7 +1906,6 @@ const char *setup_git_directory_gently(struct repository *repo, int *nongit_ok)
static struct strbuf cwd = STRBUF_INIT;
struct strbuf dir = STRBUF_INIT, gitdir = STRBUF_INIT, report = STRBUF_INIT;
const char *prefix = NULL;
- const char *ref_backend_uri;
struct repository_format repo_fmt = REPOSITORY_FORMAT_INIT;
/*
@@ -2032,6 +2031,25 @@ const char *setup_git_directory_gently(struct repository *repo, int *nongit_ok)
if (startup_info->have_repository) {
struct strbuf err = STRBUF_INIT;
+ const char *ref_backend_uri;
+
+ /*
+ * The env variable should override the repository config
+ * for 'extensions.refStorage'.
+ */
+ ref_backend_uri = getenv(GIT_REFERENCE_BACKEND_ENVIRONMENT);
+ if (ref_backend_uri) {
+ char *format;
+
+ free(repo_fmt.ref_storage_payload);
+
+ parse_reference_uri(ref_backend_uri, &format, &repo_fmt.ref_storage_payload);
+ repo_fmt.ref_storage_format = ref_storage_format_by_name(format);
+ if (repo_fmt.ref_storage_format == REF_STORAGE_FORMAT_UNKNOWN)
+ die(_("unknown ref storage format: '%s'"), format);
+
+ free(format);
+ }
if (apply_repository_format(repo, &repo_fmt,
APPLY_REPOSITORY_FORMAT_HONOR_ENV, &err) < 0)
@@ -2057,25 +2075,6 @@ const char *setup_git_directory_gently(struct repository *repo, int *nongit_ok)
setenv(GIT_PREFIX_ENVIRONMENT, "", 1);
}
- /*
- * The env variable should override the repository config
- * for 'extensions.refStorage'.
- */
- ref_backend_uri = getenv(GIT_REFERENCE_BACKEND_ENVIRONMENT);
- if (ref_backend_uri) {
- char *backend, *payload;
- enum ref_storage_format format;
-
- parse_reference_uri(ref_backend_uri, &backend, &payload);
- format = ref_storage_format_by_name(backend);
- if (format == REF_STORAGE_FORMAT_UNKNOWN)
- die(_("unknown ref storage format: '%s'"), backend);
- repo_set_ref_storage_format(repo, format, payload);
-
- free(backend);
- free(payload);
- }
-
setup_original_cwd(repo);
strbuf_release(&dir);
--
2.55.0.rc0.786.g65d90a0328.dirty
^ permalink raw reply related
* [PATCH v3 4/8] refs: unregister reference stores from "chdir_notify"
From: Patrick Steinhardt @ 2026-06-18 6:54 UTC (permalink / raw)
To: git; +Cc: Karthik Nayak, Jeff King, Justin Tobler
In-Reply-To: <20260618-b4-pks-refs-avoid-chdir-notify-reparent-v3-0-2a5669e8f486@pks.im>
When creating reference stores we register them with the "chdir_notify"
subsystem. This is required because some of the paths we track may be
relative paths, so we have to reparent them in case the current working
directory changes.
But while we register the reference stores, we never unregister them.
This can have multiple outcomes:
- For a repository's main reference database we essentially keep the
pointer alive. We never free that database, either, and our leak
checker doesn't notice because it's still registered.
- For submodule and worktree reference databases we do eventually free
them in `repo_clear()`, so we may keep pointers to free'd memory
registered. We never notice though as we don't tend to chdir around
in the middle of the process.
We never noticed either of these symptoms, but they are obviously bad.
Partially fix those issues by unregistering the reference stores when
releasing them. The leak of the main reference database will be fixed in
a subsequent commit.
Note that this requires us to use `chdir_notify_register()` instead of
`chdir_notify_reparent()`, as there is no infrastructure to unregister the
latter.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
refs/files-backend.c | 22 +++++++++++++++++++---
refs/packed-backend.c | 16 +++++++++++++++-
refs/reftable-backend.c | 16 +++++++++++++++-
3 files changed, 49 insertions(+), 5 deletions(-)
diff --git a/refs/files-backend.c b/refs/files-backend.c
index a4c7858787..296981584b 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -100,6 +100,23 @@ static void clear_loose_ref_cache(struct files_ref_store *refs)
}
}
+static void files_ref_store_reparent(const char *name UNUSED,
+ const char *old_cwd,
+ const char *new_cwd,
+ void *payload)
+{
+ struct files_ref_store *refs = payload;
+ char *tmp;
+
+ tmp = reparent_relative_path(old_cwd, new_cwd, refs->base.gitdir);
+ free(refs->base.gitdir);
+ refs->base.gitdir = tmp;
+
+ tmp = reparent_relative_path(old_cwd, new_cwd, refs->gitcommondir);
+ free(refs->gitcommondir);
+ refs->gitcommondir = tmp;
+}
+
/*
* Create a new submodule ref cache and add it to the internal
* set of caches.
@@ -128,9 +145,7 @@ static struct ref_store *files_ref_store_init(struct repository *repo,
repo_config_get_bool(repo, "core.prefersymlinkrefs", &refs->prefer_symlink_refs);
- chdir_notify_reparent("files-backend $GIT_DIR", &refs->base.gitdir);
- chdir_notify_reparent("files-backend $GIT_COMMONDIR",
- &refs->gitcommondir);
+ chdir_notify_register(NULL, files_ref_store_reparent, refs);
strbuf_release(&refdir);
@@ -182,6 +197,7 @@ static void files_ref_store_release(struct ref_store *ref_store)
free(refs->gitcommondir);
ref_store_release(refs->packed_ref_store);
free(refs->packed_ref_store);
+ chdir_notify_unregister(NULL, files_ref_store_reparent, refs);
}
static void files_reflog_path(struct files_ref_store *refs,
diff --git a/refs/packed-backend.c b/refs/packed-backend.c
index 0acde48c45..499cb55dfa 100644
--- a/refs/packed-backend.c
+++ b/refs/packed-backend.c
@@ -211,6 +211,19 @@ static size_t snapshot_hexsz(const struct snapshot *snapshot)
return snapshot->refs->base.repo->hash_algo->hexsz;
}
+static void packed_ref_store_reparent(const char *name UNUSED,
+ const char *old_cwd,
+ const char *new_cwd,
+ void *payload)
+{
+ struct packed_ref_store *refs = payload;
+ char *tmp;
+
+ tmp = reparent_relative_path(old_cwd, new_cwd, refs->path);
+ free(refs->path);
+ refs->path = tmp;
+}
+
/*
* Since packed-refs is only stored in the common dir, don't parse the
* payload and rely on the files-backend to set 'gitdir' correctly.
@@ -229,7 +242,7 @@ struct ref_store *packed_ref_store_init(struct repository *repo,
strbuf_addf(&sb, "%s/packed-refs", gitdir);
refs->path = strbuf_detach(&sb, NULL);
- chdir_notify_reparent("packed-refs", &refs->path);
+ chdir_notify_register(NULL, packed_ref_store_reparent, refs);
return ref_store;
}
@@ -274,6 +287,7 @@ static void packed_ref_store_release(struct ref_store *ref_store)
clear_snapshot(refs);
rollback_lock_file(&refs->lock);
delete_tempfile(&refs->tempfile);
+ chdir_notify_unregister(NULL, packed_ref_store_reparent, refs);
free(refs->path);
}
diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
index 4ae22922de..8c93070677 100644
--- a/refs/reftable-backend.c
+++ b/refs/reftable-backend.c
@@ -365,6 +365,19 @@ static int reftable_be_config(const char *var, const char *value,
return 0;
}
+static void reftable_be_reparent(const char *name UNUSED,
+ const char *old_cwd,
+ const char *new_cwd,
+ void *payload)
+{
+ struct reftable_ref_store *refs = payload;
+ char *tmp;
+
+ tmp = reparent_relative_path(old_cwd, new_cwd, refs->base.gitdir);
+ free(refs->base.gitdir);
+ refs->base.gitdir = tmp;
+}
+
static struct ref_store *reftable_be_init(struct repository *repo,
const char *payload,
const char *gitdir,
@@ -447,7 +460,7 @@ static struct ref_store *reftable_be_init(struct repository *repo,
goto done;
}
- chdir_notify_reparent("reftables-backend $GIT_DIR", &refs->base.gitdir);
+ chdir_notify_register(NULL, reftable_be_reparent, refs);
done:
assert(refs->err != REFTABLE_API_ERROR);
@@ -474,6 +487,7 @@ static void reftable_be_release(struct ref_store *ref_store)
free(be);
}
strmap_clear(&refs->worktree_backends, 0);
+ chdir_notify_unregister(NULL, reftable_be_reparent, refs);
}
static int reftable_be_create_on_disk(struct ref_store *ref_store,
--
2.55.0.rc0.786.g65d90a0328.dirty
^ permalink raw reply related
* [PATCH v3 5/8] chdir-notify: drop unused `chdir_notify_reparent()`
From: Patrick Steinhardt @ 2026-06-18 6:54 UTC (permalink / raw)
To: git; +Cc: Karthik Nayak, Jeff King, Justin Tobler
In-Reply-To: <20260618-b4-pks-refs-avoid-chdir-notify-reparent-v3-0-2a5669e8f486@pks.im>
With the preceding commit we've removed all callers of
`chdir_notify_reparent()`, so the function is unused now. Drop it.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
chdir-notify.c | 26 --------------------------
chdir-notify.h | 6 +-----
2 files changed, 1 insertion(+), 31 deletions(-)
diff --git a/chdir-notify.c b/chdir-notify.c
index f8bfe3cbef..1237a45e2e 100644
--- a/chdir-notify.c
+++ b/chdir-notify.c
@@ -43,32 +43,6 @@ void chdir_notify_unregister(const char *name, chdir_notify_callback cb,
}
}
-static void reparent_cb(const char *name,
- const char *old_cwd,
- const char *new_cwd,
- void *data)
-{
- char **path = data;
- char *tmp = *path;
-
- if (!tmp)
- return;
-
- *path = reparent_relative_path(old_cwd, new_cwd, tmp);
- free(tmp);
-
- if (name) {
- trace_printf_key(&trace_setup_key,
- "setup: reparent %s to '%s'",
- name, *path);
- }
-}
-
-void chdir_notify_reparent(const char *name, char **path)
-{
- chdir_notify_register(name, reparent_cb, path);
-}
-
int chdir_notify(const char *new_cwd)
{
struct strbuf old_cwd = STRBUF_INIT;
diff --git a/chdir-notify.h b/chdir-notify.h
index 81eb69d846..36b4114472 100644
--- a/chdir-notify.h
+++ b/chdir-notify.h
@@ -19,10 +19,7 @@
* chdir_notify_register("description", foo, data);
*
* In practice most callers will want to move a relative path to the new root;
- * they can use the reparent_relative_path() helper for that. If that's all
- * you're doing, you can also use the convenience function:
- *
- * chdir_notify_reparent("description", &my_path);
+ * they can use the reparent_relative_path() helper for that.
*
* Whenever a chdir event occurs, that will update my_path (if it's relative)
* to adjust for the new cwd by freeing any existing string and allocating a
@@ -43,7 +40,6 @@ typedef void (*chdir_notify_callback)(const char *name,
void chdir_notify_register(const char *name, chdir_notify_callback cb, void *data);
void chdir_notify_unregister(const char *name, chdir_notify_callback cb,
void *data);
-void chdir_notify_reparent(const char *name, char **path);
/*
*
--
2.55.0.rc0.786.g65d90a0328.dirty
^ permalink raw reply related
* [PATCH v3 6/8] repository: free main reference database
From: Patrick Steinhardt @ 2026-06-18 6:54 UTC (permalink / raw)
To: git; +Cc: Karthik Nayak, Jeff King, Justin Tobler
In-Reply-To: <20260618-b4-pks-refs-avoid-chdir-notify-reparent-v3-0-2a5669e8f486@pks.im>
While we release worktree and submodule reference databases when
clearing a repository, we don't ever release the main reference
database. This memory leak went unnoticed because its pointer is
kept alive by the "chdir_notify" subsystem.
Fix the memory leak.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
repository.c | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/repository.c b/repository.c
index 187dd471c4..e2b5c6712b 100644
--- a/repository.c
+++ b/repository.c
@@ -421,6 +421,11 @@ void repo_clear(struct repository *repo)
FREE_AND_NULL(repo->remote_state);
}
+ if (repo->refs_private) {
+ ref_store_release(repo->refs_private);
+ FREE_AND_NULL(repo->refs_private);
+ }
+
strmap_for_each_entry(&repo->submodule_ref_stores, &iter, e)
ref_store_release(e->value);
strmap_clear(&repo->submodule_ref_stores, 1);
--
2.55.0.rc0.786.g65d90a0328.dirty
^ permalink raw reply related
* [PATCH v3 7/8] refs: fix recursing `get_main_ref_store()` with "onbranch" config
From: Patrick Steinhardt @ 2026-06-18 6:54 UTC (permalink / raw)
To: git; +Cc: Karthik Nayak, Jeff King, Justin Tobler
In-Reply-To: <20260618-b4-pks-refs-avoid-chdir-notify-reparent-v3-0-2a5669e8f486@pks.im>
When we have an "onbranch" condition we need to ask the reference
database whether HEAD currently points at the configured branch. This
unfortunately creates a chicken-and-egg problem:
- The reference database needs to read the configuration so that it
can configure itself.
- The configuration needs to construct a reference database to fully
parse all of its conditionals.
The way we handle this is by simply excluding "onbranch" conditionals
when we haven't yet configured the reference database.
The mechanism for this is broken though: to verify whether or not we
have configured the reference database we check whether its format is
set to `REF_STORAGE_UNKNOWN` in `include_by_branch()`. But typically,
the format _is_ already known at that time because we set it up during
repository discovery in "setup.c".
The consequence is that we have recursion:
1. We call `get_main_ref_store()`.
2. We don't yet have a reference store, so we call `ref_store_init()`.
3. We parse the configuration required for the reference store.
4. We eventually end up in `include_by_branch()`.
5. We have already configured the reference storage format, so we end
up calling `get_main_ref_store()` again.
We still haven't finished (1) though, so `get_main_ref_store()` will now
call `ref_store_init()` a second time. The end result is that we have
constructed the same reference store twice.
Of course, as both reference stores would be assigned to `refs_private`,
we leak one of those two instances. This never surfaced as an actual
leak though because the pointer is kept alive by the "chdir_notify"
subsystem.
For now, we can fix the issue by explicitly unsetting the reference
storage format before constructing it. This makes the mentioned check
trigger as expected, and consequently we won't end up constructing a
second reference database at all. Ultimately, this means that we
consistently stop evaluating "onbranch" conditions when constructing the
main reference database.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
refs.c | 20 ++++++++++++++++++--
1 file changed, 18 insertions(+), 2 deletions(-)
diff --git a/refs.c b/refs.c
index d3caa9a633..e69b9b8ac8 100644
--- a/refs.c
+++ b/refs.c
@@ -2351,15 +2351,31 @@ void ref_store_release(struct ref_store *ref_store)
struct ref_store *get_main_ref_store(struct repository *r)
{
+ enum ref_storage_format format;
+
if (r->refs_private)
return r->refs_private;
if (!r->gitdir)
BUG("attempting to get main_ref_store outside of repository");
- r->refs_private = ref_store_init(r, r->ref_storage_format,
- r->gitdir, REF_STORE_ALL_CAPS);
+ /*
+ * When constructing the reference backend we'll end up reading the Git
+ * configuration. This means we'll also try to evaluate "onbranch"
+ * conditions.
+ *
+ * We cannot read branches when constructing the refdb, so it is not
+ * possible to evaluate those conditions in the first place. To gate
+ * their evaluation we check whether or not the reference storage
+ * format has been configured -- we thus have to temporarily set it to
+ * UNKNOWN here so that we don't end up recursing.
+ */
+ format = r->ref_storage_format;
+ r->ref_storage_format = REF_STORAGE_FORMAT_UNKNOWN;
+ r->refs_private = ref_store_init(r, format, r->gitdir, REF_STORE_ALL_CAPS);
r->refs_private = maybe_debug_wrap_ref_store(r->gitdir, r->refs_private);
+ r->ref_storage_format = format;
+
return r->refs_private;
}
--
2.55.0.rc0.786.g65d90a0328.dirty
^ permalink raw reply related
* [PATCH v3 8/8] refs: drop local buffer in `refs_compute_filesystem_location()`
From: Patrick Steinhardt @ 2026-06-18 6:54 UTC (permalink / raw)
To: git; +Cc: Karthik Nayak, Jeff King, Justin Tobler
In-Reply-To: <20260618-b4-pks-refs-avoid-chdir-notify-reparent-v3-0-2a5669e8f486@pks.im>
We're using a local buffer in `refs_compute_filesystem_location()` that
is only used so that we can fill it and then call `strbuf_realpath()` on
its result. This roundtrip isn't necessary though: `strbuf_realpath()`
already knows to use a single buffer as both input and output at the
same time. So all this does is to add a bit of confusion and an extra
memory allocation.
Drop the local buffer.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
refs.c | 8 ++------
1 file changed, 2 insertions(+), 6 deletions(-)
diff --git a/refs.c b/refs.c
index e69b9b8ac8..4912510590 100644
--- a/refs.c
+++ b/refs.c
@@ -3571,8 +3571,6 @@ void refs_compute_filesystem_location(const char *gitdir, const char *payload,
bool *is_worktree, struct strbuf *refdir,
struct strbuf *ref_common_dir)
{
- struct strbuf sb = STRBUF_INIT;
-
*is_worktree = get_common_dir_noenv(ref_common_dir, gitdir);
if (!payload) {
@@ -3586,8 +3584,8 @@ void refs_compute_filesystem_location(const char *gitdir, const char *payload,
}
if (!is_absolute_path(payload)) {
- strbuf_addf(&sb, "%s/%s", ref_common_dir->buf, payload);
- strbuf_realpath(ref_common_dir, sb.buf, 1);
+ strbuf_addf(ref_common_dir, "/%s", payload);
+ strbuf_realpath(ref_common_dir, ref_common_dir->buf, 1);
} else {
strbuf_realpath(ref_common_dir, payload, 1);
}
@@ -3600,6 +3598,4 @@ void refs_compute_filesystem_location(const char *gitdir, const char *payload,
BUG("worktree path does not contain slash");
strbuf_addf(refdir, "/worktrees/%s", wt_id + 1);
}
-
- strbuf_release(&sb);
}
--
2.55.0.rc0.786.g65d90a0328.dirty
^ permalink raw reply related
* [ANNOUNCE] Git for Windows 2.55.0-rc1
From: Johannes Schindelin @ 2026-06-18 7:07 UTC (permalink / raw)
To: git, git-packagers
Dear Git users,
I hereby announce that Git for Windows 2.55.0-rc1 is available from:
https://github.com/git-for-windows/git/releases/tag/v2.55.0-rc1.windows.1
Changes since Git for Windows v2.54.0 (April 20th 2026)
Following the MSYS2 project, on which Git for Windows is based, Windows
8.1 support will be dropped after Git for Windows v2.55.
New Features
* Comes with Git v2.55.0-rc1.
* Comes with the MSYS2 runtime (Git for Windows flavor) based on
Cygwin v3.6.9.
* Comes with Git Credential Manager v2.8.0.
* Comes with cURL v8.20.0.
* Comes with less 702.
* The FSCache now accelerates more git add scenarios.
* Comes with OpenSSL v3.5.7.
* The diff helper handling Word documents was ported from Perl to
Rust.
* Comes with Bash v5.3.15.
Bug Fixes
* A regression in v2.54.0 that could cause endless "Unlink of file
'.git/objects/pack/pack-.idx' failed. Should I try again?" loops on
older Windows 10 versions during git fetch operations was fixed.
* A bug that prevented proper shutdown of processes launched via Git
Bash under certain circumstances was fixed.
* A bug was fixed which could cause parallel checkouts to fail under
certain circumstances when the FSCache is enabled.
* Git Bash (MinTTY) now respects screen scaling settings under more
circumstances.
Git-2.55.0-rc1-64-bit.exe | 40ccf96f6cb90e1c1e987108d339bf59cf883f4f62fe1d811bf7f453f216a3c2
Git-2.55.0-rc1-arm64.exe | 32ea9f89ebdcb4b5386bb8d91ac09bddca05f97aab2bb97f3a30e4a2ee13dfd7
PortableGit-2.55.0-rc1-64-bit.7z.exe | cea2707ee1a25ef33d9db4235c1b4ae33d22c2fe618dd3d4ec8574b34ec6f3d9
PortableGit-2.55.0-rc1-arm64.7z.exe | 70da0c29b0c60d7417d698c0d1850a2c409ab89cf9d5431ab75ff203356f5132
MinGit-2.55.0-rc1-64-bit.zip | bb7d17aa2e1cf1b015b7fe2f0312465fa0e035699b0970a5898c07595a61985f
MinGit-2.55.0-rc1-arm64.zip | 49df32406f57d2886c93acf8ce2fcb9640a1b73c1829fdbb36ea069c41d216c7
MinGit-2.55.0-rc1-32-bit.zip | 7d8c7828ee2490d406a4a9132a6a2e61f955c26c0a9cdebaf05ccde9bf156ebb
MinGit-2.55.0-rc1-busybox-64-bit.zip | 22abdf14fceaa1172a3416df33fb535a790faab7584a8ba903f3f88d4d0c45af
MinGit-2.55.0-rc1-busybox-32-bit.zip | 0139489b7baae8c9738cd3ea8dcbaf946c4f758716e9d64a6b2491db0c0cc229
Git-2.55.0-rc1-64-bit.tar.bz2 | 87be9bf3d21a53b6e415e0650b4c4632e4226ae9c148a6d5b090e3521d5cfa6e
Git-2.55.0-rc1-arm64.tar.bz2 | 9d7786f3dfb29d1715ff2a73a0f08abcd298fe610c70d1117dcffdb0d15e43ce
Ciao,
Johannes
^ permalink raw reply
* [PATCH v2] SubmittingPatches: address design critiques
From: Junio C Hamano @ 2026-06-18 8:50 UTC (permalink / raw)
To: git
In-Reply-To: <xmqqv7bhxiby.fsf@gitster.g>
Contributors sometimes fail to answer fundamental design or
viability comments from reviewers and submit subsequent rounds
without addressing them. When design decisions are resolved on the
mailing list, the final justification should be recorded in the
commit messages.
Instruct authors to be particularly mindful of critiques regarding
high-level design or viability, to defend their choices on the list,
and to accompany new iterations with clearer explanations in the cover
letter, responses, and revised commit messages. Also instruct them to
explicitly document the resolution of these concerns in the commit
message body to keep the historical record complete.
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
* Rephrased the instruction in the first hunk somewhat to be a bit
more explicit, and added a missing verb to the second hunk.
Documentation/SubmittingPatches | 19 +++++++++++++++++++
1 file changed, 19 insertions(+)
diff --git a/Documentation/SubmittingPatches b/Documentation/SubmittingPatches
index f042bb5aaf..bbe759f3d9 100644
--- a/Documentation/SubmittingPatches
+++ b/Documentation/SubmittingPatches
@@ -51,6 +51,21 @@ area.
respond to them with "Reply-All" on the mailing list, while taking
them into account while preparing an updated set of patches.
+
+You should be particularly mindful of critiques regarding the
+high-level design or viability of your proposal (e.g., questioning
+whether the feature is worth implementing, or if the chosen approach
+is appropriate). Defend your design decisions on the list first, to
+avoid wasting effort on an implementation whose design is not yet
+solid.
++
+Make sure that any new version is accompanied by a much clearer
+explanation and justification (in the cover letter, your responses,
+and in the revised commit messages). Aim to make the reviewers say
+"it is now clear why we may want to do this with the updated version".
++
+Topics that fail to address fundamental design critiques without
+resolution will not be considered ready for merging.
++
It is often beneficial to allow some time for reviewers to provide
feedback before sending a new version, rather than sending an updated
series immediately after receiving a review. This helps collect broader
@@ -323,6 +338,10 @@ The body should provide a meaningful commit message, which:
. alternate solutions considered but discarded, if any.
+. records the resolution of design or viability concerns raised by the
+ community during the review, if any, ensuring the historical record
+ explains why the chosen approach was accepted over alternatives.
+
[[present-tense]]
The problem statement that describes the status quo is written in the
present tense. Write "The code does X when it is given input Y",
Range-diff against v1:
1: eb5f96ab04 ! 1: aecdcf0bda SubmittingPatches: address design critiques
@@ Documentation/SubmittingPatches: area.
respond to them with "Reply-All" on the mailing list, while taking
them into account while preparing an updated set of patches.
+
-+You would want to be particularly mindful of critiques regarding the
++You should be particularly mindful of critiques regarding the
+high-level design or viability of your proposal (e.g., questioning
+whether the feature is worth implementing, or if the chosen approach
-+is appropriate). You want to defend your design decisions on the list
-+first, because you do not want to spend too much effort in the
-+implementation if the design is not yet solid.
++is appropriate). Defend your design decisions on the list first, to
++avoid wasting effort on an implementation whose design is not yet
++solid.
++
-+Also, make sure that any new version is accompanied by a much clearer
++Make sure that any new version is accompanied by a much clearer
+explanation and justification (in the cover letter, your responses,
+and in the revised commit messages). Aim to make the reviewers say
+"it is now clear why we may want to do this with the updated version".
@@ Documentation/SubmittingPatches: The body should provide a meaningful commit mes
. alternate solutions considered but discarded, if any.
-+. the resolution of design or viability concerns raised by the
++. records the resolution of design or viability concerns raised by the
+ community during the review, if any, ensuring the historical record
+ explains why the chosen approach was accepted over alternatives.
+
--
2.55.0-rc1-93-ge727df1850
^ permalink raw reply related
* Re: [PATCH] SubmittingPatches: address design critiques
From: Kristoffer Haugsbakk @ 2026-06-18 8:55 UTC (permalink / raw)
To: Michael Montalbo, Junio C Hamano; +Cc: git
In-Reply-To: <CAC2QwmJdF+YzAQE3WDEaUrurLVkYcAA0Cgs1YAqyxYcQ0jKfqA@mail.gmail.com>
On Thu, Jun 18, 2026, at 05:53, Michael Montalbo wrote:
> Junio C Hamano wrote:
>>[snip]
>
> Two small suggestions: open with a direct imperative and replace
> "effort in the implementation" with "effort on the implementation".
>[snip]
The threading doesn’t work here. This is in reply to:
https://lore.kernel.org/git/xmqqv7bhxiby.fsf@gitster.g/
But your email has no `In-Reply-To` header.
^ permalink raw reply
* Re: [PATCH 1/2] environment: move ignore_case into repo_config_values
From: Tian Yuchen @ 2026-06-18 10:56 UTC (permalink / raw)
To: Junio C Hamano
Cc: git, ps, phillip.wood123, johannes.schindelin, stolee,
Christian Couder, Ayush Chandekar, Olamide Caleb Bello
In-Reply-To: <xmqqh5n1w0i7.fsf@gitster.g>
On 6/18/26 01:16, Junio C Hamano wrote:
> Tian Yuchen <cat@malon.dev> writes:
>
>> Note that the newly introduced getter, 'repo_get_ignore_case()',
>> intentionally avoids checking 'repo->gitdir'. This could safely
>> accommodates early dynamic probing of the filesystem during
>> 'git init' or clone operations, where the 'gitdir' might not be fully
>> initialized but the filesystem capability must be recorded.
>
> Why "could"? It either "safely accommodates" or it doesn't.
>
Okay, will change in the next reroll.
> I do not quite understand the logic behind this part. Why is it OK
> to punt until .gitdir is ready for trust-executable-bit, like it is
> done in f951ed98 (environment: move trust_executable_bit into
> repo_config_values, 2026-06-13)
>
> diff --git a/environment.c b/environment.c
> index fc3ed8bb1c..75069a884d 100644
> --- a/environment.c
> +++ b/environment.c
> @@ -142,6 +141,13 @@ int is_bare_repository(void)
> return is_bare_repository_cfg && !repo_get_work_tree(the_repository);
> }
>
> +int repo_trust_executable_bit(struct repository *repo)
> +{
> + return repo->gitdir?
> + repo_config_values(repo)->trust_executable_bit :
> + 1;
> +}
> +
>
> or hfs/ntfs in 71386c21 (environment: move 'protect_hfs' and
> 'protect_ntfs' into 'repo_config_values', 2026-06-10)
>
> diff --git a/environment.c b/environment.c
> index fc3ed8bb1c..683fe1b4d3 100644
> --- a/environment.c
> +++ b/environment.c
> @@ -142,6 +140,20 @@ int is_bare_repository(void)
> return is_bare_repository_cfg && !repo_get_work_tree(the_repository);
> }
>
> +int repo_protect_ntfs(struct repository *repo)
> +{
> + return repo->gitdir ?
> + repo_config_values(repo)->protect_ntfs :
> + PROTECT_NTFS_DEFAULT;
> +}
> +
> +int repo_protect_hfs(struct repository *repo)
> +{
> + return repo->gitdir ?
> + repo_config_values(repo)->protect_hfs :
> + PROTECT_HFS_DEFAULT;
> +}
> +
> int have_git_dir(void)
> {
> return startup_info->have_repository
>
> but not for this bit?
You're right, I made a mistake.
Earlier, when I was testing using 'repo->gitdir', the CI tests failed,
and I thought it was because the test script was forcing initial values
too early. I just realized the error was somewhere else. I'll fix it.
>
>> +int repo_get_ignore_case(struct repository *repo)
>> +{
>> + if (repo)
>> + return repo_config_values(repo)->ignore_case;
>> + return 0;
>> +}
>
> What makes ignore-case so special? Doesn't the same logic apply to
> the other three bits?
>
> Or use a more direct
>
> if (repo && repo->initialized)
> ...;
>
> for all three, as repo_config_values(repo) barfs when repo is not
> initialized?
>
> I dunno.
That makes some sense. The reasoning behind using 'repo->gitdir' is that
"as long as I know where .git is, I assume the repository has been
initialized," which may indeed be less precise than 'repo->initialized'.
Btw, I think it's better to change the getter's name from
'repo_get_ignore_case()' to 'repo_ignore_case()'. This way, it will be
consistent with the previous flags.
Thanks, yuchen
^ permalink raw reply
* [PATCH v2 0/2] environment: move ignore_case into repo_config_values
From: Tian Yuchen @ 2026-06-18 11:42 UTC (permalink / raw)
To: git; +Cc: ps, phillip.wood123, johannes.schindelin, stolee, Tian Yuchen
In-Reply-To: <20260617154929.564498-1-cat@malon.dev>
The 'core.ignorecase' configuration, stored as the global variable
'ignore_case', acts as a core filesystem capability flag.
This series continues the ongoing libification effort by moving
this global variable into 'struct repo_config_values', tying it
to the specific repository instance it was read from. This allows
us to encapsulate the configuration without altering its
eager-parsing behavior.
The getter function 'repo_ignore_case()' is introduced so
that we can safely retrieve the configuration value whilst
maintaining the correct fallback logic.
RFC Questions:
dir.c --- Performance overhead?
compat/win32/path-utils.c --- Is it appropriate to include the
repository.h header file?
Related materials:
[1] In this patch to migrate protect_hfs and protect_ntfs, the approach
of introducing getters has been endorsed.
[2] Derrick Stolee's previous attempt. The reasons for the failure are
also mentioned in [1].
Changes since V1:
- s/repo_get_ignore_case()/repo_ignore_case()
- Use repo->initialized instead of repo->gitdir
Thanks!
Mentored-by: Christian Couder christian.couder@gmail.com
Mentored-by: Ayush Chandekar ayu.chandekar@gmail.com
Mentored-by: Olamide Caleb Bello belkid98@gmail.com
Signed-off-by: Tian Yuchen cat@malon.dev
[1] https://lore.kernel.org/git/20260606143412.15443-1-cat@malon.dev/
[2] https://lore.kernel.org/git/2b4198c09cb6c04c60608d19072d419503dfe5df.1685716421.git.gitgitgadget@gmail.com/
Tian Yuchen (2):
environment: move ignore_case into repo_config_values
config: use repo_ignore_case() to access core.ignorecase
apply.c | 2 +-
builtin/fetch.c | 2 +-
builtin/mv.c | 2 +-
compat/win32/path-utils.c | 3 ++-
dir.c | 18 +++++++++---------
environment.c | 11 +++++++++--
environment.h | 9 ++++++++-
fsmonitor.c | 2 +-
name-hash.c | 6 +++---
read-cache.c | 6 +++---
refs/files-backend.c | 4 ++--
submodule.c | 2 +-
t/helper/test-lazy-init-name-hash.c | 2 +-
unpack-trees.c | 2 +-
14 files changed, 43 insertions(+), 28 deletions(-)
--
2.43.0
^ permalink raw reply
* [PATCH v2 1/2] environment: move ignore_case into repo_config_values
From: Tian Yuchen @ 2026-06-18 11:42 UTC (permalink / raw)
To: git
Cc: ps, phillip.wood123, johannes.schindelin, stolee, Tian Yuchen,
Christian Couder, Ayush Chandekar, Olamide Caleb Bello
In-Reply-To: <20260618114207.605211-1-cat@malon.dev>
The 'core.ignorecase' configuration which is stored as the
global variable 'ignore_case' acts as a core filesystem
capability flag.
Move this global variable into 'struct repo_config_values' to tie it
to the specific repository instance it was read from. This reduces
global state and aligns with the ongoing libification effort.
To ensure code readability, the getter function
'repo_ignore_case()' is introduced.
Mentored-by: Christian Couder <christian.couder@gmail.com>
Mentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>
Mentored-by: Olamide Caleb Bello <belkid98@gmail.com>
Signed-off-by: Tian Yuchen <cat@malon.dev>
---
environment.c | 8 ++++++++
environment.h | 8 ++++++++
2 files changed, 16 insertions(+)
diff --git a/environment.c b/environment.c
index fc3ed8bb1c..bfa3cb3045 100644
--- a/environment.c
+++ b/environment.c
@@ -142,6 +142,13 @@ int is_bare_repository(void)
return is_bare_repository_cfg && !repo_get_work_tree(the_repository);
}
+int repo_ignore_case(struct repository *repo)
+{
+ return (repo && repo->initialized) ?
+ repo_config_values(repo)->ignore_case :
+ 0;
+}
+
int have_git_dir(void)
{
return startup_info->have_repository
@@ -720,5 +727,6 @@ void repo_config_values_init(struct repo_config_values *cfg)
{
cfg->attributes_file = NULL;
cfg->apply_sparse_checkout = 0;
+ cfg->ignore_case = 0;
cfg->branch_track = BRANCH_TRACK_REMOTE;
}
diff --git a/environment.h b/environment.h
index 9eb97b3869..39a8bf0b49 100644
--- a/environment.h
+++ b/environment.h
@@ -91,6 +91,7 @@ struct repo_config_values {
/* section "core" config values */
char *attributes_file;
int apply_sparse_checkout;
+ int ignore_case;
/* section "branch" config values */
enum branch_track branch_track;
@@ -123,6 +124,13 @@ int git_default_config(const char *, const char *,
int git_default_core_config(const char *var, const char *value,
const struct config_context *ctx, void *cb);
+/*
+ * Getter for the `ignore_case` field of `struct repo_config_values`.
+ * It checks `repo->initialized` to prevent calling repo_config_values()`
+ * before the repository setup is fully complete or in non-git environments.
+ */
+int repo_ignore_case(struct repository *repo);
+
void repo_config_values_init(struct repo_config_values *cfg);
/*
--
2.43.0
^ permalink raw reply related
* [PATCH v2 2/2] config: use repo_ignore_case() to access core.ignorecase
From: Tian Yuchen @ 2026-06-18 11:42 UTC (permalink / raw)
To: git
Cc: ps, phillip.wood123, johannes.schindelin, stolee, Tian Yuchen,
Christian Couder, Ayush Chandekar, Olamide Caleb Bello
In-Reply-To: <20260618114207.605211-1-cat@malon.dev>
Replace the accesses to the global 'ignore_case' variable with
calls to 'repo_ignore_case(the_repository)'. This step eliminates
the 'ignore_case' global state.
Note on compat/win32/path-utils.c:
To eliminate the global state, several helper functions
(e.g. 'win32_fspathncmp()') now read from
'repo_ignore_case(the_repository)'. While this introduces
dependency on 'repository.h' into the 'compat/', it avoids massive
refactoring of the signatures across the codebase.
Mentored-by: Christian Couder <christian.couder@gmail.com>
Mentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>
Mentored-by: Olamide Caleb Bello <belkid98@gmail.com>
Signed-off-by: Tian Yuchen <cat@malon.dev>
---
apply.c | 2 +-
builtin/fetch.c | 2 +-
builtin/mv.c | 2 +-
compat/win32/path-utils.c | 3 ++-
dir.c | 18 +++++++++---------
environment.c | 3 +--
environment.h | 1 -
fsmonitor.c | 2 +-
name-hash.c | 6 +++---
read-cache.c | 6 +++---
refs/files-backend.c | 4 ++--
submodule.c | 2 +-
t/helper/test-lazy-init-name-hash.c | 2 +-
unpack-trees.c | 2 +-
14 files changed, 27 insertions(+), 28 deletions(-)
diff --git a/apply.c b/apply.c
index 249248d4f2..620c88d2a0 100644
--- a/apply.c
+++ b/apply.c
@@ -4008,7 +4008,7 @@ static int path_is_beyond_symlink_1(struct apply_state *state, struct strbuf *na
struct cache_entry *ce;
ce = index_file_exists(state->repo->index, name->buf,
- name->len, ignore_case);
+ name->len, repo_ignore_case(the_repository));
if (ce && S_ISLNK(ce->ce_mode))
return 1;
} else {
diff --git a/builtin/fetch.c b/builtin/fetch.c
index e4e8a72ed9..073e716bc4 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -1819,7 +1819,7 @@ static void ref_transaction_rejection_handler(const char *refname,
{
struct ref_rejection_data *data = cb_data;
- if (err == REF_TRANSACTION_ERROR_CASE_CONFLICT && ignore_case &&
+ if (err == REF_TRANSACTION_ERROR_CASE_CONFLICT && repo_ignore_case(the_repository) &&
!data->case_sensitive_msg_shown) {
error(_("You're on a case-insensitive filesystem, and the remote you are\n"
"trying to fetch from has references that only differ in casing. It\n"
diff --git a/builtin/mv.c b/builtin/mv.c
index 948b330639..d60582262c 100644
--- a/builtin/mv.c
+++ b/builtin/mv.c
@@ -419,7 +419,7 @@ int cmd_mv(int argc,
goto act_on_entry;
}
if (lstat(dst, &st) == 0 &&
- (!ignore_case || strcasecmp(src, dst))) {
+ (!repo_ignore_case(the_repository) || strcasecmp(src, dst))) {
bad = _("destination exists");
if (force) {
/*
diff --git a/compat/win32/path-utils.c b/compat/win32/path-utils.c
index 966ef779b9..f779f367cf 100644
--- a/compat/win32/path-utils.c
+++ b/compat/win32/path-utils.c
@@ -2,6 +2,7 @@
#include "../../git-compat-util.h"
#include "../../environment.h"
+#include "../../repository.h"
int win32_has_dos_drive_prefix(const char *path)
{
@@ -75,7 +76,7 @@ int win32_fspathncmp(const char *a, const char *b, size_t count)
} else if (is_dir_sep(*b))
return +1;
- diff = ignore_case ?
+ diff = repo_ignore_case(the_repository) ?
(unsigned char)tolower(*a) - (int)(unsigned char)tolower(*b) :
(unsigned char)*a - (int)(unsigned char)*b;
if (diff)
diff --git a/dir.c b/dir.c
index 33c81c256e..540dd372c1 100644
--- a/dir.c
+++ b/dir.c
@@ -126,7 +126,7 @@ int count_slashes(const char *s)
int git_fspathcmp(const char *a, const char *b)
{
- return ignore_case ? strcasecmp(a, b) : strcmp(a, b);
+ return repo_ignore_case(the_repository) ? strcasecmp(a, b) : strcmp(a, b);
}
int fspatheq(const char *a, const char *b)
@@ -136,7 +136,7 @@ int fspatheq(const char *a, const char *b)
int git_fspathncmp(const char *a, const char *b, size_t count)
{
- return ignore_case ? strncasecmp(a, b, count) : strncmp(a, b, count);
+ return repo_ignore_case(the_repository) ? strncasecmp(a, b, count) : strncmp(a, b, count);
}
int paths_collide(const char *a, const char *b)
@@ -153,7 +153,7 @@ int paths_collide(const char *a, const char *b)
unsigned int fspathhash(const char *str)
{
- return ignore_case ? strihash(str) : strhash(str);
+ return repo_ignore_case(the_repository) ? strihash(str) : strhash(str);
}
int git_fnmatch(const struct pathspec_item *item,
@@ -202,7 +202,7 @@ static int fnmatch_icase_mem(const char *pattern, int patternlen,
use_str = str_buf.buf;
}
- if (ignore_case)
+ if (repo_ignore_case(the_repository))
flags |= WM_CASEFOLD;
match_status = wildmatch(use_pat, use_str, flags);
@@ -1851,7 +1851,7 @@ static struct dir_entry *dir_add_name(struct dir_struct *dir,
struct index_state *istate,
const char *pathname, int len)
{
- if (index_file_exists(istate, pathname, len, ignore_case))
+ if (index_file_exists(istate, pathname, len, repo_ignore_case(the_repository)))
return NULL;
ALLOC_GROW(dir->entries, dir->nr+1, dir->internal.alloc);
@@ -1888,7 +1888,7 @@ static enum exist_status directory_exists_in_index_icase(struct index_state *ist
if (index_dir_exists(istate, dirname, len))
return index_directory;
- ce = index_file_exists(istate, dirname, len, ignore_case);
+ ce = index_file_exists(istate, dirname, len, repo_ignore_case(the_repository));
if (ce && S_ISGITLINK(ce->ce_mode))
return index_gitdir;
@@ -1907,7 +1907,7 @@ static enum exist_status directory_exists_in_index(struct index_state *istate,
{
int pos;
- if (ignore_case)
+ if (repo_ignore_case(the_repository))
return directory_exists_in_index_icase(istate, dirname, len);
pos = index_name_pos(istate, dirname, len);
@@ -2447,7 +2447,7 @@ static enum path_treatment treat_path(struct dir_struct *dir,
/* Always exclude indexed files */
has_path_in_index = !!index_file_exists(istate, path->buf, path->len,
- ignore_case);
+ repo_ignore_case(the_repository));
if (dtype != DT_DIR && has_path_in_index)
return path_none;
@@ -3201,7 +3201,7 @@ static int cmp_icase(char a, char b)
{
if (a == b)
return 0;
- if (ignore_case)
+ if (repo_ignore_case(the_repository))
return toupper(a) - toupper(b);
return a - b;
}
diff --git a/environment.c b/environment.c
index bfa3cb3045..c288c3613d 100644
--- a/environment.c
+++ b/environment.c
@@ -46,7 +46,6 @@ int trust_ctime = 1;
int check_stat = 1;
int has_symlinks = 1;
int minimum_abbrev = 4, default_abbrev = -1;
-int ignore_case;
int assume_unchanged;
int is_bare_repository_cfg = -1; /* unspecified */
int warn_on_object_refname_ambiguity = 1;
@@ -342,7 +341,7 @@ int git_default_core_config(const char *var, const char *value,
}
if (!strcmp(var, "core.ignorecase")) {
- ignore_case = git_config_bool(var, value);
+ cfg->ignore_case = git_config_bool(var, value);
return 0;
}
diff --git a/environment.h b/environment.h
index 39a8bf0b49..c15121db65 100644
--- a/environment.h
+++ b/environment.h
@@ -171,7 +171,6 @@ extern int trust_ctime;
extern int check_stat;
extern int has_symlinks;
extern int minimum_abbrev, default_abbrev;
-extern int ignore_case;
extern int assume_unchanged;
extern int warn_on_object_refname_ambiguity;
extern char *apply_default_whitespace;
diff --git a/fsmonitor.c b/fsmonitor.c
index d07dc18967..107767527e 100644
--- a/fsmonitor.c
+++ b/fsmonitor.c
@@ -453,7 +453,7 @@ static void fsmonitor_refresh_callback(struct index_state *istate, char *name)
* case-insensitive file system, try again using the name-hash
* and dir-name-hash.
*/
- if (!nr_in_cone && ignore_case) {
+ if (!nr_in_cone && repo_ignore_case(the_repository)) {
nr_in_cone = handle_using_name_hash_icase(istate, name);
if (!nr_in_cone)
nr_in_cone = handle_using_dir_name_hash_icase(
diff --git a/name-hash.c b/name-hash.c
index b91e276267..83757db874 100644
--- a/name-hash.c
+++ b/name-hash.c
@@ -126,7 +126,7 @@ static void hash_index_entry(struct index_state *istate, struct cache_entry *ce)
hashmap_add(&istate->name_hash, &ce->ent);
}
- if (ignore_case)
+ if (repo_ignore_case(the_repository))
add_dir_entry(istate, ce);
}
@@ -207,7 +207,7 @@ static int lookup_lazy_params(struct index_state *istate)
* code to build the "istate->name_hash". We don't
* need the complexity here.
*/
- if (!ignore_case)
+ if (!repo_ignore_case(the_repository))
return 0;
nr_cpus = online_cpus();
@@ -651,7 +651,7 @@ void remove_name_hash(struct index_state *istate, struct cache_entry *ce)
ce->ce_flags &= ~CE_HASHED;
hashmap_remove(&istate->name_hash, &ce->ent, ce);
- if (ignore_case)
+ if (repo_ignore_case(the_repository))
remove_dir_entry(istate, ce);
}
diff --git a/read-cache.c b/read-cache.c
index 21829102ae..fcdf0e5ef1 100644
--- a/read-cache.c
+++ b/read-cache.c
@@ -760,12 +760,12 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st,
* case of the file being added to the repository matches (is folded into) the existing
* entry's directory case.
*/
- if (ignore_case) {
+ if (repo_ignore_case(the_repository)) {
adjust_dirname_case(istate, ce->name);
}
if (!(flags & ADD_CACHE_RENORMALIZE)) {
alias = index_file_exists(istate, ce->name,
- ce_namelen(ce), ignore_case);
+ ce_namelen(ce), repo_ignore_case(the_repository));
if (alias &&
!ce_stage(alias) &&
!ie_match_stat(istate, alias, st, ce_option)) {
@@ -786,7 +786,7 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st,
} else
set_object_name_for_intent_to_add_entry(ce);
- if (ignore_case && alias && different_name(ce, alias))
+ if (repo_ignore_case(the_repository) && alias && different_name(ce, alias))
ce = create_alias_ce(istate, ce, alias);
ce->ce_flags |= CE_ADDED;
diff --git a/refs/files-backend.c b/refs/files-backend.c
index a4c7858787..c1da06b1d5 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -806,7 +806,7 @@ static enum ref_transaction_error lock_raw_ref(struct files_ref_store *refs,
} else {
unable_to_lock_message(ref_file.buf, myerr, err);
if (myerr == EEXIST) {
- if (ignore_case &&
+ if (repo_ignore_case(the_repository) &&
transaction_has_case_conflicting_update(transaction, update)) {
/*
* In case-insensitive filesystems, ensure that conflicts within a
@@ -920,7 +920,7 @@ static enum ref_transaction_error lock_raw_ref(struct files_ref_store *refs,
* conflicts between 'foo' and 'Foo/bar'. So let's lowercase
* the refname.
*/
- if (ignore_case) {
+ if (repo_ignore_case(the_repository)) {
struct strbuf lower = STRBUF_INIT;
strbuf_addstr(&lower, refname);
diff --git a/submodule.c b/submodule.c
index a939ff5072..6e7f8b9f7c 100644
--- a/submodule.c
+++ b/submodule.c
@@ -2389,7 +2389,7 @@ static int validate_submodule_encoded_git_dir(char *git_dir, const char *submodu
/* Prevent conflicts on case-folding filesystems */
repo_config_get_bool(the_repository, "core.ignorecase", &config_ignorecase);
- if (ignore_case || config_ignorecase) {
+ if (repo_ignore_case(the_repository) || config_ignorecase) {
bool suffixes_match = !strcmp(last_submodule_name, submodule_name);
return check_casefolding_conflict(git_dir, submodule_name,
suffixes_match);
diff --git a/t/helper/test-lazy-init-name-hash.c b/t/helper/test-lazy-init-name-hash.c
index e542985c94..43cead6d7d 100644
--- a/t/helper/test-lazy-init-name-hash.c
+++ b/t/helper/test-lazy-init-name-hash.c
@@ -218,7 +218,7 @@ int cmd__lazy_init_name_hash(int argc, const char **argv)
/*
* istate->dir_hash is only created when ignore_case is set.
*/
- ignore_case = 1;
+ repo_config_values(the_repository)->ignore_case = 1;
if (dump) {
if (perf || analyze > 0)
diff --git a/unpack-trees.c b/unpack-trees.c
index 998a1e6dc7..d13b004f71 100644
--- a/unpack-trees.c
+++ b/unpack-trees.c
@@ -2428,7 +2428,7 @@ static int check_ok_to_remove(const char *name, int len, int dtype,
*
* Ignore that lstat() if it matches.
*/
- if (ignore_case && icase_exists(o, name, len, st))
+ if (repo_ignore_case(the_repository) && icase_exists(o, name, len, st))
return 0;
if (o->internal.dir &&
--
2.43.0
^ permalink raw reply related
* Re: [PATCH] Fix typo in MaintNotes regarding versioning scheme
From: Tuomas Ahola @ 2026-06-18 11:48 UTC (permalink / raw)
To: Junio C Hamano; +Cc: Silas Poulson, gitgitgadget, git
In-Reply-To: <xmqqfr6czmye.fsf@gitster.g>
Junio C Hamano <gitster@pobox.com> wrote:
> Silas Poulson <silas@dyalog.com> writes:
>
> > I'm aware this is a very minor change, but it would be good to not let
> > this fall through the cracks.
>
> Thanks for noticing a typo.
>
> Will update before the next issue is sent to the mailing list. No
> point in changing it before that.
On that occasion, please consider also these fixes:
-----8<-----
Subject: [PATCH] MaintNotes: fix typos and grammar
Signed-off-by: Tuomas Ahola <taahol@utu.fi>
---
MaintNotes | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/MaintNotes b/MaintNotes
index 12ba677c36..44b29c9e10 100644
--- a/MaintNotes
+++ b/MaintNotes
@@ -82,7 +82,7 @@ available at:
There is a volunteer-run newsletter to serve our community ("Git Rev
News" https://git.github.io/rev_news/).
-Git is a member project of software freedom conservancy, a non-profit
+Git is a member project of Software Freedom Conservancy, a non-profit
organization (https://sfconservancy.org/). To reach a committee of
liaisons to the conservancy, contact them at <git@sfconservancy.org>.
@@ -245,7 +245,7 @@ by others may cause conflicts with their own work, and find people who
are working on these topics to talk to before the potential conflicts
get out of control. It would be a good idea to fork your work from
maint or master and to (1) test it by itself, (2) test a temporary
-merge of it to "next" and (3) test a temporary merge to it to "seen",
+merge of it to "next" and (3) test a temporary merge of it to "seen",
before sending it to the list (or asking GitGitGadget to send it to
the list).
@@ -262,10 +262,10 @@ using the topics that didn't make the cut in the feature release.
Some topics that used to be in "next" during the previous cycle may
get ejected from "next" when this happens.
-A natural consequence of how "next" and "seen" bundles topics together
-is that until a topic is merged to "next", updates to it is expected
+A natural consequence of how "next" and "seen" bundle topics together
+is that until a topic is merged to "next", updates to it are expected
by replacing the patch(es) in the topic with an improved version, and
-once a topic is merged to "next", updates to it needs to come as
+once a topic is merged to "next", updates to it need to come as
incremental patches, pointing out what was wrong in the previous
patches and how the problem was corrected. The idea is that if many
reviewers thought it has seen enough eyeballs and is good enough for
base-commit: f9b08c9b285c9154e41b9f5fce7506018b83dfcb
--
2.30.2
^ permalink raw reply related
* Re: [PATCH v2 0/7] More work supporting objects larger than 4GB on Windows
From: Patrick Steinhardt @ 2026-06-18 12:13 UTC (permalink / raw)
To: Johannes Schindelin via GitGitGadget
Cc: git, Kristofer Karlsson, Johannes Schindelin
In-Reply-To: <pull.2137.v2.git.1781524349.gitgitgadget@gmail.com>
On Mon, Jun 15, 2026 at 11:52:22AM +0000, Johannes Schindelin via GitGitGadget wrote:
> This patch series tries to address the problems pointed out by the expensive
> tests that now run in CI: t5608 and t7508 verify various aspects about
> objects larger than 4GB, which Git does not currently handle correctly when
> run on a platform where size_t is 64-bit and unsigned long is 32-bit.
>
> Changes vs v1:
>
> * Rebased onto master, which merged ps/odb-source-loose (with which these
> patches previously conflicted rather badly).
> * Removed superfluous size_t s variables (thanks, Patrick!).
I skimmed those parts that I was previously commenting on and am
happy with those changes. Thanks!
Patrick
^ permalink raw reply
* Re: [PATCH v2 1/5] SubmittingPatches: encourage trailer use for substantial help
From: Kristoffer Haugsbakk @ 2026-06-18 12:21 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git, Patrick Steinhardt
In-Reply-To: <xmqq4ij0vo8f.fsf@gitster.g>
On Wed, Jun 17, 2026, at 23:41, Junio C Hamano wrote:
> kristofferhaugsbakk@fastmail.com writes:
>
>> diff --git a/Documentation/SubmittingPatches b/Documentation/SubmittingPatches
>> index 176567738d4..0b12badf86d 100644
>> --- a/Documentation/SubmittingPatches
>> +++ b/Documentation/SubmittingPatches
>> @@ -443,8 +443,16 @@ identifying, and not misleading.
>> The goal of this policy is to allow us to have sufficient information to contact
>> you if questions arise about your contribution.
>>
>> +=== Commit trailers
>> [[commit-trailers]]
>> -If you like, you can put extra trailers at the end:
>
> I think majority of AsciiDoc files in this project places [[anchor]]
> before the "=== title" of a section. For example, here is how the
> patch flow section begins in SubmittingPatches:
>
> [[patch-flow]]
> === A typical life cycle of a patch series
>
> To help us understand the reason behind various guidelines given later
> in the document, first let's understand how the life cycle of a
> typical patch series for this project goes.
>
> I do not offhand know which way is kosher, but we should be
> consistent either way.
Your suspicion is correct. Skimming this I cannot seem to find any
examples where the anchor goes after the title.
git grep --extended-regexp -C1 '^\[\[' -- 'Documentation/*adoc'
I’ll fix it.
^ permalink raw reply
* Re: [PATCH] checkout: add --fetch to fetch remote before resolving start-point
From: Harald Nordgren @ 2026-06-18 12:36 UTC (permalink / raw)
To: D. Ben Knoble; +Cc: Harald Nordgren via GitGitGadget, git
In-Reply-To: <CALnO6CCNoo8y2V5KmE0KQ6qDurZELipFowcr=ZpZ3ocVB-uLjA@mail.gmail.com>
Hi Ben!
Trying to shore up some support for this topic. How do you feel about this now?
Harald
^ permalink raw reply
* Re: [PATCH v6] checkout: extend --track with a "fetch" mode to refresh start-point
From: Harald Nordgren @ 2026-06-18 12:38 UTC (permalink / raw)
To: Phillip Wood; +Cc: gitgitgadget, git
In-Reply-To: <f23eb128-958f-475f-911b-eac4f6daddff@gmail.com>
Hi Phillip!
How do you feel now, is it worth it for us to move forward with this
topic or not?
Harald
On Fri, May 8, 2026 at 3:15 PM Phillip Wood <phillip.wood123@gmail.com> wrote:
>
> Hi Harald
>
> On 07/05/2026 21:12, Harald Nordgren wrote:
> > Is this ready to move to next?
>
> I'm not particularly enthusiastic one way or the other about adding
> this, but so long as we only try to fetch when the user explicitly asks
> for it I don't particularly object. However having had a quick scan of
> the implementation I have a few comments
>
> * "--track=inherit,direct" is nonsense and should be rejected
>
> * currently "--track" has "last one wins" behavior so
> "--track=inherit --track=direct" behaves like "--track=direct". We
> should probably keep that so that "--track=fetch --track=direct"
> behaves like "--track=direct", not "--track=fetch,direct"
>
> * if "git fetch" fails and the remote tracking ref already exists then
> we should print a warning and carry on rather than dying which is more
> convenient if the user or remote server are offline.
>
> * "git checkout --track=fetch origin/branch" should respect
> remote.origin.fetch so that we fetch the ref that we're going to
> checkout. I wonder if we can share this logic with the code that
> sets the upstream branch.
>
> * "git checkout --track=fetch origin" should only fetch the remote
> ref that we're going to checkout, not all the refs from origin. i.e.
> it should read origin/HEAD to work out what to fetch.
>
> Thanks
>
> Phillip
>
^ permalink raw reply
* Re: [PATCH v5 2/2] graph: indent visual root in graph
From: Pablo Sabater @ 2026-06-18 12:42 UTC (permalink / raw)
To: Jeff King
Cc: git, ayu.chandekar, chandrapratap3519, christian.couder, gitster,
jltobler, karthik.188, phillip.wood, siddharthasthana31
In-Reply-To: <20260617202744.GA3465855@coredump.intra.peff.net>
El mié, 17 jun 2026 a las 22:27, Jeff King (<peff@peff.net>) escribió:
>
> On Sat, Jun 13, 2026 at 09:09:16PM +0200, Pablo Sabater wrote:
>
> > +/*
> > + * Iterates the commits queue searching for the next visible commit, once found
> > + * sets visibleness and visual-root flags.
> > + * Knowing if the next commit is also a visual root avoids redundant indentations
> > + *
> > + * NEEDSWORK: The queue is actively being modified by the walker, for each commit
> > + * its parents and itself get simplified and their flags set, but for the next
> > + * unrelated commit or the grandparents they are not simplified yet, which means
> > + * that a commit whose parents are all filtered will not be marked as a visual
> > + * root candidate at the lookahead.
> > + * This causes the lookahead to fail, failing to set the cascade flag to avoid
> > + * redundant indentations.
> > + * See 'test_expect_failure' at t4218-log-graph-indentation.sh.
> > + */
> > +static void graph_peek_next_visible(struct git_graph *graph,
> > + struct graph_lookahead_flags *flags)
> > +{
> > + struct commit_list *cl;
> > +
> > + flags->is_next_visible = 0;
> > + flags->is_next_visual_root = 0;
> > + flags->next_has_column = 0;
> > +
> > + for (cl = graph->revs->commits; cl; cl = cl->next) {
> > + if (get_commit_action(graph->revs, cl->item) != commit_show)
> > + continue;
> > [...]
>
> I have a feeling this may interact badly with the prio-queue introduced
> by dd4bc01c0a (revision: use priority queue for non-limited streaming
> walks, 2026-05-27). In that commit, get_revision_1() sucks all of the
> commits from revs->commits into revs->commit_queue, and then traversal
> puts the parents into that queue, not the commits list.
>
> So during the traversal, revs->commits does not hold the complete queue
> anymore. I think it does see _some_ commits, since some get placed
> directly into revs->commits and then later moved next time
> get_revision() is called. But if we instrument the code like this:
>
> diff --git a/graph.c b/graph.c
> index e0d1e2a510..8a5f17a089 100644
> --- a/graph.c
> +++ b/graph.c
> @@ -926,6 +926,10 @@ static void graph_peek_next_visible(struct git_graph *graph,
> flags->is_next_visual_root = 0;
> flags->next_has_column = 0;
>
> + warning("peeking at visible commits: %d in list, %d in queue",
> + commit_list_count(graph->revs->commits),
> + (int)graph->revs->commit_queue.nr);
> +
> for (cl = graph->revs->commits; cl; cl = cl->next) {
> if (get_commit_action(graph->revs, cl->item) != commit_show)
> continue;
>
> and run something like:
>
> ./git log --graph --oneline -- Makefile
>
> we can see that we're always considering just one commit, while there
> may be dozens or hundreds in the queue.
>
> I'm not sure what the solution is. This function wants to peek ahead in
> queue order, possibly through multiple entries. But a heap-based queue
> inherently only supports peeking at the first entry.
Hi Jeff!
Yeah, I haven't read dd4bc01c0a yet but from what you say it prob
won't work anymore, I didn't know about that series, about the
lookahead I think it could still work with some tweaks, the important
part is to set the three lookahead flags.
From what I understood, we can only get the direct next commit, but no
more reliably ordered.
The flags should be fine:
- 'is_next_visible' could need to traverse multiple entries, but it
doesn't need them to be in order. We just need to know if something
will be rendered after.
- 'next_has_column' only needs the first entry.
- 'is_next_visual_root' only needs the first entry to know if it could
be a visual root, and also if it is not the last one (but we don't
need them to be ordered for this last part).
Should I work with 'next' as a base to have dd4bc01c0a? (Sorry I've
just worked with master).
I'll try to make it work but if not, the lookahead works to avoid
_redundant_ indentations, but it would still work correctly without
it.
>
> None of the tests seem to fail, but I'm not sure if that's because I'm
> way off base in my analysis, or there's a gap in the test coverage, or
> if this case is part of the expect_failure ones mentioned in the
> comment.
>
> I noticed because I have another topic which drops the revs->commits
> list entirely (and just always uses the queue), which of course doesn't
> compile when merged with this (I merge with 'jch' for my daily driver,
> which now includes this patch).
>
> -Peff
Thanks,
Pablo
^ permalink raw reply
* [PATCH v14 0/2] checkout: --track=fetch
From: Harald Nordgren via GitGitGadget @ 2026-06-18 12:44 UTC (permalink / raw)
To: git
Cc: Ramsay Jones, D. Ben Knoble, Kristoffer Haugsbakk, Marc Branchaud,
Phillip Wood, Harald Nordgren
In-Reply-To: <pull.2281.v13.git.git.1779565714.gitgitgadget@gmail.com>
Extend checkout --track with a fetch mode to refresh start-point.
Changes in v14:
* Handle .h files in a better way.
Changes in v13:
* Create a preparatory commit that exposes find_tracking_remote_for_ref()
and advise_ambiguous_fetch_refspec() from branch.c, so checkout can reuse
the same lookup git branch --track uses.
* Use advise_ambiguous_fetch_refspec() for the "multiple remotes match"
case, so the wording matches git branch --track.
Harald Nordgren (2):
branch: expose helpers for finding the remote owning a tracking ref
checkout: extend --track with a "fetch" mode to refresh start-point
Documentation/git-checkout.adoc | 17 +-
Documentation/git-switch.adoc | 5 +-
branch.c | 96 ++++++-----
branch.h | 16 ++
builtin/checkout.c | 139 +++++++++++++++-
t/t7201-co.sh | 276 ++++++++++++++++++++++++++++++++
6 files changed, 498 insertions(+), 51 deletions(-)
base-commit: 4621f8ce5e9b97aa2e8d0d9ffe9d25df2471074d
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2281%2FHaraldNordgren%2Fcheckout-fetch-start-point-v14
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2281/HaraldNordgren/checkout-fetch-start-point-v14
Pull-Request: https://github.com/git/git/pull/2281
Range-diff vs v13:
1: 2369afad24 ! 1: f79689c23d branch: expose helpers for finding the remote owning a tracking ref
@@ branch.h
#define BRANCH_H
+#include "refspec.h"
-+#include "string-list.h"
+
++struct string_list;
struct repository;
struct strbuf;
2: 60adf0e67d ! 2: 8518f090b1 checkout: extend --track with a "fetch" mode to refresh start-point
@@ builtin/checkout.c
+#include "run-command.h"
#include "sequencer.h"
#include "setup.h"
- #include "strvec.h"
+ #include "sparse-index.h"
@@ builtin/checkout.c: struct checkout_opts {
int count_checkout_paths;
int overlay_mode;
--
gitgitgadget
^ permalink raw reply
* [PATCH v14 1/2] branch: expose helpers for finding the remote owning a tracking ref
From: Harald Nordgren via GitGitGadget @ 2026-06-18 12:44 UTC (permalink / raw)
To: git
Cc: Ramsay Jones, D. Ben Knoble, Kristoffer Haugsbakk, Marc Branchaud,
Phillip Wood, Harald Nordgren, Harald Nordgren
In-Reply-To: <pull.2281.v14.git.git.1781786652.gitgitgadget@gmail.com>
From: Harald Nordgren <haraldnordgren@gmail.com>
The remote-lookup that setup_tracking() does is useful outside
branch.c too; for example, deciding which remote to "git fetch"
from given a remote-tracking ref.
Move 'struct tracking' to branch.h and add two helpers backed by the
existing for_each_remote walk: find_tracking_remote_for_ref() and
advise_ambiguous_fetch_refspec(). setup_tracking() uses both. No
behavior change.
Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
---
branch.c | 96 ++++++++++++++++++++++++++++++--------------------------
branch.h | 16 ++++++++++
2 files changed, 68 insertions(+), 44 deletions(-)
diff --git a/branch.c b/branch.c
index 243db7d0fc..46ae7f0035 100644
--- a/branch.c
+++ b/branch.c
@@ -20,16 +20,9 @@
#include "run-command.h"
#include "strmap.h"
-struct tracking {
- struct refspec_item spec;
- struct string_list *srcs;
- const char *remote;
- int matches;
-};
-
struct find_tracked_branch_cb {
struct tracking *tracking;
- struct string_list ambiguous_remotes;
+ struct string_list *ambiguous_remotes;
};
static int find_tracked_branch(struct remote *remote, void *priv)
@@ -45,10 +38,10 @@ static int find_tracked_branch(struct remote *remote, void *priv)
break;
case 2:
/* there are at least two remotes; backfill the first one */
- string_list_append(&ftb->ambiguous_remotes, tracking->remote);
+ string_list_append(ftb->ambiguous_remotes, tracking->remote);
/* fall through */
default:
- string_list_append(&ftb->ambiguous_remotes, remote->name);
+ string_list_append(ftb->ambiguous_remotes, remote->name);
free(tracking->spec.src);
string_list_clear(tracking->srcs, 0);
break;
@@ -59,6 +52,51 @@ static int find_tracked_branch(struct remote *remote, void *priv)
return 0;
}
+void find_tracking_remote_for_ref(struct tracking *tracking,
+ struct string_list *ambiguous_remotes)
+{
+ struct find_tracked_branch_cb ftb_cb = {
+ .tracking = tracking,
+ .ambiguous_remotes = ambiguous_remotes,
+ };
+
+ for_each_remote(find_tracked_branch, &ftb_cb);
+}
+
+void advise_ambiguous_fetch_refspec(const char *dst,
+ const struct string_list *ambiguous_remotes)
+{
+ struct strbuf remotes_advice = STRBUF_INIT;
+ struct string_list_item *item;
+
+ if (!advice_enabled(ADVICE_AMBIGUOUS_FETCH_REFSPEC))
+ return;
+
+ for_each_string_list_item(item, ambiguous_remotes)
+ /*
+ * TRANSLATORS: This is a line listing a remote with duplicate
+ * refspecs in the advice message below. For RTL languages you'll
+ * probably want to swap the "%s" and leading " " space around.
+ */
+ strbuf_addf(&remotes_advice, _(" %s\n"), item->string);
+
+ /*
+ * TRANSLATORS: The second argument is a \n-delimited list of
+ * duplicate refspecs, composed above.
+ */
+ advise(_("There are multiple remotes whose fetch refspecs map to the remote\n"
+ "tracking ref '%s':\n"
+ "%s"
+ "\n"
+ "This is typically a configuration error.\n"
+ "\n"
+ "To support setting up tracking branches, ensure that\n"
+ "different remotes' fetch refspecs map into different\n"
+ "tracking namespaces."), dst,
+ remotes_advice.buf);
+ strbuf_release(&remotes_advice);
+}
+
static int should_setup_rebase(const char *origin)
{
switch (autorebase) {
@@ -254,11 +292,8 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,
{
struct tracking tracking;
struct string_list tracking_srcs = STRING_LIST_INIT_DUP;
+ struct string_list ambiguous_remotes = STRING_LIST_INIT_DUP;
int config_flags = quiet ? 0 : BRANCH_CONFIG_VERBOSE;
- struct find_tracked_branch_cb ftb_cb = {
- .tracking = &tracking,
- .ambiguous_remotes = STRING_LIST_INIT_DUP,
- };
if (!track)
BUG("asked to set up tracking, but tracking is disallowed");
@@ -267,7 +302,7 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,
tracking.spec.dst = (char *)orig_ref;
tracking.srcs = &tracking_srcs;
if (track != BRANCH_TRACK_INHERIT)
- for_each_remote(find_tracked_branch, &ftb_cb);
+ find_tracking_remote_for_ref(&tracking, &ambiguous_remotes);
else if (inherit_tracking(&tracking, orig_ref))
goto cleanup;
@@ -293,34 +328,7 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,
if (tracking.matches > 1) {
int status = die_message(_("not tracking: ambiguous information for ref '%s'"),
orig_ref);
- if (advice_enabled(ADVICE_AMBIGUOUS_FETCH_REFSPEC)) {
- struct strbuf remotes_advice = STRBUF_INIT;
- struct string_list_item *item;
-
- for_each_string_list_item(item, &ftb_cb.ambiguous_remotes)
- /*
- * TRANSLATORS: This is a line listing a remote with duplicate
- * refspecs in the advice message below. For RTL languages you'll
- * probably want to swap the "%s" and leading " " space around.
- */
- strbuf_addf(&remotes_advice, _(" %s\n"), item->string);
-
- /*
- * TRANSLATORS: The second argument is a \n-delimited list of
- * duplicate refspecs, composed above.
- */
- advise(_("There are multiple remotes whose fetch refspecs map to the remote\n"
- "tracking ref '%s':\n"
- "%s"
- "\n"
- "This is typically a configuration error.\n"
- "\n"
- "To support setting up tracking branches, ensure that\n"
- "different remotes' fetch refspecs map into different\n"
- "tracking namespaces."), orig_ref,
- remotes_advice.buf);
- strbuf_release(&remotes_advice);
- }
+ advise_ambiguous_fetch_refspec(orig_ref, &ambiguous_remotes);
exit(status);
}
@@ -347,7 +355,7 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,
cleanup:
string_list_clear(&tracking_srcs, 0);
- string_list_clear(&ftb_cb.ambiguous_remotes, 0);
+ string_list_clear(&ambiguous_remotes, 0);
}
int read_branch_desc(struct strbuf *buf, const char *branch_name)
diff --git a/branch.h b/branch.h
index 3dc6e2a0ff..c2e6725491 100644
--- a/branch.h
+++ b/branch.h
@@ -1,9 +1,25 @@
#ifndef BRANCH_H
#define BRANCH_H
+#include "refspec.h"
+
+struct string_list;
struct repository;
struct strbuf;
+struct tracking {
+ struct refspec_item spec;
+ struct string_list *srcs;
+ const char *remote;
+ int matches;
+};
+
+void find_tracking_remote_for_ref(struct tracking *tracking,
+ struct string_list *ambiguous_remotes);
+
+void advise_ambiguous_fetch_refspec(const char *dst,
+ const struct string_list *ambiguous_remotes);
+
enum branch_track {
BRANCH_TRACK_UNSPECIFIED = -1,
BRANCH_TRACK_NEVER = 0,
--
gitgitgadget
^ permalink raw reply related
* [PATCH v14 2/2] checkout: extend --track with a "fetch" mode to refresh start-point
From: Harald Nordgren via GitGitGadget @ 2026-06-18 12:44 UTC (permalink / raw)
To: git
Cc: Ramsay Jones, D. Ben Knoble, Kristoffer Haugsbakk, Marc Branchaud,
Phillip Wood, Harald Nordgren, Harald Nordgren
In-Reply-To: <pull.2281.v14.git.git.1781786652.gitgitgadget@gmail.com>
From: Harald Nordgren <haraldnordgren@gmail.com>
Add a "fetch" mode to the "--track" option of "git checkout" / "git
switch" that refreshes <start-point> before checking it out:
git checkout -b new_branch --track=fetch origin/some-branch
is shorthand for
git fetch origin some-branch
git checkout -b new_branch --track origin/some-branch
Identify the remote whose configured fetch refspec maps to
<start-point> using find_tracking_remote_for_ref() (the same lookup
"--track" uses to pick which remote to record in
branch.<name>.remote), then run "git fetch <remote> <src-ref>" for
just that ref so other remote-tracking branches are left untouched.
When <start-point> is a bare <remote> (e.g. "origin"), follow
refs/remotes/<remote>/HEAD to learn which branch to refresh. If
"git fetch" fails but the remote-tracking ref already exists locally,
warn and proceed from the existing tip; otherwise abort.
Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
---
Documentation/git-checkout.adoc | 17 +-
Documentation/git-switch.adoc | 5 +-
builtin/checkout.c | 139 +++++++++++++++-
t/t7201-co.sh | 276 ++++++++++++++++++++++++++++++++
4 files changed, 430 insertions(+), 7 deletions(-)
diff --git a/Documentation/git-checkout.adoc b/Documentation/git-checkout.adoc
index a8b3b8c2e2..20b6cae60e 100644
--- a/Documentation/git-checkout.adoc
+++ b/Documentation/git-checkout.adoc
@@ -158,11 +158,26 @@ of it").
resets _<branch>_ to the start point instead of failing.
`-t`::
-`--track[=(direct|inherit)]`::
+`--track[=(direct|inherit|fetch)[,...]]`::
When creating a new branch, set up "upstream" configuration. See
`--track` in linkgit:git-branch[1] for details. As a convenience,
--track without -b implies branch creation.
+
+The argument is a comma-separated list. `direct` (the default) and
+`inherit` select the tracking mode and are mutually exclusive. Adding
+`fetch` requests that the remote be fetched before _<start-point>_ is
+resolved, so the new branch starts from a fresh tip: when
+_<start-point>_ is in _<remote>/<branch>_ form, only that branch is
+updated; when _<start-point>_ is a bare _<remote>_ (e.g. `origin`), the
+branch named by _<remote>/HEAD_ is updated, and the checkout fails
+with a hint to configure that symref if it is not set. The checkout
+also fails if no configured remote's fetch refspec maps to
+_<start-point>_, or if more than one does (in which case the `fetch`
+cannot be unambiguously routed). If the fetch itself fails and the
+corresponding remote-tracking ref already exists, a warning is printed
+and the checkout proceeds from the existing tip; otherwise the checkout
+is aborted.
++
If no `-b` option is given, the name of the new branch will be
derived from the remote-tracking branch, by looking at the local part of
the refspec configured for the corresponding remote, and then stripping
diff --git a/Documentation/git-switch.adoc b/Documentation/git-switch.adoc
index d6c4f229a5..a8730b1da8 100644
--- a/Documentation/git-switch.adoc
+++ b/Documentation/git-switch.adoc
@@ -155,10 +155,11 @@ variable.
attached to a terminal, regardless of `--quiet`.
`-t`::
-`--track[ (direct|inherit)]`::
+`--track[=(direct|inherit|fetch)[,...]]`::
When creating a new branch, set up "upstream" configuration.
`-c` is implied. See `--track` in linkgit:git-branch[1] for
- details.
+ details, and `--track` in linkgit:git-checkout[1] for the
+ `fetch` mode.
+
If no `-c` option is given, the name of the new branch will be derived
from the remote-tracking branch, by looking at the local part of the
diff --git a/builtin/checkout.c b/builtin/checkout.c
index b78b3a1d16..37caceaefd 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -25,10 +25,12 @@
#include "preload-index.h"
#include "read-cache.h"
#include "refs.h"
+#include "refspec.h"
#include "remote.h"
#include "repo-settings.h"
#include "resolve-undo.h"
#include "revision.h"
+#include "run-command.h"
#include "sequencer.h"
#include "setup.h"
#include "sparse-index.h"
@@ -63,6 +65,7 @@ struct checkout_opts {
int count_checkout_paths;
int overlay_mode;
int dwim_new_local_branch;
+ int fetch;
int discard_changes;
int accept_ref;
int accept_pathspec;
@@ -116,6 +119,129 @@ struct branch_info {
char *checkout;
};
+static void fetch_remote_for_start_point(const char *arg, int quiet)
+{
+ struct strbuf dst = STRBUF_INIT;
+ struct tracking tracking;
+ struct string_list tracking_srcs = STRING_LIST_INIT_DUP;
+ struct string_list ambiguous_remotes = STRING_LIST_INIT_DUP;
+ struct child_process cmd = CHILD_PROCESS_INIT;
+ struct object_id oid;
+ struct remote *named_remote;
+ int bare_ns;
+
+ strbuf_addf(&dst, "refs/remotes/%s", arg);
+ if (check_refname_format(dst.buf, 0))
+ die(_("cannot fetch start-point '%s': not a valid "
+ "remote-tracking name"), arg);
+
+ named_remote = remote_get(arg);
+ bare_ns = !strchr(arg, '/') ||
+ (named_remote && remote_is_configured(named_remote, 1));
+ if (bare_ns) {
+ char *head_path = xstrfmt("refs/remotes/%s/HEAD", arg);
+ const char *head_target =
+ refs_resolve_ref_unsafe(get_main_ref_store(the_repository),
+ head_path,
+ RESOLVE_REF_READING |
+ RESOLVE_REF_NO_RECURSE,
+ &oid, NULL);
+ if (head_target &&
+ starts_with(head_target, dst.buf) &&
+ head_target[dst.len] == '/' &&
+ !check_refname_format(head_target, 0)) {
+ strbuf_reset(&dst);
+ strbuf_addstr(&dst, head_target);
+ bare_ns = 0;
+ }
+ free(head_path);
+ }
+
+ memset(&tracking, 0, sizeof(tracking));
+ tracking.spec.dst = dst.buf;
+ tracking.srcs = &tracking_srcs;
+ find_tracking_remote_for_ref(&tracking, &ambiguous_remotes);
+
+ if (tracking.matches > 1) {
+ int status = die_message(_("cannot fetch start-point '%s': "
+ "fetch refspecs of multiple remotes "
+ "map to '%s'"), arg, dst.buf);
+ advise_ambiguous_fetch_refspec(dst.buf, &ambiguous_remotes);
+ exit(status);
+ }
+
+ if (!tracking.matches) {
+ if (bare_ns && named_remote &&
+ remote_is_configured(named_remote, 1))
+ die(_("cannot fetch start-point '%s': "
+ "'refs/remotes/%s/HEAD' is not set; run "
+ "'git remote set-head %s --auto' to set it"),
+ arg, arg, arg);
+ die(_("cannot fetch start-point '%s': no configured remote's "
+ "fetch refspec matches it"), arg);
+ }
+
+ strvec_push(&cmd.args, "fetch");
+ if (quiet)
+ strvec_push(&cmd.args, "--quiet");
+ strvec_pushl(&cmd.args, tracking.remote,
+ tracking_srcs.items[0].string, NULL);
+ cmd.git_cmd = 1;
+ if (run_command(&cmd)) {
+ if (!refs_read_ref(get_main_ref_store(the_repository),
+ dst.buf, &oid))
+ warning(_("failed to fetch start-point '%s'; "
+ "using existing '%s'"), arg, dst.buf);
+ else
+ die(_("failed to fetch start-point '%s'"), arg);
+ }
+
+ string_list_clear(&tracking_srcs, 0);
+ string_list_clear(&ambiguous_remotes, 0);
+ strbuf_release(&dst);
+}
+
+static int parse_opt_checkout_track(const struct option *opt,
+ const char *arg, int unset)
+{
+ struct checkout_opts *opts = opt->value;
+ struct string_list tokens = STRING_LIST_INIT_DUP;
+ struct string_list_item *item;
+ int saw_direct = 0;
+ int ret = 0;
+
+ opts->fetch = 0;
+ if (unset) {
+ opts->track = BRANCH_TRACK_NEVER;
+ return 0;
+ }
+ opts->track = BRANCH_TRACK_EXPLICIT;
+ if (!arg)
+ return 0;
+
+ string_list_split(&tokens, arg, ",", -1);
+ for_each_string_list_item(item, &tokens) {
+ if (!strcmp(item->string, "fetch"))
+ opts->fetch = 1;
+ else if (!strcmp(item->string, "direct"))
+ saw_direct = 1;
+ else if (!strcmp(item->string, "inherit"))
+ opts->track = BRANCH_TRACK_INHERIT;
+ else {
+ ret = error(_("option `%s' expects \"%s\", \"%s\", "
+ "or \"%s\""),
+ "--track", "direct", "inherit", "fetch");
+ goto out;
+ }
+ }
+ if (saw_direct && opts->track == BRANCH_TRACK_INHERIT)
+ ret = error(_("option `%s' cannot combine \"%s\" and \"%s\""),
+ "--track", "direct", "inherit");
+out:
+ string_list_clear(&tokens, 0);
+ return ret;
+}
+
static void branch_info_release(struct branch_info *info)
{
free(info->name);
@@ -1786,10 +1912,10 @@ static struct option *add_common_switch_branch_options(
{
struct option options[] = {
OPT_BOOL('d', "detach", &opts->force_detach, N_("detach HEAD at named commit")),
- OPT_CALLBACK_F('t', "track", &opts->track, "(direct|inherit)",
+ OPT_CALLBACK_F('t', "track", opts, "(direct|inherit|fetch)[,...]",
N_("set branch tracking configuration"),
PARSE_OPT_OPTARG,
- parse_opt_tracking_mode),
+ parse_opt_checkout_track),
OPT__FORCE(&opts->force, N_("force checkout (throw away local modifications)"),
PARSE_OPT_NOCOMPLETE),
OPT_STRING(0, "orphan", &opts->new_orphan_branch, N_("new-branch"), N_("new unborn branch")),
@@ -1994,8 +2120,13 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
opts->dwim_new_local_branch &&
opts->track == BRANCH_TRACK_UNSPECIFIED &&
!opts->new_branch;
- int n = parse_branchname_arg(argc, argv, dwim_ok, which_command,
- &new_branch_info, opts, &rev);
+ int n;
+
+ if (opts->fetch)
+ fetch_remote_for_start_point(argv[0], opts->quiet);
+
+ n = parse_branchname_arg(argc, argv, dwim_ok, which_command,
+ &new_branch_info, opts, &rev);
argv += n;
argc -= n;
} else if (!opts->accept_ref && opts->from_treeish) {
diff --git a/t/t7201-co.sh b/t/t7201-co.sh
index 7613b1d2a4..1e321b1512 100755
--- a/t/t7201-co.sh
+++ b/t/t7201-co.sh
@@ -870,4 +870,280 @@ test_expect_success 'tracking info copied with autoSetupMerge=inherit' '
test_cmp_config "" --default "" branch.main2.merge
'
+test_expect_success 'setup upstream for --track=fetch tests' '
+ git checkout main &&
+ git init fetch_upstream &&
+ test_commit -C fetch_upstream u_main &&
+ git remote add fetch_upstream fetch_upstream &&
+ git fetch fetch_upstream &&
+ git -C fetch_upstream checkout -b fetch_new &&
+ test_commit -C fetch_upstream u_new
+'
+
+test_expect_success 'checkout --track=fetch -b picks up branch created upstream after clone' '
+ git checkout main &&
+ test_must_fail git rev-parse --verify refs/remotes/fetch_upstream/fetch_new &&
+ git checkout --track=fetch -b local_new fetch_upstream/fetch_new &&
+ test_cmp_rev refs/remotes/fetch_upstream/fetch_new HEAD &&
+ test_cmp_config fetch_upstream branch.local_new.remote &&
+ test_cmp_config refs/heads/fetch_new branch.local_new.merge
+'
+
+test_expect_success 'checkout --track=fetch <remote>/<branch> leaves other tracking branches untouched' '
+ git checkout main &&
+ git -C fetch_upstream checkout -b fetch_target &&
+ test_commit -C fetch_upstream u_target_pre &&
+ git -C fetch_upstream checkout -b fetch_other &&
+ test_commit -C fetch_upstream u_other_pre &&
+ git fetch fetch_upstream &&
+ other_before=$(git rev-parse refs/remotes/fetch_upstream/fetch_other) &&
+ git -C fetch_upstream checkout fetch_target &&
+ test_commit -C fetch_upstream u_target_post &&
+ git -C fetch_upstream checkout fetch_other &&
+ test_commit -C fetch_upstream u_other_post &&
+ git checkout --track=fetch -b local_target fetch_upstream/fetch_target &&
+ test_cmp_rev refs/remotes/fetch_upstream/fetch_target HEAD &&
+ test "$(git rev-parse refs/remotes/fetch_upstream/fetch_other)" = "$other_before"
+'
+
+test_expect_success 'checkout --track=fetch with bare remote name fetches only <remote>/HEAD target' '
+ git checkout main &&
+ git -C fetch_upstream checkout main &&
+ git remote set-head fetch_upstream main &&
+ git -C fetch_upstream checkout -b fetch_unrelated &&
+ test_commit -C fetch_upstream u_unrelated_pre &&
+ git fetch fetch_upstream fetch_unrelated &&
+ unrelated_before=$(git rev-parse refs/remotes/fetch_upstream/fetch_unrelated) &&
+ git -C fetch_upstream checkout main &&
+ test_commit -C fetch_upstream u_main_post &&
+ git -C fetch_upstream checkout fetch_unrelated &&
+ test_commit -C fetch_upstream u_unrelated_post &&
+ git checkout --track=fetch -b local_from_remote fetch_upstream &&
+ test_cmp_rev refs/remotes/fetch_upstream/main HEAD &&
+ test "$(git rev-parse refs/remotes/fetch_upstream/fetch_unrelated)" = "$unrelated_before"
+'
+
+test_expect_success 'checkout --track=fetch aborts and does not create branch when no existing ref' '
+ git checkout main &&
+ test_might_fail git branch -D bogus &&
+ test_must_fail git checkout --track=fetch -b bogus fetch_upstream/does_not_exist &&
+ test_must_fail git rev-parse --verify refs/heads/bogus
+'
+
+test_expect_success 'checkout --track=fetch warns and proceeds when fetch fails but ref exists' '
+ git checkout main &&
+ git -C fetch_upstream checkout -b fetch_offline &&
+ test_commit -C fetch_upstream u_offline &&
+ git fetch fetch_upstream fetch_offline &&
+ saved_url=$(git config remote.fetch_upstream.url) &&
+ test_when_finished "git config remote.fetch_upstream.url \"$saved_url\"" &&
+ git config remote.fetch_upstream.url ./does-not-exist &&
+ git checkout --track=fetch -b local_offline fetch_upstream/fetch_offline 2>err &&
+ test_grep "failed to fetch" err &&
+ test_cmp_rev refs/remotes/fetch_upstream/fetch_offline HEAD
+'
+
+test_expect_success 'checkout --track=fetch resolves through configured fetch refspec' '
+ git checkout main &&
+ git remote add fetch_custom ./fetch_upstream &&
+ test_when_finished "git remote remove fetch_custom" &&
+ git config --replace-all remote.fetch_custom.fetch \
+ "+refs/heads/*:refs/remotes/custom-ns/*" &&
+ git -C fetch_upstream checkout -b fetch_refspec &&
+ test_commit -C fetch_upstream u_refspec &&
+ test_must_fail git rev-parse --verify refs/remotes/custom-ns/fetch_refspec &&
+ git checkout --track=fetch -b local_refspec custom-ns/fetch_refspec &&
+ test_cmp_rev refs/remotes/custom-ns/fetch_refspec HEAD
+'
+
+test_expect_success 'checkout --track=fetch on namespace bare name follows <ns>/HEAD' '
+ git checkout main &&
+ git remote add fetch_ns ./fetch_upstream &&
+ test_when_finished "git remote remove fetch_ns" &&
+ test_when_finished "git update-ref -d refs/remotes/ns_alias/HEAD" &&
+ git config --replace-all remote.fetch_ns.fetch \
+ "+refs/heads/*:refs/remotes/ns_alias/*" &&
+ git fetch fetch_ns &&
+ git symbolic-ref refs/remotes/ns_alias/HEAD refs/remotes/ns_alias/main &&
+ git -C fetch_upstream checkout main &&
+ test_commit -C fetch_upstream u_ns_post &&
+ git checkout --track=fetch -b local_ns ns_alias &&
+ test_cmp_rev refs/remotes/ns_alias/main HEAD &&
+ test_cmp_config fetch_ns branch.local_ns.remote &&
+ test_cmp_config refs/heads/main branch.local_ns.merge
+'
+
+test_expect_success '--track=fetch on bare hierarchical remote name follows <ns>/HEAD' '
+ git checkout main &&
+ git remote add nested/bare ./fetch_upstream &&
+ test_when_finished "git remote remove nested/bare" &&
+ test_when_finished "git update-ref -d refs/remotes/nested/bare/HEAD" &&
+ git fetch nested/bare &&
+ git symbolic-ref refs/remotes/nested/bare/HEAD \
+ refs/remotes/nested/bare/main &&
+ git -C fetch_upstream checkout main &&
+ test_commit -C fetch_upstream u_nested_bare_post &&
+ git checkout --track=fetch -b local_nested_bare nested/bare &&
+ test_cmp_rev refs/remotes/nested/bare/main HEAD
+'
+
+test_expect_success 'checkout --track=fetch handles hierarchical remote name' '
+ git checkout main &&
+ git remote add nested/remote ./fetch_upstream &&
+ test_when_finished "git remote remove nested/remote" &&
+ git -C fetch_upstream checkout -b fetch_hier &&
+ test_commit -C fetch_upstream u_hier &&
+ test_must_fail git rev-parse --verify refs/remotes/nested/remote/fetch_hier &&
+ git checkout --track=fetch -b local_hier nested/remote/fetch_hier &&
+ test_cmp_rev refs/remotes/nested/remote/fetch_hier HEAD
+'
+
+test_expect_success 'checkout --track=fetch dies on bare remote name with no <ns>/HEAD' '
+ git checkout main &&
+ git remote add fetch_nohead ./fetch_upstream &&
+ test_when_finished "git remote remove fetch_nohead" &&
+ test_might_fail git symbolic-ref -d refs/remotes/fetch_nohead/HEAD &&
+ test_must_fail git checkout --track=fetch -b local_nohead fetch_nohead 2>err &&
+ test_grep "refs/remotes/fetch_nohead/HEAD" err &&
+ test_grep "git remote set-head fetch_nohead --auto" err &&
+ test_must_fail git rev-parse --verify refs/heads/local_nohead
+'
+
+test_expect_success 'checkout --track=fetch on bare unknown name does not suggest set-head' '
+ git checkout main &&
+ test_must_fail git rev-parse --verify refs/remotes/no_such_ns/HEAD &&
+ test_must_fail git config --get remote.no_such_ns.url &&
+ test_must_fail git checkout --track=fetch -b local_unknown no_such_ns 2>err &&
+ test_grep "no configured remote" err &&
+ test_grep ! "set-head" err &&
+ test_must_fail git rev-parse --verify refs/heads/local_unknown
+'
+
+test_expect_success 'checkout --track=fetch rejects <ns>/HEAD pointing outside namespace' '
+ git checkout main &&
+ git remote add fetch_crossns ./fetch_upstream &&
+ test_when_finished "git remote remove fetch_crossns" &&
+ test_when_finished "git update-ref -d refs/remotes/fetch_crossns/HEAD" &&
+ git fetch fetch_crossns &&
+ git symbolic-ref refs/remotes/fetch_crossns/HEAD \
+ refs/remotes/fetch_upstream/u_main &&
+ test_must_fail git checkout --track=fetch -b local_crossns fetch_crossns 2>err &&
+ test_grep "refs/remotes/fetch_crossns/HEAD" err &&
+ test_must_fail git rev-parse --verify refs/heads/local_crossns
+'
+
+test_expect_success 'checkout --track=fetch dies on ambiguous fetch refspec match' '
+ git checkout main &&
+ git remote add fetch_ambig_a ./fetch_upstream &&
+ git remote add fetch_ambig_b ./fetch_upstream &&
+ test_when_finished "git remote remove fetch_ambig_a" &&
+ test_when_finished "git remote remove fetch_ambig_b" &&
+ git config --replace-all remote.fetch_ambig_a.fetch \
+ "+refs/heads/*:refs/remotes/ambig_ns/*" &&
+ git config --replace-all remote.fetch_ambig_b.fetch \
+ "+refs/heads/*:refs/remotes/ambig_ns/*" &&
+ git -C fetch_upstream checkout -b fetch_ambig &&
+ test_commit -C fetch_upstream u_ambig &&
+ test_must_fail git checkout --track=fetch -b local_ambig ambig_ns/fetch_ambig 2>err &&
+ test_grep "fetch_ambig_a" err &&
+ test_grep "fetch_ambig_b" err &&
+ test_grep "tracking namespaces" err &&
+ test_must_fail git rev-parse --verify refs/heads/local_ambig
+'
+
+test_expect_success 'checkout --track=fetch rejects invalid refname components' '
+ git checkout main &&
+ test_must_fail git checkout --track=fetch -b local_invalid "foo..bar" 2>err &&
+ test_grep "valid" err &&
+ test_must_fail git rev-parse --verify refs/heads/local_invalid
+'
+
+test_expect_success 'checkout --track=fetch,inherit rejects invalid refname components' '
+ git checkout main &&
+ test_must_fail git checkout --track=fetch,inherit -b local_invalid \
+ "foo..bar" 2>err &&
+ test_grep "valid" err &&
+ test_must_fail git rev-parse --verify refs/heads/local_invalid
+'
+
+test_expect_success 'checkout --track=inherit,direct is rejected' '
+ test_must_fail git checkout --track=inherit,direct -b bad fetch_upstream/fetch_new 2>err &&
+ test_grep "cannot combine" err
+'
+
+test_expect_success 'checkout --track=direct,inherit is rejected' '
+ test_must_fail git checkout --track=direct,inherit -b bad fetch_upstream/fetch_new 2>err &&
+ test_grep "cannot combine" err
+'
+
+test_expect_success 'checkout --track=fetch then --track=direct drops fetch (last-one-wins)' '
+ git checkout main &&
+ git -C fetch_upstream checkout -b fetch_lastwin &&
+ test_commit -C fetch_upstream u_lastwin &&
+ test_must_fail git rev-parse --verify refs/remotes/fetch_upstream/fetch_lastwin &&
+ test_must_fail git checkout --track=fetch --track=direct \
+ -b local_lastwin fetch_upstream/fetch_lastwin &&
+ test_must_fail git rev-parse --verify refs/remotes/fetch_upstream/fetch_lastwin
+'
+
+test_expect_success 'checkout --track=fetch then --no-track drops fetch' '
+ git checkout main &&
+ git -C fetch_upstream checkout -b fetch_notrack &&
+ test_commit -C fetch_upstream u_notrack &&
+ test_must_fail git rev-parse --verify refs/remotes/fetch_upstream/fetch_notrack &&
+ test_must_fail git checkout --track=fetch --no-track \
+ -b local_notrack fetch_upstream/fetch_notrack &&
+ test_must_fail git rev-parse --verify refs/remotes/fetch_upstream/fetch_notrack
+'
+
+test_expect_success 'checkout --track=fetch,inherit fetches remote-tracking start-point' '
+ git checkout main &&
+ git -C fetch_upstream checkout -b fetch_inherit &&
+ test_commit -C fetch_upstream u_inherit &&
+ test_must_fail git rev-parse --verify refs/remotes/fetch_upstream/fetch_inherit &&
+ git checkout --track=fetch,inherit -b local_inherit \
+ fetch_upstream/fetch_inherit &&
+ test_cmp_rev refs/remotes/fetch_upstream/fetch_inherit HEAD
+'
+
+test_expect_success 'checkout --track=fetch,inherit errors when start-point does not map to a remote' '
+ git checkout main &&
+ test_must_fail git checkout --track=fetch,inherit -b bad main 2>err &&
+ test_grep "no configured remote" err &&
+ test_must_fail git rev-parse --verify refs/heads/bad
+'
+
+test_expect_success 'checkout --track=fetch on local start-point errors' '
+ git checkout main &&
+ test_must_fail git checkout --track=fetch -b bad main 2>err &&
+ test_grep "no configured remote" err &&
+ test_must_fail git rev-parse --verify refs/heads/bad
+'
+
+test_expect_success 'checkout --track=bogus reports an error' '
+ git checkout main &&
+ test_must_fail git checkout --track=bogus -b bogus_branch fetch_upstream/fetch_new 2>err &&
+ test_grep "expects" err
+'
+
+test_expect_success 'checkout -q --track=fetch silences the fetch output' '
+ git checkout main &&
+ git -C fetch_upstream checkout -b fetch_quiet &&
+ test_commit -C fetch_upstream u_quiet &&
+ test_must_fail git rev-parse --verify refs/remotes/fetch_upstream/fetch_quiet &&
+ git checkout -q --track=fetch -b local_quiet \
+ fetch_upstream/fetch_quiet 2>err &&
+ test_grep ! "-> fetch_upstream/fetch_quiet" err &&
+ test_cmp_rev refs/remotes/fetch_upstream/fetch_quiet HEAD
+'
+
+test_expect_success 'switch --track=fetch -c picks up branch created upstream after clone' '
+ git checkout main &&
+ git -C fetch_upstream checkout -b fetch_switch &&
+ test_commit -C fetch_upstream u_switch &&
+ test_must_fail git rev-parse --verify refs/remotes/fetch_upstream/fetch_switch &&
+ git switch --track=fetch -c local_switch fetch_upstream/fetch_switch &&
+ test_cmp_rev refs/remotes/fetch_upstream/fetch_switch HEAD
+'
+
test_done
--
gitgitgadget
^ permalink raw reply related
* Re: [PATCH v2 0/2] environment: move ignore_case into repo_config_values
From: Junio C Hamano @ 2026-06-18 13:14 UTC (permalink / raw)
To: Tian Yuchen; +Cc: git, ps, phillip.wood123, johannes.schindelin, stolee
In-Reply-To: <20260618114207.605211-1-cat@malon.dev>
Tian Yuchen <cat@malon.dev> writes:
> Related materials:
>
> [1] In this patch to migrate protect_hfs and protect_ntfs, the approach
> of introducing getters has been endorsed.
>
> [2] Derrick Stolee's previous attempt. The reasons for the failure are
> also mentioned in [1].
[1] here refers to the starting message of the whole hfs/ntfs thing.
Do you mean that people must read the entire thread to find out what
the reasons for the failure was? For that matter, it is not clear,
unless readers read the whole thread, where the approach of using
getters was "endorsed", either.
> [1] https://lore.kernel.org/git/20260606143412.15443-1-cat@malon.dev/
> [2] https://lore.kernel.org/git/2b4198c09cb6c04c60608d19072d419503dfe5df.1685716421.git.gitgitgadget@gmail.com/
> Changes since V1:
>
> - s/repo_get_ignore_case()/repo_ignore_case()
>
> - Use repo->initialized instead of repo->gitdir
I do not think I have any objections to these changes from the
previous iteration. There may be some other things in the new
iteration but I'll have to go in and read the patches to find them
out (if they exist).
Thanks.
^ 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