Git development
 help / color / mirror / Atom feed
* Re: [PATCH] t4216: fix no-op test that breaks TAP output
From: Junio C Hamano @ 2026-06-19 16:29 UTC (permalink / raw)
  To: Taylor Blau; +Cc: Patrick Steinhardt, git, Todd Zullinger, Jeff King
In-Reply-To: <ajVMZpjTKiXc7TRe@nand.local>

Taylor Blau <me@ttaylorr.com> writes:

> Given this and the above, I would probably err on the side of
> designating this as 'test_lazy_prereq' or otherwise silencing the output
> of 'test_cmp' so that this does not taint the TAP output.

We can argue the merit and demerit with a good log message.  The
central issue at hand is how precious 52a9 in the script lost by
this patch is (in other words, are we checking more than "is our
char signed or unsigned?").

By the way, I do not quite get the _BY_DEFAULT in the name
SIGNED_CHAR_BY_DEFAULT.  The builder may have configured to use
signed char on a platform that can handle both and their char is by
default unsigned, and under such a condition, we would set this
prerequisite, even though the default on such a platform is
unsigned, no?

^ permalink raw reply

* Bug: Checkout in sparse mode can overwrite uncommited files
From: charmocc @ 2026-06-19 16:31 UTC (permalink / raw)
  To: git@vger.kernel.org


Hey. Today I discovered inconsistent behavior in sparse mode that may lead to data loss during initial checkout. Please have a look.

What did you do before the bug happened? (Steps to reproduce your issue)

$ git init foo
$ cd foo
$ echo 1 > file
$ git add file
$ git commit -m 'test'
$ cd ..

$ git clone --no-checkout foo bar
$ cd bar
$ echo 2 > file
$ git checkout
error: The following untracked working tree files would be overwritten by checkout:
	file
Please move or remove them before you switch branches.
Aborting
$ cat file
2
$ git sparse-checkout set
$ git checkout
warning: The following paths were already present and thus not updated despite sparse patterns:
	file

After fixing the above paths, you may want to run `git sparse-checkout reapply`.
Your branch is up to date with 'origin/master'.
$ cat file
1

What did you expect to happen? (Expected behavior)

Checkout should fail due to uncommited changes

What happened instead? (Actual behavior)

After enabling sparse mode checkout overwrites file in working directory

What's different between what you expected and what actually happened?

In normal mode checkout is aborted correctly but in sparse it overwrites data and gives misleading message

Anything else you want to add:

[System Info]
git version:
git version 2.53.0
cpu: x86_64
no commit associated with this build
sizeof-long: 8
sizeof-size_t: 8
shell-path: /bin/sh
rust: disabled
gettext: enabled
libcurl: 8.18.0
zlib: 1.3.1
SHA-1: SHA1_DC
SHA-256: SHA256_BLK
default-ref-format: files
default-hash: sha1
uname: Linux 7.0.0-22-generic #22-Ubuntu SMP PREEMPT_DYNAMIC Mon May 25 15:54:34 UTC 2026 x86_64
compiler info: gnuc: 15.2
libc info: glibc: 2.43
$SHELL (typically, interactive shell): /bin/bash


[Enabled Hooks]


^ permalink raw reply

* Re: [PATCH] help: prompt user to run corrected command on typo
From: Junio C Hamano @ 2026-06-19 16:37 UTC (permalink / raw)
  To: Jishnu C K; +Cc: Justin Tobler, git
In-Reply-To: <6a34dc40.2c570c9e.381c97.203f@mx.google.com>

Jishnu C K <jishnuck26@gmail.com> writes:

> You're right that `help.autocorrect=prompt` exists and is similar.
> Our change differs in two ways:
>
> 1. No configuration needed. The existing prompt mode requires the user

I do not particularly see it as an advantage.

> 2. The prompt includes the original arguments. `help.autocorrect=prompt`
>    shows only:
>
>      Run 'checkout' instead [y/N]?
>
>    Our prompt shows the full corrected invocation:
>
>      Did you mean 'git checkout neo'? [y/N]
>
>    This lets the user confirm exactly what will run, including their
>    original arguments, before pressing 'y'.

This may be an improvement, but is there a reason why such a change
must be done as a parallel and unrelated (re)implementation and not
as an incrementa improvement to the code that implements the
existing feature?




^ permalink raw reply

* [PATCH v4 0/1] environment: move protect_hfs and protect_ntfs into repo_config_values
From: Tian Yuchen @ 2026-06-19 16:38 UTC (permalink / raw)
  To: git; +Cc: Tian Yuchen, Christian Couder, Ayush Chandekar,
	Olamide Caleb Bello

Hi everyone,

This series continues the ongoing libification effort by moving the
global filesystem variables, 'protect_hfs' and 'protect_ntfs', into
'struct repo_config_values'.

Place them within the per-repository configuration structure
aligns with our goal of removing global states.

For reviewers familiar with previous libification efforts, Derrick Stolee
attempted to wrap this kind of filesystem-level variable using a
lazy-loaded global accessor get_int_config_global() [1].

However, as Glen Choo pointed out in his review of that series [2],
it is strongly preferred to use plain fields in a repository-scoped
struct over global lazy-loaders, provided those fields are properly
initialized during the setup process.

By moving these variables into repo_config_values and parsing
them eagerly, we successfully tie the filesystem security flags
to the specific repository instance without altering the timing
of configuration warnings or introducing new global states.

Thanks!

Change since V3:

 - In repo_protect_hfs() and repo_protect_ntfs(), change repo->gitdir to
 using (repo && repo->initialized).

[1] https://lore.kernel.org/git/a42dd9397d07b2dc4a0d7e75bfe1af2e46cad262.1685716420.git.gitgitgadget@gmail.com/
[2] https://lore.kernel.org/git/kl6lbkhpzujf.fsf@chooglen-macbookpro.roam.corp.google.com/
[3] https://lore.kernel.org/git/20260612160527.167203-1-cat@malon.dev/

Mentored-by: Christian Couder <christian.couder@gmail.com>
Mentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>
Mentored-by: Olamide Caleb Bello <belkid98@gmail.com>
Signed-off-by: Tian Yuchen <cat@malon.dev>

Tian Yuchen (1):
  environment: move 'protect_hfs' and 'protect_ntfs' into
    'repo_config_values'

 compat/mingw.c             |  2 +-
 environment.c              | 22 ++++++++++++++++++----
 environment.h              | 12 ++++++++++--
 read-cache.c               |  7 ++++---
 t/helper/test-path-utils.c | 24 +++++++++++++++---------
 5 files changed, 48 insertions(+), 19 deletions(-)

-- 
2.43.0


^ permalink raw reply

* [PATCH v4 1/1] environment: move 'protect_hfs' and 'protect_ntfs' into 'repo_config_values'
From: Tian Yuchen @ 2026-06-19 16:38 UTC (permalink / raw)
  To: git; +Cc: Tian Yuchen, Christian Couder, Ayush Chandekar,
	Olamide Caleb Bello
In-Reply-To: <20260619163823.652091-1-cat@malon.dev>

Move the global 'protect_hfs' and 'protect_ntfs' configurations
into the repository-specific 'repo_config_values' struct.
This will help with the elimination of 'the_repository'

To ensure code readability, the getter functions
'repo_protect_hfs()' and 'repo_protect_ntfs()'
have been introduced.

For now, associated functions access this configuration by
explicitly falling back to 'the_repository', which needs to
be addressed in the future.

Note: In 't/helper/test-path-utils.c', there is a function
'protect_ntfs_hfs_benchmark()' where these two global
variables are used as loop iterators. New local variables
have been created to replace them.

Mentored-by: Christian Couder <christian.couder@gmail.com>
Mentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>
Mentored-by: Olamide Caleb Bello <belkid98@gmail.com>
Signed-off-by: Tian Yuchen <cat@malon.dev>
---
 compat/mingw.c             |  2 +-
 environment.c              | 22 ++++++++++++++++++----
 environment.h              | 12 ++++++++++--
 read-cache.c               |  7 ++++---
 t/helper/test-path-utils.c | 24 +++++++++++++++---------
 5 files changed, 48 insertions(+), 19 deletions(-)

diff --git a/compat/mingw.c b/compat/mingw.c
index aa7525f419..af87df77fd 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -3392,7 +3392,7 @@ int is_valid_win32_path(const char *path, int allow_literal_nul)
 	const char *p = path;
 	int preceding_space_or_period = 0, i = 0, periods = 0;
 
-	if (!protect_ntfs)
+	if (!repo_protect_ntfs(the_repository))
 		return 1;
 
 	skip_dos_drive_prefix((char **)&path);
diff --git a/environment.c b/environment.c
index fc3ed8bb1c..f34f6fc750 100644
--- a/environment.c
+++ b/environment.c
@@ -82,12 +82,10 @@ unsigned long pack_size_limit_cfg;
 #ifndef PROTECT_HFS_DEFAULT
 #define PROTECT_HFS_DEFAULT 0
 #endif
-int protect_hfs = PROTECT_HFS_DEFAULT;
 
 #ifndef PROTECT_NTFS_DEFAULT
 #define PROTECT_NTFS_DEFAULT 1
 #endif
-int protect_ntfs = PROTECT_NTFS_DEFAULT;
 
 /*
  * The character that begins a commented line in user-editable file
@@ -142,6 +140,20 @@ int is_bare_repository(void)
 	return is_bare_repository_cfg && !repo_get_work_tree(the_repository);
 }
 
+int repo_protect_ntfs(struct repository *repo)
+{
+	return (repo && repo->initialized) ?
+		repo_config_values(repo)->protect_ntfs :
+		PROTECT_NTFS_DEFAULT;
+}
+
+int repo_protect_hfs(struct repository *repo)
+{
+	return (repo && repo->initialized) ?
+		repo_config_values(repo)->protect_hfs :
+		PROTECT_HFS_DEFAULT;
+}
+
 int have_git_dir(void)
 {
 	return startup_info->have_repository
@@ -541,12 +553,12 @@ int git_default_core_config(const char *var, const char *value,
 	}
 
 	if (!strcmp(var, "core.protecthfs")) {
-		protect_hfs = git_config_bool(var, value);
+		cfg->protect_hfs = git_config_bool(var, value);
 		return 0;
 	}
 
 	if (!strcmp(var, "core.protectntfs")) {
-		protect_ntfs = git_config_bool(var, value);
+		cfg->protect_ntfs = git_config_bool(var, value);
 		return 0;
 	}
 
@@ -720,5 +732,7 @@ void repo_config_values_init(struct repo_config_values *cfg)
 {
 	cfg->attributes_file = NULL;
 	cfg->apply_sparse_checkout = 0;
+	cfg->protect_hfs = PROTECT_HFS_DEFAULT;
+	cfg->protect_ntfs = PROTECT_NTFS_DEFAULT;
 	cfg->branch_track = BRANCH_TRACK_REMOTE;
 }
diff --git a/environment.h b/environment.h
index 9eb97b3869..b1ae4a70de 100644
--- a/environment.h
+++ b/environment.h
@@ -91,6 +91,8 @@ struct repo_config_values {
 	/* section "core" config values */
 	char *attributes_file;
 	int apply_sparse_checkout;
+	int protect_hfs;
+	int protect_ntfs;
 
 	/* section "branch" config values */
 	enum branch_track branch_track;
@@ -123,6 +125,14 @@ int git_default_config(const char *, const char *,
 int git_default_core_config(const char *var, const char *value,
 			    const struct config_context *ctx, void *cb);
 
+/*
+ * Getters for the `protect_hfs` and `protect_ntfs` fields of `struct repo_config_values`.
+ * They check `repo->initialized` to prevent calling `repo_config_values()`
+ * before the repository setup is fully complete or in non-git environments.
+ */
+int repo_protect_hfs(struct repository *repo);
+int repo_protect_ntfs(struct repository *repo);
+
 void repo_config_values_init(struct repo_config_values *cfg);
 
 /*
@@ -173,8 +183,6 @@ extern int pack_compression_level;
 extern unsigned long pack_size_limit_cfg;
 
 extern int precomposed_unicode;
-extern int protect_hfs;
-extern int protect_ntfs;
 
 extern int core_sparse_checkout_cone;
 extern int sparse_expect_files_outside_of_patterns;
diff --git a/read-cache.c b/read-cache.c
index 21829102ae..2c6a60c756 100644
--- a/read-cache.c
+++ b/read-cache.c
@@ -1002,7 +1002,7 @@ static enum verify_path_result verify_path_internal(const char *path,
 			return PATH_OK;
 		if (is_dir_sep(c)) {
 inside:
-			if (protect_hfs) {
+			if (repo_protect_hfs(the_repository)) {
 
 				if (is_hfs_dotgit(path))
 					return PATH_INVALID;
@@ -1011,7 +1011,7 @@ static enum verify_path_result verify_path_internal(const char *path,
 						return PATH_INVALID;
 				}
 			}
-			if (protect_ntfs) {
+			if (repo_protect_ntfs(the_repository)) {
 #if defined GIT_WINDOWS_NATIVE || defined __CYGWIN__
 				if (c == '\\')
 					return PATH_INVALID;
@@ -1035,7 +1035,8 @@ static enum verify_path_result verify_path_internal(const char *path,
 			if (c == '\0')
 				return S_ISDIR(mode) ? PATH_DIR_WITH_SEP :
 						       PATH_INVALID;
-		} else if (c == '\\' && protect_ntfs) {
+		} else if (c == '\\' &&
+			   repo_protect_ntfs(the_repository)) {
 			if (is_ntfs_dotgit(path))
 				return PATH_INVALID;
 			if (S_ISLNK(mode)) {
diff --git a/t/helper/test-path-utils.c b/t/helper/test-path-utils.c
index 15eb44485c..f77b3f9d70 100644
--- a/t/helper/test-path-utils.c
+++ b/t/helper/test-path-utils.c
@@ -250,6 +250,7 @@ static int protect_ntfs_hfs_benchmark(int argc, const char **argv)
 	double m[3][2], v[3][2];
 	uint64_t cumul;
 	double cumul2;
+	int ntfs, hfs;
 
 	if (argc > 1 && !strcmp(argv[1], "--with-symlink-mode")) {
 		file_mode = 0120000;
@@ -276,8 +277,13 @@ static int protect_ntfs_hfs_benchmark(int argc, const char **argv)
 			names[i][--len] = (char)(' ' + (my_random() % ('\x7f' - ' ')));
 	}
 
-	for (protect_ntfs = 0; protect_ntfs < 2; protect_ntfs++)
-		for (protect_hfs = 0; protect_hfs < 2; protect_hfs++) {
+	if (!the_repository->gitdir)
+		the_repository->gitdir = xstrdup(".git");
+
+	for (ntfs = 0; ntfs < 2; ntfs++)
+		for (hfs = 0; hfs < 2; hfs++) {
+			repo_config_values(the_repository)->protect_ntfs = ntfs;
+			repo_config_values(the_repository)->protect_hfs = hfs;
 			cumul = 0;
 			cumul2 = 0;
 			for (i = 0; i < repetitions; i++) {
@@ -285,18 +291,18 @@ static int protect_ntfs_hfs_benchmark(int argc, const char **argv)
 				for (j = 0; j < nr; j++)
 					verify_path(names[j], file_mode);
 				end = getnanotime();
-				printf("protect_ntfs = %d, protect_hfs = %d: %lfms\n", protect_ntfs, protect_hfs, (end-begin) / (double)1e6);
+				printf("protect_ntfs = %d, protect_hfs = %d: %lfms\n", ntfs, hfs, (end-begin) / (double)1e6);
 				cumul += end - begin;
 				cumul2 += (end - begin) * (end - begin);
 			}
-			m[protect_ntfs][protect_hfs] = cumul / (double)repetitions;
-			v[protect_ntfs][protect_hfs] = my_sqrt(cumul2 / (double)repetitions - m[protect_ntfs][protect_hfs] * m[protect_ntfs][protect_hfs]);
-			printf("mean: %lfms, stddev: %lfms\n", m[protect_ntfs][protect_hfs] / (double)1e6, v[protect_ntfs][protect_hfs] / (double)1e6);
+			m[ntfs][hfs] = cumul / (double)repetitions;
+			v[ntfs][hfs] = my_sqrt(cumul2 / (double)repetitions - m[ntfs][hfs] * m[ntfs][hfs]);
+			printf("mean: %lfms, stddev: %lfms\n", m[ntfs][hfs] / (double)1e6, v[ntfs][hfs] / (double)1e6);
 		}
 
-	for (protect_ntfs = 0; protect_ntfs < 2; protect_ntfs++)
-		for (protect_hfs = 0; protect_hfs < 2; protect_hfs++)
-			printf("ntfs=%d/hfs=%d: %lf%% slower\n", protect_ntfs, protect_hfs, (m[protect_ntfs][protect_hfs] - m[0][0]) * 100 / m[0][0]);
+	for (ntfs = 0; ntfs < 2; ntfs++)
+		for (hfs = 0; hfs < 2; hfs++)
+			printf("ntfs=%d/hfs=%d: %lf%% slower\n", ntfs, hfs, (m[ntfs][hfs] - m[0][0]) * 100 / m[0][0]);
 
 	return 0;
 }
-- 
2.43.0


^ permalink raw reply related

* Re: [PATCH v3 15/17] odb/source-packed: stub out remaining functions
From: Junio C Hamano @ 2026-06-19 16:40 UTC (permalink / raw)
  To: Patrick Steinhardt; +Cc: git, Karthik Nayak, Justin Tobler
In-Reply-To: <ajTRK4nBxyv3YIgs@pks.im>

Patrick Steinhardt <ps@pks.im> writes:

>> diff --git a/odb/source-packed.c b/odb/source-packed.c
>> index 42c28fba0e..decc81aa52 100644
>> --- a/odb/source-packed.c
>> +++ b/odb/source-packed.c
>> @@ -503,7 +503,7 @@ static int odb_source_packed_freshen_object(struct odb_source *source,
>>  
>>  static int odb_source_packed_write_object(struct odb_source *source UNUSED,
>>  					  const void *buf UNUSED,
>> -					  unsigned long len UNUSED,
>> +					  size_t len UNUSED,
>>  					  enum object_type type UNUSED,
>>  					  struct object_id *oid UNUSED,
>>  					  struct object_id *compat_oid UNUSED,
>
> Thanks for the heads up, the change looks obviously correct to me. I'm
> also happy to send a rebased version -- just give me a nudge and I'll do
> that.

Nah, I expect the other topic would be ready and among the first
batch to graduate post 2.55 final, so it is not too much of a hassle
to carry the merge-fix around for me.  I do not even expect I need
"refs rename" for this, as the merge order will unlikely to be
flipped ;-)

THanks.

^ permalink raw reply

* Re: Strange behavior of "git log" with file argument
From: Junio C Hamano @ 2026-06-19 17:02 UTC (permalink / raw)
  To: Vincent Lefevre; +Cc: git
In-Reply-To: <20260619154448.GA769454@qaa.vinc17.org>

Vincent Lefevre <vincent@vinc17.net> writes:

> "git log git-gui/git-gui--askyesno.sh" outputs nothing. To get logs, I
> can add the -m option. In particular, this shows 3 non-merge commits.

This is a known joy of subtree-merge hack.

You could probably do 

    $ git log -- git-gui/git-gui--askyesno.sh git-gui--askyesno.sh

The thing is, in git-gui project, git-gui--askyesno.sh script is at
the root level of its working tree, and we are subtree-merging it in
a subdirectory.  Once the history traversal realizes that a change
to the script came from git-gui history, it would need to be told
that it needs to pay attention to git-gui--askyesno.sh at the root
tree as well.

^ permalink raw reply

* [PATCH v2] help: include arguments in autocorrect=prompt message
From: Jishnu C K @ 2026-06-19 17:04 UTC (permalink / raw)
  To: git; +Cc: gitster, Justin Tobler
In-Reply-To: <ajQuqTB580gqNP8D@denethor>

v2: Reworked as an incremental improvement to the existing
autocorrect=prompt code path rather than a parallel reimplementation,
per feedback from Junio and Justin.

---
From a4e8fb6fd6dd6a501e565c7500cbf927d7cb0b42 Mon Sep 17 00:00:00 2001
From: calicomills <jishnuck26@gmail.com>
Date: Fri, 19 Jun 2026 13:01:40 +0530
Subject: [PATCH v2 v2] help: include arguments in autocorrect=prompt message

When 'help.autocorrect=prompt' is configured and the user mistypes
a git command, the prompt currently shows only the corrected command
name:

  Run 'checkout' instead [y/N]?

This leaves the user unsure whether their original arguments will be
preserved. Update the prompt to include the full corrected invocation:

  Run 'git checkout neo' instead [y/N]?

The help_unknown_cmd() signature is updated to accept the args vector
so the prompt can show the original arguments alongside the corrected
command name. Callers that do not have access to the args (e.g.
builtin/help.c) pass NULL, which is handled gracefully.

Signed-off-by: calicomills <jishnuck26@gmail.com>
---
 help.c                      | 49 +++++++++++++----------------------
 t/t9003-help-autocorrect.sh | 51 +++++--------------------------------
 2 files changed, 23 insertions(+), 77 deletions(-)

diff --git a/help.c b/help.c
index 30f32a7206..9ea4c076e1 100644
--- a/help.c
+++ b/help.c
@@ -739,7 +739,16 @@ char *help_unknown_cmd(const char *cmd, const struct strvec *args)
 		else if (cfg.autocorrect == AUTOCORRECT_PROMPT) {
 			char *answer;
 			struct strbuf msg = STRBUF_INIT;
-			strbuf_addf(&msg, _("Run '%s' instead [y/N]? "), assumed);
+			struct strbuf full_cmd = STRBUF_INIT;
+			strbuf_addstr(&full_cmd, assumed);
+			if (args) {
+				for (size_t j = 1; j < args->nr; j++) {
+					strbuf_addch(&full_cmd, ' ');
+					strbuf_addstr(&full_cmd, args->v[j]);
+				}
+			}
+			strbuf_addf(&msg, _("Run 'git %s' instead [y/N]? "), full_cmd.buf);
+			strbuf_release(&full_cmd);
 			answer = git_prompt(msg.buf, PROMPT_ECHO);
 			strbuf_release(&msg);
 			if (!(starts_with(answer, "y") ||
@@ -762,37 +771,13 @@ char *help_unknown_cmd(const char *cmd, const struct strvec *args)
 	fprintf_ln(stderr, _("git: '%s' is not a git command. See 'git --help'."), cmd);
 
 	if (SIMILAR_ENOUGH(best_similarity)) {
-		if (n == 1 && isatty(0) && isatty(2)) {
-			char *answer;
-			struct strbuf msg = STRBUF_INIT;
-			struct strbuf full_cmd = STRBUF_INIT;
-			strbuf_addstr(&full_cmd, main_cmds.names[0]->name);
-			if (args) {
-				for (size_t j = 1; j < args->nr; j++) {
-					strbuf_addch(&full_cmd, ' ');
-					strbuf_addstr(&full_cmd, args->v[j]);
-				}
-			}
-			strbuf_addf(&msg, _("\nDid you mean 'git %s'? [y/N] "),
-				    full_cmd.buf);
-			strbuf_release(&full_cmd);
-			answer = git_prompt(msg.buf, PROMPT_ECHO);
-			strbuf_release(&msg);
-			if (starts_with(answer, "y") || starts_with(answer, "Y")) {
-				char *assumed = xstrdup(main_cmds.names[0]->name);
-				cmdnames_release(&cfg.aliases);
-				cmdnames_release(&main_cmds);
-				cmdnames_release(&other_cmds);
-				return assumed;
-			}
-		} else {
-			fprintf_ln(stderr,
-				   Q_("\nThe most similar command is",
-				      "\nThe most similar commands are",
-				   n));
-			for (i = 0; i < n; i++)
-				fprintf(stderr, "\t%s\n", main_cmds.names[i]->name);
-		}
+		fprintf_ln(stderr,
+			   Q_("\nThe most similar command is",
+			      "\nThe most similar commands are",
+			   n));
+
+		for (i = 0; i < n; i++)
+			fprintf(stderr, "\t%s\n", main_cmds.names[i]->name);
 	}
 
 	exit(1);
diff --git a/t/t9003-help-autocorrect.sh b/t/t9003-help-autocorrect.sh
index 6fe2da1595..75821d63e1 100755
--- a/t/t9003-help-autocorrect.sh
+++ b/t/t9003-help-autocorrect.sh
@@ -70,57 +70,18 @@ test_expect_success 'autocorrect works in work tree created from bare repo' '
 	git -C worktree -c help.autocorrect=immediate status
 '
 
-# Default behaviour (no help.autocorrect set): when there is exactly one
-# similar command but the session is non-interactive, fall back to printing
-# the suggestion list and exiting rather than showing a prompt.
-test_expect_success 'default: single match non-interactive shows suggestion and fails' '
-	test_might_fail git config --unset help.autocorrect &&
-
-	test_must_fail git lfg 2>actual &&
-	grep "most similar command" actual &&
-	grep "lgf" actual
-'
-
-test_expect_success 'default: multiple matches non-interactive shows list and fails' '
-	test_might_fail git config --unset help.autocorrect &&
-
-	test_must_fail git com 2>actual &&
-	grep "most similar commands" actual &&
-	grep "commit" actual
-'
-
-# Interactive prompt tests require a real TTY.  On macOS the TTY prereq is
-# skipped due to IO::Pty reliability issues; these tests run on Linux CI.
-test_expect_success TTY 'default: single match interactive, answer y runs command' '
-	git config --unset help.autocorrect &&
-
-	write_script git-typotest <<-\EOF &&
-		echo typotest-ran
-	EOF
-	PATH="$PATH:." export PATH &&
-
-	# Feed "y" to /dev/tty via a wrapper that answers the prompt
-	write_script answer-prompt <<-\EOF &&
-		# Write the answer to the controlling terminal
-		printf "y\n" >/dev/tty
-		exec "$@"
-	EOF
-
-	test_terminal ./answer-prompt git typotest 2>err >out &&
-	grep "typotest-ran" out &&
-	grep "Did you mean" err
-'
-
-test_expect_success TTY 'default: single match interactive, answer n exits cleanly' '
-	git config --unset help.autocorrect &&
+# autocorrect=prompt should include the original arguments in the prompt.
+# Requires a TTY; skipped on macOS due to IO::Pty reliability issues.
+test_expect_success TTY 'autocorrect=prompt includes arguments in prompt' '
+	git config help.autocorrect prompt &&
 
 	write_script answer-prompt-no <<-\EOF &&
 		printf "n\n" >/dev/tty
 		exec "$@"
 	EOF
 
-	test_must_fail test_terminal ./answer-prompt-no git typotest 2>err &&
-	grep "Did you mean" err
+	test_must_fail test_terminal ./answer-prompt-no git lfg --oneline 2>actual &&
+	grep "lgf --oneline" actual
 '
 
 test_done
-- 
2.50.1



^ permalink raw reply related

* Re: [PATCH v4 0/1] environment: move protect_hfs and protect_ntfs into repo_config_values
From: Junio C Hamano @ 2026-06-19 17:14 UTC (permalink / raw)
  To: Tian Yuchen; +Cc: git, Christian Couder, Ayush Chandekar, Olamide Caleb Bello
In-Reply-To: <20260619163823.652091-1-cat@malon.dev>

Tian Yuchen <cat@malon.dev> writes:

> This series continues the ongoing libification effort by moving the
> global filesystem variables, 'protect_hfs' and 'protect_ntfs', into
> 'struct repo_config_values'.
> ...
> Change since V3:
>
>  - In repo_protect_hfs() and repo_protect_ntfs(), change repo->gitdir to
>  using (repo && repo->initialized).

While I think that it is a good change for consistency with other
two topics, the hfs/ntfs topic is already in 'next', so it needs to
be handled differently.  Namely, a topic in 'next' should not be
replaced, but be improved by additional patches on top.

In this particular case case, I think it would be good to have "to
match how we refrain from calling repo_config_values() on an
uninitialized instance of a repository object in other two topics
that deal with X bit and Y bit, check the repo->initialized bit
instead of the repo->gitdir member" or something like that in the
log message to explain why we are making the change, perhaps.

The patch text may look like this.

 environment.c | 4 ++--
 environment.h | 4 ++--
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git c/environment.c w/environment.c
index 683fe1b4d3..f34f6fc750 100644
--- c/environment.c
+++ w/environment.c
@@ -142,14 +142,14 @@ int is_bare_repository(void)
 
 int repo_protect_ntfs(struct repository *repo)
 {
-	return repo->gitdir ?
+	return (repo && repo->initialized) ?
 		repo_config_values(repo)->protect_ntfs :
 		PROTECT_NTFS_DEFAULT;
 }
 
 int repo_protect_hfs(struct repository *repo)
 {
-	return repo->gitdir ?
+	return (repo && repo->initialized) ?
 		repo_config_values(repo)->protect_hfs :
 		PROTECT_HFS_DEFAULT;
 }
diff --git c/environment.h w/environment.h
index fdd9775900..b1ae4a70de 100644
--- c/environment.h
+++ w/environment.h
@@ -127,8 +127,8 @@ int git_default_core_config(const char *var, const char *value,
 
 /*
  * Getters for the `protect_hfs` and `protect_ntfs` fields of `struct repo_config_values`.
- * They check `repo->gitdir` to prevent calling repo_config_values()
- * before the configuration is loaded or in bare environments.
+ * They check `repo->initialized` to prevent calling `repo_config_values()`
+ * before the repository setup is fully complete or in non-git environments.
  */
 int repo_protect_hfs(struct repository *repo);
 int repo_protect_ntfs(struct repository *repo);


^ permalink raw reply related

* Re: [PATCH v4 0/1] environment: move protect_hfs and protect_ntfs into repo_config_values
From: Junio C Hamano @ 2026-06-19 17:25 UTC (permalink / raw)
  To: Tian Yuchen; +Cc: git, Christian Couder, Ayush Chandekar, Olamide Caleb Bello
In-Reply-To: <xmqqo6h6jvuk.fsf@gitster.g>

Junio C Hamano <gitster@pobox.com> writes:

> diff --git c/environment.h w/environment.h
> index fdd9775900..b1ae4a70de 100644
> --- c/environment.h
> +++ w/environment.h
> @@ -127,8 +127,8 @@ int git_default_core_config(const char *var, const char *value,
>  
>  /*
>   * Getters for the `protect_hfs` and `protect_ntfs` fields of `struct repo_config_values`.
> - * They check `repo->gitdir` to prevent calling repo_config_values()
> - * before the configuration is loaded or in bare environments.
> + * They check `repo->initialized` to prevent calling `repo_config_values()`
> + * before the repository setup is fully complete or in non-git environments.
>   */
>  int repo_protect_hfs(struct repository *repo);
>  int repo_protect_ntfs(struct repository *repo);

Another thing we should remember (but should *NOT* do while these
topics are still in flight) to do is to consolidate these comments
into one.  The hfs and htfs getters are covered by the same single
comment, but ignorecase and trustexecutable bit getters have their
own comments, only because they came in different topics.  We should
conslidate them into a single comment block once all of these have
landed in 'master', which may happen soon after 2.55 final gets
tagged.


^ permalink raw reply

* Re: [PATCH v2] Makefile: dedup archives in $(LIBS) so link recipes don't repeat them
From: Harald Nordgren @ 2026-06-19 20:25 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Harald Nordgren via GitGitGadget, git
In-Reply-To: <xmqqldcamtat.fsf@gitster.g>

Thanks for a good lesson about linkers!

Good idea to fix it through the same LD_MAJOR_VERSION as we did for
the __DATA,__common alignment, makes it a lot cleaner.


Harald

^ permalink raw reply

* [PATCH v3] config.mak.uname: avoid macOS dup-library warning
From: Harald Nordgren via GitGitGadget @ 2026-06-19 20:32 UTC (permalink / raw)
  To: git; +Cc: Harald Nordgren, Harald Nordgren
In-Reply-To: <pull.2314.v2.git.git.1780610623006.gitgitgadget@gmail.com>

From: Harald Nordgren <haraldnordgren@gmail.com>

Building on macOS with Xcode 15 or newer emits:

    ld: warning: ignoring duplicate libraries: 'libgit.a',
    'target/release/libgitcore.a'

Some link recipes list the same archive twice, which is harmless.
Quiet the warning instead.

Pass -Wl,-no_warn_duplicate_libraries on Xcode 15 and newer, whose
linkers added both the warning and the suppression flag (ld64-907
and dyld-1009). Earlier linkers reject the flag, so gate on the
linker version. Broaden the existing -fno-common version probe to
also match the "ld64-NNN" and "dyld-NNN" forms Xcode 15 reports.

Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
---
    Makefile: drop duplicate %.a from test-helper link rule
    
    Fix warning of duplicate libraries on macOS.
    
    Changes in v3:
    
     * Suppress the warning at the linker rather than dedup the archive list
     * Pass -Wl,-no_warn_duplicate_libraries in config.mak.uname, gated on
       the linker version (reuses the probe added for -fno-common), and
       broaden the regex to match all three PROJECT:{ld64,dyld,ld}-NNN forms
     * Floor of 907 and the version forms (ld64-907, dyld-1009.5) per meson:
       https://github.com/mesonbuild/meson/blob/master/mesonbuild/linkers/linkers.py

Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2314%2FHaraldNordgren%2Fmakefile-test-helper-dedup-libs-v3
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2314/HaraldNordgren/makefile-test-helper-dedup-libs-v3
Pull-Request: https://github.com/git/git/pull/2314

Range-diff vs v2:

 1:  0ef442ea05 < -:  ---------- Makefile: dedup archives in $(LIBS) so link recipes don't repeat them
 -:  ---------- > 1:  5bf560c5ad config.mak.uname: avoid macOS dup-library warning


 config.mak.uname | 11 +++++++++--
 1 file changed, 9 insertions(+), 2 deletions(-)

diff --git a/config.mak.uname b/config.mak.uname
index 8719e09f66..9ebd240378 100644
--- a/config.mak.uname
+++ b/config.mak.uname
@@ -173,8 +173,15 @@ ifeq ($(uname_S),Darwin)
 		NEEDS_GOOD_LIBICONV = UnfortunatelyYes
         endif
 
-	# Silence Xcode 16.3+ linker warning about __DATA,__common alignment.
-	LD_MAJOR_VERSION = $(shell ld -v 2>&1 | sed -n 's/.*PROJECT:ld-\([0-9]*\).*/\1/p')
+	# ld reports "PROJECT:{ld,ld64,dyld}-NNN", match any of the three.
+	LD_MAJOR_VERSION = $(shell ld -v 2>&1 | sed -n 's/.*PROJECT:[^ ]*-\([0-9][0-9]*\).*/\1/p')
+
+	# Silence the Xcode 15+ warning about archives listed more than once.
+        ifeq ($(shell test -n "$(LD_MAJOR_VERSION)" && test "$(LD_MAJOR_VERSION)" -ge 907 && echo 1),1)
+		BASIC_LDFLAGS += -Wl,-no_warn_duplicate_libraries
+        endif
+
+	# Silence the Xcode 16.3+ warning about __DATA,__common alignment.
         ifeq ($(shell test -n "$(LD_MAJOR_VERSION)" && test "$(LD_MAJOR_VERSION)" -ge 1167 && echo 1),1)
 		BASIC_CFLAGS += -fno-common
         endif

base-commit: 95e20213faefeb95df29277c58ac1980ab68f701
-- 
gitgitgadget

^ permalink raw reply related

* Re: [PATCH v3] config.mak.uname: avoid macOS dup-library warning
From: Junio C Hamano @ 2026-06-19 22:27 UTC (permalink / raw)
  To: Harald Nordgren via GitGitGadget; +Cc: git, Harald Nordgren
In-Reply-To: <pull.2314.v3.git.git.1781901127385.gitgitgadget@gmail.com>

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

> From: Harald Nordgren <haraldnordgren@gmail.com>
>
> Building on macOS with Xcode 15 or newer emits:
>
>     ld: warning: ignoring duplicate libraries: 'libgit.a',
>     'target/release/libgitcore.a'
>
> Some link recipes list the same archive twice, which is harmless.
> Quiet the warning instead.
>
> Pass -Wl,-no_warn_duplicate_libraries on Xcode 15 and newer, whose
> linkers added both the warning and the suppression flag (ld64-907
> and dyld-1009). Earlier linkers reject the flag, so gate on the
> linker version. Broaden the existing -fno-common version probe to
> also match the "ld64-NNN" and "dyld-NNN" forms Xcode 15 reports.
>
> Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
> ---

Yeah, this looks like what I expected.

A few things to note.

 * Can folks with different versions of Xcode (or is 15 sufficiently
   old that practically nobody is expected to have anything older?)
   test this patch?

 * We only patch Makefile here; can folks who use meson report how
   well your build goes?

Thanks.

>  config.mak.uname | 11 +++++++++--
>  1 file changed, 9 insertions(+), 2 deletions(-)
>
> diff --git a/config.mak.uname b/config.mak.uname
> index 8719e09f66..9ebd240378 100644
> --- a/config.mak.uname
> +++ b/config.mak.uname
> @@ -173,8 +173,15 @@ ifeq ($(uname_S),Darwin)
>  		NEEDS_GOOD_LIBICONV = UnfortunatelyYes
>          endif
>  
> -	# Silence Xcode 16.3+ linker warning about __DATA,__common alignment.
> -	LD_MAJOR_VERSION = $(shell ld -v 2>&1 | sed -n 's/.*PROJECT:ld-\([0-9]*\).*/\1/p')
> +	# ld reports "PROJECT:{ld,ld64,dyld}-NNN", match any of the three.
> +	LD_MAJOR_VERSION = $(shell ld -v 2>&1 | sed -n 's/.*PROJECT:[^ ]*-\([0-9][0-9]*\).*/\1/p')
> +
> +	# Silence the Xcode 15+ warning about archives listed more than once.
> +        ifeq ($(shell test -n "$(LD_MAJOR_VERSION)" && test "$(LD_MAJOR_VERSION)" -ge 907 && echo 1),1)
> +		BASIC_LDFLAGS += -Wl,-no_warn_duplicate_libraries
> +        endif
> +
> +	# Silence the Xcode 16.3+ warning about __DATA,__common alignment.
>          ifeq ($(shell test -n "$(LD_MAJOR_VERSION)" && test "$(LD_MAJOR_VERSION)" -ge 1167 && echo 1),1)
>  		BASIC_CFLAGS += -fno-common
>          endif
>
> base-commit: 95e20213faefeb95df29277c58ac1980ab68f701

^ permalink raw reply

* Re: [PATCH v3] SubmittingPatches: address design critiques
From: Michael Montalbo @ 2026-06-19 22:40 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: code, git, Michael Montalbo

Junio C Hamano <gitster@pobox.com> writes:
> +Be particularly mindful of critiques regarding the high-level design
> +or viability of your proposal (e.g., questioning if the feature is
> +worth implementing, or if the chosen approach is appropriate).  Defend
> +your design decisions on the list first, work with reviewers and other
> +members to improve the design before revising the implementation, to
> +avoid wasting effort on an implementation before its design is solid.

Slight reflow suggestions:

  Defend your design decisions on the list first; work with reviewers and
  other members to improve the design before revising the implementation.
  This will avoid wasting effort on an implementation before its design is
  solid.

The rest looks good to me!

^ permalink raw reply

* What's cooking in git.git (Jun 2026, #07)
From: Junio C Hamano @ 2026-06-20  1:33 UTC (permalink / raw)
  To: git

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

Git 2.55-rc1 has been tagged and pushed out.  There may be a few
more topics in 'next' that we may want to include in the release
that I didn't manage or I forgot (please let me know), but basically
this development cycle is over, the tree is feature-frozen, and
remaining topics in 'next' will stay in "Will cook in 'next'"
instead of "Will merge to 'master'" state.  We'd want to force
ourselves to concentrate on addressing topics that are important
fixes but still in the "Needs review" state, and of course, find any
correct any regressions relative to Git 2.54, until we are ready to
tag Git 2.55 final.

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

With maint, master, next, seen, todo:

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

With all the integration branches and topics broken out:

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

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

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

Release tarballs are available at:

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

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

* dl/posix-unused-warning-clang (2026-06-13) 3 commits
  (merged to 'next' on 2026-06-15 at 1d7e627c24)
 + compat/posix.h: simplify GIT_GNUC_PREREQ() comparison
 + compat/posix.h: clean up GIT_GNUC_PREREQ() and UNUSED
 + compat/posix.h: enable UNUSED warning messages for Clang

 The UNUSED macro in 'compat/posix.h' has been updated to use a
 newly introduced GIT_CLANG_PREREQ macro for compiler version
 checks, and the existing GIT_GNUC_PREREQ macro has been modernized
 to use explicit major/minor comparisons rather than bit-shifting.
 cf. <ai-8Y1r9zbWfdY8p@pks.im>
 source: <20260613122711.38662-1-dominik.loidolt@univie.ac.at>


* en/commit-graph-timestamp-fix (2026-06-13) 1 commit
  (merged to 'next' on 2026-06-16 at 13248b8196)
 + commit-graph: use timestamp_t for max parent generation accumulator

 compute_reachable_generation_numbers() in commit-graph used a 32-bit
 integer to accumulate parent generations, which is OK for generation
 number v1 (topological levels), but with generation number v2
 (adjusted committer timestamps), it truncated timestamps beyond
 2106.  Fixed by widening the accumulator to timestamp_t.
 cf. <09e50180-e165-48d8-a9d0-485283342f5c@gmail.com>
 source: <pull.2148.git.1781420271100.gitgitgadget@gmail.com>


* jc/t1400-fifo-cleanup (2026-06-10) 1 commit
  (merged to 'next' on 2026-06-15 at 7d5acd110a)
 + t1400: have fifo test clean after itself

 Test cleanup.
 cf. <aiqs5Wq2Di-6yW0D@pks.im>
 source: <xmqqo6hit6rn.fsf@gitster.g>


* js/objects-larger-than-4gb-on-windows (2026-06-18) 1 commit
  (merged to 'next' on 2026-06-18 at 99d7cf9126)
 + zlib: properly clamp to uLong

 A hotfix to an earlier attempt to update code paths that assumed
 "unsigned long" was long enough for "size_t".
 source: <pull.2153.git.1781790619424.gitgitgadget@gmail.com>


* ps/transport-helper-tsan-fix (2026-06-09) 1 commit
  (merged to 'next' on 2026-06-15 at 0857e6696f)
 + transport-helper: fix TSAN race in transfer_debug()

 The TSAN race in transfer_debug() within transport-helper.c has been
 resolved by initializing the debug flag early in
 bidirectional_transfer_loop() before spawning worker threads, allowing
 the removal of a TSAN suppression.
 cf. <20260609002833.GE358144@coredump.intra.peff.net>
 cf. <20260611083320.GI2191159@coredump.intra.peff.net>
 source: <20260609134741.4727-2-pushkarkumarsingh1970@gmail.com>


* ta/doc-config-adoc-fixes (2026-06-11) 3 commits
  (merged to 'next' on 2026-06-15 at 93340b5cf0)
 + doc: git-config: escape erroneous highlight markup
 + doc: config/sideband: fix description list delimiter
 + doc: config: terminate runaway lists

 Various AsciiDoc markup fixes in 'git config' documentation and
 related files to ensure lists and formatting are rendered correctly.
 cf. <20260612045329.GA593075@coredump.intra.peff.net>
 source: <20260611161946.12166-1-taahol@utu.fi>


* td/describe-tag-iteration (2026-06-10) 1 commit
  (merged to 'next' on 2026-06-15 at 1ae171f3b7)
 + describe: limit default ref iteration to tags

 'git describe' has been taught to pass the 'refs/tags/' prefix down to
 the ref iterator when '--all' is not requested, avoiding unnecessary
 iteration over non-tag refs.
 cf. <20260611064912.GC2191159@coredump.intra.peff.net>
 source: <20260610-describe-tag-ref-scope-v3-1-5aa63ab279f7@gmail.com>


* td/ls-files-pathspec-prefilter (2026-06-11) 1 commit
  (merged to 'next' on 2026-06-15 at 38918c4cfd)
 + ls-files: filter pathspec before lstat

 `git ls-files --modified` and `git ls-files --deleted` have been
 optimized to filter with pathspec before calling lstat() when there is
 only a single pathspec item, avoiding unnecessary filesystem access
 for entries that will not be shown.
 cf. <xmqqfr2tnfk0.fsf@gitster.g>
 source: <20260611-ls-files-pathspec-lstat-v3-1-f967e1a00c13@gmail.com>

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

* jc/submittingpatches-design-critiques (2026-06-19) 1 commit
 - SubmittingPatches: address design critiques

 The documentation in SubmittingPatches has been updated to clarify how
 patch contributors should respond to design and viability critiques,
 and how the resolution of such critiques should be recorded in the
 final commit messages.

 Needs review.
 source: <xmqqik7eld2g.fsf_-_@gitster.g>


* wy/doc-clarify-review-replies (2026-06-17) 2 commits
 - doc: advise batching patch rerolls
 - doc: encourage review replies before rerolling

 Documentation on community contribution guidelines has been updated to
 encourage replying to review comments before rerolling, and to advise
 a default limit of at most one reroll per day to give reviewers across
 different time zones enough time to participate.

 Expecting a reroll.
 cf. <ajVCD51lLvHreyJB@wyuan.org>
 source: <cover.1781714757.git.wy@wyuan.org>


* ps/gitlab-ci-windows (2026-06-15) 1 commit
 - gitlab-ci: migrate Windows builds away from Chocolatey

 Wean the Windows builds in GitLab CI procedure away from
 (unfortunately unreliable) Chocolatey to install dependencies.

 Will merge to 'next'?
 cf. <ajL1677NQShTO6tD@denethor>
 source: <20260615-b4-pks-gitlab-ci-drop-chocolatey-v1-1-51a6e7d5e388@pks.im>


* ty/migrate-ignorecase (2026-06-19) 2 commits
 - config: use repo_ignore_case() to access core.ignorecase
 - environment: move ignore_case into repo_config_values

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

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


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

 "git log -L<range>:<path>" learned to limit various "diff" operations
 like --stat, --check, -G, to the specified range:path.

 Waiting for response(s) to review comment(s).
 cf. <xmqq8q8bpl03.fsf@gitster.g>
 source: <pull.2152.git.1781806593.gitgitgadget@gmail.com>


* hn/history-squash (2026-06-18) 6 commits
 - SQUASH??? avoid test_grep lint triggering on uses of raw grep
 - SQUASH??? avoid test number clash by moving 3454-h-squash to 3455
 - history: re-edit a squash with every message
 - history: add squash subcommand to fold a range
 - history: give commit_tree_ext a message template
 - history: extract helper for a commit's parent tree

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

 Waiting for response(s) to review comment(s).
 cf. <ajU8T2JFJTdk1hr2@pks.im>
 source: <pull.2337.v3.git.git.1781810226.gitgitgadget@gmail.com>


* ps/t4216-tap-fix (2026-06-19) 1 commit
 - t4216: fix no-op test that breaks TAP output

 TAP output breakage fix.

 Waiting for response(s) to review comment(s).
 cf. <xmqqa4sqlchz.fsf@gitster.g>
 source: <20260619-pks-t4216-drop-unused-prereq-v1-1-2ce0d7bea088@pks.im>


* hn/macos-linker-warning (2026-06-19) 1 commit
 - config.mak.uname: avoid macOS dup-library warning

 Xcode 15 and later has a linker set to complain when the same library
 archive is listed twice on the command line.  Squelch the annoyance.

 Will merge to 'next'?
 source: <pull.2314.v3.git.git.1781901127385.gitgitgadget@gmail.com>

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

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

 Configuration file locking now retries for a short period, avoiding
 failures when multiple processes attempt to update the configuration
 simultaneously.

 Waiting for response(s) to review comment(s) for too long, stalled.
 cf. <agrIrGwSMFlKTx9x@pks.im>
 source: <20260517132111.1014901-1-joerg@thalheim.io>

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

* mh/fetch-follow-remote-head-config (2026-06-19) 8 commits
 - fetch: fixup a misaligned comment
 - fetch: add configuration variable fetch.followRemoteHEAD
 - fetch: refactor do_fetch handling of followRemoteHEAD
 - fetch: return 0 on known git_fetch_config
 - fetch: rename function report_set_head
 - t5510: cleanup remote in followRemoteHEAD dangling ref test
 - doc: explain fetchRemoteHEADWarn advice
 - fetch: fixup set_head advice for warn-if-not-branch

 The `fetch.followRemoteHEAD` configuration variable has been added to
 provide a default for the per-remote `remote.<name>.followRemoteHEAD`
 setting.

 Will merge to 'next'?
 cf. <xmqqcxxp1j2t.fsf@gitster.g>
 source: <20260619094751.2996804-1-m@lfurio.us>


* ps/refs-writing-subcommands (2026-06-17) 5 commits
 - builtin/refs: add "rename" subcommand
 - builtin/refs: add "create" subcommand
 - builtin/refs: add "update" subcommand
 - builtin/refs: add "delete" subcommand
 - builtin/refs: drop `the_repository`

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

 Needs review.
 source: <20260617-pks-refs-writing-subcommands-v2-0-07f3d18336f9@pks.im>


* po/hash-object-size-t (2026-06-16) 6 commits
 - hash-object: add a >4GB/LLP64 test case using filtered input
 - hash-object: add another >4GB/LLP64 test case
 - hash-object --stdin: verify that it works with >4GB/LLP64
 - hash algorithms: use size_t for section lengths
 - object-file.c: use size_t for header lengths
 - hash-object: demonstrate a >4GB/LLP64 problem

 Support for hashing loose or packed objects larger than 4GB on Windows
 and other LLP64 platforms has been improved by converting object header
 buffers and data-handling functions from 'unsigned long' to 'size_t'.

 Will merge to 'next'.
 cf. <ajOQthRjhD3hRM9w@pks.im>
 source: <pull.2138.v2.git.1781621398.gitgitgadget@gmail.com>


* kh/submittingpatches-trailers (2026-06-18) 5 commits
 - SubmittingPatches: note that trailer order matters
 - SubmittingPatches: be consistent with trailer markup
 - SubmittingPatches: document Based-on-patch-by trailer
 - SubmittingPatches: discourage common Linux trailers
 - SubmittingPatches: encourage trailer use for substantial help

 The trailer sections in SubmittingPatches have been updated to
 encourage use of standard trailers.

 Will merge to 'next'?
 cf. <xmqq4ij0vo8f.fsf@gitster.g>
 source: <V3_CV_SubPatches_trailers.9ec@msgid.xyz>


* mv/log-follow-mergy (2026-06-14) 1 commit
 - log: improve --follow following renames for non-linear history

 "git log --follow" has been updated to handle non-linear history, in
 which the path being tracked gets renamed differently in multiple
 history lines, better.

 Needs review.
 source: <ai-aE83w02xPRlPr@collabora.com>


* wy/doc-myfirstcontribution-trim-quotes (2026-06-11) 1 commit
 - MyFirstContribution: mention trimming quoted text in replies

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

 Comments?
 cf. <xmqqcxxwljue.fsf@gitster.g>
 source: <080402ff0ac8127b654dccea59a1bf643df62a5c.1781186476.git.wy@wyuan.org>


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

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

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


* mm/test-grep-lint (2026-06-12) 6 commits
 - t: add greplint to detect bare grep assertions
 - t: convert grep assertions to test_grep
 - t: fix Lexer line count for $() inside double-quoted strings
 - t: extract chainlint's parser into shared module
 - t: fix grep assertions missing file arguments
 - t/README: document test_grep helper

 Needs review.
 source: <pull.2135.v2.git.1781323575.gitgitgadget@gmail.com>


* js/objects-larger-than-4gb-on-windows-more (2026-06-15) 7 commits
  (merged to 'next' on 2026-06-18 at 2b3ac350e6)
 + odb: use size_t for object_info.sizep and the size APIs
 + packfile,delta: drop the `cast_size_t_to_ulong()` wrappers
 + pack-objects: use size_t for in-core object sizes
 + packfile: widen unpack_entry()'s size out-parameter to size_t
 + pack-objects(check_pack_inflate()): use size_t instead of unsigned long
 + patch-delta: use size_t for sizes
 + compat/msvc: use _chsize_s for ftruncate

 Will cook in 'next'.
 cf. <ajPhBn7n1wR-sii4@pks.im>
 source: <pull.2137.v2.git.1781524349.gitgitgadget@gmail.com>


* kw/gitattributes-typofix (2026-06-15) 1 commit
  (merged to 'next' on 2026-06-17 at 14ff167ef8)
 + gitattributes: fix eol attribute for Perl scripts

 Will cook in 'next'.
 cf. <ai-5vfY8D84UhsB4@pks.im>
 source: <pull.2151.v2.git.1781510039164.gitgitgadget@gmail.com>


* rs/cat-file-default-format-optim (2026-06-14) 1 commit
  (merged to 'next' on 2026-06-17 at 43ed8b3969)
 + cat-file: speed up default format

 Will cook in 'next'.
 cf. <20260615165326.GA91269@coredump.intra.peff.net>
 source: <5a7ed929-6fe0-496c-83bd-65dee57c2241@web.de>


* js/parseopt-subcommand-autocorrection (2026-04-27) 11 commits
 - SQUASH???
 - doc: document autocorrect API
 - parseopt: add tests for subcommand autocorrection
 - parseopt: enable subcommand autocorrection for git-remote and git-notes
 - parseopt: autocorrect mistyped subcommands
 - autocorrect: provide config resolution API
 - autocorrect: rename AUTOCORRECT_SHOW to AUTOCORRECT_HINT
 - autocorrect: use mode and delay instead of magic numbers
 - help: move tty check for autocorrection to autocorrect.c
 - help: make autocorrect handling reusable
 - parseopt: extract subcommand handling from parse_options_step()

 The parse-options library learned to auto-correct misspelled
 subcommand names.

 Expecting a reroll.
 cf. <xmqq33yzd9yf.fsf@gitster.g>
 cf. <SY0P300MB0801E50FCB7EB2F45CD15208CE042@SY0P300MB0801.AUSP300.PROD.OUTLOOK.COM>
 source: <SY0P300MB0801677A2A1E0FD38D06A841CE2A2@SY0P300MB0801.AUSP300.PROD.OUTLOOK.COM>


* kk/prio-queue-get-put-fusion (2026-06-08) 2 commits
 - prio-queue: fold lazy_queue into prio_queue for automatic get+put fusion
 - prio-queue: rename .nr to .nr_ and add accessor helpers

 The lazy priority queue optimization pattern (deferring actual removal
 in prio_queue_get() to allow get+put fusion) has been folded directly
 into prio_queue itself, speeding up commit traversal workflows and
 simplifying callers.

 Needs review.
 source: <pull.2140.v4.git.1780945851.gitgitgadget@gmail.com>


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

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

 Needs review.
 source: <20260612-ref-filter-memoized-contains-v4-0-5ed39fd001dd@gmail.com>


* tc/replay-linearize (2026-06-16) 4 commits
 - SQUASH??? prepare for mm/test-grep-lint
 - replay: offer an option to linearize the commit topology
 - replay: add helper to put entry into mapped_commits
 - replay: refactor enum replay_mode into a bool

 git replay learns --linearize option to drop merge commits and
 linearize the replayed history, mimicking git rebase
 --no-rebase-merges.

 Needs review.
 source: <20260616-toon-git-replay-drop-merges-v3-0-153e9eb99ce1@iotcl.com>


* ps/setup-drop-global-state (2026-06-10) 8 commits
  (merged to 'next' on 2026-06-15 at d9a8b88d47)
 + treewide: drop USE_THE_REPOSITORY_VARIABLE
 + environment: stop using `the_repository` in `is_bare_repository()`
 + environment: split up concerns of `is_bare_repository_cfg`
 + builtin/init: stop modifying `is_bare_repository_cfg`
 + setup: remove global `git_work_tree_cfg` variable
 + builtin/init: simplify logic to configure worktree
 + builtin/init: stop modifying global `git_work_tree_cfg` variable
 + Merge branch 'ps/setup-centralize-odb-creation' into ps/setup-drop-global-state

 Continuation of "setup.c" refactoring to drop remaining global state
 (`git_work_tree_cfg`, `is_bare_repository_cfg`). The most notable
 outcome is that `is_bare_repository()` has been updated to no longer
 implicitly rely on `the_repository`.

 Will cook in 'next'.
 cf. <airVOrTboNDDGBak@denethor>
 cf. <87ldckyygk.fsf@emacs.iotcl.com>
 source: <20260611-b4-pks-setup-drop-global-state-v2-0-a6f7269c841d@pks.im>


* ps/refs-avoid-chdir-notify-reparent (2026-06-19) 11 commits
 - refs: drop local buffer in `refs_compute_filesystem_location()`
 - refs: fix recursing `get_main_ref_store()` with "onbranch" config
 - refs/reftable-backend: manually parse "core.sharedRepository"
 - refs: move parsing of "core.logAllRefUpdates" back into ref stores
 - repository: free main reference database
 - chdir-notify: drop unused `chdir_notify_reparent()`
 - refs: unregister reference stores from "chdir_notify"
 - setup: don't apply "GIT_REFERENCE_BACKEND" without a repository
 - setup: stop applying repository format twice
 - setup: inline `check_and_apply_repository_format()`
 - Merge branch 'ps/setup-centralize-odb-creation' into ps/refs-avoid-chdir-notify-reparent

 The reference backends have been converted to always use absolute
 paths internally. This allows dropping the calls to
 `chdir_notify_reparent()` and fixes a memory leak in how the
 reference database is constructed with an "onbranch" condition.

 Needs review.
 source: <20260619-b4-pks-refs-avoid-chdir-notify-reparent-v4-0-a6472be7acc4@pks.im>


* ps/odb-source-packed (2026-06-16) 18 commits
  (merged to 'next' on 2026-06-19 at dcf0c084e4)
 + odb/source-packed: drop pointer to "files" parent source
 + midx: refactor interfaces to work on "packed" source
 + odb/source-packed: stub out remaining functions
 + odb/source-packed: wire up `freshen_object()` callback
 + odb/source-packed: wire up `find_abbrev_len()` callback
 + odb/source-packed: wire up `count_objects()` callback
 + odb/source-packed: wire up `for_each_object()` callback
 + odb/source-packed: wire up `read_object_stream()` callback
 + odb/source-packed: wire up `read_object_info()` callback
 + packfile: use higher-level interface to implement `has_object_pack()`
 + odb/source-packed: wire up `reprepare()` callback
 + odb/source-packed: wire up `close()` callback
 + odb/source-packed: start converting to a proper `struct odb_source`
 + odb/source-packed: store pointer to "files" instead of generic source
 + packfile: move packed source into "odb/" subsystem
 + packfile: split out packfile list logic
 + packfile: rename `struct packfile_store` to `odb_source_packed`
 + Merge branch 'ps/odb-source-loose' into ps/odb-source-packed

 The packed object source has been refactored into a proper struct
 odb_source.

 Will cook in 'next'.
 cf. <ajK2QKdW-TdflfR0@denethor>
 source: <20260617-pks-odb-source-packed-v3-0-b5c7583cd795@pks.im>


* td/ref-filter-restore-prefix-iteration (2026-06-12) 1 commit
  (merged to 'next' on 2026-06-19 at a19dbb4193)
 + ref-filter: restore prefix-scoped iteration

 Commands that list branches and tags (like git branch and git tag)
 have been optimized to pass the namespace prefix when initializing
 their ref iterator, avoiding a loose-ref scaling regression in
 repositories with many unrelated loose references.

 Will cook in 'next'.
 cf. <xmqqik7fsv2m.fsf@gitster.g>
 source: <20260612-fix-git-branch-regression-v4-1-f150038c02f4@gmail.com>


* ty/move-protect-hfs-ntfs (2026-06-10) 1 commit
  (merged to 'next' on 2026-06-15 at c2a30ca954)
 + environment: move 'protect_hfs' and 'protect_ntfs' into 'repo_config_values'

 The global configuration variables protect_hfs and protect_ntfs have
 been migrated into struct repo_config_values to tie them to
 per-repository configuration state.

 Will cook in 'next'.
 cf. <CAP8UFD35Tiy1_fqpjq8P-z=ZhzR3MTiThqfCs977652umRoSEQ@mail.gmail.com>
 cf. <xmqqse6uwdnz.fsf@gitster.g>
 source: <20260610124353.149874-2-cat@malon.dev>


* ps/cat-file-remote-object-info (2026-06-19) 12 commits
 - cat-file: make remote-object-info allow-list dynamic
 - cat-file: validate remote atoms with allow_list
 - cat-file: add remote-object-info to batch-command
 - transport: add client support for object-info
 - serve: advertise object-info feature
 - fetch-pack: move fetch initialization
 - connect: refactor packet writing
 - fetch-pack: move function to connect.c
 - t1006: split test utility functions into new "lib-cat-file.sh"
 - cat-file: declare loop counter inside for()
 - git-compat-util: add strtoul_ul() with error handling
 - transport-helper: fix memory leak of helper on disconnect

 The `remote-object-info` command has been added to `git cat-file
 --batch-command`, allowing clients to request object metadata
 (currently size) from a remote server via protocol v2 without
 downloading the entire object.

 The client dynamically filters format placeholders based on
 server-advertised capabilities and safely returns empty strings for
 inapplicable or unsupported fields.

 Needs review.
 source: <20260619-ps-eric-work-rebase-v13-0-3d4c7315d2f8@gmail.com>


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

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

 Expecting a reroll.
 cf. <5144a29d-a53f-4446-beff-e1f549345bf9@nvidia.com>
 source: <20260602161150.1527493-1-aplattner@nvidia.com>


* ps/doc-recommend-b4 (2026-06-15) 3 commits
  (merged to 'next' on 2026-06-17 at dd9a463369)
 + b4: introduce configuration for the Git project
 + MyFirstContribution: recommend the use of b4
 + MyFirstContribution: recommend shallow threading of cover letters

 Project-specific configuration for b4 has been introduced, and the
 documentation has been updated to recommend using it as a
 streamlined method for submitting patches.

 Will cook in 'next'.
 cf. <87eci7yomp.fsf@emacs.iotcl.com>
 source: <20260615-pks-b4-v4-0-22cfca8f19c5@pks.im>


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

 "git rebase --update-refs" has been taught to resolve local branch
 symrefs to their referents before queuing updates. This correctly
 skips aliases of the current branch and avoids duplicate updates for
 underlying real branches, fixing failures when branch aliases (like a
 default branch rename) are present.

 Waiting for response(s) to review comment(s).
 cf. <f982c386-e329-4ab0-b695-e540bcb9de3d@gmail.com>
 source: <pull.2126.v2.git.1780482436865.gitgitgadget@gmail.com>


* mm/diff-process-hunks (2026-06-14) 6 commits
 - blame: consult diff process for no-hunk detection
 - diff: bypass diff process with --no-ext-diff and in format-patch
 - diff: add long-running diff process via diff.<driver>.process
 - sub-process: separate process lifecycle from hashmap management
 - userdiff: add diff.<driver>.process config
 - xdiff: support external hunks via xpparam_t

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

 Expecting a reroll.
 cf. <CAC2Qwm+P=fZOtpfMPeMiSXf3Afk6OLYpTP8Br78_PRA8WNL1Wg@mail.gmail.com>
 cf. <CAC2Qwm+P=fZOtpfMPeMiSXf3Afk6OLYpTP8Br78_PRA8WNL1Wg@mail.gmail.com>
 source: <pull.2120.v4.git.1781463564.gitgitgadget@gmail.com>


* tb/pack-path-walk-bitmap-delta-islands (2026-06-15) 6 commits
 - SQUASH???
 - pack-objects: support `--delta-islands` with `--path-walk`
 - pack-objects: extract `record_tree_depth()` helper
 - pack-objects: support reachability bitmaps with `--path-walk`
 - t/perf: drop p5311's lookup-table permutation
 - Merge branch 'ds/path-walk-filters' into tb/pack-path-walk-bitmap-delta-islands

 The pack-objects command now supports using reachability bitmaps and
 delta-islands concurrently with the `--path-walk` option, allowing
 faster packaging by falling back to path-walk when bitmaps cannot
 fully satisfy the request.

 Waiting for response(s) to review comment(s).
 cf. <849c659f-efa8-430a-bfac-0c26a3ed1aaa@gmail.com>
 cf. <xmqqjyrzbjyf.fsf@gitster.g>
 cf. <7afdaf77-07f5-4d48-955d-e153d148f647@gmail.com>
 source: <cover.1780438896.git.me@ttaylorr.com>


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

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

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


* kk/prio-queue-cascade-sift (2026-06-01) 1 commit
 - prio-queue: use cascade-down for faster extract-min

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

 Expecting a reroll.
 cf. <CAL71e4Ob-B5MJ5DPY+_tzpj6nyrbQ5WutxED2T93SWJV6kJGPA@mail.gmail.com>
 cf. <CAL71e4MYNiScZjTwkApjDAjRh2LM0_SP59h5HCTywV-Pua03tw@mail.gmail.com>
 source: <pull.2132.v2.git.1780301856444.gitgitgadget@gmail.com>


* jk/repo-info-path-keys (2026-06-15) 4 commits
 - repo: add path.gitdir with absolute and relative suffix formatting
 - repo: add path.commondir with absolute and relative suffix formatting
 - rev-parse: use append_formatted_path() for path formatting
 - path: introduce append_formatted_path() for shared path formatting

 The "git repo info" command has been taught new keys to output both
 absolute and relative paths for "gitdir" and "commondir", supported by
 a new path-formatting helper extracted from "git rev-parse".

 Expecting a reroll.
 cf. <CA+rGoLfhhRNrSReeJ1grhy+2K3BSrikTCNgGpCaGqc4fFp3Lfg@mail.gmail.com>
 source: <20260616044953.184806-1-jayatheerthkulkarni2005@gmail.com>


* ps/history-drop (2026-06-15) 10 commits
 - builtin/history: implement "drop" subcommand
 - builtin/history: split handling of ref updates into two phases
 - reset: stop assuming that the caller passes in a clean index
 - reset: allow the caller to specify the current HEAD object
 - reset: introduce ability to skip updating HEAD
 - reset: introduce dry-run mode
 - reset: modernize flags passed to `reset_working_tree()`
 - reset: rename `reset_head()`
 - reset: drop `USE_THE_REPOSITORY_VARIABLE`
 - read-cache: split out function to drop unmerged entries to stage 0

 The experimental "git history" command has been taught a new "drop"
 subcommand to remove a commit and replay its descendants onto its
 parent.

 Needs review.
 source: <20260615-b4-pks-history-drop-v6-0-2e329e536d78@pks.im>


* jk/setup-gitfile-diag-fix (2026-06-16) 1 commit
  (merged to 'next' on 2026-06-18 at b63b3d1f25)
 + read_gitfile(): simplify NOT_A_REPO error message

 A regression in the error diagnosis code for invalid .git files has
 been fixed, avoiding a potential NULL-pointer crash when reporting
 that a .git file does not point to a valid repository.

 Will cook in 'next'.
 cf. <xmqqjyry4hax.fsf@gitster.g>
 source: <20260616123516.GA2301231@coredump.intra.peff.net>


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

 Documentation updates.

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


* za/completion-hide-dotfiles (2026-05-26) 1 commit
 - completion: hide dotfiles for selected path completion

 The path completion for commands like `git rm` and `git mv`, is being
 updated to hide dotfiles by default, unless the user explicitly starts
 the path with a dot, matching standard shell-completion behavior.

 Waiting for response(s) to review comment(s).
 cf. <xmqqik7qusuc.fsf@gitster.g>
 source: <pull.2311.v2.git.git.1779808987825.gitgitgadget@gmail.com>


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

 The -m/-F/-c/-C options to supply commit log message from outside the
 editor are now supported for all "git commit --fixup" variations.

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


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

 Doc update for "git replay" to actually refer to its configuration
 variables.

 Needs review.
 source: <V3_CV_doc_replay_config.780@msgid.xyz>


* hn/status-pull-advice-qualified (2026-05-21) 1 commit
  (merged to 'next' on 2026-06-15 at 898a4df940)
 + remote: qualify "git pull" advice for non-upstream compareBranches

 Advice shown by "git status" when the local branch is behind or has
 diverged from its push branch has been updated to suggest "git pull
 <remote> <branch>".

 Will cook in 'next'.
 cf. <xmqq7bo6xuok.fsf@gitster.g>
 source: <pull.2301.v4.git.git.1779372367317.gitgitgadget@gmail.com>


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

 "git branch" command learned "--delete-merged" option to remove
 local branches that have already been merged to the remote-tracking
 branches they track.

 Waiting for response(s) to review comment(s).
 cf. <78b6dfdd-df61-4c44-96eb-b527cb26243c@gmail.com>
 cf. <f68e2a11-02a5-47b9-a01a-458eba821c37@gmail.com>
 cf. <37f2a483-c8bf-4c24-84de-c6233cc20b25@gmail.com>
 cf. <xmqq33yimsdp.fsf@gitster.g>
 source: <pull.2285.v16.git.git.1781810729.gitgitgadget@gmail.com>


* cc/promisor-auto-config-url-more (2026-05-27) 8 commits
  (merged to 'next' on 2026-06-15 at d1c99e75cc)
 + doc: promisor: improve acceptFromServer entry
 + promisor-remote: auto-configure unknown remotes
 + promisor-remote: trust known remotes matching acceptFromServerUrl
 + promisor-remote: introduce promisor.acceptFromServerUrl
 + promisor-remote: add 'local_name' to 'struct promisor_info'
 + urlmatch: add url_normalize_pattern() helper
 + urlmatch: change 'allow_globs' arg to bool
 + t5710: simplify 'mkdir X' followed by 'git -C X init'

 The handling of promisor-remote protocol capability has been
 loosened to allow the other side to add to the list of promisor
 remotes via the promisor.acceptFromServerURL configuration
 variable.

 Will cook in 'next'.
 cf. <877bo7294j.fsf@emacs.iotcl.com>
 cf. <xmqqh5naxwfc.fsf@gitster.g>
 source: <20260527140820.1438165-1-christian.couder@gmail.com>


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

 "git checkout --track=..." learned to optionally fetch the branch
 from the remote the new branch will work with.

 Needs review.
 source: <pull.2281.v14.git.git.1781786652.gitgitgadget@gmail.com>


* en/ort-harden-against-corrupt-trees (2026-06-13) 5 commits
  (merged to 'next' on 2026-06-18 at e51bee59ca)
 + cache-tree: fix verify_cache() to catch non-adjacent D/F conflicts
 + merge-ort: abort merge when trees have duplicate entries
 + merge-ort: free diff pairs queue in clear_or_reinit_internal_opts()
 + merge-ort: drop unnecessary show_all_errors from collect_merge_info()
 + merge-ort: propagate callback errors from traverse_trees_wrapper()

 "ort" merge backend handles merging corrupt trees better by
 aborting when it should.

 Will cook in 'next'.
 cf. <xmqq5x3ldu4h.fsf@gitster.g>
 source: <pull.2096.v2.git.1781419047.gitgitgadget@gmail.com>


* pw/status-rebase-todo (2026-05-01) 2 commits
 - status: improve rebase todo list parsing
 - sequencer: factor out parsing of todo commands

 The display of the rebase todo list in "git status" has been
 improved to correctly abbreviate object IDs for more commands and
 avoid misinterpreting refs as object IDs.

 Waiting for response(s) to review comment(s).
 cf. <xmqqqzmdoya9.fsf@gitster.g>
 source: <cover.1777648598.git.phillip.wood@dunelm.org.uk>


* cl/conditional-config-on-worktree-path (2026-05-24) 2 commits
 - config: add "worktree" and "worktree/i" includeIf conditions
 - config: refactor include_by_gitdir() into include_by_path()

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

 Waiting for response(s) to review comment(s).
 cf. <xmqq8q97et9b.fsf@gitster.g>
 source: <20260525-includeif-worktree-v5-0-1efe525d025a@black-desk.cn>


* ps/shift-root-in-graph (2026-06-13) 2 commits
 - graph: indent visual root in graph
 - lib-log-graph: move check_graph function

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

 Waiting for response(s) to review comment(s).
 cf. <20260617202744.GA3465855@coredump.intra.peff.net>
 source: <20260613-ps-pre-commit-indent-v5-0-8d308efea63d@gmail.com>

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

* kk/fetch-store-ref-optimization (2026-05-24) 1 commit
 . fetch: pass transport to post-fetch connectivity check

 When fetching from a transport that provides a self-contained pack,
 pass the transport pointer to the post-fetch `check_connected()` call
 to optimize connectivity check.

 Retracted.
 cf. <CAL71e4MrVqC1=AR6x0_8S=8kVqPdDkhgCZRb4etFsxTzd6s_8Q@mail.gmail.com>
 source: <pull.2123.git.1779625693328.gitgitgadget@gmail.com>


* lp/repack-propagate-promisor-debugging-info (2026-04-18) 6 commits
 . repack-promisor: add missing headers
 . t7703: test for promisor file content after geometric repack
 . t7700: test for promisor file content after repack
 . repack-promisor: preserve content of promisor files after repack
 . repack-promisor add helper to fill promisor file after repack
 . pack-write: add explanation to promisor file content

 When fetching objects into a lazily cloned repository, .promisor
 files are created with information meant to help debugging.  "git
 repack" has been taught to carry this information forward to
 packfiles that are newly created.

 Retracted.
 cf. <agx_GPfBKpkSc3Gx@lorenzo-VM>
 source: <cover.1776384902.git.lorenzo.pegorari2002@gmail.com>


* cs/subtree-split-recursion (2026-03-05) 3 commits
 . contrib/subtree: reduce recursion during split
 . contrib/subtree: functionalize split traversal
 . contrib/subtree: reduce function side-effects

 When processing large history graphs on Debian or Ubuntu, "git
 subtree" can die with a "recursion depth reached" error.

 Retracted.
 cf. <0915b5cc-5cbb-4cce-a832-147f85d4ff1f@howdoi.land>
 source: <20260305-cs-subtree-split-recursion-v2-0-7266be870ba9@howdoi.land>


* jc/neuter-sideband-post-3.0 (2026-03-05) 2 commits
 . sideband: delay sanitizing by default to Git v3.0
 . Merge branch 'jc/neuter-sideband-fixup' into jc/neuter-sideband-post-3.0

 The final step, split from earlier attempt by Dscho, to loosen the
 sideband restriction for now and tighten later at Git v3.0 boundary.

 Retracted.
 cf. <xmqqzf11oz7a.fsf@gitster.g>
 source: <20260305233452.3727126-8-gitster@pobox.com>


* kk/remove-get-reachable-subset (2026-06-11) 1 commit
 . commit-reach: remove get_reachable_subset()

 API clean-up.

 Retracted.
 cf. <CAL71e4P3Oq08xVPZ+dxQ8L5PKekPJN0RsL4pTicom1og7-1D=A@mail.gmail.com>
 source: <pull.2144.v2.git.1781178567862.gitgitgadget@gmail.com>


* ds/config-no-includes (2026-06-08) 3 commits
 . git: add --no-includes top-level option
 . config: add GIT_CONFIG_INCLUDES
 . git-config.adoc: fix paragraph break

 Two new mechanisms, the GIT_CONFIG_INCLUDES environment variable and
 the top-level --no-includes command-line option, have been introduced
 to ignore configuration include directives.

 Retracted.
 cf. <539713c4-b291-42e6-8541-a16a454518f5@gmail.com>
 source: <pull.2139.git.1780927027.gitgitgadget@gmail.com>

^ permalink raw reply

* [GSoC Patch v6 0/4] teach git repo info to handle path keys
From: K Jayatheerth @ 2026-06-20  3:16 UTC (permalink / raw)
  To: git
  Cc: jltobler, lucasseikioshiro, gitster, phillip.wood, sandals,
	kumarayushjha123, a3205153416, kristofferhaugsbakk, K Jayatheerth
In-Reply-To: <20260601151950.30686-1-jayatheerthkulkarni2005@gmail.com>

Hi!

This series teaches `git repo info` to handle `path.*`
keys, allowing scripts to reliably discover core
repository paths without resorting to `git rev-parse`.

The patches are structured as follows:

1. path: Extract the localized path-formatting logic
   out of `rev-parse` and expose it globally via
   `path.h` using clear append semantics.

2. rev-parse: Delegate the command's path-printing
   helper to the newly shared path engine, while
   leaving its existing option-parsing untouched.

3. repo: Introduce `path.commondir.absolute` and
   `path.commondir.relative` alongside a robust,
   isolated test helper.

4. repo: Introduce `path.gitdir.absolute` and
   `path.gitdir.relative` using the same standardized
   formatting rules.

Changes since v5:

* Dropped `PATH_FORMAT_DEFAULT` from the shared
  `path_format` enum in path.h. It only existed to let
  rev-parse track "no format was requested", which is a
  rev-parse-specific concern that other callers of
  `append_formatted_path()` shouldn't need to reason
  about (Phillip).

* Reverted `print_path()` in builtin/rev-parse.c to keep
  its original `format_type` and `default_type` local
  enums completely untouched (Phillip).

* As a result, patch 2 is now much smaller: it only
  touches the body of `print_path()`.

Tagging Justin Tobler, Lucas Seiki Oshiro, Junio,
Phillip Wood, brian m. carlson, and Ayush Jha.

Thanks again for the careful review!

K Jayatheerth (4):
  path: introduce append_formatted_path() for shared path formatting
  rev-parse: use append_formatted_path() for path formatting
  repo: add path.commondir with absolute and relative suffix formatting
  repo: add path.gitdir with absolute and relative suffix formatting

 Documentation/git-repo.adoc | 15 ++++++++
 builtin/repo.c              | 50 +++++++++++++++++++++++++
 builtin/rev-parse.c         | 73 +++++++++++++++----------------------
 path.c                      | 69 +++++++++++++++++++++++++++++++++++
 path.h                      | 30 +++++++++++++++
 t/t1900-repo-info.sh        | 58 +++++++++++++++++++++++++++++
 6 files changed, 251 insertions(+), 44 deletions(-)

Range-diff against v5:
1:  31bc2c96e9 ! 1:  bb8bb40030 path: introduce append_formatted_path() for shared path formatting
    @@ path.c: char *xdg_cache_home(const char *filename)
     +			   const char *prefix, enum path_format format)
     +{
     +	switch (format) {
    -+	case PATH_FORMAT_DEFAULT:
     +	case PATH_FORMAT_UNMODIFIED:
     +		strbuf_addstr(dest, path);
     +		break;
    @@ path.h: enum scld_error safe_create_leading_directories_no_share(char *path);
     + * The formatting strategy to apply when writing a path into a buffer.
     + */
     +enum path_format {
    -+	/*
    -+	 * Represents the default formatting behavior. Treated as
    -+	 * PATH_FORMAT_UNMODIFIED by append_formatted_path().
    -+	 */
    -+	PATH_FORMAT_DEFAULT,
    -+
     +	/* Output the path exactly as-is without any modifications. */
     +	PATH_FORMAT_UNMODIFIED,
     +
2:  12af24ffc3 ! 2:  0ab0e4bde3 rev-parse: use append_formatted_path() for path formatting
    @@ Metadata
      ## Commit message ##
         rev-parse: use append_formatted_path() for path formatting
     
    -    Now that path formatting logic lives in a shared helper, keeping a
    -    duplicate implementation in rev-parse is unnecessary and risks the
    -    two diverging over time.
    +    Now that the core path-formatting algorithm lives in
    +    append_formatted_path(), print_path() doesn't need to duplicate it.
     
    -    Replace the local format_type and default_type enums and the
    -    hand-rolled formatting logic with a call to append_formatted_path().
    -    Introduce PATH_FORMAT_DEFAULT as the initial value of arg_path_format
    -    so that per-path fallback behavior is resolved in print_path() rather
    -    than leaked into the shared helper.
    +    Replace the body of print_path() with a small mapping from rev-parse's
    +    existing format_type/default_type pair to the shared path_format enum,
    +    then delegate to append_formatted_path(). The two local enums, and
    +    every call site that uses them throughout cmd_rev_parse(), are left
    +    untouched.
     
         Mentored-by: Justin Tobler <jltobler@gmail.com>
         Mentored-by: Lucas Seiki Oshiro <lucasseikioshiro@gmail.com>
         Signed-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>
     
      ## builtin/rev-parse.c ##
    -@@ builtin/rev-parse.c: static void handle_ref_opt(const char *pattern, const char *prefix)
    - 	clear_ref_exclusions(&ref_excludes);
    - }
    +@@ builtin/rev-parse.c: enum default_type {
    + 	DEFAULT_UNMODIFIED,
    + };
      
    --enum format_type {
    --	/* We would like a relative path. */
    --	FORMAT_RELATIVE,
    --	/* We would like a canonical absolute path. */
    --	FORMAT_CANONICAL,
    --	/* We would like the default behavior. */
    --	FORMAT_DEFAULT,
    --};
    --
    --enum default_type {
    --	/* Our default is a relative path. */
    --	DEFAULT_RELATIVE,
    --	/* Our default is a relative path if there's a shared root. */
    --	DEFAULT_RELATIVE_IF_SHARED,
    --	/* Our default is a canonical absolute path. */
    --	DEFAULT_CANONICAL,
    --	/* Our default is not to modify the item. */
    --	DEFAULT_UNMODIFIED,
    --};
    --
     -static void print_path(const char *path, const char *prefix, enum format_type format, enum default_type def)
     +static void print_path(const char *path, const char *prefix,
    -+		       enum path_format arg_path_format, enum path_format def_format)
    ++		       enum format_type format, enum default_type def)
      {
     -	char *cwd = NULL;
     -	/*
    @@ builtin/rev-parse.c: static void handle_ref_opt(const char *pattern, const char
     -		if (!is_absolute_path(prefix)) {
     -			strbuf_realpath_forgiving(&prefixbuf, prefix, 1);
     -			prefix = prefixbuf.buf;
    --		}
    ++	struct strbuf sb = STRBUF_INIT;
    ++	enum path_format fmt;
    ++
    ++	if (format == FORMAT_RELATIVE) {
    ++		fmt = PATH_FORMAT_RELATIVE;
    ++	} else if (format == FORMAT_CANONICAL) {
    ++		fmt = PATH_FORMAT_CANONICAL;
    ++	} else /* FORMAT_DEFAULT */ {
    ++		switch (def) {
    ++		case DEFAULT_RELATIVE:
    ++			fmt = PATH_FORMAT_RELATIVE;
    ++			break;
    ++		case DEFAULT_RELATIVE_IF_SHARED:
    ++			fmt = PATH_FORMAT_RELATIVE_IF_SHARED;
    ++			break;
    ++		case DEFAULT_CANONICAL:
    ++			fmt = PATH_FORMAT_CANONICAL;
    ++			break;
    ++		case DEFAULT_UNMODIFIED:
    ++		default:
    ++			fmt = PATH_FORMAT_UNMODIFIED;
    ++			break;
    + 		}
     -		puts(relative_path(path, prefix, &buf));
     -		strbuf_release(&buf);
     -		strbuf_release(&realbuf);
    @@ builtin/rev-parse.c: static void handle_ref_opt(const char *pattern, const char
     -		strbuf_realpath_forgiving(&buf, path, 1);
     -		puts(buf.buf);
     -		strbuf_release(&buf);
    --	}
    + 	}
     -	free(cwd);
    -+	struct strbuf sb = STRBUF_INIT;
    -+	/* If the user didn't explicitly specify a format, fallback to the path-specific default. */
    -+	enum path_format fmt = (arg_path_format != PATH_FORMAT_DEFAULT) ? arg_path_format : def_format;
     +
     +	append_formatted_path(&sb, path, prefix, fmt);
     +	puts(sb.buf);
    @@ builtin/rev-parse.c: static void handle_ref_opt(const char *pattern, const char
      }
      
      int cmd_rev_parse(int argc,
    -@@ builtin/rev-parse.c: int cmd_rev_parse(int argc,
    - 	const char *name = NULL;
    - 	struct strbuf buf = STRBUF_INIT;
    - 	int seen_end_of_options = 0;
    --	enum format_type format = FORMAT_DEFAULT;
    -+	enum path_format arg_path_format = PATH_FORMAT_DEFAULT;
    - 
    - 	show_usage_if_asked(argc, argv, builtin_rev_parse_usage);
    - 
    -@@ builtin/rev-parse.c: int cmd_rev_parse(int argc,
    - 					die(_("--git-path requires an argument"));
    - 				print_path(repo_git_path_replace(the_repository, &buf,
    - 								 "%s", argv[i + 1]), prefix,
    --						format,
    --						DEFAULT_RELATIVE_IF_SHARED);
    -+						arg_path_format,
    -+						PATH_FORMAT_RELATIVE_IF_SHARED);
    - 				i++;
    - 				continue;
    - 			}
    -@@ builtin/rev-parse.c: int cmd_rev_parse(int argc,
    - 				if (!arg)
    - 					die(_("--path-format requires an argument"));
    - 				if (!strcmp(arg, "absolute")) {
    --					format = FORMAT_CANONICAL;
    -+					arg_path_format = PATH_FORMAT_CANONICAL;
    - 				} else if (!strcmp(arg, "relative")) {
    --					format = FORMAT_RELATIVE;
    -+					arg_path_format = PATH_FORMAT_RELATIVE;
    - 				} else {
    - 					die(_("unknown argument to --path-format: %s"), arg);
    - 				}
    -@@ builtin/rev-parse.c: int cmd_rev_parse(int argc,
    - 			if (!strcmp(arg, "--show-toplevel")) {
    - 				const char *work_tree = repo_get_work_tree(the_repository);
    - 				if (work_tree)
    --					print_path(work_tree, prefix, format, DEFAULT_UNMODIFIED);
    -+					print_path(work_tree, prefix, arg_path_format, PATH_FORMAT_UNMODIFIED);
    - 				else
    - 					die(_("this operation must be run in a work tree"));
    - 				continue;
    -@@ builtin/rev-parse.c: int cmd_rev_parse(int argc,
    - 			if (!strcmp(arg, "--show-superproject-working-tree")) {
    - 				struct strbuf superproject = STRBUF_INIT;
    - 				if (get_superproject_working_tree(&superproject))
    --					print_path(superproject.buf, prefix, format, DEFAULT_UNMODIFIED);
    -+					print_path(superproject.buf, prefix, arg_path_format, PATH_FORMAT_UNMODIFIED);
    - 				strbuf_release(&superproject);
    - 				continue;
    - 			}
    -@@ builtin/rev-parse.c: int cmd_rev_parse(int argc,
    - 				const char *gitdir = getenv(GIT_DIR_ENVIRONMENT);
    - 				char *cwd;
    - 				int len;
    --				enum format_type wanted = format;
    -+				enum path_format wanted = arg_path_format;
    - 				if (arg[2] == 'g') {	/* --git-dir */
    - 					if (gitdir) {
    --						print_path(gitdir, prefix, format, DEFAULT_UNMODIFIED);
    -+						print_path(gitdir, prefix, arg_path_format, PATH_FORMAT_UNMODIFIED);
    - 						continue;
    - 					}
    - 					if (!prefix) {
    --						print_path(".git", prefix, format, DEFAULT_UNMODIFIED);
    -+						print_path(".git", prefix, arg_path_format, PATH_FORMAT_UNMODIFIED);
    - 						continue;
    - 					}
    - 				} else {		/* --absolute-git-dir */
    --					wanted = FORMAT_CANONICAL;
    -+					wanted = PATH_FORMAT_CANONICAL;
    - 					if (!gitdir && !prefix)
    - 						gitdir = ".git";
    - 					if (gitdir) {
    -@@ builtin/rev-parse.c: int cmd_rev_parse(int argc,
    - 				strbuf_reset(&buf);
    - 				strbuf_addf(&buf, "%s%s.git", cwd, len && cwd[len-1] != '/' ? "/" : "");
    - 				free(cwd);
    --				print_path(buf.buf, prefix, wanted, DEFAULT_CANONICAL);
    -+				print_path(buf.buf, prefix, wanted, PATH_FORMAT_CANONICAL);
    - 				continue;
    - 			}
    - 			if (!strcmp(arg, "--git-common-dir")) {
    --				print_path(repo_get_common_dir(the_repository), prefix, format, DEFAULT_RELATIVE_IF_SHARED);
    -+				print_path(repo_get_common_dir(the_repository), prefix, arg_path_format, PATH_FORMAT_RELATIVE_IF_SHARED);
    - 				continue;
    - 			}
    - 			if (!strcmp(arg, "--is-inside-git-dir")) {
    -@@ builtin/rev-parse.c: int cmd_rev_parse(int argc,
    - 				if (the_repository->index->split_index) {
    - 					const struct object_id *oid = &the_repository->index->split_index->base_oid;
    - 					const char *path = repo_git_path_replace(the_repository, &buf, "sharedindex.%s", oid_to_hex(oid));
    --					print_path(path, prefix, format, DEFAULT_RELATIVE);
    -+					print_path(path, prefix, arg_path_format, PATH_FORMAT_RELATIVE);
    - 				}
    - 				continue;
    - 			}
3:  7aecf1e806 = 3:  a50c75a55b repo: add path.commondir with absolute and relative suffix formatting
4:  f30010b76c = 4:  1dd22e5cd4 repo: add path.gitdir with absolute and relative suffix formatting
-- 
2.54.0


^ permalink raw reply

* [GSoC Patch v6 1/4] path: introduce append_formatted_path() for shared path formatting
From: K Jayatheerth @ 2026-06-20  3:16 UTC (permalink / raw)
  To: git
  Cc: jltobler, lucasseikioshiro, gitster, phillip.wood, sandals,
	kumarayushjha123, a3205153416, kristofferhaugsbakk, K Jayatheerth
In-Reply-To: <20260620031644.353772-1-jayatheerthkulkarni2005@gmail.com>

The path-formatting logic in builtin/rev-parse.c is tightly coupled
to that command and writes directly to stdout, making it impossible
for other builtins to reuse.

Extract the core algorithm into append_formatted_path() in path.c
and expose a path_format enum in path.h so that any builtin can
format paths consistently without duplicating logic.

Mentored-by: Justin Tobler <jltobler@gmail.com>
Mentored-by: Lucas Seiki Oshiro <lucasseikioshiro@gmail.com>
Signed-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>
---
 path.c | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 path.h | 30 +++++++++++++++++++++++++
 2 files changed, 99 insertions(+)

diff --git a/path.c b/path.c
index d7e17bf174..6d8e892ada 100644
--- a/path.c
+++ b/path.c
@@ -1579,6 +1579,75 @@ char *xdg_cache_home(const char *filename)
 	return NULL;
 }
 
+void append_formatted_path(struct strbuf *dest, const char *path,
+			   const char *prefix, enum path_format format)
+{
+	switch (format) {
+	case PATH_FORMAT_UNMODIFIED:
+		strbuf_addstr(dest, path);
+		break;
+
+	case PATH_FORMAT_RELATIVE: {
+		struct strbuf relative_buf = STRBUF_INIT;
+		struct strbuf real_path = STRBUF_INIT;
+		struct strbuf real_prefix = STRBUF_INIT;
+		char *cwd = NULL;
+
+		/*
+		 * We don't ever produce a relative path if prefix is NULL,
+		 * so set the prefix to the current directory so that we can
+		 * produce a relative path whenever possible.
+		 */
+		if (!prefix)
+			prefix = cwd = xgetcwd();
+
+		if (!is_absolute_path(path)) {
+			strbuf_realpath_forgiving(&real_path, path, 1);
+			path = real_path.buf;
+		}
+		if (!is_absolute_path(prefix)) {
+			strbuf_realpath_forgiving(&real_prefix, prefix, 1);
+			prefix = real_prefix.buf;
+		}
+
+		strbuf_addstr(dest, relative_path(path, prefix, &relative_buf));
+
+		strbuf_release(&relative_buf);
+		strbuf_release(&real_path);
+		strbuf_release(&real_prefix);
+		free(cwd);
+		break;
+	}
+
+	case PATH_FORMAT_RELATIVE_IF_SHARED: {
+		struct strbuf relative_buf = STRBUF_INIT;
+
+		/*
+		 * If we're using RELATIVE_IF_SHARED mode, then we want an
+		 * absolute path unless the two share a common prefix, so don't
+		 * default the prefix to the current working directory. Doing so
+		 * would cause a relative path to always be produced if possible.
+		 */
+		strbuf_addstr(dest, relative_path(path, prefix, &relative_buf));
+		strbuf_release(&relative_buf);
+		break;
+	}
+
+	case PATH_FORMAT_CANONICAL: {
+		struct strbuf canonical_buf = STRBUF_INIT;
+
+		strbuf_realpath_forgiving(&canonical_buf, path, 1);
+		strbuf_addbuf(dest, &canonical_buf);
+
+		strbuf_release(&canonical_buf);
+		break;
+	}
+
+	default:
+		BUG("unknown path_format value %d", format);
+	}
+}
+
 REPO_GIT_PATH_FUNC(squash_msg, "SQUASH_MSG")
 REPO_GIT_PATH_FUNC(merge_msg, "MERGE_MSG")
 REPO_GIT_PATH_FUNC(merge_rr, "MERGE_RR")
diff --git a/path.h b/path.h
index 4c2958a903..4d982a2c8e 100644
--- a/path.h
+++ b/path.h
@@ -262,6 +262,36 @@ enum scld_error safe_create_leading_directories_no_share(char *path);
 int safe_create_file_with_leading_directories(struct repository *repo,
 					      const char *path);
 
+/**
+ * The formatting strategy to apply when writing a path into a buffer.
+ */
+enum path_format {
+	/* Output the path exactly as-is without any modifications. */
+	PATH_FORMAT_UNMODIFIED,
+
+	/* Output a path relative to the provided directory prefix. */
+	PATH_FORMAT_RELATIVE,
+
+	/* Output a relative path only if the path shares a root with the prefix. */
+	PATH_FORMAT_RELATIVE_IF_SHARED,
+
+	/* Output a fully resolved, absolute canonical path. */
+	PATH_FORMAT_CANONICAL
+};
+
+/**
+ * Format a path according to the specified formatting strategy and append
+ * the result to the given strbuf.
+ *
+ * `dest`   : The string buffer to append the formatted path to.
+ * `path`   : The path string that needs to be formatted.
+ * `prefix` : The directory prefix to calculate relative offsets against.
+ * Pass NULL to default to the current working directory where applicable.
+ * `format` : The formatting behavior rule to execute.
+ */
+void append_formatted_path(struct strbuf *dest, const char *path,
+			   const char *prefix, enum path_format format);
+
 # ifdef USE_THE_REPOSITORY_VARIABLE
 #  include "strbuf.h"
 #  include "repository.h"
-- 
2.54.0


^ permalink raw reply related

* [GSoC Patch v6 2/4] rev-parse: use append_formatted_path() for path formatting
From: K Jayatheerth @ 2026-06-20  3:16 UTC (permalink / raw)
  To: git
  Cc: jltobler, lucasseikioshiro, gitster, phillip.wood, sandals,
	kumarayushjha123, a3205153416, kristofferhaugsbakk, K Jayatheerth
In-Reply-To: <20260620031644.353772-1-jayatheerthkulkarni2005@gmail.com>

Now that the core path-formatting algorithm lives in
append_formatted_path(), print_path() doesn't need to duplicate it.

Replace the body of print_path() with a small mapping from rev-parse's
existing format_type/default_type pair to the shared path_format enum,
then delegate to append_formatted_path(). The two local enums, and
every call site that uses them throughout cmd_rev_parse(), are left
untouched.

Mentored-by: Justin Tobler <jltobler@gmail.com>
Mentored-by: Lucas Seiki Oshiro <lucasseikioshiro@gmail.com>
Signed-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>
---
 builtin/rev-parse.c | 73 ++++++++++++++++++---------------------------
 1 file changed, 29 insertions(+), 44 deletions(-)

diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c
index bb882678fe..6de01466db 100644
--- a/builtin/rev-parse.c
+++ b/builtin/rev-parse.c
@@ -653,53 +653,38 @@ enum default_type {
 	DEFAULT_UNMODIFIED,
 };
 
-static void print_path(const char *path, const char *prefix, enum format_type format, enum default_type def)
+static void print_path(const char *path, const char *prefix,
+		       enum format_type format, enum default_type def)
 {
-	char *cwd = NULL;
-	/*
-	 * We don't ever produce a relative path if prefix is NULL, so set the
-	 * prefix to the current directory so that we can produce a relative
-	 * path whenever possible.  If we're using RELATIVE_IF_SHARED mode, then
-	 * we want an absolute path unless the two share a common prefix, so don't
-	 * set it in that case, since doing so causes a relative path to always
-	 * be produced if possible.
-	 */
-	if (!prefix && (format != FORMAT_DEFAULT || def != DEFAULT_RELATIVE_IF_SHARED))
-		prefix = cwd = xgetcwd();
-	if (format == FORMAT_DEFAULT && def == DEFAULT_UNMODIFIED) {
-		puts(path);
-	} else if (format == FORMAT_RELATIVE ||
-		  (format == FORMAT_DEFAULT && def == DEFAULT_RELATIVE)) {
-		/*
-		 * In order for relative_path to work as expected, we need to
-		 * make sure that both paths are absolute paths.  If we don't,
-		 * we can end up with an unexpected absolute path that the user
-		 * didn't want.
-		 */
-		struct strbuf buf = STRBUF_INIT, realbuf = STRBUF_INIT, prefixbuf = STRBUF_INIT;
-		if (!is_absolute_path(path)) {
-			strbuf_realpath_forgiving(&realbuf, path,  1);
-			path = realbuf.buf;
-		}
-		if (!is_absolute_path(prefix)) {
-			strbuf_realpath_forgiving(&prefixbuf, prefix, 1);
-			prefix = prefixbuf.buf;
+	struct strbuf sb = STRBUF_INIT;
+	enum path_format fmt;
+
+	if (format == FORMAT_RELATIVE) {
+		fmt = PATH_FORMAT_RELATIVE;
+	} else if (format == FORMAT_CANONICAL) {
+		fmt = PATH_FORMAT_CANONICAL;
+	} else /* FORMAT_DEFAULT */ {
+		switch (def) {
+		case DEFAULT_RELATIVE:
+			fmt = PATH_FORMAT_RELATIVE;
+			break;
+		case DEFAULT_RELATIVE_IF_SHARED:
+			fmt = PATH_FORMAT_RELATIVE_IF_SHARED;
+			break;
+		case DEFAULT_CANONICAL:
+			fmt = PATH_FORMAT_CANONICAL;
+			break;
+		case DEFAULT_UNMODIFIED:
+		default:
+			fmt = PATH_FORMAT_UNMODIFIED;
+			break;
 		}
-		puts(relative_path(path, prefix, &buf));
-		strbuf_release(&buf);
-		strbuf_release(&realbuf);
-		strbuf_release(&prefixbuf);
-	} else if (format == FORMAT_DEFAULT && def == DEFAULT_RELATIVE_IF_SHARED) {
-		struct strbuf buf = STRBUF_INIT;
-		puts(relative_path(path, prefix, &buf));
-		strbuf_release(&buf);
-	} else {
-		struct strbuf buf = STRBUF_INIT;
-		strbuf_realpath_forgiving(&buf, path, 1);
-		puts(buf.buf);
-		strbuf_release(&buf);
 	}
-	free(cwd);
+
+	append_formatted_path(&sb, path, prefix, fmt);
+	puts(sb.buf);
+
+	strbuf_release(&sb);
 }
 
 int cmd_rev_parse(int argc,
-- 
2.54.0


^ permalink raw reply related

* [GSoC Patch v6 3/4] repo: add path.commondir with absolute and relative suffix formatting
From: K Jayatheerth @ 2026-06-20  3:16 UTC (permalink / raw)
  To: git
  Cc: jltobler, lucasseikioshiro, gitster, phillip.wood, sandals,
	kumarayushjha123, a3205153416, kristofferhaugsbakk, K Jayatheerth
In-Reply-To: <20260620031644.353772-1-jayatheerthkulkarni2005@gmail.com>

Scripts working with worktree setups need a reliable way to discover
the common directory, which diverges from the git directory when
multiple worktrees are in use. There is no way to retrieve this path
from git repo info today.

Introduce path.commondir.absolute and path.commondir.relative keys.
Exposing explicit format variants rather than a single key with a
default avoids ambiguity for scripts that require predictable output.

Mentored-by: Justin Tobler <jltobler@gmail.com>
Mentored-by: Lucas Seiki Oshiro <lucasseikioshiro@gmail.com>
Signed-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>
---
 Documentation/git-repo.adoc |  9 +++++++
 builtin/repo.c              | 26 +++++++++++++++++++
 t/t1900-repo-info.sh        | 52 +++++++++++++++++++++++++++++++++++++
 3 files changed, 87 insertions(+)

diff --git a/Documentation/git-repo.adoc b/Documentation/git-repo.adoc
index 42262c1983..890c34051d 100644
--- a/Documentation/git-repo.adoc
+++ b/Documentation/git-repo.adoc
@@ -104,6 +104,15 @@ values that they return:
 `object.format`::
 	The object format (hash algorithm) used in the repository.
 
+`path.commondir.absolute`::
+	The canonical absolute path to the Git repository's common
+	directory (the shared `.git` directory containing objects,
+	refs, and global configuration).
+
+`path.commondir.relative`::
+	The path to the Git repository's common directory relative to
+	the current working directory.
+
 `references.format`::
 	The reference storage format. The valid values are:
 +
diff --git a/builtin/repo.c b/builtin/repo.c
index 71a5c1c29c..c4cc3bf3fc 100644
--- a/builtin/repo.c
+++ b/builtin/repo.c
@@ -7,12 +7,14 @@
 #include "hex.h"
 #include "odb.h"
 #include "parse-options.h"
+#include "path.h"
 #include "path-walk.h"
 #include "progress.h"
 #include "quote.h"
 #include "ref-filter.h"
 #include "refs.h"
 #include "revision.h"
+#include "setup.h"
 #include "strbuf.h"
 #include "string-list.h"
 #include "shallow.h"
@@ -75,6 +77,28 @@ static int get_object_format(struct repository *repo, struct strbuf *buf)
 	return 0;
 }
 
+static int get_path_commondir_absolute(struct repository *repo, struct strbuf *buf)
+{
+	const char *common_dir = repo_get_common_dir(repo);
+
+	if (!common_dir)
+		return error(_("unable to get common directory"));
+
+	append_formatted_path(buf, common_dir, startup_info->prefix, PATH_FORMAT_CANONICAL);
+	return 0;
+}
+
+static int get_path_commondir_relative(struct repository *repo, struct strbuf *buf)
+{
+	const char *common_dir = repo_get_common_dir(repo);
+
+	if (!common_dir)
+		return error(_("unable to get common directory"));
+
+	append_formatted_path(buf, common_dir, startup_info->prefix, PATH_FORMAT_RELATIVE);
+	return 0;
+}
+
 static int get_references_format(struct repository *repo, struct strbuf *buf)
 {
 	strbuf_addstr(buf,
@@ -87,6 +111,8 @@ static const struct repo_info_field repo_info_field[] = {
 	{ "layout.bare", get_layout_bare },
 	{ "layout.shallow", get_layout_shallow },
 	{ "object.format", get_object_format },
+	{ "path.commondir.absolute", get_path_commondir_absolute },
+	{ "path.commondir.relative", get_path_commondir_relative },
 	{ "references.format", get_references_format },
 };
 
diff --git a/t/t1900-repo-info.sh b/t/t1900-repo-info.sh
index 39bb77dda0..09158d29f9 100755
--- a/t/t1900-repo-info.sh
+++ b/t/t1900-repo-info.sh
@@ -155,4 +155,56 @@ test_expect_success 'git repo info -h shows only repo info usage' '
 	test_grep ! "git repo structure" actual
 '
 
+# Helper function to test path keys in both absolute and relative formats.
+# $1: label for the test
+# $2: field_name (e.g., commondir)
+# $3: expected_dir (the directory name, e.g., .git or custom-common)
+# $4: init_command (extra setup like exporting env vars)
+test_repo_info_path () {
+	label=$1
+	field_name=$2
+	expected_dir=$3
+	init_command=$4
+
+	test_expect_success "absolute: $label" '
+		test_when_finished "rm -rf repo" &&
+		git init repo &&
+		(
+			mkdir -p repo/sub &&
+			cd repo/sub &&
+			ROOT="$(test-tool path-utils real_path ..)" && export ROOT &&
+			eval "$init_command" &&
+			echo "path.$field_name.absolute=$ROOT/$expected_dir" >expect &&
+			git repo info "path.$field_name.absolute" >actual &&
+			test_cmp expect actual
+		)
+	'
+
+	test_expect_success "relative: $label" '
+		test_when_finished "rm -rf repo" &&
+		git init repo &&
+		(
+			mkdir -p repo/sub &&
+			cd repo/sub &&
+			ROOT="$(test-tool path-utils real_path ..)" && export ROOT &&
+			eval "$init_command" &&
+			echo "path.$field_name.relative=../$expected_dir" >expect &&
+			git repo info "path.$field_name.relative" >actual &&
+			test_cmp expect actual
+		)
+	'
+}
+
+test_repo_info_path 'commondir standard' 'commondir' '.git'
+
+test_repo_info_path 'commondir with GIT_COMMON_DIR and GIT_DIR' 'commondir' \
+	'custom-common' \
+	'GIT_COMMON_DIR="$ROOT/custom-common" && export GIT_COMMON_DIR &&
+	 GIT_DIR="../.git" && export GIT_DIR &&
+	 git init --bare "$ROOT/custom-common"'
+
+test_repo_info_path 'commondir with only GIT_DIR' 'commondir' \
+	'.git' \
+	'GIT_DIR="../.git" && export GIT_DIR'
+
 test_done
-- 
2.54.0


^ permalink raw reply related

* [GSoC Patch v6 4/4] repo: add path.gitdir with absolute and relative suffix formatting
From: K Jayatheerth @ 2026-06-20  3:16 UTC (permalink / raw)
  To: git
  Cc: jltobler, lucasseikioshiro, gitster, phillip.wood, sandals,
	kumarayushjha123, a3205153416, kristofferhaugsbakk, K Jayatheerth
In-Reply-To: <20260620031644.353772-1-jayatheerthkulkarni2005@gmail.com>

Scripts need a stable way to locate the git directory without
parsing rev-parse output or relying on its flag-driven path format
selection. There is no way to retrieve this path from git repo info
today.

Introduce path.gitdir.absolute and path.gitdir.relative keys,
consistent with the path.commondir keys added in the previous patch.
Reuse the test_repo_info_path helper introduced there to validate
both variants.

Mentored-by: Justin Tobler <jltobler@gmail.com>
Mentored-by: Lucas Seiki Oshiro <lucasseikioshiro@gmail.com>
Signed-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>
---
 Documentation/git-repo.adoc |  6 ++++++
 builtin/repo.c              | 24 ++++++++++++++++++++++++
 t/t1900-repo-info.sh        |  6 ++++++
 3 files changed, 36 insertions(+)

diff --git a/Documentation/git-repo.adoc b/Documentation/git-repo.adoc
index 890c34051d..ed7d80c690 100644
--- a/Documentation/git-repo.adoc
+++ b/Documentation/git-repo.adoc
@@ -113,6 +113,12 @@ values that they return:
 	The path to the Git repository's common directory relative to
 	the current working directory.
 
+`path.gitdir.absolute`::
+	The canonical absolute path to the Git repository directory (the `.git` directory).
+
+`path.gitdir.relative`::
+	The path to the Git repository directory relative to the current working directory.
+
 `references.format`::
 	The reference storage format. The valid values are:
 +
diff --git a/builtin/repo.c b/builtin/repo.c
index c4cc3bf3fc..9a312d127a 100644
--- a/builtin/repo.c
+++ b/builtin/repo.c
@@ -99,6 +99,28 @@ static int get_path_commondir_relative(struct repository *repo, struct strbuf *b
 	return 0;
 }
 
+static int get_path_gitdir_absolute(struct repository *repo, struct strbuf *buf)
+{
+	const char *git_dir = repo_get_git_dir(repo);
+
+	if (!git_dir)
+		return error(_("unable to get git directory"));
+
+	append_formatted_path(buf, git_dir, startup_info->prefix, PATH_FORMAT_CANONICAL);
+	return 0;
+}
+
+static int get_path_gitdir_relative(struct repository *repo, struct strbuf *buf)
+{
+	const char *git_dir = repo_get_git_dir(repo);
+
+	if (!git_dir)
+		return error(_("unable to get git directory"));
+
+	append_formatted_path(buf, git_dir, startup_info->prefix, PATH_FORMAT_RELATIVE);
+	return 0;
+}
+
 static int get_references_format(struct repository *repo, struct strbuf *buf)
 {
 	strbuf_addstr(buf,
@@ -113,6 +135,8 @@ static const struct repo_info_field repo_info_field[] = {
 	{ "object.format", get_object_format },
 	{ "path.commondir.absolute", get_path_commondir_absolute },
 	{ "path.commondir.relative", get_path_commondir_relative },
+	{ "path.gitdir.absolute", get_path_gitdir_absolute },
+	{ "path.gitdir.relative", get_path_gitdir_relative },
 	{ "references.format", get_references_format },
 };
 
diff --git a/t/t1900-repo-info.sh b/t/t1900-repo-info.sh
index 09158d29f9..ae8c22c817 100755
--- a/t/t1900-repo-info.sh
+++ b/t/t1900-repo-info.sh
@@ -207,4 +207,10 @@ test_repo_info_path 'commondir with only GIT_DIR' 'commondir' \
 	'.git' \
 	'GIT_DIR="../.git" && export GIT_DIR'
 
+test_repo_info_path 'gitdir standard' 'gitdir' '.git'
+
+test_repo_info_path 'gitdir with explicit GIT_DIR' 'gitdir' \
+	'.git' \
+	'GIT_DIR="../.git" && export GIT_DIR'
+
 test_done
-- 
2.54.0


^ permalink raw reply related

* Re: [PATCH v14 4/6] branch: add --prune-merged <branch>
From: Harald Nordgren @ 2026-06-20  9:04 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Phillip Wood, Harald Nordgren via GitGitGadget, git,
	Kristoffer Haugsbakk, Johannes Sixt
In-Reply-To: <xmqq33yimsdp.fsf@gitster.g>

>  - Move the @{upstream} of feature2 to the branch that "merged"
>    feature1 and caused its removal.  Asking feature2@{upstream}
>    would answer origin/master, which feature1 was removed after
>    getting merged.

I think this is a strong option.

As a side note: I was annoyed before when GitHub didn't re-assign base
automatically when doing stacked PR's, so merging in the first branch
caused developers to  merge in the second PR into essentially a dead
feature branch instead of master, if they forgot to manually change
it. But I think GitHub has fixed this now so the second PR gets its
base changed to default branch.

Two caveats:

- How to handle recursion: b1 has b2 as upstream and b2 has b3 as
upstream, and both b2 and b3 have been merged? Not good if it's just
luck which order the branches get walked, but also we don't want to
have to do many passes, two passes is not even guaranteed to be
enough.
- What about when b3 has itself as upstream? I guess then we can just
remove the upstream of b2. Overall, I don't think it's a huge problem
when a branch gets no upstream, so maybe just warn about it.


Harald

^ permalink raw reply

* [PATCH v6 0/3] graph: indent visual roots in graph
From: Pablo Sabater @ 2026-06-20 10:11 UTC (permalink / raw)
  To: git
  Cc: krka, ayu.chandekar, chandrapratap3519, christian.couder, gitster,
	jltobler, karthik.188, pabloosabaterr, peff, phillip.wood,
	siddharthasthana31
In-Reply-To: <20260613-ps-pre-commit-indent-v5-0-8d308efea63d@gmail.com>

When rendering a graph, if the history contains multiple "visual roots",
actual roots or commits that look like roots (i.e. have their parents
filtered out) can end up being vertically adjacent to unrelated commits,
falsely appearing to be related.

A fix for this issue was already attempted [1] a while ago.

This series adds indentation to the visual root commits, so they cannot be
vertically adjacent anymore making it easier to identify them.

before indentation:

	* A
	* B1
	* B2
	* C1
	* C2

after indentation:

	  * A
	* B1
	 \
	  * B2
	* C1
	* C2

Indents the visual root commits that have still commits to show after them, and
if they have children it connects them with an edge at a new row.

If there are multiple visual roots adjacent in history, the indentation starts
with the second one, avoiding redundant indentation of the first one and cascades
after the second.

	* A
	  * B
	    * C
	* D1
	* D2

This series first commit is a cleanup that brings a common function from t4215
and t6016 to a graph functions file which they both use, so the new test file
for indentation, t4218, can use it as well.

There are two main limitations to predict if the next commit will be a
visual root candidate:

1. The peek only gives us the next entry reliably, we cannot see past it
   reliably in order.

2. Even if we could peek past in order, its parents might not have been
   simplified yet, so a future commit that will become a visual root is
   not detected as a visual root in peek-time.

This causes the cascading to not be set and result in a extra
indentation. For example:

Given:

	* A unrelated (visual root)
	* B child of C
	* C visual root WILL BE FILTERED OUT
	* D unrelated (visual root)

The actual output is:

	  * A
	    * B
	* D

But we wanted:

	* A
	  * B
	* D

A test has been added to t4218 and a NEEDSWORK to the lookahead function
to document this edge case but I'm not that familiar with revision.c.
Maybe there's a better way to make the lookahead more reliable.

[1]: https://lore.kernel.org/git/xmqqwnwajbuj.fsf@gitster.c.googlers.com/

V5 DIFF:

- Added new commit with lookahead functions to abstract the commit
  traverse to the graph. Changed the lookahead function from graph to
  call this new functions.
- Added new test with two unrelated branches with merges.
- Fixed test_expect_failure to have the correct expected output.
- Simplified the NEEDSWORK.

Signed-off-by: Pablo Sabater <pabloosabaterr@gmail.com>
---
Pablo Sabater (3):
      lib-log-graph: move check_graph function
      revision: add peek functions for lookahead
      graph: indent visual root in graph

 graph.c                                    | 271 +++++++++++++++++
 revision.c                                 |  38 +++
 revision.h                                 |  10 +
 t/lib-log-graph.sh                         |   5 +
 t/meson.build                              |   1 +
 t/t4215-log-skewed-merges.sh               |  33 +-
 t/t4218-log-graph-indentation.sh           | 468 +++++++++++++++++++++++++++++
 t/t6016-rev-list-graph-simplify-history.sh |  25 +-
 8 files changed, 817 insertions(+), 34 deletions(-)
---
base-commit: 95e20213faefeb95df29277c58ac1980ab68f701
change-id: 20260612-ps-pre-commit-indent-39ca72816382

Best regards,
--  
Pablo Sabater <pabloosabaterr@gmail.com>

^ permalink raw reply

* [PATCH v6 1/3] lib-log-graph: move check_graph function
From: Pablo Sabater @ 2026-06-20 10:11 UTC (permalink / raw)
  To: git
  Cc: krka, ayu.chandekar, chandrapratap3519, christian.couder, gitster,
	jltobler, karthik.188, pabloosabaterr, peff, phillip.wood,
	siddharthasthana31
In-Reply-To: <20260620-ps-pre-commit-indent-v6-0-cdc6d8fd5fbc@gmail.com>

check_graph is a function shared in the test files t4215 and t6016 used
to format the output graph, but instead of being in a file called by
both test, the function code is repeated in each file.

Move check_graph to lib-log-graph.sh file which both tests already
import graph functions from, renaming it to lib_test_check_graph.

This function is needed for the following commit which includes graph
tests in a new file and requires check_graph.

Mentored-by: Karthik Nayak <karthik.188@gmail.com>
Mentored-by: Chandra Pratap <chandrapratap3519@gmail.com>
Signed-off-by: Pablo Sabater <pabloosabaterr@gmail.com>
---
 t/lib-log-graph.sh                         |  5 +++++
 t/t4215-log-skewed-merges.sh               | 33 +++++++++++++-----------------
 t/t6016-rev-list-graph-simplify-history.sh | 25 +++++++++-------------
 3 files changed, 29 insertions(+), 34 deletions(-)

diff --git a/t/lib-log-graph.sh b/t/lib-log-graph.sh
index bf952ef920..1eae8f60c2 100644
--- a/t/lib-log-graph.sh
+++ b/t/lib-log-graph.sh
@@ -26,3 +26,8 @@ lib_test_cmp_colored_graph () {
 	test_decode_color <output.colors.raw | sed "s/ *\$//" >output.colors &&
 	test_cmp expect.colors output.colors
 }
+
+lib_test_check_graph () {
+	cat >expect &&
+	lib_test_cmp_graph --format=%s "$@"
+}
diff --git a/t/t4215-log-skewed-merges.sh b/t/t4215-log-skewed-merges.sh
index 1612f05f1b..eebab71039 100755
--- a/t/t4215-log-skewed-merges.sh
+++ b/t/t4215-log-skewed-merges.sh
@@ -5,11 +5,6 @@ test_description='git log --graph of skewed merges'
 . ./test-lib.sh
 . "$TEST_DIRECTORY"/lib-log-graph.sh
 
-check_graph () {
-	cat >expect &&
-	lib_test_cmp_graph --format=%s "$@"
-}
-
 test_expect_success 'log --graph with merge fusing with its left and right neighbors' '
 	git checkout --orphan _p &&
 	test_commit A &&
@@ -21,7 +16,7 @@ test_expect_success 'log --graph with merge fusing with its left and right neigh
 	git checkout _p && git merge --no-ff _r -m G &&
 	git checkout @^^ && git merge --no-ff _p -m H &&
 
-	check_graph <<-\EOF
+	lib_test_check_graph <<-\EOF
 	*   H
 	|\
 	| *   G
@@ -49,7 +44,7 @@ test_expect_success 'log --graph with left-skewed merge' '
 	git checkout 0_p && git merge --no-ff 0_s -m 0_G &&
 	git checkout @^ && git merge --no-ff 0_q 0_r 0_t 0_p -m 0_H &&
 
-	check_graph <<-\EOF
+	lib_test_check_graph <<-\EOF
 	*-----.   0_H
 	|\ \ \ \
 	| | | | * 0_G
@@ -83,7 +78,7 @@ test_expect_success 'log --graph with nested left-skewed merge' '
 	git checkout 1_p && git merge --no-ff 1_r -m 1_G &&
 	git checkout @^^ && git merge --no-ff 1_p -m 1_H &&
 
-	check_graph <<-\EOF
+	lib_test_check_graph <<-\EOF
 	*   1_H
 	|\
 	| *   1_G
@@ -115,7 +110,7 @@ test_expect_success 'log --graph with nested left-skewed merge following normal
 	git checkout -b 2_s @^^ && git merge --no-ff 2_q -m 2_J &&
 	git checkout 2_p && git merge --no-ff 2_s -m 2_K &&
 
-	check_graph <<-\EOF
+	lib_test_check_graph <<-\EOF
 	*   2_K
 	|\
 	| *   2_J
@@ -151,7 +146,7 @@ test_expect_success 'log --graph with nested right-skewed merge following left-s
 	git checkout 3_p && git merge --no-ff 3_r -m 3_H &&
 	git checkout @^^ && git merge --no-ff 3_p -m 3_J &&
 
-	check_graph <<-\EOF
+	lib_test_check_graph <<-\EOF
 	*   3_J
 	|\
 	| *   3_H
@@ -182,7 +177,7 @@ test_expect_success 'log --graph with right-skewed merge following a left-skewed
 	git merge --no-ff 4_p -m 4_G &&
 	git checkout @^^ && git merge --no-ff 4_s -m 4_H &&
 
-	check_graph --date-order <<-\EOF
+	lib_test_check_graph --date-order <<-\EOF
 	*   4_H
 	|\
 	| *   4_G
@@ -218,7 +213,7 @@ test_expect_success 'log --graph with octopus merge with column joining its penu
 	git checkout 5_r &&
 	git merge --no-ff 5_s -m 5_H &&
 
-	check_graph <<-\EOF
+	lib_test_check_graph <<-\EOF
 	*   5_H
 	|\
 	| *-.   5_G
@@ -257,7 +252,7 @@ test_expect_success 'log --graph with multiple tips' '
 	git checkout 6_1 &&
 	git merge --no-ff 6_2 -m 6_I &&
 
-	check_graph 6_1 6_3 6_5 <<-\EOF
+	lib_test_check_graph 6_1 6_3 6_5 <<-\EOF
 	*   6_I
 	|\
 	| | *   6_H
@@ -334,7 +329,7 @@ test_expect_success 'log --graph with multiple tips' '
 	git checkout -b M_7 7_1 &&
 	git merge --no-ff 7_2 7_3 -m 7_M4 &&
 
-	check_graph M_1 M_3 M_5 M_7 <<-\EOF
+	lib_test_check_graph M_1 M_3 M_5 M_7 <<-\EOF
 	*   7_M1
 	|\
 	| | *   7_M2
@@ -371,7 +366,7 @@ test_expect_success 'log --graph with multiple tips' '
 '
 
 test_expect_success 'log --graph --graph-lane-limit=2 limited to two lanes' '
-	check_graph --graph-lane-limit=2 M_7 <<-\EOF
+	lib_test_check_graph --graph-lane-limit=2 M_7 <<-\EOF
 	*-.   7_M4
 	|\ \
 	| | * 7_G
@@ -388,7 +383,7 @@ test_expect_success 'log --graph --graph-lane-limit=2 limited to two lanes' '
 '
 
 test_expect_success 'log --graph --graph-lane-limit=1 truncate mid octopus merge' '
-	check_graph --graph-lane-limit=1 M_7 <<-\EOF
+	lib_test_check_graph --graph-lane-limit=1 M_7 <<-\EOF
 	*-~  7_M4
 	|\~
 	| ~ 7_G
@@ -405,7 +400,7 @@ test_expect_success 'log --graph --graph-lane-limit=1 truncate mid octopus merge
 '
 
 test_expect_success 'log --graph --graph-lane-limit=3 limited to three lanes' '
-	check_graph --graph-lane-limit=3 M_1 M_3 M_5 M_7 <<-\EOF
+	lib_test_check_graph --graph-lane-limit=3 M_1 M_3 M_5 M_7 <<-\EOF
 	*   7_M1
 	|\
 	| | *   7_M2
@@ -441,7 +436,7 @@ test_expect_success 'log --graph --graph-lane-limit=3 limited to three lanes' '
 '
 
 test_expect_success 'log --graph --graph-lane-limit=6 check if it only shows first of 3 parent merge' '
-	check_graph --graph-lane-limit=6 M_1 M_3 M_5 M_7 <<-\EOF
+	lib_test_check_graph --graph-lane-limit=6 M_1 M_3 M_5 M_7 <<-\EOF
 	*   7_M1
 	|\
 	| | *   7_M2
@@ -478,7 +473,7 @@ test_expect_success 'log --graph --graph-lane-limit=6 check if it only shows fir
 '
 
 test_expect_success 'log --graph --graph-lane-limit=7 check if it shows all 3 parent merge' '
-	check_graph --graph-lane-limit=7 M_1 M_3 M_5 M_7 <<-\EOF
+	lib_test_check_graph --graph-lane-limit=7 M_1 M_3 M_5 M_7 <<-\EOF
 	*   7_M1
 	|\
 	| | *   7_M2
diff --git a/t/t6016-rev-list-graph-simplify-history.sh b/t/t6016-rev-list-graph-simplify-history.sh
index 54b0a6f5f8..e0d9c3c1ac 100755
--- a/t/t6016-rev-list-graph-simplify-history.sh
+++ b/t/t6016-rev-list-graph-simplify-history.sh
@@ -13,11 +13,6 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
 . ./test-lib.sh
 . "$TEST_DIRECTORY"/lib-log-graph.sh
 
-check_graph () {
-	cat >expect &&
-	lib_test_cmp_graph --format=%s "$@"
-}
-
 test_expect_success 'set up rev-list --graph test' '
 	# 3 commits on branch A
 	test_commit A1 foo.txt &&
@@ -54,7 +49,7 @@ test_expect_success 'set up rev-list --graph test' '
 '
 
 test_expect_success '--graph --all' '
-	check_graph --all <<-\EOF
+	lib_test_check_graph --all <<-\EOF
 	* A7
 	*   A6
 	|\
@@ -82,7 +77,7 @@ test_expect_success '--graph --all' '
 # that undecorated merges are interesting, even with --simplify-by-decoration
 test_expect_success '--graph --simplify-by-decoration' '
 	git tag -d A4 &&
-	check_graph --all --simplify-by-decoration <<-\EOF
+	lib_test_check_graph --all --simplify-by-decoration <<-\EOF
 	* A7
 	*   A6
 	|\
@@ -114,7 +109,7 @@ test_expect_success 'setup: get rid of decorations on B' '
 
 # Graph with branch B simplified away
 test_expect_success '--graph --simplify-by-decoration prune branch B' '
-	check_graph --simplify-by-decoration --all <<-\EOF
+	lib_test_check_graph --simplify-by-decoration --all <<-\EOF
 	* A7
 	*   A6
 	|\
@@ -133,7 +128,7 @@ test_expect_success '--graph --simplify-by-decoration prune branch B' '
 '
 
 test_expect_success '--graph --full-history -- bar.txt' '
-	check_graph --full-history --all -- bar.txt <<-\EOF
+	lib_test_check_graph --full-history --all -- bar.txt <<-\EOF
 	* A7
 	*   A6
 	|\
@@ -148,7 +143,7 @@ test_expect_success '--graph --full-history -- bar.txt' '
 '
 
 test_expect_success '--graph --full-history --simplify-merges -- bar.txt' '
-	check_graph --full-history --simplify-merges --all -- bar.txt <<-\EOF
+	lib_test_check_graph --full-history --simplify-merges --all -- bar.txt <<-\EOF
 	* A7
 	*   A6
 	|\
@@ -161,7 +156,7 @@ test_expect_success '--graph --full-history --simplify-merges -- bar.txt' '
 '
 
 test_expect_success '--graph -- bar.txt' '
-	check_graph --all -- bar.txt <<-\EOF
+	lib_test_check_graph --all -- bar.txt <<-\EOF
 	* A7
 	* A5
 	* A3
@@ -172,7 +167,7 @@ test_expect_success '--graph -- bar.txt' '
 '
 
 test_expect_success '--graph --sparse -- bar.txt' '
-	check_graph --sparse --all -- bar.txt <<-\EOF
+	lib_test_check_graph --sparse --all -- bar.txt <<-\EOF
 	* A7
 	* A6
 	* A5
@@ -189,7 +184,7 @@ test_expect_success '--graph --sparse -- bar.txt' '
 '
 
 test_expect_success '--graph ^C4' '
-	check_graph --all ^C4 <<-\EOF
+	lib_test_check_graph --all ^C4 <<-\EOF
 	* A7
 	* A6
 	* A5
@@ -202,7 +197,7 @@ test_expect_success '--graph ^C4' '
 '
 
 test_expect_success '--graph ^C3' '
-	check_graph --all ^C3 <<-\EOF
+	lib_test_check_graph --all ^C3 <<-\EOF
 	* A7
 	*   A6
 	|\
@@ -220,7 +215,7 @@ test_expect_success '--graph ^C3' '
 # that important, but this test depends on it.  If the ordering ever changes
 # in the code, we'll need to update this test.
 test_expect_success '--graph --boundary ^C3' '
-	check_graph --boundary --all ^C3 <<-\EOF
+	lib_test_check_graph --boundary --all ^C3 <<-\EOF
 	* A7
 	*   A6
 	|\

-- 
2.54.0

^ permalink raw reply related

* [PATCH v6 2/3] revision: add peek functions for lookahead
From: Pablo Sabater @ 2026-06-20 10:11 UTC (permalink / raw)
  To: git
  Cc: krka, ayu.chandekar, chandrapratap3519, christian.couder, gitster,
	jltobler, karthik.188, pabloosabaterr, peff, phillip.wood,
	siddharthasthana31, Kristofer Karlsson
In-Reply-To: <20260620-ps-pre-commit-indent-v6-0-cdc6d8fd5fbc@gmail.com>

The graph code in a subsequent commit needs to be able to look ahead in
order to set indentation-related flags.

Using revs->commits is brittle and the data structure that holds the
pending commits might change in the future.

Add two functions that abstract this for the graph.

Helped-by: Kristofer Karlsson <stoansen@gmail.com>
Signed-off-by: Pablo Sabater <pabloosabaterr@gmail.com>
---
 revision.c | 38 ++++++++++++++++++++++++++++++++++++++
 revision.h | 10 ++++++++++
 2 files changed, 48 insertions(+)

diff --git a/revision.c b/revision.c
index e91d7e1f11..a472a28853 100644
--- a/revision.c
+++ b/revision.c
@@ -3708,6 +3708,44 @@ static unsigned int count_explore_walked;
 static unsigned int count_indegree_walked;
 static unsigned int count_topo_walked;
 
+struct commit *revision_peek_next_commit (struct rev_info *revs)
+{
+	struct topo_walk_info *info = revs->topo_walk_info;
+
+	if (info)
+		return prio_queue_peek(&info->topo_queue);
+	if (revs->commits)
+		return revs->commits->item;
+
+	return NULL;
+}
+
+int revision_has_commits_after (struct rev_info *revs, int n)
+{
+	struct topo_walk_info *info = revs->topo_walk_info;
+
+	if (info) {
+		int visible = 0;
+		for (size_t i = 0; i < info->topo_queue.nr && visible < n; i++) {
+			struct commit *c = info->topo_queue.array[i].data;
+			if (get_commit_action(revs, c) == commit_show)
+				visible++;
+		}
+		return visible > n-1;
+	}
+	if (revs->commits) {
+		struct commit_list *cl;
+		int visible = 0;
+		for (cl = revs->commits; cl && visible < n; cl = cl->next) {
+			if (get_commit_action(revs, cl->item) == commit_show)
+				visible++;
+		}
+		return visible > n-1;
+	}
+
+	return 0;
+}
+
 static void trace2_topo_walk_statistics_atexit(void)
 {
 	struct json_writer jw = JSON_WRITER_INIT;
diff --git a/revision.h b/revision.h
index 00c392be37..a10c6b0940 100644
--- a/revision.h
+++ b/revision.h
@@ -572,4 +572,14 @@ int rewrite_parents(struct rev_info *revs,
  */
 struct commit_list *get_saved_parents(struct rev_info *revs, const struct commit *commit);
 
+/*
+ * Peek into revision's next commit without consuming it.
+ */
+struct commit *revision_peek_next_commit(struct rev_info *revs);
+
+/*
+ * Check if there are n more commits to be shown yet.
+ */
+int revision_has_commits_after(struct rev_info *revs, int n);
+
 #endif

-- 
2.54.0

^ permalink raw reply related

* [PATCH v6 3/3] graph: indent visual root in graph
From: Pablo Sabater @ 2026-06-20 10:11 UTC (permalink / raw)
  To: git
  Cc: krka, ayu.chandekar, chandrapratap3519, christian.couder, gitster,
	jltobler, karthik.188, pabloosabaterr, peff, phillip.wood,
	siddharthasthana31
In-Reply-To: <20260620-ps-pre-commit-indent-v6-0-cdc6d8fd5fbc@gmail.com>

When rendering a graph, if the history contains multiple "visual roots",
actual roots or commits that look like roots (i.e. have their parents
filtered out) can end up being vertically adjacent to unrelated commits,
falsely appearing to be related.

A fix for this issue was already attempted [1] a while ago.

This happens because the commits fill the space from left to right and
when a visual root ends, its column becomes free for the following
commit even if they are not related. Once this happens the unrelated
commit is rendered below the visual root. Because there is no special
character or way to identify when a visual root is rendered making the
graph confusing.

By indenting the visual roots when there are still commits to show the
vertical adjacency can be avoided.

Add is_visual_root flag to git_graph making it visible in all graph states,
give graph_update() a new function, graph_is_visual_root() to know if the
current commit is a visual root and set is_visual_root.
The different handled cases are:

- If a visual root has children: similar to GRAPH_PRE_COMMIT state when
  octopus merges need space, an edge row needs to be printed to connect
  the child with the indented visual root. A new state GRAPH_PRE_ROOT is
  needed to connect the child with the visual root:

    * child of the visual root
     \ GRAPH_PRE_ROOT
      * visual root indented

- If a visual root is child-less we can skip GRAPH_PRE_ROOT state and
  render the indented commit directly.

      * visual root indented
    * unrelated commit

- If two or more visual roots are adjacent: by having a lookahead to the
  next commit that will be rendered, if the next commit is also a visual
  root and we are on a visual root, meaning two visual root adjacent in
  the history, the top one can omit the indent, making the one below to
  indent only once, if there are more adjacent visual commits, the
  indentation will increase for each adjacent one, cascading.

    * visual root
      * visual root
        * visual root
    * last commit

  Even if the last commit is a root, because there is nothing that will be
  rendered below we can omit the indentation on purpose.

There are two main limitations to predict if the next commit will be a
visual root candidate:

1. The peek only gives us the next entry reliably, we cannot see past it
   reliably in order.

2. Even if we could peek past in order, its parents might not have been
   simplified yet, so a future commit that will become a visual root is
   not detected as a visual root in peek-time.

This causes the cascading to not be set and result in a extra
indentation. For example:

Given:

	* A unrelated (visual root)
	* B child of C
	* C visual root WILL BE FILTERED OUT
	* D unrelated (visual root)

The actual output is:

	  * A
	    * B
	* D

But we wanted:

	* A
	  * B
	* D

The output isn't broken as unrelated commits are successfully separated
by indentation, but an indent level should have been avoided.

Create a new test file for graph indentations test called
't4218-log-graph-indentation.sh'.

The filtered parents edge case is documented as a NEEDSWORK on the
lookahead function and it has its own 'test_expect_failure' at 't4218'.

[1]: https://lore.kernel.org/git/xmqqwnwajbuj.fsf@gitster.c.googlers.com/

Mentored-by: Karthik Nayak <karthik.188@gmail.com>
Mentored-by: Chandra Pratap <chandrapratap3519@gmail.com>
Signed-off-by: Pablo Sabater <pabloosabaterr@gmail.com>
---
 graph.c                          | 271 +++++++++++++++++++++++
 t/meson.build                    |   1 +
 t/t4218-log-graph-indentation.sh | 468 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 740 insertions(+)

diff --git a/graph.c b/graph.c
index 842282685f..7263aa6283 100644
--- a/graph.c
+++ b/graph.c
@@ -60,12 +60,23 @@ struct column {
 	 * index into column_colors.
 	 */
 	unsigned short color;
+	/*
+	 * Marks if a commit is a non-first parent of a merge. These columns are
+	 * already visually connected to the merge commit and do not need
+	 * indentation.
+	 *
+	 * The first parent is the one that inherits the column and it can need
+	 * indentation if turns out to be a visual root and there's still
+	 * commits to render.
+	 */
+	unsigned is_merge_parent:1;
 };
 
 enum graph_state {
 	GRAPH_PADDING,
 	GRAPH_SKIP,
 	GRAPH_PRE_COMMIT,
+	GRAPH_PRE_ROOT,
 	GRAPH_COMMIT,
 	GRAPH_POST_MERGE,
 	GRAPH_COLLAPSING
@@ -315,6 +326,48 @@ struct git_graph {
 	 * diff_output_prefix_callback().
 	 */
 	struct strbuf prefix_buf;
+
+	/*
+	 * If a commit is a visual root, we need to indent it to prevent
+	 * unrelated commits from being vertically adjacent to it.
+	 */
+	unsigned is_visual_root:1;
+
+	/*
+	 * Indentation increases for each visual root adjacent to another visual
+	 * root, making visual root commits indentation cascade.
+	 */
+	unsigned int visual_root_depth;
+
+	/*
+	 * When a visual root is adjacent to other visual roots, the first one
+	 * can avoid indentation and the rest cascades, increasing the indentation
+	 * for each one.
+	 */
+	unsigned visual_root_cascade:1;
+
+	/*
+	 * Set when the current commit was already present in graph->columns
+	 * before being processed.
+	 */
+	unsigned commit_in_columns:1;
+};
+
+struct graph_lookahead_flags {
+	/*
+	 * Set when there will be a commit after the current one that will be
+	 * rendered.
+	 */
+	unsigned int is_next_visible:1;
+	/*
+	 * Set when the next visible commit is candidate to be a visual root.
+	 */
+	unsigned int is_next_visual_root:1;
+	/*
+	 * Set when the next visible commit will be rendered under the current
+	 * commit.
+	 */
+	unsigned int next_has_column:1;
 };
 
 static inline int graph_needs_truncation(struct git_graph *graph, int lane)
@@ -388,6 +441,8 @@ struct git_graph *graph_init(struct rev_info *opt)
 	graph->num_columns = 0;
 	graph->num_new_columns = 0;
 	graph->mapping_size = 0;
+	graph->visual_root_depth = 0;
+	graph->visual_root_cascade = 0;
 	/*
 	 * Start the column color at the maximum value, since we'll
 	 * always increment it for the first commit we output.
@@ -561,6 +616,11 @@ static void graph_insert_into_new_columns(struct git_graph *graph,
 					  struct commit *commit,
 					  int idx)
 {
+	/*
+	 * Get the initial merge_layout before it's modified to know if this
+	 * is a merge.
+	 */
+	int initial_merge_layout = graph->merge_layout;
 	int i = graph_find_new_column_by_commit(graph, commit);
 	int mapping_idx;
 
@@ -572,6 +632,7 @@ static void graph_insert_into_new_columns(struct git_graph *graph,
 		i = graph->num_new_columns++;
 		graph->new_columns[i].commit = commit;
 		graph->new_columns[i].color = graph_find_commit_color(graph, commit);
+		graph->new_columns[i].is_merge_parent = 0;
 	}
 
 	if (graph->num_parents > 1 && idx > -1 && graph->merge_layout == -1) {
@@ -610,6 +671,12 @@ static void graph_insert_into_new_columns(struct git_graph *graph,
 	}
 
 	graph->mapping[mapping_idx] = i;
+
+	/*
+	 * Mark non-first parents of a merge.
+	 */
+	if (graph->num_parents > 1 && initial_merge_layout >= 0 && idx > -1)
+		graph->new_columns[i].is_merge_parent = 1;
 }
 
 static void graph_update_columns(struct git_graph *graph)
@@ -701,10 +768,20 @@ static void graph_update_columns(struct git_graph *graph)
 			if (graph->num_parents == 0)
 				graph->width += 2;
 		} else {
+			int j;
 			graph_insert_into_new_columns(graph, col_commit, -1);
+			/*
+			 * This column is not the current commit, but we need to
+			 * propagate the flag until the commit is processed.
+			 */
+			j = graph_find_new_column_by_commit(graph, col_commit);
+			if (j >= 0 && graph->columns[i].is_merge_parent)
+				graph->new_columns[j].is_merge_parent = 1;
 		}
 	}
 
+	graph->commit_in_columns = is_commit_in_columns;
+
 	/*
 	 * If graph_max_lanes is set, cap the width
 	 */
@@ -763,9 +840,144 @@ static int graph_needs_pre_commit_line(struct git_graph *graph)
 	       graph->expansion_row < graph_num_expansion_rows(graph);
 }
 
+/*
+ * A commit can be a visual root when:
+ * - It has no parents.
+ *
+ * - It has parents but they are all filtered out and
+ *   commit->parents arrives NULL.
+ *
+ * - It is not a boundary commit. Boundary commits also have no visible
+ *   parents, but they are not selected as visual roots because they cannot
+ *.  cause the ambiguity of being vertically adjacent because:
+ *
+ *   1. A boundary only appears because an included commit is its child.
+ *      Children are always above, and the renderer draws an edge down to
+ *      the boundary from that child. Rather than starting a column like a
+ *      visual root would do, it inherits its child column.
+ *
+ *   2. Included commits cannot appear below a boundary. Boundaries are
+ *      ancestors of the exclusion point; if an included commit were an
+ *      ancestor of the boundary it would be excluded and not rendered.
+ *      Boundaries therefore always sink to the bottom.
+ */
+static int graph_is_visual_root_candidate(struct commit *c)
+{
+	return c->parents == NULL && !(c->object.flags & BOUNDARY);
+}
+
+static int graph_is_visual_root(struct git_graph *graph,
+				struct graph_lookahead_flags *flags)
+{
+	/*
+	 * This must be only called for the current commit as graph contains
+	 * the state for the current commit only.
+	 *
+	 * To check if a commit is a visual root, call graph_is_visual_root_candidate()
+	 * but we won't know if it is really a visual root until we get to the
+	 * next commit state.
+	 *
+	 * The current commit is an actual visual root if it is a candidate and
+	 * the commit is not a non-first parent of a merge.
+	 *
+	 *   *
+	 *   |\
+	 *   | *    <- it is a visual root candidate but it shouldn't be indented
+	 *   *         because it is already connected by an edge.
+	 *   ^         if commit_in_columns && is_merge_parent means the commit
+	 *   |         was put by a merge and is connected.
+	 *   |
+	 *   `-------- if !is_next_visible means we're on the last commit, avoid
+	 *             indentation unless the one before is a visual root, then
+	 *             we need to differentiate from the one above.
+	 *
+	 * If next_has_columns means that the next commit has
+	 * already a column, so it will not be rendered below, the
+	 * current commit has to act as the last commit and omit
+	 * indentation.
+	 */
+	return graph_is_visual_root_candidate(graph->commit) &&
+	       !(graph->commit_in_columns &&
+		 graph->columns[graph->commit_index].is_merge_parent) &&
+	       flags->is_next_visible &&
+	       (!flags->next_has_column || graph->visual_root_depth > 0);
+}
+
+/*
+ * Iterates the commits queue searching for the next visible commit, once found
+ * sets visibleness and visual-root flags.
+ * Knowing if the next commit is also a visual root avoids redundant indentations
+ *
+ * NEEDSWORK: There are two main limitations to predict if the next commit will be
+ * a visual root candidate:
+ *
+ * 1. The peek only gives us the next entry reliably, we cannot see past it
+ *    reliably in order.
+ *
+ * 2. Even if we could peek past in order, its parents might not have been
+ *    simplified yet, so a future commit that will become a visual root is
+ *    not detected as a visual root in peek-time.
+ *
+ * This results in a redundant indentation.
+ *
+ * See 'test_expect_failure' at t4218-log-graph-indentation.sh.
+ */
+static void graph_peek_next_visible(struct git_graph *graph,
+				    struct graph_lookahead_flags *flags)
+{
+	struct commit *next;
+
+	flags->is_next_visible = 0;
+	flags->is_next_visual_root = 0;
+	flags->next_has_column = 0;
+
+	next = revision_peek_next_commit(graph->revs);
+	if (!next)
+		return;
+
+	if (get_commit_action(graph->revs, next) != commit_show) {
+		/*
+		 * next commit won't be shown but there could be a visible
+		 * commit still.
+		 */
+		if (revision_has_commits_after(graph->revs, 1))
+			flags->is_next_visible = 1;
+		return;
+	}
+
+	flags->is_next_visible = 1;
+	flags->next_has_column = graph_find_new_column_by_commit(graph, next) >= 0;
+
+	if (!graph_is_visual_root_candidate(next))
+		return;
+	/*
+	 * Next commit is a visual root candidate but we don't want the last
+	 * commit to get indented, check if its not the last visible commit
+	 *
+	 * We do not need graph->commit_in_columns or is_merge_parent,
+	 * because we only need to know whether the next one might be a
+	 * visual root, affecting the current commit where the cascade
+	 * would have to be set and the first visual root not indented.
+	 *
+	 * It will set next_is_visual_root to true for merge parents that
+	 * graph_is_visual_root() would return false, but if the next is
+	 * a merge parent, the current commit is the child and cannot
+	 * be a visual root and therefore having no effect.
+	 */
+	if (revision_has_commits_after(graph->revs, 2))
+		flags->is_next_visual_root = 1;
+}
+
+static int graph_needs_pre_root_line(struct git_graph *graph)
+{
+	return graph->commit_in_columns && graph->is_visual_root &&
+	       graph->num_columns > 0 && !graph->visual_root_cascade;
+}
+
 void graph_update(struct git_graph *graph, struct commit *commit)
 {
 	struct commit_list *parent;
+	struct graph_lookahead_flags flags;
 
 	/*
 	 * Set the new commit
@@ -796,6 +1008,23 @@ void graph_update(struct git_graph *graph, struct commit *commit)
 	 */
 	graph_update_columns(graph);
 
+	graph_peek_next_visible(graph, &flags);
+
+	graph->is_visual_root = graph_is_visual_root(graph, &flags);
+
+	if (graph->is_visual_root) {
+		/*
+		 * If next is a visual root we can omit the indent for the first
+		 * visual root and start cascading.
+		 */
+		if (!graph->visual_root_depth && flags.is_next_visual_root)
+			graph->visual_root_cascade = 1;
+		graph->visual_root_depth++;
+	} else {
+		graph->visual_root_depth = 0;
+		graph->visual_root_cascade = 0;
+	}
+
 	graph->expansion_row = 0;
 
 	/*
@@ -813,11 +1042,16 @@ void graph_update(struct git_graph *graph, struct commit *commit)
 	 * room for it.  We need to do this only if there is a branch row
 	 * (or more) to the right of this commit.
 	 *
+	 * If it is a visual root, we need to print an extra row to
+	 * connect the indentation.
+	 *
 	 * If there are less than 3 parents, we can immediately print the
 	 * commit line.
 	 */
 	if (graph->state != GRAPH_PADDING)
 		graph->state = GRAPH_SKIP;
+	else if (graph_needs_pre_root_line(graph))
+		graph->state = GRAPH_PRE_ROOT;
 	else if (graph_needs_pre_commit_line(graph))
 		graph->state = GRAPH_PRE_COMMIT;
 	else
@@ -1065,6 +1299,17 @@ static void graph_output_commit_line(struct git_graph *graph, struct graph_line
 
 		if (col_commit == graph->commit) {
 			seen_this = 1;
+			if (graph->is_visual_root) {
+				int depth = graph->visual_root_depth;
+				/*
+				 * Each visual column is 2 characters wide.
+				 * Omit the indentation for the first visual
+				 * root in cascade mode.
+				 */
+				int padding = (depth - graph->visual_root_cascade) * 2;
+				graph_line_addchars(line, ' ', padding);
+				graph->width += padding;
+			}
 			graph_output_commit_char(graph, line);
 
 			if (graph_needs_truncation(graph, i)) {
@@ -1436,6 +1681,29 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct graph_l
 		graph_update_state(graph, GRAPH_PADDING);
 }
 
+static void graph_output_pre_root_line(struct git_graph *graph, struct graph_line *line)
+{
+	/*
+	 * This function adds a row before a visual root, to connect the
+	 * branch to the indented commit. It should only be called on a
+	 * visual root.
+	 */
+	assert(graph->is_visual_root);
+
+	for (size_t i = 0; i < graph->num_columns; i++) {
+		struct column *col = &graph->columns[i];
+		if (col->commit == graph->commit) {
+			graph_line_addch(line, ' ');
+			graph_line_write_column(line, col, '\\');
+		} else {
+			graph_line_write_column(line, col, '|');
+		}
+		graph_line_addch(line, ' ');
+	}
+
+	graph_update_state(graph, GRAPH_COMMIT);
+}
+
 int graph_next_line(struct git_graph *graph, struct strbuf *sb)
 {
 	int shown_commit_line = 0;
@@ -1461,6 +1729,9 @@ int graph_next_line(struct git_graph *graph, struct strbuf *sb)
 	case GRAPH_PRE_COMMIT:
 		graph_output_pre_commit_line(graph, &line);
 		break;
+	case GRAPH_PRE_ROOT:
+		graph_output_pre_root_line(graph, &line);
+		break;
 	case GRAPH_COMMIT:
 		graph_output_commit_line(graph, &line);
 		shown_commit_line = 1;
diff --git a/t/meson.build b/t/meson.build
index 3219264fe7..6093ff469b 100644
--- a/t/meson.build
+++ b/t/meson.build
@@ -576,6 +576,7 @@ integration_tests = [
   't4215-log-skewed-merges.sh',
   't4216-log-bloom.sh',
   't4217-log-limit.sh',
+  't4218-log-graph-indentation.sh',
   't4252-am-options.sh',
   't4253-am-keep-cr-dos.sh',
   't4254-am-corrupt.sh',
diff --git a/t/t4218-log-graph-indentation.sh b/t/t4218-log-graph-indentation.sh
new file mode 100755
index 0000000000..005ad7c30c
--- /dev/null
+++ b/t/t4218-log-graph-indentation.sh
@@ -0,0 +1,468 @@
+#!/bin/sh
+
+test_description='git log --graph visual root indentations'
+
+. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-log-graph.sh
+
+check_graph_with_description () {
+	cat >expect &&
+	lib_test_cmp_graph --format="%s%ndescription%nsecond-line" "$@"
+}
+
+create_orphan () {
+	git checkout --orphan "$1" &&
+	{ git rm -rf . || true; }
+}
+
+# disable commit-graph topo order to have the graph to render in different
+# ways (used in --first-parent tests to have multiple visual roots while a
+# column is active at the same time).
+unset_commit_graph() {
+	sane_unset GIT_TEST_COMMIT_GRAPH &&
+	rm -f .git/objects/info/commit-graph &&
+	rm -rf .git/objects/info/commit-graphs
+}
+
+test_expect_success 'single root commit is not indented' '
+	create_orphan _1 && test_commit 1_A &&
+	lib_test_check_graph _1 <<-\EOF
+	* 1_A
+	EOF
+'
+
+test_expect_success 'visual root indented before unrelated branch' '
+	create_orphan _2 && test_commit 2_A && test_commit 2_B &&
+	create_orphan _3 && test_commit 3_A &&
+	lib_test_check_graph _2 _3 <<-\EOF
+	  * 3_A
+	* 2_B
+	* 2_A
+	EOF
+'
+
+test_expect_success 'visual root indentation with --left-right' '
+	lib_test_check_graph --left-right _2..._3 <<-\EOF
+	  > 3_A
+	< 2_B
+	< 2_A
+	EOF
+'
+
+# A better case of why indentation is still needed with '--left-right' flag is
+# that unrelated branches can be on the same side, so it's needed to
+# differentiate visual roots on the same side.
+test_expect_success 'visual root indentation with --left-right having unrelated commits on the same side' '
+	lib_test_check_graph --left-right _2..._3 _1 <<-\EOF
+	  > 3_A
+	< 2_B
+	 \
+	  < 2_A
+	> 1_A
+	EOF
+'
+
+test_expect_success 'visual root indents the description also' '
+	check_graph_with_description _2 _3 <<-\EOF
+	  * 3_A
+	    description
+	    second-line
+	* 2_B
+	| description
+	| second-line
+	* 2_A
+	  description
+	  second-line
+	EOF
+'
+
+test_expect_success 'indented visual root parent gets connected to its child' '
+	create_orphan _4 && test_commit 4_A && test_commit 4_B &&
+	create_orphan _5 && test_commit 5_A && test_commit 5_B &&
+	lib_test_check_graph _4 _5<<-\EOF
+	* 5_B
+	 \
+	  * 5_A
+	* 4_B
+	* 4_A
+	EOF
+'
+
+test_expect_success 'indented visual root parent gets connected to its child with description' '
+	check_graph_with_description _4 _5 <<-\EOF
+	* 5_B
+	| description
+	| second-line
+	 \
+	  * 5_A
+	    description
+	    second-line
+	* 4_B
+	| description
+	| second-line
+	* 4_A
+	  description
+	  second-line
+	EOF
+'
+
+test_expect_success 'visual roots cascade and last root does not' '
+	create_orphan _7 && test_commit 7_A && test_commit 7_B &&
+	create_orphan _8 && test_commit 8_A &&
+	create_orphan _9 && test_commit 9_A &&
+	create_orphan _10 && test_commit 10_A &&
+	lib_test_check_graph _7 _8 _9 _10  <<-\EOF
+	* 10_A
+	  * 9_A
+	    * 8_A
+	* 7_B
+	* 7_A
+	EOF
+'
+
+test_expect_success 'last root does not cascade' '
+	lib_test_check_graph _8 _9 _10 <<-\EOF
+	* 10_A
+	  * 9_A
+	* 8_A
+	EOF
+'
+
+test_expect_success 'merge parents are roots between them but they do not indent' '
+	create_orphan _11 && test_commit 11_A &&
+	create_orphan _12 && test_commit 12_A &&
+	create_orphan _13 && test_commit 13_A &&
+	git checkout _11 &&
+	TREE=$(git write-tree) &&
+	MERGE=$(git commit-tree $TREE -p _11 -p _12 -p _13 -m 11_octopus) &&
+	git reset --hard $MERGE &&
+	lib_test_check_graph _11 <<-\EOF
+	*-.   11_octopus
+	|\ \
+	| | * 13_A
+	| * 12_A
+	* 11_A
+	EOF
+'
+
+# The last parent of a merge can be indented if nothing related to it needs to
+# be rendered after, if it's another visual root, merge parent must not get
+# indented but rather activate cascading.
+test_expect_success 'merge then unrelated visual root and unrelated branch' '
+	create_orphan _16 && test_commit 16_A && test_commit 16_B &&
+	create_orphan _17 && test_commit 17_A &&
+	create_orphan _18 && test_commit 18_A &&
+	create_orphan _19 && test_commit 19_A &&
+	create_orphan _20 && test_commit 20_A &&
+	git checkout _18 &&
+	TREE=$(git write-tree) &&
+	MERGE=$(git commit-tree $TREE -p _18 -p _19 -p _20 -m 18_octopus) &&
+	git reset --hard $MERGE &&
+	lib_test_check_graph _18 _17 _16 <<-\EOF
+	*-.   18_octopus
+	|\ \
+	| | * 20_A
+	| * 19_A
+	* 18_A
+	  * 17_A
+	* 16_B
+	* 16_A
+	EOF
+'
+
+# The last commit root does not get indented, if the next thing after the root
+# merge parent is the last commit, indent the merge parent.
+test_expect_success 'merge then unrelated root indents merge parent' '
+	lib_test_check_graph _18 _17 <<-\EOF
+	*-.   18_octopus
+	|\ \
+	| | * 20_A
+	| * 19_A
+	 \
+	  * 18_A
+	* 17_A
+	EOF
+'
+
+test_expect_success 'merge then unrelated branch indents merge parent' '
+	lib_test_check_graph _18 _16 <<-\EOF
+	*-.   18_octopus
+	|\ \
+	| | * 20_A
+	| * 19_A
+	 \
+	  * 18_A
+	* 16_B
+	* 16_A
+	EOF
+'
+
+test_expect_success 'two-parent merge of orphans' '
+	create_orphan _21 && test_commit 21_A &&
+	create_orphan _22 && test_commit 22_A &&
+	git checkout _21 &&
+	TREE=$(git write-tree) &&
+	MERGE=$(git commit-tree $TREE -p _21 -p _22 -m 21_merge) &&
+	git reset --hard $MERGE &&
+	lib_test_check_graph _21 <<-\EOF
+	*   21_merge
+	|\
+	| * 22_A
+	* 21_A
+	EOF
+'
+
+test_expect_success 'commit with filtered parent becomes a visual root' '
+	create_orphan _23 &&
+	echo test >other.txt &&
+	git add other.txt &&
+	git commit -m "23_A" &&
+	echo test >foo.txt &&
+	git add foo.txt &&
+	git commit -m "23_B" &&
+	create_orphan _24 &&
+	echo test >foo.txt &&
+	git add foo.txt &&
+	git commit -m "24_A" &&
+	lib_test_check_graph _23 _24 -- foo.txt <<-\EOF
+	  * 23_B
+	* 24_A
+	EOF
+'
+
+# When a commit's parent will be filtered, the lookahead cannot reliably predict
+# if the next commit will be shown because the filtering has not happened yet at
+# peek-time. This makes cascade to not be set causing an extra indentation.
+#
+# Expected:
+#
+# A
+#   B
+# D
+#
+# Output:
+#
+#   A
+#     B
+# D
+#
+# This will happen for any case where we find ourselves with the next commit
+# being a unrelated child of a parent that will be filtered.
+#
+# instead of the expected:
+test_expect_failure 'filtered parent cascading edge case' '
+	create_orphan _25 &&
+	echo test >other.txt &&
+	git add other.txt &&
+	git commit -m "C-filtered" &&
+
+	echo test >foo.txt &&
+	git add foo.txt &&
+	git commit -m "B (child of filtered)" &&
+
+	create_orphan _26 &&
+	echo test >foo.txt &&
+	git add foo.txt &&
+	git commit -m "A (visual root)" &&
+
+	create_orphan _27 &&
+	echo test >foo.txt &&
+	git add foo.txt &&
+	git commit -m "D (last)" &&
+
+	lib_test_check_graph _25 _26 _27 -- foo.txt <<-\EOF
+	* B (child of filtered)
+	  * A (visual root)
+	* D (last)
+	EOF
+'
+
+test_expect_failure 'multiple filtered parents in sequence' '
+	create_orphan _44 &&
+	echo a >other.txt && git add other.txt && git commit -m "44_F" &&
+	echo b >foo.txt && git add foo.txt && git commit -m "44_C" &&
+
+	create_orphan _45 &&
+	echo c >other.txt && git add other.txt && git commit -m "45_F" &&
+	echo d >foo.txt && git add foo.txt && git commit -m "45_C" &&
+
+	create_orphan _46 &&
+	echo e >foo.txt && git add foo.txt && git commit -m "46_A" &&
+
+	lib_test_check_graph _44 _45 _46 -- foo.txt <<-\EOF
+	* 44_C
+	  * 45_C
+	* 46_A
+	EOF
+'
+
+# This tests prove why there is no need to have indentation for boundary
+# commits.
+#
+# Boundary commits rather than starting a column they 'inherit' the one of
+# its child so there will always be an edge that connects it removing the
+# ambiguity.
+test_expect_success 'unrelated boundaries are not ambiguous' '
+	create_orphan _28 && test_commit 28_A && test_commit 28_B &&
+	test_commit 28_C &&
+	create_orphan _29 && test_commit 29_A && test_commit 29_B &&
+	lib_test_check_graph --boundary 28_A.._28 29_A.._29 <<-\EOF
+	* 29_B
+	| * 28_C
+	| * 28_B
+	| o 28_A
+	o 29_A
+	EOF
+'
+
+# Same structure as t6016
+test_expect_success 'boundary commits big test' '
+	# 3 commits on branch _30
+	create_orphan _30 &&
+	test_commit 30_A &&
+	test_commit 30_B &&
+	test_commit 30_C &&
+
+	# 2 commits on branch _31, started from 30_A
+	git checkout -b _31 30_A &&
+	test_commit 31_A &&
+	test_commit 31_B &&
+
+	# 2 commits on branch _32, started from 30_B
+	git checkout -b _32 30_B &&
+	test_commit 32_A &&
+	test_commit 32_B &&
+
+	# Octopus merge _31 and _32 into -30
+	git checkout _30 &&
+	git merge _31 _32 -m 30_D &&
+	git tag 30_D &&
+	test_commit 30_E &&
+
+	# More commits on _32, then merge _32 into _30
+	git checkout _32 &&
+	test_commit 32_C &&
+	test_commit 32_D &&
+	git checkout _30 &&
+	git merge -s ours _32 -m 30_F &&
+	git tag 30_F &&
+	test_commit 30_G &&
+	lib_test_check_graph --boundary _30 _31 _32 ^32_C <<-\EOF
+	* 30_G
+	*   30_F
+	|\
+	| * 32_D
+	* | 30_E
+	| |
+	|  \
+	*-. \   30_D
+	|\ \ \
+	| * | | 31_B
+	| * | | 31_A
+	* | | | 30_C
+	o | | | 30_B
+	|/ / /
+	o / / 30_A
+	 / /
+	| o 32_C
+	|/
+	o 32_B
+	EOF
+'
+
+# Filter by --first-parent and then forcing the filtered parents to be shown.
+test_expect_success '--first-parent flag with the filtered parents' '
+	(
+		unset_commit_graph &&
+		create_orphan _35 && test_commit 35_A && test_commit 35_B &&
+		create_orphan _36 && test_commit 36_A &&
+		create_orphan _37 && test_commit 37_A &&
+		git checkout _35 &&
+		TREE=$(git write-tree) &&
+		MERGE=$(git commit-tree $TREE -p _35 -p _36 -p _37 -m 35_octopus) &&
+		git reset --hard $MERGE &&
+		lib_test_check_graph --first-parent _35 _36 _37 <<-\EOF
+		* 35_octopus
+		| * 37_A
+		|   * 36_A
+		* 35_B
+		* 35_A
+		EOF
+	)
+'
+
+test_expect_success '--first-parent with filtered parents but one has a child' '
+	(
+		unset_commit_graph &&
+		create_orphan _38 && test_commit 38_A && test_commit 38_B &&
+		create_orphan _39 && test_commit 39_A &&
+		create_orphan _40 && test_commit 40_A && test_commit 40_B &&
+		git checkout _38 &&
+		TREE=$(git write-tree) &&
+		MERGE=$(git commit-tree $TREE -p _38 -p _39 -p _40 -m 38_octopus) &&
+		git reset --hard $MERGE &&
+		lib_test_check_graph --first-parent _38 _39 _40 <<-\EOF
+		* 38_octopus
+		| * 40_B
+		| * 40_A
+		|   * 39_A
+		* 38_B
+		* 38_A
+		EOF
+	)
+'
+
+test_expect_success '--first-parent with filtered parents but both have childs' '
+	(
+		unset_commit_graph &&
+		create_orphan _41 && test_commit 41_A && test_commit 41_B &&
+		create_orphan _42 && test_commit 42_A && test_commit 42_B &&
+		create_orphan _43 && test_commit 43_A && test_commit 43_B &&
+		git checkout _41 &&
+		TREE=$(git write-tree) &&
+		MERGE=$(git commit-tree $TREE -p _41 -p _42 -p _43 -m 41_octopus) &&
+		git reset --hard $MERGE &&
+		lib_test_check_graph --first-parent _41 _42 _43 <<-\EOF
+		* 41_octopus
+		| * 43_B
+		|  \
+		|   * 43_A
+		| * 42_B
+		| * 42_A
+		* 41_B
+		* 41_A
+		EOF
+	)
+'
+
+test_expect_success 'two unrelated merges' '
+	create_orphan _50 && test_commit 50_A &&
+	git checkout -b _51 &&
+	test_commit 51_A && test_commit 51_B &&
+	git checkout _50 &&
+	git merge --no-ff _51 -m 50_B &&
+
+	create_orphan _52 && test_commit 52_A &&
+	git checkout -b _53 &&
+	test_commit 53_A && test_commit 53_B &&
+	git checkout _52 &&
+	git merge --no-ff _53 -m 52_B &&
+
+	lib_test_check_graph _52 _50 <<-\EOF
+	*   52_B
+	|\
+	| * 53_B
+	| * 53_A
+	|/
+	 \
+	  * 52_A
+	*   50_B
+	|\
+	| * 51_B
+	| * 51_A
+	|/
+	* 50_A
+	EOF
+'
+
+test_done

-- 
2.54.0

^ permalink raw reply related


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