Git development
 help / color / mirror / Atom feed
* Re: [PATCH] hex: add and use strbuf_add_oid_hex()
From: Jeff King @ 2026-05-13 16:01 UTC (permalink / raw)
  To: René Scharfe; +Cc: Git List
In-Reply-To: <183aa0fd-d455-4ec9-9c42-d511fac8b3e4@web.de>

On Wed, May 13, 2026 at 05:49:11PM +0200, René Scharfe wrote:

> Add a function for adding the full hexadecimal hash value of an object
> ID to a strbuf.  It's thread-safe and slightly more efficient than using
> strbuf_addstr() with oid_to_hex() because it doesn't have to determine
> the length of the string or copy it from the intermediate static buffer.
> 
> Add and apply a semantic patch to use it throughout the code base.
> 
> I get a tiny speedup for git log showing a single hash per commit:
> 
> Benchmark 1: ./git_main log --format=%H
>   Time (mean ± σ):      91.2 ms ±   0.7 ms    [User: 51.9 ms, System: 38.6 ms]
>   Range (min … max):    89.8 ms …  92.6 ms    31 runs
> 
> Benchmark 2: ./git log --format=%H
>   Time (mean ± σ):      90.5 ms ±   0.7 ms    [User: 51.0 ms, System: 38.8 ms]
>   Range (min … max):    89.2 ms …  92.3 ms    32 runs

Probably the most extreme benchmark would be:

  git cat-file --batch-all-objects --batch-check='%(objectname)'

which is really just dumping the oids from packfiles. I got ~3% speedup,
though like yours it's within the run-to-run noise.

I think this is worth doing solely for removing more instances of global
buffers, though.

-Peff

^ permalink raw reply

* Re: [BUG] "git diff --word-diff" gives a diff while they are only space changes
From: Michael Montalbo @ 2026-05-13 15:52 UTC (permalink / raw)
  To: Vincent Lefevre; +Cc: git, j6t
In-Reply-To: <20260512211707.GB1516810@qaa.vinc17.org>

On 2026-05-12 21:17 UTC, Vincent Lefevre wrote:
> Yes, this would be useful.

I've submitted a patch for this:
https://lore.kernel.org/git/pull.2113.git.1778686956622.gitgitgadget@gmail.com/T/#u

On Tue, May 12, 2026 at 2:17 PM Vincent Lefevre <vincent@vinc17.net> wrote:
>
> On 2026-05-12 13:56:19 -0700, Michael Montalbo wrote:
> > Maybe something like this would be worth adding to the docs:
> [...]
> > +Word diff works by finding word-level changes within each hunk of
> > +the line-level diff.  The line-level alignment determines which
> > +changed lines are compared to each other, which can affect the
> > +word-level output.
> [...]
>
> Yes, this would be useful.
>
> --
> Vincent Lefèvre <vincent@vinc17.net> - Web: <https://www.vinc17.net/>
> 100% accessible validated (X)HTML - Blog: <https://www.vinc17.net/blog/>
> Work: CR INRIA - computer arithmetic / Pascaline project (LIP, ENS-Lyon)

^ permalink raw reply

* [PATCH] hex: add and use strbuf_add_oid_hex()
From: René Scharfe @ 2026-05-13 15:49 UTC (permalink / raw)
  To: Git List

Add a function for adding the full hexadecimal hash value of an object
ID to a strbuf.  It's thread-safe and slightly more efficient than using
strbuf_addstr() with oid_to_hex() because it doesn't have to determine
the length of the string or copy it from the intermediate static buffer.

Add and apply a semantic patch to use it throughout the code base.

I get a tiny speedup for git log showing a single hash per commit:

Benchmark 1: ./git_main log --format=%H
  Time (mean ± σ):      91.2 ms ±   0.7 ms    [User: 51.9 ms, System: 38.6 ms]
  Range (min … max):    89.8 ms …  92.6 ms    31 runs

Benchmark 2: ./git log --format=%H
  Time (mean ± σ):      90.5 ms ±   0.7 ms    [User: 51.0 ms, System: 38.8 ms]
  Range (min … max):    89.2 ms …  92.3 ms    32 runs

Summary
  ./git log --format=%H ran
    1.01 ± 0.01 times faster than ./git_main log --format=%H

Signed-off-by: René Scharfe <l.s.r@web.de>
---
 bisect.c                      |  2 +-
 builtin/bisect.c              |  2 +-
 builtin/cat-file.c            |  5 ++---
 builtin/replace.c             |  2 +-
 convert.c                     |  2 +-
 fsck.c                        |  2 +-
 hex.c                         | 10 ++++++++++
 hex.h                         |  5 +++++
 pretty.c                      |  8 ++++----
 refs.c                        |  2 +-
 sequencer.c                   |  4 ++--
 shallow.c                     |  2 +-
 tools/coccinelle/strbuf.cocci |  6 ++++++
 transport-helper.c            |  2 +-
 14 files changed, 37 insertions(+), 17 deletions(-)

diff --git a/bisect.c b/bisect.c
index ef17a442e5..e67226a6dc 100644
--- a/bisect.c
+++ b/bisect.c
@@ -512,7 +512,7 @@ static char *join_oid_array_hex(struct oid_array *array, char delim)
 	int i;
 
 	for (i = 0; i < array->nr; i++) {
-		strbuf_addstr(&joined_hexs, oid_to_hex(array->oid + i));
+		strbuf_add_oid_hex(&joined_hexs, array->oid + i);
 		if (i + 1 < array->nr)
 			strbuf_addch(&joined_hexs, delim);
 	}
diff --git a/builtin/bisect.c b/builtin/bisect.c
index 4520e585d0..0f679e7af9 100644
--- a/builtin/bisect.c
+++ b/builtin/bisect.c
@@ -833,7 +833,7 @@ static enum bisect_error bisect_start(struct bisect_terms *terms, int argc,
 		if (!repo_get_oid(the_repository, head, &head_oid) &&
 		    !starts_with(head, "refs/heads/")) {
 			strbuf_reset(&start_head);
-			strbuf_addstr(&start_head, oid_to_hex(&head_oid));
+			strbuf_add_oid_hex(&start_head, &head_oid);
 		} else if (!repo_get_oid(the_repository, head, &head_oid) &&
 			   skip_prefix(head, "refs/heads/", &head)) {
 			strbuf_addstr(&start_head, head);
diff --git a/builtin/cat-file.c b/builtin/cat-file.c
index d9fbad5358..f015e5f415 100644
--- a/builtin/cat-file.c
+++ b/builtin/cat-file.c
@@ -320,7 +320,7 @@ static int expand_atom(struct strbuf *sb, const char *atom, int len,
 {
 	if (is_atom("objectname", atom, len)) {
 		if (!data->mark_query)
-			strbuf_addstr(sb, oid_to_hex(&data->oid));
+			strbuf_add_oid_hex(sb, &data->oid);
 	} else if (is_atom("objecttype", atom, len)) {
 		if (data->mark_query)
 			data->info.typep = &data->type;
@@ -345,8 +345,7 @@ static int expand_atom(struct strbuf *sb, const char *atom, int len,
 		if (data->mark_query)
 			data->info.delta_base_oid = &data->delta_base_oid;
 		else
-			strbuf_addstr(sb,
-				      oid_to_hex(&data->delta_base_oid));
+			strbuf_add_oid_hex(sb, &data->delta_base_oid);
 	} else if (is_atom("objectmode", atom, len)) {
 		if (!data->mark_query && !(S_IFINVALID == data->mode))
 			strbuf_addf(sb, "%06o", data->mode);
diff --git a/builtin/replace.c b/builtin/replace.c
index 4c62c5ab58..aed6b2c8de 100644
--- a/builtin/replace.c
+++ b/builtin/replace.c
@@ -127,7 +127,7 @@ static int for_each_replace_name(const char **argv, each_replace_name_fn fn)
 		}
 
 		strbuf_setlen(&ref, base_len);
-		strbuf_addstr(&ref, oid_to_hex(&oid));
+		strbuf_add_oid_hex(&ref, &oid);
 		full_hex = ref.buf + base_len;
 
 		if (refs_read_ref(get_main_ref_store(the_repository), ref.buf, &oid)) {
diff --git a/convert.c b/convert.c
index eae36c8a59..036506842c 100644
--- a/convert.c
+++ b/convert.c
@@ -1239,7 +1239,7 @@ static int ident_to_worktree(const char *src, size_t len,
 
 		/* step 4: substitute */
 		strbuf_addstr(buf, "Id: ");
-		strbuf_addstr(buf, oid_to_hex(&oid));
+		strbuf_add_oid_hex(buf, &oid);
 		strbuf_addstr(buf, " $");
 	}
 	strbuf_add(buf, src, len);
diff --git a/fsck.c b/fsck.c
index b72200c352..b4ffee6a04 100644
--- a/fsck.c
+++ b/fsck.c
@@ -344,7 +344,7 @@ const char *fsck_describe_object(struct fsck_options *options,
 	buf = bufs + b;
 	b = (b + 1) % ARRAY_SIZE(bufs);
 	strbuf_reset(buf);
-	strbuf_addstr(buf, oid_to_hex(oid));
+	strbuf_add_oid_hex(buf, oid);
 	if (name)
 		strbuf_addf(buf, " (%s)", name);
 
diff --git a/hex.c b/hex.c
index bc756722ca..f02832140d 100644
--- a/hex.c
+++ b/hex.c
@@ -3,6 +3,7 @@
 #include "git-compat-util.h"
 #include "hash.h"
 #include "hex.h"
+#include "strbuf.h"
 
 static int get_hash_hex_algop(const char *hex, unsigned char *hash,
 			      const struct git_hash_algo *algop)
@@ -122,3 +123,12 @@ char *oid_to_hex(const struct object_id *oid)
 {
 	return hash_to_hex_algop(oid->hash, &hash_algos[oid->algo]);
 }
+
+void strbuf_add_oid_hex(struct strbuf *sb, const struct object_id *oid)
+{
+	const struct git_hash_algo *algop = oid->algo ?
+		&hash_algos[oid->algo] : the_hash_algo;
+	strbuf_grow(sb, algop->hexsz);
+	hash_to_hex_algop_r(sb->buf + sb->len, oid->hash, algop);
+	strbuf_setlen(sb, sb->len + algop->hexsz);
+}
diff --git a/hex.h b/hex.h
index 1e9a65d83a..f15c7e2220 100644
--- a/hex.h
+++ b/hex.h
@@ -33,6 +33,11 @@ char *oid_to_hex_r(char *out, const struct object_id *oid);
 char *hash_to_hex_algop(const unsigned char *hash, const struct git_hash_algo *);	/* static buffer result! */
 char *oid_to_hex(const struct object_id *oid);						/* same static buffer */
 
+struct strbuf;
+
+/* Apply oid_to_hex_r() to a strbuf to append the hexadecimal hash. */
+void strbuf_add_oid_hex(struct strbuf *sb, const struct object_id *oid);
+
 /*
  * Parse a 40-character hexadecimal object ID starting from hex, updating the
  * pointer specified by end when parsing stops.  The resulting object ID is
diff --git a/pretty.c b/pretty.c
index 814803980b..2684223946 100644
--- a/pretty.c
+++ b/pretty.c
@@ -662,7 +662,7 @@ static void add_merge_info(const struct pretty_print_context *pp,
 		if (pp->abbrev)
 			strbuf_add_unique_abbrev(sb, oidp, pp->abbrev);
 		else
-			strbuf_addstr(sb, oid_to_hex(oidp));
+			strbuf_add_oid_hex(sb, oidp);
 		parent = parent->next;
 	}
 	strbuf_addch(sb, '\n');
@@ -1567,7 +1567,7 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
 	switch (placeholder[0]) {
 	case 'H':		/* commit hash */
 		strbuf_addstr(sb, diff_get_color(c->auto_color, DIFF_COMMIT));
-		strbuf_addstr(sb, oid_to_hex(&commit->object.oid));
+		strbuf_add_oid_hex(sb, &commit->object.oid);
 		strbuf_addstr(sb, diff_get_color(c->auto_color, DIFF_RESET));
 		return 1;
 	case 'h':		/* abbreviated commit hash */
@@ -1577,7 +1577,7 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
 		strbuf_addstr(sb, diff_get_color(c->auto_color, DIFF_RESET));
 		return 1;
 	case 'T':		/* tree hash */
-		strbuf_addstr(sb, oid_to_hex(get_commit_tree_oid(commit)));
+		strbuf_add_oid_hex(sb, get_commit_tree_oid(commit));
 		return 1;
 	case 't':		/* abbreviated tree hash */
 		strbuf_add_unique_abbrev(sb,
@@ -1588,7 +1588,7 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
 		for (p = commit->parents; p; p = p->next) {
 			if (p != commit->parents)
 				strbuf_addch(sb, ' ');
-			strbuf_addstr(sb, oid_to_hex(&p->item->object.oid));
+			strbuf_add_oid_hex(sb, &p->item->object.oid);
 		}
 		return 1;
 	case 'p':		/* abbreviated parent hashes */
diff --git a/refs.c b/refs.c
index 844785219d..ee92f18d41 100644
--- a/refs.c
+++ b/refs.c
@@ -2498,7 +2498,7 @@ int refs_update_symref_extended(struct ref_store *refs, const char *ref,
 	if (referent && refs_read_symbolic_ref(refs, ref, referent) == NOT_A_SYMREF) {
 		struct object_id oid;
 		if (!refs_read_ref(refs, ref, &oid)) {
-			strbuf_addstr(referent, oid_to_hex(&oid));
+			strbuf_add_oid_hex(referent, &oid);
 			ret = NOT_A_SYMREF;
 		}
 	}
diff --git a/sequencer.c b/sequencer.c
index b7d8dca47f..b4df04b672 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -2223,7 +2223,7 @@ static void refer_to_commit(struct repository *r, struct strbuf *msgbuf,
 		repo_format_commit_message(r, commit,
 					   "%h (%s, %ad)", msgbuf, &ctx);
 	} else {
-		strbuf_addstr(msgbuf, oid_to_hex(&commit->object.oid));
+		strbuf_add_oid_hex(msgbuf, &commit->object.oid);
 	}
 }
 
@@ -2395,7 +2395,7 @@ static int do_pick_commit(struct repository *r,
 			if (!has_conforming_footer(&ctx->message, NULL, 0))
 				strbuf_addch(&ctx->message, '\n');
 			strbuf_addstr(&ctx->message, cherry_picked_prefix);
-			strbuf_addstr(&ctx->message, oid_to_hex(&commit->object.oid));
+			strbuf_add_oid_hex(&ctx->message, &commit->object.oid);
 			strbuf_addstr(&ctx->message, ")\n");
 		}
 		if (!is_fixup(command))
diff --git a/shallow.c b/shallow.c
index a8ad92e303..b4b4e2e32a 100644
--- a/shallow.c
+++ b/shallow.c
@@ -395,7 +395,7 @@ static int write_shallow_commits_1(struct strbuf *out, int use_pack_protocol,
 	if (!extra)
 		return data.count;
 	for (size_t i = 0; i < extra->nr; i++) {
-		strbuf_addstr(out, oid_to_hex(extra->oid + i));
+		strbuf_add_oid_hex(out, extra->oid + i);
 		strbuf_addch(out, '\n');
 		data.count++;
 	}
diff --git a/tools/coccinelle/strbuf.cocci b/tools/coccinelle/strbuf.cocci
index f586128329..667903d1d4 100644
--- a/tools/coccinelle/strbuf.cocci
+++ b/tools/coccinelle/strbuf.cocci
@@ -78,3 +78,9 @@ struct strbuf SB;
 @@
 - SB.buf ? SB.buf : ""
 + SB.buf
+
+@@
+expression SB, OID;
+@@
+- strbuf_addstr(SB, oid_to_hex(OID))
++ strbuf_add_oid_hex(SB, OID)
diff --git a/transport-helper.c b/transport-helper.c
index 4614036c99..4a54769789 100644
--- a/transport-helper.c
+++ b/transport-helper.c
@@ -1051,7 +1051,7 @@ static int push_refs_with_push(struct transport *transport,
 			if (ref->peer_ref)
 				strbuf_addstr(&buf, ref->peer_ref->name);
 			else
-				strbuf_addstr(&buf, oid_to_hex(&ref->new_oid));
+				strbuf_add_oid_hex(&buf, &ref->new_oid);
 		}
 		strbuf_addch(&buf, ':');
 		strbuf_addstr(&buf, ref->name);
-- 
2.54.0

^ permalink raw reply related

* [PATCH] doc: clarify that --word-diff operates on line-level hunks
From: Michael Montalbo via GitGitGadget @ 2026-05-13 15:42 UTC (permalink / raw)
  To: git; +Cc: Michael Montalbo, Michael Montalbo

From: Michael Montalbo <mmontalbo@gmail.com>

The --word-diff documentation describes the output modes and
word-regex mechanics but does not explain that word-diff operates
within the hunks produced by the line-level diff rather than
performing an independent word-stream comparison.  This can
surprise users when the line-level alignment causes word-level
changes to appear even though the words in both files are
identical.

Add a short note explaining the two-stage relationship.

Signed-off-by: Michael Montalbo <mmontalbo@gmail.com>
---
    doc: clarify that --word-diff operates on line-level hunks
    
    CC: Vincent Lefevre vincent@vinc17.net, Johannes Sixt j6t@kdbg.org

Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-2113%2Fmmontalbo%2Fmm%2Fdoc-word-diff-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-2113/mmontalbo/mm/doc-word-diff-v1
Pull-Request: https://github.com/gitgitgadget/git/pull/2113

 Documentation/diff-options.adoc | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/Documentation/diff-options.adoc b/Documentation/diff-options.adoc
index 8a63b5e164..665473e61a 100644
--- a/Documentation/diff-options.adoc
+++ b/Documentation/diff-options.adoc
@@ -457,6 +457,11 @@ endif::git-diff[]
 +
 Note that despite the name of the first mode, color is used to
 highlight the changed parts in all modes if enabled.
++
+Word diff works by finding word-level changes within each hunk of
+the line-level diff.  The line-level alignment determines which
+changed lines are compared to each other, which can affect the
+word-level output.
 
 `--word-diff-regex=<regex>`::
 	Use _<regex>_ to decide what a word is, instead of considering

base-commit: 94f057755b7941b321fd11fec1b2e3ca5313a4e0
-- 
gitgitgadget

^ permalink raw reply related

* Re: Bug: lowercase "head" resolves to wrong commit in linked worktrees on case-insensitive filesystems
From: D. Ben Knoble @ 2026-05-13 15:42 UTC (permalink / raw)
  To: Alexander Sandström; +Cc: git
In-Reply-To: <95BE8E60-1684-4E0A-9E46-E61E81D06CE1@alexandersandstrom.se>

On Wed, May 13, 2026 at 4:27 AM Alexander Sandström
<mail@alexandersandstrom.se> wrote:
>
> Hello everyone,
>
> I ran into a bug that took me a while to figure out.
>
> I'm sadly not a good enough C programmer to submit a proper patch,
> but perhaps this bug report will at least be indexed by search engines
> and help others that might have this issue to understand the cause.
>
> My guess is that it will happen much more frequently now that
> worktrees are more popular.
>
> **Report**
>
> On case-insensitive filesystems (macOS APFS/HFS+), `git rev-parse head`
> (lowercase) in a linked worktree resolves to the main worktree's HEAD
> rather than the current worktree's HEAD. This causes commands like
> `git reset --soft head~1` to silently operate on the wrong commit.

See also https://lore.kernel.org/git/20240701033145.GB610406@coredump.intra.peff.net/
and https://lore.kernel.org/git/8BABB6F0-517F-4AA0-9FF9-92AF8C33CD0E@strongestfamilies.com/

In short, it's a known issue (that I don't think we're going to solve
in the ref store where refs are just named files). Using reftables
ought to make the papercuts go away.

-- 
D. Ben Knoble

^ permalink raw reply

* Re: [PATCH v3] ignore: note info/exclude lives in GIT_COMMON_DIR, not GIT_DIR
From: Phillip Wood @ 2026-05-13 14:02 UTC (permalink / raw)
  To: D. Ben Knoble, git
  Cc: brian m . carlson, Patrick Steinhardt, Taylor Blau, Caleb White,
	Junio C Hamano, Elijah Newren, Andrew Berry, Jeff King,
	Derrick Stolee, Phillip Wood, Shreyansh Paliwal, Dan Drake,
	Alex Galvin
In-Reply-To: <ec97ad3f054e90b675f099a36a81a23bb4b2a0ed.1778620784.git.ben.knoble+github@gmail.com>

Hi Ben

On 12/05/2026 22:21, D. Ben Knoble wrote:
> gitignore(5) says that the per-repository ignore file is
> $GIT_DIR/info/exclude, but in a worktree that is not the case:
> 
>      git rev-parse --git-path info/exclude
>      /path/to/main/worktree/.git/info/exclude
>      git rev-parse --git-common-dir
>      /path/to/main/worktree/.git
> 
> We actually use $GIT_COMMON_DIR/info/exclude. Adjust the documentation
> and some code comments to say so.

Thanks for the re-roll, this looks good to me

Phillip

> Signed-off-by: D. Ben Knoble <ben.knoble+github@gmail.com>
> ---
> 
> Notes (benknoble/commits):
>      Changes in v3:
>      
>      Adjust more occurrences
>      
>      Link to v2: <d58b6e921d3005c6170fc6c47f175214acb3fa68.1778249267.git.ben.knoble+github@gmail.com>
>      
>      Changes in v2:
>      
>      Only adjust the documentation.
>      
>      brian points out that a more general extension would allow using more
>      info/ files as "per-worktree," which I don't have the impetus to
>      implement myself.
>      
>      Phillip and Junio asked for a concrete use case:
>      
>          A colleague is developing a tool for managing the "skill files" of
>          various LLM tools (Claude, Windsurf, etc.). The files have
>          requirements that make it hard to generically ignore them (e.g.,
>          filenames and front-matter have to match), but different tasks
>          (corresponding to worktrees) may want different active skills, so it
>          is desirable to ignore the files. Think of this like node_modules.
>      
>          Unfortunately, since per-worktree ignores don't work, the current
>          solution is to put a .gitignore file in the corresponding directory
>          with the installed skills that ignores itself and the installed
>          skills.
>      
>      Since overall reactions seem fairly negative (or require a more general
>      extension, which I think is probably the right course but not simply
>      implemented), I've opted to adjust the docs. They originally confused
>      me, as I was surprised when my colleague reported that per-worktree
>      ignores didn't work (the docs imply they should by use of $GIT_DIR).
>      
>      Link to v1: <e3ee0a11b566dd2cc605447c111ae4620bce0fe6.1777050300.git.ben.knoble+github@gmail.com>
>      
>      v1 notes:
>      
>      Discussed briefly at https://lore.kernel.org/git/CALnO6CCXmA+ATT7CuyWkU6P8qmLCCpMi5Ppr1c78s0heznpVyw@mail.gmail.com/T
>      
>      This is based on next (4f69b47b94 (Merge branch 'ps/test-set-e-clean'
>      into next, 2026-04-23)) but cleanly applies to master (94f057755b (Git
>      2.54, 2026-04-19)) and seen (50541634cb (Merge branch
>      'js/parseopt-subcommand-autocorrection' into seen, 2026-04-23)).
> 
>   Documentation/git-ls-files.adoc    |  2 +-
>   Documentation/git-svn.adoc         |  2 +-
>   Documentation/gitformat-index.adoc |  4 ++--
>   Documentation/gitignore.adoc       | 12 ++++++------
>   dir.c                              |  4 ++--
>   dir.h                              |  2 +-
>   6 files changed, 13 insertions(+), 13 deletions(-)
> 
> diff --git a/Documentation/git-ls-files.adoc b/Documentation/git-ls-files.adoc
> index 58c529afbe..2b175388e1 100644
> --- a/Documentation/git-ls-files.adoc
> +++ b/Documentation/git-ls-files.adoc
> @@ -331,7 +331,7 @@ can give `--exclude-per-directory=.gitignore`, and then specify:
>     1. The file specified by the `core.excludesfile` configuration
>        variable, if exists, or the `$XDG_CONFIG_HOME/git/ignore` file.
>   
> -  2. The `$GIT_DIR/info/exclude` file.
> +  2. The `$GIT_COMMON_DIR/info/exclude` file.
>   
>   via the `--exclude-from=` option.
>   
> diff --git a/Documentation/git-svn.adoc b/Documentation/git-svn.adoc
> index c26c12bab3..2a7fa60465 100644
> --- a/Documentation/git-svn.adoc
> +++ b/Documentation/git-svn.adoc
> @@ -439,7 +439,7 @@ Any other arguments are passed directly to 'git log'
>   'show-ignore'::
>   	Recursively finds and lists the svn:ignore and svn:global-ignores
>   	properties on directories. The output is suitable for appending to
> -	the $GIT_DIR/info/exclude file.
> +	the $GIT_COMMON_DIR/info/exclude file.
>   
>   'mkdirs'::
>   	Attempts to recreate empty directories that core Git cannot track
> diff --git a/Documentation/gitformat-index.adoc b/Documentation/gitformat-index.adoc
> index 145cace1fe..f6a427cb49 100644
> --- a/Documentation/gitformat-index.adoc
> +++ b/Documentation/gitformat-index.adoc
> @@ -291,14 +291,14 @@ Git index format
>       sequence in variable width encoding. Each string describes the
>       environment where the cache can be used.
>   
> -  - Stat data of $GIT_DIR/info/exclude. See "Index entry" section from
> +  - Stat data of $GIT_COMMON_DIR/info/exclude. See "Index entry" section from
>       ctime field until "file size".
>   
>     - Stat data of core.excludesFile
>   
>     - 32-bit dir_flags (see struct dir_struct)
>   
> -  - Hash of $GIT_DIR/info/exclude. A null hash means the file
> +  - Hash of $GIT_COMMON_DIR/info/exclude. A null hash means the file
>       does not exist.
>   
>     - Hash of core.excludesFile. A null hash means the file does
> diff --git a/Documentation/gitignore.adoc b/Documentation/gitignore.adoc
> index a3d24e5c34..7979e50f18 100644
> --- a/Documentation/gitignore.adoc
> +++ b/Documentation/gitignore.adoc
> @@ -7,7 +7,7 @@ gitignore - Specifies intentionally untracked files to ignore
>   
>   SYNOPSIS
>   --------
> -$XDG_CONFIG_HOME/git/ignore, $GIT_DIR/info/exclude, .gitignore
> +$XDG_CONFIG_HOME/git/ignore, $GIT_COMMON_DIR/info/exclude, .gitignore
>   
>   DESCRIPTION
>   -----------
> @@ -34,7 +34,7 @@ precedence, the last matching pattern decides the outcome):
>      includes such `.gitignore` files in its repository, containing patterns for
>      files generated as part of the project build.
>   
> - * Patterns read from `$GIT_DIR/info/exclude`.
> + * Patterns read from `$GIT_COMMON_DIR/info/exclude`.
>   
>    * Patterns read from the file specified by the configuration
>      variable `core.excludesFile`.
> @@ -50,7 +50,7 @@ be used.
>      specific to a particular repository but which do not need to be shared
>      with other related repositories (e.g., auxiliary files that live inside
>      the repository but are specific to one user's workflow) should go into
> -   the `$GIT_DIR/info/exclude` file.
> +   the `$GIT_COMMON_DIR/info/exclude` file.
>   
>    * Patterns which a user wants Git to
>      ignore in all situations (e.g., backup or temporary files generated by
> @@ -97,7 +97,7 @@ PATTERN FORMAT
>      match at any level below the `.gitignore` level.
>   
>    - Patterns read from exclude sources that are outside the working tree,
> -   such as $GIT_DIR/info/exclude and core.excludesFile, are treated as if
> +   such as $GIT_COMMON_DIR/info/exclude and core.excludesFile, are treated as if
>      they are specified at the root of the working tree, i.e. a leading "/"
>      in such patterns anchors the match at the root of the repository.
>   
> @@ -146,8 +146,8 @@ CONFIGURATION
>   
>   The optional configuration variable `core.excludesFile` indicates a path to a
>   file containing patterns of file names to exclude, similar to
> -`$GIT_DIR/info/exclude`.  Patterns in the exclude file are used in addition to
> -those in `$GIT_DIR/info/exclude`.
> +`$GIT_COMMON_DIR/info/exclude`. Patterns in the exclude file are used in
> +addition to those in `$GIT_COMMON_DIR/info/exclude`.
>   
>   NOTES
>   -----
> diff --git a/dir.c b/dir.c
> index fcb8f6dd2a..33c81c256e 100644
> --- a/dir.c
> +++ b/dir.c
> @@ -2985,7 +2985,7 @@ static struct untracked_cache_dir *validate_untracked_cache(struct dir_struct *d
>   		return NULL;
>   
>   	/*
> -	 * We only support $GIT_DIR/info/exclude and core.excludesfile
> +	 * We only support $GIT_COMMON_DIR/info/exclude and core.excludesfile
>   	 * as the global ignore rule files. Any other additions
>   	 * (e.g. from command line) invalidate the cache. This
>   	 * condition also catches running setup_standard_excludes()
> @@ -3078,7 +3078,7 @@ static struct untracked_cache_dir *validate_untracked_cache(struct dir_struct *d
>   		istate->cache_changed |= UNTRACKED_CHANGED;
>   	}
>   
> -	/* Validate $GIT_DIR/info/exclude and core.excludesfile */
> +	/* Validate $GIT_COMMON_DIR/info/exclude and core.excludesfile */
>   	root = dir->untracked->root;
>   	if (!oideq(&dir->internal.ss_info_exclude.oid,
>   		   &dir->untracked->ss_info_exclude.oid)) {
> diff --git a/dir.h b/dir.h
> index 20d4a078d6..83e0f648a8 100644
> --- a/dir.h
> +++ b/dir.h
> @@ -153,7 +153,7 @@ struct oid_stat {
>    *   - The list of files and directories of the directory in question
>    *   - The $GIT_DIR/index
>    *   - dir_struct flags
> - *   - The content of $GIT_DIR/info/exclude
> + *   - The content of $GIT_COMMON_DIR/info/exclude
>    *   - The content of core.excludesfile
>    *   - The content (or the lack) of .gitignore of all parent directories
>    *     from $GIT_WORK_TREE
> 
> base-commit: 59709faab07346122d819453f4ad6f3ccdaf618e


^ permalink raw reply

* Re: [PATCH v4 2/2] config: add "worktree" and "worktree/i" includeIf conditions
From: Phillip Wood @ 2026-05-13 13:59 UTC (permalink / raw)
  To: me, git
  Cc: Kristoffer Haugsbakk, Junio C Hamano, Patrick Steinhardt,
	Phillip Wood
In-Reply-To: <20260513-includeif-worktree-v4-2-f8e6212d1fba@black-desk.cn>

On 13/05/2026 09:08, Chen Linxuan via B4 Relay wrote:
> From: Chen Linxuan <me@black-desk.cn>
> 
> diff --git a/Documentation/config.adoc b/Documentation/config.adoc
> index 62eebe7c5450..6299b1e3a019 100644
> --- a/Documentation/config.adoc
> +++ b/Documentation/config.adoc
> @@ -146,6 +146,46 @@ refer to linkgit:gitignore[5] for details. For convenience:
>   	This is the same as `gitdir` except that matching is done
>   	case-insensitively (e.g. on case-insensitive file systems)
>   
> +`worktree`::
> +	The data that follows the keyword `worktree` and a colon is used as a
> +	glob pattern. If the working directory of the current worktree matches
> +	the pattern, the include condition is met.
> ++
> +The worktree location is the path where files are checked out (as returned
> +by `git rev-parse --show-toplevel`). This is different from `gitdir`, which
> +matches the `.git` directory path. In a linked worktree, the worktree path
> +is the directory where that worktree's files are located, not the main
> +repository's `.git` directory.
> ++
> +The pattern uses the same glob syntax as `gitdir` (including `~/`, `./`,
> +`**/`, and trailing-`/` prefix matching). This condition will never match
> +in a bare repository (which has no worktree).
> ++
> +This is useful when you want to apply configuration based on where the
> +working tree is located on the filesystem. For example, a contributor who
> +works on the same project both personally and as an employee can use
> +different `user.name` and `user.email` values depending on which directory
> +the worktree is checked out under:
> ++
> +----
> +[includeIf "worktree:/home/user/work/"]
> +    path = ~/.config/git/work.inc
> +[includeIf "worktree:/home/user/personal/"]
> +    path = ~/.config/git/personal.inc
> +----
> ++
> +While `extensions.worktreeConfig` (see linkgit:git-worktree[1]) also supports
> +per-worktree configuration, it stores the config inside each repository's
> +`.git/config.worktree` file and requires running `git config --worktree`
> +inside each worktree individually. In contrast, `includeIf "worktree:..."`
> +can be set once in a global or system-level configuration file (e.g.
> +`~/.config/git/config`) and applies to all repositories at once based on
> +their worktree location.

Thanks for expanding the documentation - this looks good to me.

Phillip

> +`worktree/i`::
> +	This is the same as `worktree` except that matching is done
> +	case-insensitively (e.g. on case-insensitive file systems)
> +
>   `onbranch`::
>   	The data that follows the keyword `onbranch` and a colon is taken to be a
>   	pattern with standard globbing wildcards and two additional
> @@ -244,6 +284,14 @@ Example
>   [includeIf "gitdir:~/to/group/"]
>   	path = /path/to/foo.inc
>   
> +; include if the worktree is at /path/to/project-build
> +[includeIf "worktree:/path/to/project-build"]
> +	path = build-config.inc
> +
> +; include for all worktrees inside /path/to/group
> +[includeIf "worktree:/path/to/group/"]
> +	path = group-config.inc
> +
>   ; relative paths are always relative to the including
>   ; file (if the condition is true); their location is not
>   ; affected by the condition
> diff --git a/config.c b/config.c
> index 7d5dae0e8450..6d0c2d0725e4 100644
> --- a/config.c
> +++ b/config.c
> @@ -400,6 +400,12 @@ static int include_condition_is_true(const struct key_value_info *kvi,
>   		return include_by_path(kvi, opts->git_dir, cond, cond_len, 0);
>   	else if (skip_prefix_mem(cond, cond_len, "gitdir/i:", &cond, &cond_len))
>   		return include_by_path(kvi, opts->git_dir, cond, cond_len, 1);
> +	else if (skip_prefix_mem(cond, cond_len, "worktree:", &cond, &cond_len))
> +		return include_by_path(kvi, inc->repo ? repo_get_work_tree(inc->repo) : NULL,
> +				       cond, cond_len, 0);
> +	else if (skip_prefix_mem(cond, cond_len, "worktree/i:", &cond, &cond_len))
> +		return include_by_path(kvi, inc->repo ? repo_get_work_tree(inc->repo) : NULL,
> +				       cond, cond_len, 1);
>   	else if (skip_prefix_mem(cond, cond_len, "onbranch:", &cond, &cond_len))
>   		return include_by_branch(inc, cond, cond_len);
>   	else if (skip_prefix_mem(cond, cond_len, "hasconfig:remote.*.url:", &cond,
> diff --git a/t/t1305-config-include.sh b/t/t1305-config-include.sh
> index 6e51f892f320..07b6fb649cd2 100755
> --- a/t/t1305-config-include.sh
> +++ b/t/t1305-config-include.sh
> @@ -396,4 +396,117 @@ test_expect_success 'onbranch without repository but explicit nonexistent Git di
>   	test_must_fail nongit git --git-dir=nonexistent config get foo.bar
>   '
>   
> +# worktree: conditional include tests
> +
> +test_expect_success 'conditional include, worktree bare repo' '
> +	git init --bare wt-bare &&
> +	(
> +		cd wt-bare &&
> +		echo "[includeIf \"worktree:/\"]path=bar-bare" >>config &&
> +		echo "[test]wtbare=1" >bar-bare &&
> +		test_must_fail git config test.wtbare
> +	)
> +'
> +
> +test_expect_success 'conditional include, worktree multiple worktrees' '
> +	git init wt-multi &&
> +	(
> +		cd wt-multi &&
> +		test_commit initial &&
> +		git worktree add -b linked-branch ../wt-linked HEAD &&
> +		git worktree add -b prefix-branch ../wt-prefix/linked HEAD
> +	) &&
> +	wt_main="$(cd wt-multi && pwd)" &&
> +	wt_linked="$(cd wt-linked && pwd)" &&
> +	wt_prefix_parent="$(cd wt-prefix && pwd)" &&
> +	cat >>wt-multi/.git/config <<-EOF &&
> +	[includeIf "worktree:$wt_main"]
> +		path = main-config
> +	[includeIf "worktree:$wt_linked"]
> +		path = linked-config
> +	[includeIf "worktree:$wt_prefix_parent/"]
> +		path = prefix-config
> +	EOF
> +	echo "[test]mainvar=main" >wt-multi/.git/main-config &&
> +	echo "[test]linkedvar=linked" >wt-multi/.git/linked-config &&
> +	echo "[test]prefixvar=prefix" >wt-multi/.git/prefix-config &&
> +	echo main >expect &&
> +	git -C wt-multi config test.mainvar >actual &&
> +	test_cmp expect actual &&
> +	test_must_fail git -C wt-multi config test.linkedvar &&
> +	test_must_fail git -C wt-multi config test.prefixvar &&
> +	echo linked >expect &&
> +	git -C wt-linked config test.linkedvar >actual &&
> +	test_cmp expect actual &&
> +	test_must_fail git -C wt-linked config test.mainvar &&
> +	test_must_fail git -C wt-linked config test.prefixvar &&
> +	echo prefix >expect &&
> +	git -C wt-prefix/linked config test.prefixvar >actual &&
> +	test_cmp expect actual &&
> +	test_must_fail git -C wt-prefix/linked config test.mainvar &&
> +	test_must_fail git -C wt-prefix/linked config test.linkedvar
> +'
> +
> +test_expect_success SYMLINKS 'conditional include, worktree resolves symlinks' '
> +	mkdir real-wt &&
> +	ln -s real-wt link-wt &&
> +	git init link-wt/repo &&
> +	(
> +		cd link-wt/repo &&
> +		# repo->worktree resolves symlinks, so use real path in pattern
> +		echo "[includeIf \"worktree:**/real-wt/repo\"]path=bar-link" >>.git/config &&
> +		echo "[test]wtlink=2" >.git/bar-link &&
> +		echo 2 >expect &&
> +		git config test.wtlink >actual &&
> +		test_cmp expect actual
> +	)
> +'
> +
> +test_expect_success 'conditional include, worktree, icase' '
> +	git init wt-icase &&
> +	(
> +		cd wt-icase &&
> +		test_commit initial &&
> +		wt_path="$(pwd)" &&
> +		wt_upper=$(echo "$wt_path" | tr a-z A-Z) &&
> +		echo "[includeIf \"worktree/i:$wt_upper\"]path=icase-inc" >>.git/config &&
> +		echo "[test]wticase=1" >.git/icase-inc &&
> +		echo 1 >expect &&
> +		git config test.wticase >actual &&
> +		test_cmp expect actual
> +	)
> +'
> +
> +# The "worktree" condition cannot match during early config reading
> +# because the repository object is not yet fully initialized and
> +# repo_get_work_tree() returns NULL.
> +test_expect_success 'conditional include, worktree does not match in early config' '
> +	git init wt-early &&
> +	(
> +		cd wt-early &&
> +		test_commit initial &&
> +		wt_path="$(pwd)" &&
> +		echo "[includeIf \"worktree:$wt_path\"]path=early-inc" >>.git/config &&
> +		echo "[test]wtearly=1" >.git/early-inc &&
> +		test-tool config read_early_config test.wtearly >actual &&
> +		test_must_be_empty actual
> +	)
> +'
> +
> +test_expect_success 'conditional include, worktree without repository' '
> +	test_when_finished "rm -f .gitconfig config.inc" &&
> +	git config set -f .gitconfig "includeIf.worktree:/.path" config.inc &&
> +	git config set -f config.inc foo.bar baz &&
> +	git config get foo.bar &&
> +	test_must_fail nongit git config get foo.bar
> +'
> +
> +test_expect_success 'conditional include, worktree without repository but explicit nonexistent Git directory' '
> +	test_when_finished "rm -f .gitconfig config.inc" &&
> +	git config set -f .gitconfig "includeIf.worktree:/.path" config.inc &&
> +	git config set -f config.inc foo.bar baz &&
> +	git config get foo.bar &&
> +	test_must_fail nongit git --git-dir=nonexistent config get foo.bar
> +'
> +
>   test_done
> 


^ permalink raw reply

* [PATCH] config: suggest the correct form when key contains "="
From: Harald Nordgren via GitGitGadget @ 2026-05-13 13:58 UTC (permalink / raw)
  To: git; +Cc: Harald Nordgren, Harald Nordgren

From: Harald Nordgren <haraldnordgren@gmail.com>

When a user types "git config foo.bar=baz", git_config_parse_key()
rejects the key with "error: invalid key: foo.bar=baz" but gives no
indication of what the user should have written.  The mistake is a
common one for users who reach for INI-file syntax or for the
"--flag=value" convention used by other command-line tools.

Since "=" is never a valid character in a config key, treat its
presence as a strong signal of this specific mistake and follow the
error with a one-line suggestion in the "(did you mean ...)" style
used elsewhere in git, e.g.:

    $ git config pull.rebase=false
    error: invalid key: pull.rebase=false
      (did you mean "git config set pull.rebase false"?)

The hint is emitted only when the offending character is "="; other
invalid characters (newlines, "@", etc.) keep their existing error
unchanged.

Signed-off-by: Harald Nordgren <harald.nordgren@kostdoktorn.se>
---
    config: suggest the correct form when key contains "="

Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2302%2FHaraldNordgren%2Fconfig-hint-equals-key-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2302/HaraldNordgren/config-hint-equals-key-v1
Pull-Request: https://github.com/git/git/pull/2302

 config.c | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/config.c b/config.c
index a1b92fe083..6e658d71d1 100644
--- a/config.c
+++ b/config.c
@@ -580,6 +580,10 @@ int git_config_parse_key(const char *key, char **store_key, size_t *baselen_)
 			if (!iskeychar(c) ||
 			    (i == baselen + 1 && !isalpha(c))) {
 				error(_("invalid key: %s"), key);
+				if (c == '=')
+					fprintf_ln(stderr,
+						   _("  (did you mean \"git config set %.*s %s\"?)"),
+						   (int)i, key, key + i + 1);
 				goto out_free_ret_1;
 			}
 			c = tolower(c);

base-commit: 59ff4886a579f4bc91e976fe18590b9ae02c7a08
-- 
gitgitgadget

^ permalink raw reply related

* Re: [PATCH v8 0/5] branch: prune-merged
From: Junio C Hamano @ 2026-05-13 13:46 UTC (permalink / raw)
  To: Harald Nordgren via GitGitGadget
  Cc: git, Kristoffer Haugsbakk, Johannes Sixt, Harald Nordgren
In-Reply-To: <pull.2285.v8.git.git.1778605658.gitgitgadget@gmail.com>

"Harald Nordgren via GitGitGadget" <gitgitgadget@gmail.com> writes:

>      +    Delete the local branches that --forked <remote> would list, but
>      +    only those whose tip is reachable from their configured upstream
>      +    remote-tracking branch (branch.<name>.merge): the work has already
>      +    landed on the upstream it tracks, so the local copy is no longer
>      +    needed.
>      +
>      +    A branch whose upstream no longer resolves locally is left alone --
>      +    its disappearance is not, on its own, evidence that the work was
>      +    integrated.

That matches my understanding of the original motivation of this
topic a lot better than the previous round.

>      +    integrated. With --force, skip the reachability check and delete
>      +    every branch in the candidate set.

I am not sure if this is a good idea at all.  The option is called
prune-MERGED and with or without --force, mergedness should be what
determines if a branch is deleted.

To perform an equivalent of

	$ git branch -D $(git branch --forked <remote>)

it would be better not to (ab)use the more commonly useful and much
safer "--prune-merged", and let's not add "--prune-forked" either as
a short-cut.  A nuclear option should be made harder to trigger, ot
easier to trigger by confusion between "--prune-{merged,forked}".

^ permalink raw reply

* Re: [PATCH v3 1/2] builtin/maintenance: fix locking with "--detach"
From: Junio C Hamano @ 2026-05-13 10:06 UTC (permalink / raw)
  To: Patrick Steinhardt
  Cc: git, Jean-Christophe Manciot, Mikael Magnusson, Jeff King,
	Taylor Blau, Derrick Stolee
In-Reply-To: <20260513-pks-maintenance-fix-lock-with-detach-v3-1-f27a1ac82891@pks.im>

Patrick Steinhardt <ps@pks.im> writes:

> Note that this is a broader fix, as we now always reassign tempfiles
> when daemonizing. This is a natural consequence of the semantics of
> `daemonize()` though, as it essentially promises to continue running the
> current process in the background.

Exactly.  I do agree that it is the right wy to look at it.  The
process that daemonise creates and leaves in the background is
logically the process that continues to execute the service the
process the user started, and unless the original process explicitly
says "we are done serving this thing" and cleans up tempfile or
lockfile it needed to serve that thing, it is natural to make the
surviving process to take over the responsibility.


^ permalink raw reply

* [PATCH v2] remote: qualify "git pull" advice for non-upstream branches
From: Harald Nordgren via GitGitGadget @ 2026-05-13  9:50 UTC (permalink / raw)
  To: git; +Cc: Harald Nordgren, Harald Nordgren
In-Reply-To: <pull.2301.git.git.1778623888178.gitgitgadget@gmail.com>

From: Harald Nordgren <haraldnordgren@gmail.com>

When "git status" reports the local branch is behind the push
branch, the advice suggested a bare "git pull". That follows the
upstream, which may live on a different remote, so emit
"git pull <remote> <branch>" instead.

Also enable the pull advice for push-branch comparisons; it was
previously only set for the upstream.

Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
---
    remote: qualify "git pull" advice for non-upstream branches
    
    Remove the message only when diverged from push-branch, because it was
    in the way when I used it now on my real branch.

Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2301%2FHaraldNordgren%2Fstatus-pull-advice-qualified-v2
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2301/HaraldNordgren/status-pull-advice-qualified-v2
Pull-Request: https://github.com/git/git/pull/2301

Range-diff vs v1:

 1:  0a06883cc8 ! 1:  1f06873f82 remote: qualify "git pull" advice for non-upstream branches
     @@ Metadata
       ## Commit message ##
          remote: qualify "git pull" advice for non-upstream branches
      
     -    When "git status" reports the local branch is behind or has diverged
     -    from the push branch, the advice suggested a bare "git pull". That
     -    follows the upstream, which may live on a different remote, so emit
     +    When "git status" reports the local branch is behind the push
     +    branch, the advice suggested a bare "git pull". That follows the
     +    upstream, which may live on a different remote, so emit
          "git pull <remote> <branch>" instead.
      
     -    Also enable the pull and divergence advice for push-branch
     -    comparisons; they were previously only set for the upstream.
     +    Also enable the pull advice for push-branch comparisons; it was
     +    previously only set for the upstream.
      
          Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
      
     @@ remote.c: int format_tracking_info(struct branch *branch, struct strbuf *sb,
       			strbuf_addstr(sb, "\n");
       
      -		if (is_upstream)
     -+		if (is_upstream || is_push) {
     ++		if (is_upstream || is_push)
       			flags |= ENABLE_ADVICE_PULL;
      -		if (is_push)
     -+			if (show_divergence_advice)
     -+				flags |= ENABLE_ADVICE_DIVERGENCE;
     -+		}
     +-			flags |= ENABLE_ADVICE_PUSH;
     + 		if (show_divergence_advice && is_upstream)
     + 			flags |= ENABLE_ADVICE_DIVERGENCE;
      +		if (is_push) {
     - 			flags |= ENABLE_ADVICE_PUSH;
     --		if (show_divergence_advice && is_upstream)
     --			flags |= ENABLE_ADVICE_DIVERGENCE;
     ++			flags |= ENABLE_ADVICE_PUSH;
      +			push_remote_name = pushremote_for_branch(branch, NULL);
      +			if (push_remote_name &&
      +			    skip_prefix(full_ref, "refs/remotes/", &push_branch_name) &&
     @@ remote.c: int format_tracking_info(struct branch *branch, struct strbuf *sb,
       
      
       ## t/t6040-tracking-info.sh ##
     -@@ t/t6040-tracking-info.sh: test_expect_success 'status.compareBranches with diverged push branch' '
     - 
     - 	Your branch and ${SQ}origin/feature8${SQ} have diverged,
     - 	and have 1 and 1 different commits each, respectively.
     -+	  (use "git pull origin feature8" if you want to integrate the remote branch with yours)
     - 
     - 	nothing to commit, working tree clean
     - 	EOF
      @@ t/t6040-tracking-info.sh: test_expect_success 'status.compareBranches with remapped push and upstream remo
       	test_cmp expect actual
       '


 remote.c                 | 44 ++++++++++++++++++++++++++++++++--------
 t/t6040-tracking-info.sh | 40 ++++++++++++++++++++++++++++++++++++
 2 files changed, 75 insertions(+), 9 deletions(-)

diff --git a/remote.c b/remote.c
index a664cd166a..e096fdb674 100644
--- a/remote.c
+++ b/remote.c
@@ -2267,6 +2267,8 @@ static void format_branch_comparison(struct strbuf *sb,
 				     bool up_to_date,
 				     int ours, int theirs,
 				     const char *branch_name,
+				     const char *push_remote_name,
+				     const char *push_branch_name,
 				     enum ahead_behind_flags abf,
 				     unsigned flags)
 {
@@ -2302,9 +2304,15 @@ static void format_branch_comparison(struct strbuf *sb,
 			       "and can be fast-forwarded.\n",
 			   theirs),
 			branch_name, theirs);
-		if (use_pull_advice && advice_enabled(ADVICE_STATUS_HINTS))
-			strbuf_addstr(sb,
-				_("  (use \"git pull\" to update your local branch)\n"));
+		if (use_pull_advice && advice_enabled(ADVICE_STATUS_HINTS)) {
+			if (push_remote_name && push_branch_name)
+				strbuf_addf(sb,
+					_("  (use \"git pull %s %s\" to update your local branch)\n"),
+					push_remote_name, push_branch_name);
+			else
+				strbuf_addstr(sb,
+					_("  (use \"git pull\" to update your local branch)\n"));
+		}
 	} else {
 		strbuf_addf(sb,
 			Q_("Your branch and '%s' have diverged,\n"
@@ -2315,9 +2323,15 @@ static void format_branch_comparison(struct strbuf *sb,
 			       "respectively.\n",
 			   ours + theirs),
 			branch_name, ours, theirs);
-		if (use_divergence_advice && advice_enabled(ADVICE_STATUS_HINTS))
-			strbuf_addstr(sb,
-				_("  (use \"git pull\" if you want to integrate the remote branch with yours)\n"));
+		if (use_divergence_advice && advice_enabled(ADVICE_STATUS_HINTS)) {
+			if (push_remote_name && push_branch_name)
+				strbuf_addf(sb,
+					_("  (use \"git pull %s %s\" if you want to integrate the remote branch with yours)\n"),
+					push_remote_name, push_branch_name);
+			else
+				strbuf_addstr(sb,
+					_("  (use \"git pull\" if you want to integrate the remote branch with yours)\n"));
+		}
 	}
 }
 
@@ -2355,6 +2369,8 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
 		int ours, theirs, cmp;
 		int is_upstream, is_push;
 		unsigned flags = 0;
+		const char *push_remote_name = NULL;
+		const char *push_branch_name = NULL;
 
 		full_ref = resolve_compare_branch(branch,
 						  branches.items[i].string);
@@ -2396,13 +2412,23 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
 		if (reported)
 			strbuf_addstr(sb, "\n");
 
-		if (is_upstream)
+		if (is_upstream || is_push)
 			flags |= ENABLE_ADVICE_PULL;
-		if (is_push)
-			flags |= ENABLE_ADVICE_PUSH;
 		if (show_divergence_advice && is_upstream)
 			flags |= ENABLE_ADVICE_DIVERGENCE;
+		if (is_push) {
+			flags |= ENABLE_ADVICE_PUSH;
+			push_remote_name = pushremote_for_branch(branch, NULL);
+			if (push_remote_name &&
+			    skip_prefix(full_ref, "refs/remotes/", &push_branch_name) &&
+			    skip_prefix(push_branch_name, push_remote_name, &push_branch_name) &&
+			    *push_branch_name == '/')
+				push_branch_name++;
+			else
+				push_remote_name = NULL;
+		}
 		format_branch_comparison(sb, !cmp, ours, theirs, short_ref,
+					 push_remote_name, push_branch_name,
 					 abf, flags);
 		reported = 1;
 
diff --git a/t/t6040-tracking-info.sh b/t/t6040-tracking-info.sh
index 0242b5bf7a..b686bf356a 100755
--- a/t/t6040-tracking-info.sh
+++ b/t/t6040-tracking-info.sh
@@ -646,4 +646,44 @@ test_expect_success 'status.compareBranches with remapped push and upstream remo
 	test_cmp expect actual
 '
 
+test_expect_success 'status.compareBranches with behind push branch suggests qualified pull' '
+	test_config -C test push.default current &&
+	test_config -C test remote.pushDefault origin &&
+	test_config -C test status.compareBranches "@{upstream} @{push}" &&
+	git -C test checkout -b feature13 upstream/main &&
+	(cd test && advance work13) &&
+	git -C test push origin &&
+	git -C test reset --hard HEAD^ &&
+	git -C test status >actual &&
+	cat >expect <<-EOF &&
+	On branch feature13
+	Your branch is up to date with ${SQ}upstream/main${SQ}.
+
+	Your branch is behind ${SQ}origin/feature13${SQ} by 1 commit, and can be fast-forwarded.
+	  (use "git pull origin feature13" to update your local branch)
+
+	nothing to commit, working tree clean
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success 'status.compareBranches with remapped push and behind push branch' '
+	test_config -C test remote.pushDefault origin &&
+	test_config -C test remote.origin.push refs/heads/feature14:refs/heads/remapped14 &&
+	test_config -C test status.compareBranches "@{push}" &&
+	git -C test checkout -b feature14 upstream/main &&
+	(cd test && advance work14) &&
+	git -C test push &&
+	git -C test reset --hard HEAD^ &&
+	git -C test status >actual &&
+	cat >expect <<-EOF &&
+	On branch feature14
+	Your branch is behind ${SQ}origin/remapped14${SQ} by 1 commit, and can be fast-forwarded.
+	  (use "git pull origin remapped14" to update your local branch)
+
+	nothing to commit, working tree clean
+	EOF
+	test_cmp expect actual
+'
+
 test_done

base-commit: 59ff4886a579f4bc91e976fe18590b9ae02c7a08
-- 
gitgitgadget

^ permalink raw reply related

* Bug: lowercase "head" resolves to wrong commit in linked worktrees on case-insensitive filesystems
From: Alexander Sandström @ 2026-05-13  8:18 UTC (permalink / raw)
  To: git

Hello everyone,

I ran into a bug that took me a while to figure out. 

I'm sadly not a good enough C programmer to submit a proper patch,
but perhaps this bug report will at least be indexed by search engines
and help others that might have this issue to understand the cause. 

My guess is that it will happen much more frequently now that 
worktrees are more popular.

**Report**

On case-insensitive filesystems (macOS APFS/HFS+), `git rev-parse head`
(lowercase) in a linked worktree resolves to the main worktree's HEAD
rather than the current worktree's HEAD. This causes commands like
`git reset --soft head~1` to silently operate on the wrong commit.

**Setup**

```sh
$ git init main && cd main
$ git commit --allow-empty -m "base"
$ git commit --allow-empty -m "main-only"
$ git worktree add ../linked HEAD~1
$ cd ../linked
$ git commit --allow-empty -m "linked-only"
```

**Expected** `head` and `HEAD` resolve to the same commit in the
linked worktree (or `head` is rejected as an unknown revision).

**Actual**

```
$ cd ../linked
$ git rev-parse HEAD
<commit: "linked-only">
$ git rev-parse head
<commit: "main-only">
```

`HEAD` (uppercase) correctly resolves via the per-worktree ref at
`.git/worktrees/linked/HEAD`. But lowercase `head` falls through to
general ref resolution, which opens a file named `head` on disk. On a
case-insensitive filesystem, this matches `.git/HEAD`, the main
worktree's HEAD, instead of the linked worktree's HEAD.

Without worktrees the bug is latent: `.git/HEAD` is the only HEAD file,
so the wrong codepath happens to produce the correct result. The bug
becomes observable only with linked worktrees, where the main and linked
worktree HEADs diverge.

**Impact** `git reset --soft head~1` in a linked worktree silently
resets to the wrong commit, staging unexpected changes. This is
particularly confusing because there is no error or warning. The
command appears to succeed.

I realize one argument might simply be "lower-case head isn't a thing",
so feel free to disregard if that is the projects stance.

**Possible fix** During ref resolution, when the input string matches
`HEAD` case-insensitively but is not exactly `HEAD`, git could either:
- reject it with an error (matching Linux behavior, where lowercase
  `head` fails with "unknown revision"), or
- normalize it to `HEAD` and route through the per-worktree codepath.

**Environment**
- git 2.53.0
- macOS 15.6 (APFS, case-insensitive)


Regards,
Alexander


^ permalink raw reply

* [PATCH v4 1/2] config: refactor include_by_gitdir() into include_by_path()
From: Chen Linxuan via B4 Relay @ 2026-05-13  8:08 UTC (permalink / raw)
  To: git
  Cc: Kristoffer Haugsbakk, Junio C Hamano, Patrick Steinhardt,
	Chen Linxuan, Phillip Wood
In-Reply-To: <20260513-includeif-worktree-v4-0-f8e6212d1fba@black-desk.cn>

From: Chen Linxuan <me@black-desk.cn>

The include_by_gitdir() function matches the realpath of a given
path against a glob pattern, but its interface is tightly coupled to
the gitdir condition: it takes a struct config_options *opts and
extracts opts->git_dir internally.

Refactor it into a more generic include_by_path() helper that takes
a const char *path parameter directly, and update the gitdir and
gitdir/i callers to pass opts->git_dir explicitly.  No behavior
change, just preparing for the addition of a new worktree condition
that will reuse the same path-matching logic with a different path.

Signed-off-by: Chen Linxuan <me@black-desk.cn>
---
 config.c | 19 ++++++++-----------
 1 file changed, 8 insertions(+), 11 deletions(-)

diff --git a/config.c b/config.c
index 156f2a24fa00..7d5dae0e8450 100644
--- a/config.c
+++ b/config.c
@@ -235,23 +235,20 @@ static int prepare_include_condition_pattern(const struct key_value_info *kvi,
 	return 0;
 }
 
-static int include_by_gitdir(const struct key_value_info *kvi,
-			     const struct config_options *opts,
-			     const char *cond, size_t cond_len, int icase)
+static int include_by_path(const struct key_value_info *kvi,
+			   const char *path,
+			   const char *cond, size_t cond_len, int icase)
 {
 	struct strbuf text = STRBUF_INIT;
 	struct strbuf pattern = STRBUF_INIT;
 	size_t prefix;
 	int ret = 0;
-	const char *git_dir;
 	int already_tried_absolute = 0;
 
-	if (opts->git_dir)
-		git_dir = opts->git_dir;
-	else
+	if (!path)
 		goto done;
 
-	strbuf_realpath(&text, git_dir, 1);
+	strbuf_realpath(&text, path, 1);
 	strbuf_add(&pattern, cond, cond_len);
 	ret = prepare_include_condition_pattern(kvi, &pattern, &prefix);
 	if (ret < 0)
@@ -284,7 +281,7 @@ static int include_by_gitdir(const struct key_value_info *kvi,
 		 * which'll do the right thing
 		 */
 		strbuf_reset(&text);
-		strbuf_add_absolute_path(&text, git_dir);
+		strbuf_add_absolute_path(&text, path);
 		already_tried_absolute = 1;
 		goto again;
 	}
@@ -400,9 +397,9 @@ static int include_condition_is_true(const struct key_value_info *kvi,
 	const struct config_options *opts = inc->opts;
 
 	if (skip_prefix_mem(cond, cond_len, "gitdir:", &cond, &cond_len))
-		return include_by_gitdir(kvi, opts, cond, cond_len, 0);
+		return include_by_path(kvi, opts->git_dir, cond, cond_len, 0);
 	else if (skip_prefix_mem(cond, cond_len, "gitdir/i:", &cond, &cond_len))
-		return include_by_gitdir(kvi, opts, cond, cond_len, 1);
+		return include_by_path(kvi, opts->git_dir, cond, cond_len, 1);
 	else if (skip_prefix_mem(cond, cond_len, "onbranch:", &cond, &cond_len))
 		return include_by_branch(inc, cond, cond_len);
 	else if (skip_prefix_mem(cond, cond_len, "hasconfig:remote.*.url:", &cond,

-- 
2.53.0



^ permalink raw reply related

* [PATCH v4 2/2] config: add "worktree" and "worktree/i" includeIf conditions
From: Chen Linxuan via B4 Relay @ 2026-05-13  8:08 UTC (permalink / raw)
  To: git
  Cc: Kristoffer Haugsbakk, Junio C Hamano, Patrick Steinhardt,
	Chen Linxuan, Phillip Wood
In-Reply-To: <20260513-includeif-worktree-v4-0-f8e6212d1fba@black-desk.cn>

From: Chen Linxuan <me@black-desk.cn>

The includeIf mechanism already supports matching on the .git
directory path (gitdir) and the currently checked out branch
(onbranch).  But in multi-worktree setups the .git directory of a
linked worktree points into the main repository's .git/worktrees/
area, which makes gitdir patterns cumbersome when one wants to
include config based on the working tree's checkout path instead.

Introduce two new condition keywords:

  - worktree:<pattern> matches the realpath of the current worktree's
    working directory (i.e. repo_get_work_tree()) against a glob
    pattern.  This is the path returned by git rev-parse
    --show-toplevel.

  - worktree/i:<pattern> is the case-insensitive variant.

The implementation reuses the include_by_path() helper introduced in
the previous commit, passing the worktree path in place of the
gitdir.  The condition never matches in bare repositories (where
there is no worktree) or during early config reading (where no
repository is available).

Add documentation describing the new conditions, including a comparison
with extensions.worktreeConfig.  Add tests covering bare repositories,
multiple worktrees, symlinked worktree paths, case-insensitive matching,
early config reading, and non-repository scenarios.

Signed-off-by: Chen Linxuan <me@black-desk.cn>
---
 Documentation/config.adoc |  48 ++++++++++++++++++++
 config.c                  |   6 +++
 t/t1305-config-include.sh | 113 ++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 167 insertions(+)

diff --git a/Documentation/config.adoc b/Documentation/config.adoc
index 62eebe7c5450..6299b1e3a019 100644
--- a/Documentation/config.adoc
+++ b/Documentation/config.adoc
@@ -146,6 +146,46 @@ refer to linkgit:gitignore[5] for details. For convenience:
 	This is the same as `gitdir` except that matching is done
 	case-insensitively (e.g. on case-insensitive file systems)
 
+`worktree`::
+	The data that follows the keyword `worktree` and a colon is used as a
+	glob pattern. If the working directory of the current worktree matches
+	the pattern, the include condition is met.
++
+The worktree location is the path where files are checked out (as returned
+by `git rev-parse --show-toplevel`). This is different from `gitdir`, which
+matches the `.git` directory path. In a linked worktree, the worktree path
+is the directory where that worktree's files are located, not the main
+repository's `.git` directory.
++
+The pattern uses the same glob syntax as `gitdir` (including `~/`, `./`,
+`**/`, and trailing-`/` prefix matching). This condition will never match
+in a bare repository (which has no worktree).
++
+This is useful when you want to apply configuration based on where the
+working tree is located on the filesystem. For example, a contributor who
+works on the same project both personally and as an employee can use
+different `user.name` and `user.email` values depending on which directory
+the worktree is checked out under:
++
+----
+[includeIf "worktree:/home/user/work/"]
+    path = ~/.config/git/work.inc
+[includeIf "worktree:/home/user/personal/"]
+    path = ~/.config/git/personal.inc
+----
++
+While `extensions.worktreeConfig` (see linkgit:git-worktree[1]) also supports
+per-worktree configuration, it stores the config inside each repository's
+`.git/config.worktree` file and requires running `git config --worktree`
+inside each worktree individually. In contrast, `includeIf "worktree:..."`
+can be set once in a global or system-level configuration file (e.g.
+`~/.config/git/config`) and applies to all repositories at once based on
+their worktree location.
+
+`worktree/i`::
+	This is the same as `worktree` except that matching is done
+	case-insensitively (e.g. on case-insensitive file systems)
+
 `onbranch`::
 	The data that follows the keyword `onbranch` and a colon is taken to be a
 	pattern with standard globbing wildcards and two additional
@@ -244,6 +284,14 @@ Example
 [includeIf "gitdir:~/to/group/"]
 	path = /path/to/foo.inc
 
+; include if the worktree is at /path/to/project-build
+[includeIf "worktree:/path/to/project-build"]
+	path = build-config.inc
+
+; include for all worktrees inside /path/to/group
+[includeIf "worktree:/path/to/group/"]
+	path = group-config.inc
+
 ; relative paths are always relative to the including
 ; file (if the condition is true); their location is not
 ; affected by the condition
diff --git a/config.c b/config.c
index 7d5dae0e8450..6d0c2d0725e4 100644
--- a/config.c
+++ b/config.c
@@ -400,6 +400,12 @@ static int include_condition_is_true(const struct key_value_info *kvi,
 		return include_by_path(kvi, opts->git_dir, cond, cond_len, 0);
 	else if (skip_prefix_mem(cond, cond_len, "gitdir/i:", &cond, &cond_len))
 		return include_by_path(kvi, opts->git_dir, cond, cond_len, 1);
+	else if (skip_prefix_mem(cond, cond_len, "worktree:", &cond, &cond_len))
+		return include_by_path(kvi, inc->repo ? repo_get_work_tree(inc->repo) : NULL,
+				       cond, cond_len, 0);
+	else if (skip_prefix_mem(cond, cond_len, "worktree/i:", &cond, &cond_len))
+		return include_by_path(kvi, inc->repo ? repo_get_work_tree(inc->repo) : NULL,
+				       cond, cond_len, 1);
 	else if (skip_prefix_mem(cond, cond_len, "onbranch:", &cond, &cond_len))
 		return include_by_branch(inc, cond, cond_len);
 	else if (skip_prefix_mem(cond, cond_len, "hasconfig:remote.*.url:", &cond,
diff --git a/t/t1305-config-include.sh b/t/t1305-config-include.sh
index 6e51f892f320..07b6fb649cd2 100755
--- a/t/t1305-config-include.sh
+++ b/t/t1305-config-include.sh
@@ -396,4 +396,117 @@ test_expect_success 'onbranch without repository but explicit nonexistent Git di
 	test_must_fail nongit git --git-dir=nonexistent config get foo.bar
 '
 
+# worktree: conditional include tests
+
+test_expect_success 'conditional include, worktree bare repo' '
+	git init --bare wt-bare &&
+	(
+		cd wt-bare &&
+		echo "[includeIf \"worktree:/\"]path=bar-bare" >>config &&
+		echo "[test]wtbare=1" >bar-bare &&
+		test_must_fail git config test.wtbare
+	)
+'
+
+test_expect_success 'conditional include, worktree multiple worktrees' '
+	git init wt-multi &&
+	(
+		cd wt-multi &&
+		test_commit initial &&
+		git worktree add -b linked-branch ../wt-linked HEAD &&
+		git worktree add -b prefix-branch ../wt-prefix/linked HEAD
+	) &&
+	wt_main="$(cd wt-multi && pwd)" &&
+	wt_linked="$(cd wt-linked && pwd)" &&
+	wt_prefix_parent="$(cd wt-prefix && pwd)" &&
+	cat >>wt-multi/.git/config <<-EOF &&
+	[includeIf "worktree:$wt_main"]
+		path = main-config
+	[includeIf "worktree:$wt_linked"]
+		path = linked-config
+	[includeIf "worktree:$wt_prefix_parent/"]
+		path = prefix-config
+	EOF
+	echo "[test]mainvar=main" >wt-multi/.git/main-config &&
+	echo "[test]linkedvar=linked" >wt-multi/.git/linked-config &&
+	echo "[test]prefixvar=prefix" >wt-multi/.git/prefix-config &&
+	echo main >expect &&
+	git -C wt-multi config test.mainvar >actual &&
+	test_cmp expect actual &&
+	test_must_fail git -C wt-multi config test.linkedvar &&
+	test_must_fail git -C wt-multi config test.prefixvar &&
+	echo linked >expect &&
+	git -C wt-linked config test.linkedvar >actual &&
+	test_cmp expect actual &&
+	test_must_fail git -C wt-linked config test.mainvar &&
+	test_must_fail git -C wt-linked config test.prefixvar &&
+	echo prefix >expect &&
+	git -C wt-prefix/linked config test.prefixvar >actual &&
+	test_cmp expect actual &&
+	test_must_fail git -C wt-prefix/linked config test.mainvar &&
+	test_must_fail git -C wt-prefix/linked config test.linkedvar
+'
+
+test_expect_success SYMLINKS 'conditional include, worktree resolves symlinks' '
+	mkdir real-wt &&
+	ln -s real-wt link-wt &&
+	git init link-wt/repo &&
+	(
+		cd link-wt/repo &&
+		# repo->worktree resolves symlinks, so use real path in pattern
+		echo "[includeIf \"worktree:**/real-wt/repo\"]path=bar-link" >>.git/config &&
+		echo "[test]wtlink=2" >.git/bar-link &&
+		echo 2 >expect &&
+		git config test.wtlink >actual &&
+		test_cmp expect actual
+	)
+'
+
+test_expect_success 'conditional include, worktree, icase' '
+	git init wt-icase &&
+	(
+		cd wt-icase &&
+		test_commit initial &&
+		wt_path="$(pwd)" &&
+		wt_upper=$(echo "$wt_path" | tr a-z A-Z) &&
+		echo "[includeIf \"worktree/i:$wt_upper\"]path=icase-inc" >>.git/config &&
+		echo "[test]wticase=1" >.git/icase-inc &&
+		echo 1 >expect &&
+		git config test.wticase >actual &&
+		test_cmp expect actual
+	)
+'
+
+# The "worktree" condition cannot match during early config reading
+# because the repository object is not yet fully initialized and
+# repo_get_work_tree() returns NULL.
+test_expect_success 'conditional include, worktree does not match in early config' '
+	git init wt-early &&
+	(
+		cd wt-early &&
+		test_commit initial &&
+		wt_path="$(pwd)" &&
+		echo "[includeIf \"worktree:$wt_path\"]path=early-inc" >>.git/config &&
+		echo "[test]wtearly=1" >.git/early-inc &&
+		test-tool config read_early_config test.wtearly >actual &&
+		test_must_be_empty actual
+	)
+'
+
+test_expect_success 'conditional include, worktree without repository' '
+	test_when_finished "rm -f .gitconfig config.inc" &&
+	git config set -f .gitconfig "includeIf.worktree:/.path" config.inc &&
+	git config set -f config.inc foo.bar baz &&
+	git config get foo.bar &&
+	test_must_fail nongit git config get foo.bar
+'
+
+test_expect_success 'conditional include, worktree without repository but explicit nonexistent Git directory' '
+	test_when_finished "rm -f .gitconfig config.inc" &&
+	git config set -f .gitconfig "includeIf.worktree:/.path" config.inc &&
+	git config set -f config.inc foo.bar baz &&
+	git config get foo.bar &&
+	test_must_fail nongit git --git-dir=nonexistent config get foo.bar
+'
+
 test_done

-- 
2.53.0



^ permalink raw reply related

* [PATCH v4 0/2] includeIf: add "worktree" condition for matching working tree path
From: Chen Linxuan via B4 Relay @ 2026-05-13  8:08 UTC (permalink / raw)
  To: git
  Cc: Kristoffer Haugsbakk, Junio C Hamano, Patrick Steinhardt,
	Chen Linxuan, Phillip Wood

The `includeIf` mechanism already supports matching on the `.git`
directory path (`gitdir`) and the currently checked out branch
(`onbranch`).  But in multi-worktree setups the `.git` directory of a
linked worktree points into the main repository's `.git/worktrees/`
area, which makes `gitdir` patterns cumbersome when one wants to
include config based on the working tree's checkout path instead.

Introduce two new condition keywords:

  - `worktree:<pattern>` matches the realpath of the current worktree's
    working directory against a glob pattern.
  - `worktree/i:<pattern>` is the case-insensitive variant.

Supported pattern features: glob wildcards, `**/` and `/**`, `~`
expansion, `./` relative paths, and trailing-`/` prefix matching.
The condition never matches in a bare repository.

Signed-off-by: Chen Linxuan <me@black-desk.cn>
---
Changes in v4:
- Deduplicate the worktree pattern documentation by referencing the
  gitdir syntax instead of repeating the full pattern description
  (suggested by Patrick Steinhardt).
- Add documentation comparing includeIf "worktree:" with
  extensions.worktreeConfig, including a concrete use case example
  (suggested by Phillip Wood, Junio C Hamano).
- Add a test verifying that the worktree condition does not match
  during early config reading (suggested by Patrick Steinhardt).
- Add tests for the non-repository (nongit) scenario (suggested by
  Patrick Steinhardt).
- Add a test for the case-insensitive "worktree/i" variant
- Link to v3: https://lore.kernel.org/r/20260403-includeif-worktree-v3-0-109ce5782b03@black-desk.cn

Changes in v3:
- Apply Junio's suggestion.
- Link to v2: https://lore.kernel.org/r/20260402-includeif-worktree-v2-0-36e339b898d7@black-desk.cn

Changes in v2:

- Add missing signed-off-by lines.
- Link to v1: https://lore.kernel.org/r/20260401-includeif-worktree-v1-0-906db69f2c79@black-desk.cn

---
Chen Linxuan (2):
      config: refactor include_by_gitdir() into include_by_path()
      config: add "worktree" and "worktree/i" includeIf conditions

 Documentation/config.adoc |  48 ++++++++++++++++++++
 config.c                  |  25 +++++-----
 t/t1305-config-include.sh | 113 ++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 175 insertions(+), 11 deletions(-)
---
base-commit: 270e10ad6dda3379ea0da7efd11e4fbf2cd7a325
change-id: 20260401-includeif-worktree-fcb64950dfba

Best regards,
-- 
Chen Linxuan <me@black-desk.cn>



^ permalink raw reply

* [PATCH v3 2/2] run-command: honor "gc.auto" for auto-maintenance
From: Patrick Steinhardt @ 2026-05-13  7:31 UTC (permalink / raw)
  To: git
  Cc: Jean-Christophe Manciot, Mikael Magnusson, Jeff King, Taylor Blau,
	Derrick Stolee, Junio C Hamano
In-Reply-To: <20260513-pks-maintenance-fix-lock-with-detach-v3-0-f27a1ac82891@pks.im>

The "gc.auto" configuration has traditionally been used to turn off
running git-gc(1) as part of our auto-maintenance. We have eventually
switched over to git-maintenance(1) in a95ce12430 (maintenance: replace
run_auto_gc(), 2020-09-17), and with 1942d48380 (maintenance: optionally
skip --auto process, 2020-08-28) we have introduced "maintenance.auto"
to control whether or not to run auto-maintenance.

At that point though we still shelled out to git-gc(1) internally. So
if "gc.auto=0" was set we would still _execute_ git-maintenance(1), but
the command would have exited fast because git-gc(1) itself knew to
honor the config key.

This has recently changed though, as we have adapted the default
maintenance strategy to not use git-gc(1) anymore. The consequence is
that "gc.auto=0" doesn't have an effect anymore, which is a somewhat
surprising change in behaviour for our users.

Adapt `run_auto_maintenance()` so that it knows to also read "gc.auto",
similar to how it also reads both "maintenance.autoDetach" and
"gc.autoDetach".

Reported-by: Jean-Christophe Manciot <actionmystique@gmail.com>
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 run-command.c          | 10 +++++++---
 t/t7900-maintenance.sh | 25 +++++++++++++++++++++++++
 2 files changed, 32 insertions(+), 3 deletions(-)

diff --git a/run-command.c b/run-command.c
index c146a56532..28202a81d8 100644
--- a/run-command.c
+++ b/run-command.c
@@ -1944,10 +1944,14 @@ void run_processes_parallel(const struct run_process_parallel_opts *opts)
 int prepare_auto_maintenance(struct repository *r, int quiet,
 			     struct child_process *maint)
 {
-	int enabled, auto_detach;
+	int enabled = 1, auto_detach;
 
-	if (!repo_config_get_bool(r, "maintenance.auto", &enabled) &&
-	    !enabled)
+	if (repo_config_get_bool(r, "maintenance.auto", &enabled)) {
+		int gc_threshold;
+		if (!repo_config_get_int(r, "gc.auto", &gc_threshold))
+			enabled = gc_threshold > 0;
+	}
+	if (!enabled)
 		return 0;
 
 	/*
diff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh
index df0bbc1669..97c8c701bb 100755
--- a/t/t7900-maintenance.sh
+++ b/t/t7900-maintenance.sh
@@ -73,6 +73,31 @@ test_expect_success 'maintenance.auto config option' '
 	test_subcommand ! git maintenance run --auto --quiet --detach <false
 '
 
+test_expect_success 'gc.auto config option' '
+	GIT_TRACE2_EVENT="$(pwd)/default" git commit --quiet --allow-empty -m 1 &&
+	test_subcommand git maintenance run --auto --quiet --detach <default &&
+	GIT_TRACE2_EVENT="$(pwd)/true" \
+		git -c gc.auto=1 commit --quiet --allow-empty -m 2 &&
+	test_subcommand git maintenance run --auto --quiet --detach <true &&
+	GIT_TRACE2_EVENT="$(pwd)/false" \
+		git -c gc.auto=0 commit --quiet --allow-empty -m 3 &&
+	test_subcommand ! git maintenance run --auto --quiet --detach <false
+'
+
+test_expect_success 'maintenance.auto overrides gc.auto' '
+	test_when_finished "rm -f trace" &&
+
+	test_config maintenance.auto false &&
+	test_config gc.auto 1 &&
+	GIT_TRACE2_EVENT="$(pwd)/trace" git commit --quiet --allow-empty -m 1 &&
+	test_subcommand ! git maintenance run --auto --quiet --detach <trace &&
+
+	test_config maintenance.auto true &&
+	test_config gc.auto 0 &&
+	GIT_TRACE2_EVENT="$(pwd)/trace" git commit --quiet --allow-empty -m 1 &&
+	test_subcommand git maintenance run --auto --quiet --detach <trace
+'
+
 for cfg in maintenance.autoDetach gc.autoDetach
 do
 	test_expect_success "$cfg=true config option" '

-- 
2.54.0.709.gd731d7959a.dirty


^ permalink raw reply related

* [PATCH v3 1/2] builtin/maintenance: fix locking with "--detach"
From: Patrick Steinhardt @ 2026-05-13  7:31 UTC (permalink / raw)
  To: git
  Cc: Jean-Christophe Manciot, Mikael Magnusson, Jeff King, Taylor Blau,
	Derrick Stolee, Junio C Hamano
In-Reply-To: <20260513-pks-maintenance-fix-lock-with-detach-v3-0-f27a1ac82891@pks.im>

When running git-maintenance(1), we create a lockfile that is supposed
to keep other maintenance processes from running at the same time. This
lockfile is broken though in case the "--detach" flag is passed: the
lockfile is created by the parent process and will be cleaned up either
manually or on exit. But when detaching, the parent will exit before all
of the background maintenance tasks have been run, and consequently the
lock only covers a smaller part of the whole maintenance process.

Fix this bug by reassigning all tempfiles from the parent process to the
child process when daemonizing so that it becomes the responsibility of
the child to clean them up.

Note that this is a broader fix, as we now always reassign tempfiles
when daemonizing. This is a natural consequence of the semantics of
`daemonize()` though, as it essentially promises to continue running the
current process in the background. It is thus sensible to have that
function perform the whole dance of assigning resources to the child
process, including tempfiles.

There's only a single other caller in "daemon.c", but that process
doesn't create any tempfiles before the call to `daemonize()` and is
thus not impacted by this change.

Reported-by: Jean-Christophe Manciot <actionmystique@gmail.com>
Helped-by: Jeff King <peff@peff.net>
Helped-by: Derrick Stolee <stolee@gmail.com>
Co-authored-by: Taylor Blau <me@ttaylorr.com>
Signed-off-by: Taylor Blau <me@ttaylorr.com>
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 setup.c                | 16 +++++++++++++-
 setup.h                | 15 +++++++++++++
 t/t7900-maintenance.sh | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++
 tempfile.c             | 12 +++++++++++
 tempfile.h             | 11 ++++++++++
 5 files changed, 111 insertions(+), 1 deletion(-)

diff --git a/setup.c b/setup.c
index 7ec4427368..14445a71a4 100644
--- a/setup.c
+++ b/setup.c
@@ -2162,12 +2162,26 @@ int daemonize(void)
 	errno = ENOSYS;
 	return -1;
 #else
-	switch (fork()) {
+	pid_t parent_pid = getpid();
+	pid_t child_pid = fork();
+
+	switch (child_pid) {
 		case 0:
+			/*
+			 * We're in the child process, so we take ownership of
+			 * all tempfiles.
+			 */
+			reassign_tempfile_ownership(parent_pid, getpid());
 			break;
 		case -1:
 			die_errno(_("fork failed"));
 		default:
+			/*
+			 * We're in the parent process, so we drop ownership of
+			 * all tempfiles to prevent us from removing them upon
+			 * exit.
+			 */
+			reassign_tempfile_ownership(parent_pid, child_pid);
 			exit(0);
 	}
 	if (setsid() == -1)
diff --git a/setup.h b/setup.h
index 80bc6e5f07..b5bc5f280c 100644
--- a/setup.h
+++ b/setup.h
@@ -149,6 +149,21 @@ void verify_non_filename(const char *prefix, const char *name);
 int path_inside_repo(const char *prefix, const char *path);
 
 void sanitize_stdfds(void);
+
+/*
+ * Daemonize the current process by forking and then exiting the parent
+ * process. Returns 0 when successful, in which case the parent process will
+ * have exited and it's the child process that continues to run the code.
+ * Otherwise, a negative error code is returned and the parent process will
+ * continue execution.
+ *
+ * Note that this function will also perform the following changes:
+ *
+ *   - Standard file descriptors in the child process are closed.
+ *   - The child process is made a session leader via setsid(3p).
+ *   - All tempfiles owned by the parent process are reassigned to the
+ *     daemonized child process.
+ */
 int daemonize(void);
 
 /*
diff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh
index 4700beacc1..df0bbc1669 100755
--- a/t/t7900-maintenance.sh
+++ b/t/t7900-maintenance.sh
@@ -1438,6 +1438,64 @@ test_expect_success '--no-detach causes maintenance to not run in background' '
 	)
 '
 
+test_expect_success PIPE '--detach holds maintenance lock until daemonized child exits' '
+	test_when_finished "rm -rf repo" &&
+	git init repo &&
+	(
+		cd repo &&
+
+		git config maintenance.auto false &&
+		git config core.lockfilepid true &&
+
+		git remote add origin /does/not/exist &&
+		git config set remote.origin.uploadpack "cat fifo-uploadpack" &&
+
+		mkfifo fifo-uploadpack fifo-maint &&
+
+		# Open the maintenance FIFO, as otherwise spawning
+		# git-maintenance(1) would block. Note that we need to open it
+		# as read-write, as otherwise we would block here already.
+		exec 9<>fifo-maint &&
+
+		{ git maintenance run --task=prefetch --detach 7>&9 & } &&
+		parent="$!" &&
+
+		# Reap the parent process so that the exec call below will not
+		# get SIGCHLD.
+		wait "$parent" &&
+
+		# Open the git-upload-pack(1) FIFO for writing, which will
+		# block until the upload-pack script opens it for reading. Once
+		# exec returns, we know that the daemonized child is alive and
+		# pinned.
+		exec 8>fifo-uploadpack &&
+
+		test_path_is_file .git/objects/maintenance.lock &&
+		test_path_is_file .git/objects/"maintenance~pid.lock" &&
+
+		# Verify that the maintenance.lock still exists, and
+		# that it was created by the parent process, not the
+		# child.
+		echo "pid $parent" >expect &&
+		test_cmp expect .git/objects/"maintenance~pid.lock" &&
+
+		# Reopen the maintenance FIFO as read-only so that
+		# git-maintenance(1) is the only writer. This will cause it to
+		# close the FIFO once the process exits.
+		exec 9<&- &&
+		exec 9<fifo-maint &&
+
+		# Close the FIFO used by git-upload-pack(1) to unblock it and
+		# then wait until the maintenance FIFO is closed by
+		# git-maintenance(1), indicating that it has exited.
+		exec 8>&- &&
+		cat <&9 &&
+
+		test_path_is_missing .git/objects/maintenance.lock &&
+		test_path_is_missing .git/objects/"maintenance~pid.lock"
+	)
+'
+
 test_expect_success '--detach causes maintenance to run in background' '
 	test_when_finished "rm -rf repo" &&
 	git init repo &&
diff --git a/tempfile.c b/tempfile.c
index 82dfa3d82f..f0fdf58279 100644
--- a/tempfile.c
+++ b/tempfile.c
@@ -373,3 +373,15 @@ int delete_tempfile(struct tempfile **tempfile_p)
 
 	return err ? -1 : 0;
 }
+
+void reassign_tempfile_ownership(pid_t from, pid_t to)
+{
+	volatile struct volatile_list_head *pos;
+
+	list_for_each(pos, &tempfile_list) {
+		struct tempfile *p = list_entry(pos, struct tempfile, list);
+
+		if (is_tempfile_active(p) && p->owner == from)
+			p->owner = to;
+	}
+}
diff --git a/tempfile.h b/tempfile.h
index 2d2ae5b657..2227a095fd 100644
--- a/tempfile.h
+++ b/tempfile.h
@@ -282,4 +282,15 @@ int delete_tempfile(struct tempfile **tempfile_p);
  */
 int rename_tempfile(struct tempfile **tempfile_p, const char *path);
 
+/*
+ * Reassign ownership of all active tempfiles whose `owner` field matches
+ * `from` to `to`.
+ *
+ * This is intended for use by `daemonize()`; after `fork(2)`-ing, the parent
+ * transfers ownership to the daemonized child so that its atexit handler does
+ * not unlink tempfiles that should outlive it, and the child claims the
+ * inherited tempfiles so that they are cleaned up when the daemon exits.
+ */
+void reassign_tempfile_ownership(pid_t from, pid_t to);
+
 #endif /* TEMPFILE_H */

-- 
2.54.0.709.gd731d7959a.dirty


^ permalink raw reply related

* [PATCH v3 0/2] builtin/maintenance: fix locking and respect "gc.auto"
From: Patrick Steinhardt @ 2026-05-13  7:31 UTC (permalink / raw)
  To: git
  Cc: Jean-Christophe Manciot, Mikael Magnusson, Jeff King, Taylor Blau,
	Derrick Stolee, Junio C Hamano
In-Reply-To: <20260511-pks-maintenance-fix-lock-with-detach-v1-0-ccd7d62c9a40@pks.im>

Hi,

this patch series addresses the issues reported in [1]. The series is
built on top of Git 2.54.0.

Changes in v3:
  - Use Taylor's approach to reassign all tempfiles when daemonizing.
  - Link to v2: https://patch.msgid.link/20260512-pks-maintenance-fix-lock-with-detach-v2-0-dc6f2d284b6d@pks.im

Changes in v2:
  - Clarify comment when dropping ownership of the lock in the parent
    process.
  - Properly treat "gc.auto" as an integer, not a boolean.
  - Link to v1: https://patch.msgid.link/20260511-pks-maintenance-fix-lock-with-detach-v1-0-ccd7d62c9a40@pks.im

Thanks!

Patrick

[1]: <CAKcFC3arsYExb5dCMQspo4V9UFDadFaj8Q4PUsMWZJw_eYrMzA@mail.gmail.com>

---
Patrick Steinhardt (2):
      builtin/maintenance: fix locking with "--detach"
      run-command: honor "gc.auto" for auto-maintenance

 run-command.c          | 10 ++++--
 setup.c                | 16 +++++++++-
 setup.h                | 15 +++++++++
 t/t7900-maintenance.sh | 83 ++++++++++++++++++++++++++++++++++++++++++++++++++
 tempfile.c             | 12 ++++++++
 tempfile.h             | 11 +++++++
 6 files changed, 143 insertions(+), 4 deletions(-)

Range-diff versus v2:

1:  5eb608ad43 < -:  ---------- builtin/maintenance: fix locking with "--detach"
-:  ---------- > 1:  3fc8872f36 builtin/maintenance: fix locking with "--detach"
2:  15e2ae8104 = 2:  665a6c9a50 run-command: honor "gc.auto" for auto-maintenance

---
base-commit: 13ef77ce6e222bef3ab145642e6ef1486075211c
change-id: 20260511-pks-maintenance-fix-lock-with-detach-a608e9b6adeb


^ permalink raw reply

* [PATCH] sideband: allow ANSI SGR with colon-separated subfields
From: grawity @ 2026-05-13  7:08 UTC (permalink / raw)
  To: git; +Cc: Mantas Mikulėnas, Junio C Hamano, Johannes Schindelin

From: Mantas Mikulėnas <grawity@gmail.com>

The SGR values used for 256-color formatting are officially defined to
be a single field with :-separated subfields (e.g. "\e[1;38:5:XX;40m")
despite the more common but kludgy use of separate values (which then
become context-dependent and lead to misinterpretation by incompatible
terminals).

See also: https://github.com/ThomasDickey/xterm-snapshots/blob/6380a3eaed857c182ea6cfa78cd706966b2628d0/charproc.c#L2047-L2118

Signed-off-by: Mantas Mikulėnas <grawity@gmail.com>
---
 sideband.c | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/sideband.c b/sideband.c
index 04282a568e..6cf70ef6f6 100644
--- a/sideband.c
+++ b/sideband.c
@@ -163,6 +163,10 @@ static int handle_ansi_sequence(struct strbuf *dest, const char *src, int n)
 	 *
 	 * ESC [ [<n> [; <n>]*] m
 	 *
+	 * where <n> can be either zero-length, or a decimal number, or a
+	 * series of decimal numbers separated by a colon (for 256-color or
+	 * true-color codes).
+	 *
 	 * These are part of the Select Graphic Rendition sequences which
 	 * contain more than just color sequences, for more details see
 	 * https://en.wikipedia.org/wiki/ANSI_escape_code#SGR.
@@ -210,7 +214,7 @@ static int handle_ansi_sequence(struct strbuf *dest, const char *src, int n)
 			strbuf_add(dest, src, i + 1);
 			return i;
 		}
-		if (!isdigit(src[i]) && src[i] != ';')
+		if (!isdigit(src[i]) && src[i] != ':' && src[i] != ';')
 			break;
 	}
 
-- 
2.54.0


^ permalink raw reply related

* Re: [PATCH v2 1/2] builtin/maintenance: fix locking with "--detach"
From: Patrick Steinhardt @ 2026-05-13  6:23 UTC (permalink / raw)
  To: Taylor Blau
  Cc: git, Jean-Christophe Manciot, Mikael Magnusson, Jeff King,
	Derrick Stolee, Junio C Hamano
In-Reply-To: <agOYLsJF4rHESk9k@nand.local>

On Tue, May 12, 2026 at 05:14:22PM -0400, Taylor Blau wrote:
> On Tue, May 12, 2026 at 10:30:30AM +0200, Patrick Steinhardt wrote:
> > diff --git a/builtin/gc.c b/builtin/gc.c
> > index 3a71e314c9..d866c19b92 100644
> > --- a/builtin/gc.c
> > +++ b/builtin/gc.c
> > @@ -1810,10 +1810,33 @@ static int maintenance_run_tasks(struct maintenance_run_opts *opts,
> >  				   TASK_PHASE_FOREGROUND))
> >  			result = 1;
> >
> > -	/* Failure to daemonize is ok, we'll continue in foreground. */
> >  	if (opts->detach > 0) {
> > +		pid_t child_pid;
> > +
> >  		trace2_region_enter("maintenance", "detach", the_repository);
> > -		daemonize();
> > +
> > +		child_pid = daemonize_without_exit();
> > +		if (!child_pid) {
> > +			/*
> > +			 * We're in the child process, so we take ownership of
> > +			 * the lockfile.
> > +			 */
> > +			lock_file_reassign_owner(&lk, getpid());
> > +		} else if (child_pid > 0) {
> > +			/*
> > +			 * We're in the parent process, so we drop ownership of
> > +			 * the lockfile to prevent us from removing it upon
> > +			 * exit.
> > +			 */
> > +			lock_file_reassign_owner(&lk, child_pid);
> > +			exit(0);
> > +		} else {
> > +			/*
> > +			 * Failure to daemonize is ok, we'll continue in
> > +			 * foreground.
> > +			 */
> > +		}
> > +
> 
> This works almost identically as the original implementation I shared[1]
> original thread discussing this issue, but it takes a slightly different
> approach.
> 
> Instead of doing this automatically as a part of daemonize(), this patch
> only adjusts this singular call-site, and forces us to introduce a new
> function daemonize_without_exit() in order to facilitate it.
> 
> I personally prefer the other approach, for many of the reasons that
> Peff wrote about in [2, 3]. There are two questions in my mind that I
> would be curious of your thoughts on:
> 
>  * Should we transfer ownership of all tempfiles, or a subset of them?
> 
>  * Should we perform that transfer automatically via daemonize(), or
>    manually via its callers?
> 
> Judging from the other parts of the original thread, I think the answer
> to the first question is an unambiguous "yes". The second question I
> think there is less of a consensus on, but I'd like to advocate for
> doing this automatically via daemonize().
> 
> To summarize some of my thoughts after reading [2], I think that because
> daemonize() is about letting a process continue on in the background
> rather than fork()-ing children and then continuing on ourselves, *not*
> automatically handing those off feels like an accident waiting to
> happen.
> 
> I can't think of a situation where someone would want to daemonize() but
> not have the new process assume ownership of its locks and tempfiles. I
> think an alternative approach here would be to document the behavior I'm
> proposing above clearly in daemonize(). Should a caller arise in the
> future that *doesn't* want to this behavior, then we could introduce a
> function similar to your daemonize_without_exit() (maybe in this case it
> would be daemonize_without_transfer(), since not exiting in a function
> that is supposed to daemonize feels awkward).
> 
> I guess what I'm saying is that I feel that future daemonize() callers
> are much more likely to want this behavior than not, and putting this in
> daemonize() feels like the safer option between the two.
> 
> FWIW, I feel fairly strongly about ^ this, but I'm of course happy to
> discuss more if you feel differently.
> 
> (If you do end up taking that approach, that makes the C changes
> identical to the ones I shared in [1], so you are free to forge my
> Co-authored-by and Signed-off-by lines if you want to take that approach
> instead.)

Hmm. I was initially aiming for a more minimal fix, and that's why I
decided to make only this one callsite reassign tempfiles. But there's
only two callsites right now, and thinking a bit more about the problem
makes me agree with your take on it.

Will adapt, thanks for pushing back!

Patrick

^ permalink raw reply

* Re: [PATCH v3 2/2] config: add "worktree" and "worktree/i" includeIf conditions
From: Patrick Steinhardt @ 2026-05-13  5:56 UTC (permalink / raw)
  To: Chen Linxuan; +Cc: git, gitster, kristofferhaugsbakk
In-Reply-To: <DIH7FB91JHU1.3OOTDQ6QEZZJZ@black-desk.cn>

On Wed, May 13, 2026 at 10:47:48AM +0800, Chen Linxuan wrote:
> On Tue, 12 May 2026 09:14:03 +0200, Patrick Steinhardt wrote:
> > Just because it was explicitly mentioned: we might also want to have a
> > test that verifies this works with early-config parsing. We already have
> > a similar test for "gitdir:" in "conditional include, early config
> > reading".
> 
> As I wrote in the commit message, this is not going to work with
> early-config parsing. I am working on the fix. But I am not quite
> sure that this is a must-fix issue or not.

If it's not working we should have a test for this regardless, I think.
We should verify the current behaviour around it and either mark it as
`test_expect_success` if that behaviour is intended, or with
`test_expect_failure` if it's not.

Thanks!

Patrick

^ permalink raw reply

* Re: [PATCH v3 2/2] config: add "worktree" and "worktree/i" includeIf conditions
From: Chen Linxuan @ 2026-05-13  2:55 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Phillip Wood, Patrick Steinhardt, me, git, Kristoffer Haugsbakk
In-Reply-To: <xmqqo6iklid5.fsf@gitster.g>

On Wed, May 13, 2026 at 12:09 AM Junio C Hamano <gitster@pobox.com> wrote:
>
> Phillip Wood <phillip.wood123@gmail.com> writes:
>
> > On 12/05/2026 08:14, Patrick Steinhardt wrote:
> > ...
> >>> Introduce two new condition keywords:
> >>>
> >>>    - worktree:<pattern> matches the realpath of the current worktree's
> >>>      working directory (i.e. repo_get_work_tree()) against a glob
> >>>      pattern.  This is the path returned by git rev-parse
> >>>      --show-toplevel.
> >>>
> >>>    - worktree/i:<pattern> is the case-insensitive variant.
> >>
> >> Seems sensible.
> >
> > We already support per-worktree config settings via
> > extensions.worktreeConfig, so it would be helpful to explain why it is
> > more convenient to set the config based on the worktree's path, rather
> > than just running "git config --worktree" inside the worktree. Do you
> > have multiple repositories with worktrees checked out under a common
> > prefix that you want to share the same config setting?

Yes, that is exactly why I added this feature. I contribute to the
Linux kernel both as an employee and as an individual. I want to
automatically use my company email address whenever I create a
worktree under a specific directory.

^ permalink raw reply

* Re: [PATCH v3 2/2] config: add "worktree" and "worktree/i" includeIf conditions
From: Chen Linxuan @ 2026-05-13  2:47 UTC (permalink / raw)
  To: ps; +Cc: git, gitster, kristofferhaugsbakk, me
In-Reply-To: <agLTO0amktCWMsiE@pks.im>

On Tue, 12 May 2026 09:14:03 +0200, Patrick Steinhardt wrote:
> On Fri, Apr 03, 2026 at 03:02:29PM +0800, Chen Linxuan via B4 Relay wrote:
> > ...
> > The implementation reuses the include_by_path() helper introduced in
> > the previous commit, passing the worktree path in place of the
> > gitdir.  The condition never matches in bare repositories (where
> > there is no worktree) or during early config reading (where no
> > repository is available).
> 
> Right. This is because `repo_get_work_tree()` would return a NULL
> pointer in these cases, and `include_by_path()` exits early in that
> case.
> 
> ...
> 
> This whole listing here is the exact same as we have for the `gitdir`
> condition. Can we maybe deduplicate these into a common section?

Sure, will be updated in V4.

> ...
>
> Just because it was explicitly mentioned: we might also want to have a
> test that verifies this works with early-config parsing. We already have
> a similar test for "gitdir:" in "conditional include, early config
> reading".

As I wrote in the commit message, this is not going to work with
early-config parsing. I am working on the fix. But I am not quite
sure that this is a must-fix issue or not.

> And should we also have a "nongit" branch where we verify outside a
> repository?

Sure, will be added in V4.

Chen Linxuan

^ permalink raw reply

* Re: [PATCH v2 0/4] diff: reject negative context values
From: Junio C Hamano @ 2026-05-13  1:16 UTC (permalink / raw)
  To: Michael Montalbo via GitGitGadget; +Cc: git, Michael Montalbo
In-Reply-To: <pull.2105.v2.git.1778609423.gitgitgadget@gmail.com>

"Michael Montalbo via GitGitGadget" <gitgitgadget@gmail.com> writes:

> Changes since v1:
>
> Patch 1 and 4: Rewrote commit message to not imply NONEG was related to the
> bug.
>
> Patch 4: Trimmed to just clarify what "negated" means, without documenting
> what PARSE_OPT_NONEG does not do.

Thanks.  Will queue.  I have nothing more to add, but I will hold
off on marking it for 'next' to give others a chance to comment.

^ permalink raw reply

* Re: [PATCH] http: handle absolute-path alternates from server root
From: Junio C Hamano @ 2026-05-13  1:10 UTC (permalink / raw)
  To: Jeff King; +Cc: git, slonkazoid
In-Reply-To: <20260512162619.GA69813@coredump.intra.peff.net>

Jeff King <peff@peff.net> writes:

>           ... Probably in a way that makes it totally invalid, but
>           if you were very unlucky you could turn something like:
>
>              http://victim.com.evil.domain:8000
>
>           into:
>
>             http://victim.com
>
> 	  Which looks like the start of a redirect attack, except that
> 	  the attacker could just have written "http://victim.com" in
> 	  the first place! Either way we feed it to
> 	  is_alternate_allowed(), which is where we check redirect and
> 	  protocol rules.

Yuck.  I know I am the guilty party who introduced the dumb HTTP
walker but I wish we could kill it off after all these years. I did
not even recall that we supported the alternate object store in the
"protocol" until I saw this patch X-<.

> I think we can just treat this like a regular bug.

Absolutely.  Thanks.

^ permalink raw reply


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