Git development
 help / color / mirror / Atom feed
* [GSoC Patch v5 3/4] repo: add path.commondir with absolute and relative suffix formatting
From: K Jayatheerth @ 2026-06-16  4:49 UTC (permalink / raw)
  To: git
  Cc: jltobler, lucasseikioshiro, gitster, phillip.wood, sandals,
	kumarayushjha123, a3205153416, K Jayatheerth
In-Reply-To: <20260616044953.184806-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 v5 2/4] rev-parse: use append_formatted_path() for path formatting
From: K Jayatheerth @ 2026-06-16  4:49 UTC (permalink / raw)
  To: git
  Cc: jltobler, lucasseikioshiro, gitster, phillip.wood, sandals,
	kumarayushjha123, a3205153416, K Jayatheerth
In-Reply-To: <20260616044953.184806-1-jayatheerthkulkarni2005@gmail.com>

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.

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.

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 | 104 ++++++++++----------------------------------
 1 file changed, 24 insertions(+), 80 deletions(-)

diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c
index 218b5f34d6..1fdcb946a7 100644
--- a/builtin/rev-parse.c
+++ b/builtin/rev-parse.c
@@ -632,73 +632,17 @@ static void handle_ref_opt(const char *pattern, const char *prefix)
 	clear_ref_exclusions(&ref_excludes);
 }
 
-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)
 {
-	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;
-		}
-		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);
+	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);
+
+	strbuf_release(&sb);
 }
 
 int cmd_rev_parse(int argc,
@@ -717,7 +661,7 @@ 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);
 
@@ -797,8 +741,8 @@ 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;
 			}
@@ -820,9 +764,9 @@ 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);
 				}
@@ -985,7 +929,7 @@ 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;
@@ -993,7 +937,7 @@ 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;
 			}
@@ -1028,18 +972,18 @@ 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) {
@@ -1055,11 +999,11 @@ 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")) {
@@ -1089,7 +1033,7 @@ 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;
 			}
-- 
2.54.0


^ permalink raw reply related

* [GSoC Patch v5 1/4] path: introduce append_formatted_path() for shared path formatting
From: K Jayatheerth @ 2026-06-16  4:49 UTC (permalink / raw)
  To: git
  Cc: jltobler, lucasseikioshiro, gitster, phillip.wood, sandals,
	kumarayushjha123, a3205153416, K Jayatheerth
In-Reply-To: <20260616044953.184806-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 | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 path.h | 36 ++++++++++++++++++++++++++++++
 2 files changed, 106 insertions(+)

diff --git a/path.c b/path.c
index d7e17bf174..5e83e3e4f6 100644
--- a/path.c
+++ b/path.c
@@ -1579,6 +1579,76 @@ 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_DEFAULT:
+	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 0434ba5e07..6aca53b100 100644
--- a/path.h
+++ b/path.h
@@ -262,6 +262,42 @@ 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 {
+	/*
+	 * 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,
+
+	/* 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 v5 0/4] teach git repo info to handle path keys
From: K Jayatheerth @ 2026-06-16  4:49 UTC (permalink / raw)
  To: git
  Cc: jltobler, lucasseikioshiro, gitster, phillip.wood, sandals,
	kumarayushjha123, a3205153416, 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: Refactor the command to leverage the
   newly shared path engine.

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 v4:
* Simplified the `test_repo_info_path` helper by dropping the `repo_name` 
  argument and utilizing `test_when_finished "rm -rf repo"` to handle 
  repository setup/teardown inline. This ensures perfect test isolation.
* Condensed the redundant `expect_absolute_suffix` and `expect_relative` 
  test helper arguments into a single `expected_dir` argument, reducing 
  the helper signature to 4 arguments (Justin).
* Added a contextual comment in `builtin/rev-parse.c`'s `print_path()` 
  explaining why `PATH_FORMAT_DEFAULT` is intercepted and overridden with 
  a path-specific fallback (Justin).
* Trimmed the verbose test helper explanations from the commit messages 
  in patches 3 and 4.

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         | 104 +++++++++---------------------------
 path.c                      |  70 ++++++++++++++++++++++++
 path.h                      |  36 +++++++++++++
 t/t1900-repo-info.sh        |  58 ++++++++++++++++++++
 6 files changed, 253 insertions(+), 80 deletions(-)

Range-diff against v4:
1:  a396b4f8e6 = 1:  a396b4f8e6 path: introduce append_formatted_path() for shared path formatting
2:  16198f96d1 ! 2:  16b42a51d2 rev-parse: use append_formatted_path() for path formatting
    @@ builtin/rev-parse.c: static void handle_ref_opt(const char *pattern, const char
     -	}
     -	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);
3:  b45c6f0d12 ! 3:  38b733ea64 repo: add path.commondir with absolute and relative suffix formatting
    @@ Commit message
         Exposing explicit format variants rather than a single key with a
         default avoids ambiguity for scripts that require predictable output.
     
    -    Add a test helper test_repo_info_path that creates isolated
    -    repositories per test case to prevent state leaks, captures the repo
    -    root before changing directories to avoid eval, and accepts an optional
    -    init_command to cover environment variable overrides such as
    -    GIT_COMMON_DIR and GIT_DIR.
    -
         Mentored-by: Justin Tobler <jltobler@gmail.com>
         Mentored-by: Lucas Seiki Oshiro <lucasseikioshiro@gmail.com>
         Signed-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>
    @@ t/t1900-repo-info.sh: test_expect_success 'git repo info -h shows only repo info
     +# Helper function to test path keys in both absolute and relative formats.
     +# $1: label for the test
     +# $2: field_name (e.g., commondir)
    -+# $3: unique repo name for isolation
    -+# $4: expect_absolute (suffix appended to repo root)
    -+# $5: expect_relative (the relative path string expected)
    -+# $6: init_command (extra setup like exporting env vars)
    ++# $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
    -+	repo_name=$3
    -+	expect_absolute_suffix=$4
    -+	expect_relative=$5
    -+	init_command=$6
    -+
    -+	absolute_root="$repo_name-absolute"
    -+	relative_root="$repo_name-relative"
    -+
    -+	test_expect_success "setup: $label" '
    -+		git init "$absolute_root" &&
    -+		git init "$relative_root" &&
    -+		mkdir -p "$absolute_root/sub" "$relative_root/sub"
    -+	'
    ++	expected_dir=$3
    ++	init_command=$4
     +
     +	test_expect_success "absolute: $label" '
    ++		test_when_finished "rm -rf repo" &&
    ++		git init repo &&
     +		(
    -+			cd "$absolute_root/sub" &&
    ++			mkdir -p repo/sub &&
    ++			cd repo/sub &&
     +			ROOT="$(test-tool path-utils real_path ..)" && export ROOT &&
     +			eval "$init_command" &&
    -+			expect_path="$ROOT${expect_absolute_suffix:+/$expect_absolute_suffix}" &&
    -+			echo "path.$field_name.absolute=$expect_path" >expect &&
    ++			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 &&
     +		(
    -+			cd "$relative_root/sub" &&
    ++			mkdir -p repo/sub &&
    ++			cd repo/sub &&
     +			ROOT="$(test-tool path-utils real_path ..)" && export ROOT &&
     +			eval "$init_command" &&
    -+			echo "path.$field_name.relative=$expect_relative" >expect &&
    ++			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' 'commondir-std' \
    -+	'.git' '../.git'
    ++test_repo_info_path 'commondir standard' 'commondir' '.git'
     +
     +test_repo_info_path 'commondir with GIT_COMMON_DIR and GIT_DIR' 'commondir' \
    -+	'commondir-envs' 'custom-common' '../custom-common' \
    ++	'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' \
    -+	'commondir-only-gitdir' '.git' '../.git' \
    ++	'.git' \
     +	'GIT_DIR="../.git" && export GIT_DIR'
     +
      test_done
4:  b5234ffe3e ! 4:  ead1117332 repo: add path.gitdir with absolute and relative suffix formatting
    @@ builtin/repo.c: static const struct repo_info_field repo_info_field[] = {
     
      ## t/t1900-repo-info.sh ##
     @@ t/t1900-repo-info.sh: test_repo_info_path 'commondir with only GIT_DIR' 'commondir' \
    - 	'commondir-only-gitdir' '.git' '../.git' \
    + 	'.git' \
      	'GIT_DIR="../.git" && export GIT_DIR'
      
    -+test_repo_info_path 'gitdir standard' 'gitdir' 'gitdir-std' \
    -+	'.git' '../.git'
    ++test_repo_info_path 'gitdir standard' 'gitdir' '.git'
     +
     +test_repo_info_path 'gitdir with explicit GIT_DIR' 'gitdir' \
    -+	'gitdir-env' '.git' '../.git' \
    ++	'.git' \
     +	'GIT_DIR="../.git" && export GIT_DIR'
     +
      test_done
-- 
2.54.0

^ permalink raw reply

* Re: [GSoC Patch v4 2/4] rev-parse: use append_formatted_path() for path formatting
From: K Jayatheerth @ 2026-06-16  4:19 UTC (permalink / raw)
  To: Justin Tobler
  Cc: git, a3205153416, gitster, kumarayushjha123, lucasseikioshiro,
	phillip.wood, sandals, kristofferhaugsbakk
In-Reply-To: <ajAy1it6CGDQzVes@denethor>

Hey Justin,

>
> Without context, it might be a bit confusing to readers as to why we
> override PATH_FORMAT_DEFAULT without our own provided default. It may be
> worth leaving a comment to provide some breadcrumbs.
>
> The rest of this patch looks good to me.

That makes sense.
That's a minor change.
I will add proper comments!


> > Add a test helper test_repo_info_path that creates isolated
> > repositories per test case to prevent state leaks, captures the repo
> > root before changing directories to avoid eval, and accepts an optional
> > init_command to cover environment variable overrides such as
> > GIT_COMMON_DIR and GIT_DIR.
>
> I'm not sure this last paragraph in the log message provides much value.
> To me it's a bit verbose and focuses mostly on what the test helper is
> doing. Maybe we can just omit this section? If we want to have a note
> though maybe we could say something like:
>
>   Each path key is expected to have an absolute and relative form. To
>   reduce duplication, a test_repo_info_path helper function is
>   introduced to configure and exercise both cases.
>

Now that I think about it
Maybe removing it is a better option.

I mean the patch itself contains the test and it has comments explaining
the test itself.

I am gonna remove the last para in the next series.
Thanks for pointing that out!


> > +test_repo_info_path () {
> > +     label=$1
> > +     field_name=$2
> > +     repo_name=$3
> > +     expect_absolute_suffix=$4
> > +     expect_relative=$5
> > +     init_command=$6
>
> I may be overthinking it, but I can't help but feel this test helper is
> overly complicated. I wonder if we can simlify and reduce the number of
> arguments. For example, could we programatically construct the label
> from the field name and init_command instead of explicitly passing it?
>

That’s a fair question
But I personally don't think the helper is overly complicated.
I think a lot of the current helper can be mapped with test_repo_info's
structure itself.

The existing helper uses a very similar 5-argument signature (label,
init_command, repo_name, key, expected_value)
and separates the setup step from the assertion steps.

Regarding the labels, I'd prefer to keep them explicitly passed in.
Programmatically constructing the label from the init_command could
result in messy
or hard-to-read test descriptions in the console output,
and having explicit strings makes it much easier to debug when a
specific test fails.


> > +     absolute_root="$repo_name-absolute"
> > +     relative_root="$repo_name-relative"
> > +
> > +     test_expect_success "setup: $label" '
> > +             git init "$absolute_root" &&
> > +             git init "$relative_root" &&
> > +             mkdir -p "$absolute_root/sub" "$relative_root/sub"
> > +     '
>
> Do really need this setup test case? Could we instead embed the setup in
> both test cases below? Something like:
>
>         test_when_finished rm -rf repo &&
>         git init repo &&
>         (
>           mkdir repo/sub &&
>           cd repo/sub &&
>           ...
>         )
>

That's a much more elegant way to handle it.
I will incorporate this in v5!

> With something like this, each test case is responsible to creating its
> own repo and cleaning it up when finished. Then we could avoid have to
> provide a separate repo name for each set of test cases and remove the
> repo_name argument.
>

True,
Thanks!


> > +test_repo_info_path 'commondir standard' 'commondir' 'commondir-std' \
> > +     '.git' '../.git'
> > +
> > +test_repo_info_path 'commondir with GIT_COMMON_DIR and GIT_DIR' 'commondir' \
> > +     'commondir-envs' 'custom-common' '../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' \
> > +     'commondir-only-gitdir' '.git' '../.git' \
>
> For each of these test cases, the `expect_absolute_suffix` and
> `expect_relative` and exactly the same. This also appears to be the case
> for the test cases in the next patch. Do these really need to be
> configurable at all? Can we just embed them directly in each test case
> assertion? Or maybe future keys will need this to be configurable?
>

You're right that passing both is completely redundant!
However, the path does still need to be configurable because the directory
name changes between test cases (e.g., `.git` in the standard case vs.
`custom-common` when GIT_COMMON_DIR is exported).

Since the relative path is always just `../` appended to the directory name,
I will condense these two arguments into a single `expected_dir` argument in v5.
The helper will then just construct `$ROOT/$expected_dir` and
`../$expected_dir` internally.

> > +     'GIT_DIR="../.git" && export GIT_DIR'
> > +
> >  test_done
>
> Overall the rest of the patch looks good to me.
>

Thanks again!
These are helpful.

Regards,
- K Jayatheerth

^ permalink raw reply

* Re: [GSoC] [Blog] week 3: Improving the new git repo command
From: K Jayatheerth @ 2026-06-16  3:41 UTC (permalink / raw)
  To: GIT Mailing-list, Justin Tobler, Lucas Seiki Oshiro
In-Reply-To: <CA+rGoLeNzxaTrq50jE=at=0ecnZ5Diy+Q-0McG-R+XFTQ7oMow@mail.gmail.com>

Hi!

My Week 3 GSoC blog is live!
https://jayatheerth.com/blogs/gsoc/week-3-next-steps

Feel free to give it a read and share any feedback ; )

Regards,
- K Jayatheerth

^ permalink raw reply

* SHA-1/SHA-256 interoperability work is functional
From: brian m. carlson @ 2026-06-16  0:17 UTC (permalink / raw)
  To: git

[-- Attachment #1: Type: text/plain, Size: 6043 bytes --]

I'm pleased to announce that I have Git fully passing the testsuite and
CI in interoperability mode, both with SHA-256 and SHA-1 as the main
algorithm.  While this is very exciting, the work is not ready to send
to the list and is effectively a draft, since there is still cleanup
and efficiency work to be done.

What is fully functional:

* The testsuite.
* The protocol, including extensions for mapping objects to support
  shallow clones, submodules, and partial clones and interoperability
  with remotes of different algorithms.
* A full complement of functionality for everyday use cases.

Features which are currently unsupported (and which may or may not be
supported in the future):

* Filtered bundles are unsupported because there is currently no way to provide
	a mapping.
* Multi-pack index cannot be used as the sole pack index format because it does
	not yet provide mappings.
* Pack index v1 and v2 cannot be used because they do not provide object
	mappings.  Git automatically uses pack index v3 instead when necessary, which
	does handle mappings.
* Packfile URIs are not supported because the protocol-provided packfile is not
	complete and its objects cannot be mapped.
* Large object promisors cannot be used if the server does not actually have
	the entire history, since the server must have a complete history in order to
	provide object mappings.
* `git fast-import` does not accept submodules in compatibility mode because
	there is no provision for mappings.
* Remote helpers do not emit signatures in the compatibility algorithm for
	signed tags.
* The WebDAV-based HTTP protocol doesn't support interoperability due to
  the lack of a way to distribute mappings.

Some additional things that may need to be improved:

* We have some recursive delta resolution code in `git index-pack` that
  will need to be made iterative to avoid stack overflows.
* We need to batch object maps whenever we write them, since having too
  many causes `git gc` to kick off frequently (which can be seen in some
  tests).  This will require substantial refactoring of code like `git
  add`, since any time we write an object we must be sure to always
  write the mapping (even if we `die`).
* We will probably want to move the object map repacking code out of
  `git gc` into a separate command that we can call manually.
* We may want some debugging tools for pack index v3 and other data
  formats so that we can show the mapping of objects.
* We will probably want to be able to sign in only one algorithm instead
  of always in both.  Users may not want to sign the SHA-1 format for
  security reasons.
* There is some extremely basic code for `^{sha1}` and `^{sha256}` to
  help `git rev-list` perform connectivity checks for remotes using the
  compatibility algorithm, but it is very much incomplete and will need
  to be completed or fenced off.
* Operations can be rather slow in some cases and we'll want to see what
  speedups we can perform.
* Probably other things I have forgotten.

There is new documentation in `Documentation/gitformat-hash.adoc` that
outlines the requirements for using the protocol.  The protocol
restrictions described there are hard technical limitations that cannot
be avoided; I've intentionally made things as featureful as they can be.
This imposes real restrictions on using protocol interoperability with many
projects, including Git and Linux[0].  Interested parties may wish to look
at t1017 to see what's tested vis-à-vis the protocol and
interoperability.

On the end of the series is a small amount of new Rust code that moves
us in the direction of object file conversion in Rust.  All the pointer
arithmetic makes me very nervous from a security perspective, especially
in network-facing code, so my hope is to eventually port that code over.
Note that Rust is already required for the interoperability work, so
this doesn't add any new dependencies.

I also have some unpublished code that could start on in-place migration
functionality much like is done with `git refs migrate` once the Rust
prerequisites above are ready.  This could also make it possible at some
point to migrate any relevant submodules as part of the migration of the
main repository.  I may or may not complete this work, but perhaps
someone else will want to pick it up if I do not.  I'm certain that such
code would be valuable to many people, including forges.

I intend to rebase and tidy this work for at least a little while, but
don't actually intend to send it upstream, since there are some
technical limitations that prevent me from doing so.  I have, however,
been in contact with someone (who may identify themselves if they
choose) who is interested in getting some of the polishing and
upstreaming work done, which I deeply appreciate.

If you're interested in testing or perusing the work, you may get it
from the `sha256-interop` branch of https://github.com/bk2204/git.git.
Please note that it may be rebased, rewound, or otherwise folded,
spindled, or mutilated at any time.

Even though the testsuite is passing earlier than I expected, I don't
expect it to make Git 3.0, nor do I think we should delay Git 3.0 for
this work.  There are approximately 200 patches currently (and more to
come if we add in-place migration tooling), so it seems very unlikely
that we could get the entire series upstream in any reasonable amount of
time.  We will also very much want to give this time in an experimental
state so people can try it out and report back on things that should be
improved, which is further evidence that it's not right for Git 3.0.

I'm happy to answer any further questions if folks have any.

[0] Git uses submodules, which would need to be rewritten first in order
to migrate, and Linux uses mergetags for which the tagged object is not
in the history (which makes mapping the commit containing it fail).
-- 
brian m. carlson (they/them)
Toronto, Ontario, CA

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 325 bytes --]

^ permalink raw reply

* Re: [PATCH] cat-file: speed up default format
From: René Scharfe @ 2026-06-15 21:53 UTC (permalink / raw)
  To: Jeff King; +Cc: Git List
In-Reply-To: <20260615170652.GB91269@coredump.intra.peff.net>

On 6/15/26 7:06 PM, Jeff King wrote:
> On Mon, Jun 15, 2026 at 12:53:26PM -0400, Jeff King wrote:
> 
>> It uses per-atom callback functions which is nice and clean, though we
>> might be able to do even better with a big ugly switch() statement.
> 
> Being the curious sort, I swapped it out for a big switch statement.
> Patch below, but it does not seem to be any faster.
> 
> So the bottom line is I think you could gain a little bit of performance
> by pre-parsing (versus strbuf_expand() on each object). Around 3% for
> something that actually looks at the objects, though more like 15% if
> for just dumping the objectnames.
> 
> IMHO that is probably not worth it for a custom parsing system just for
> cat-file.  But if we were to finally unify ref-filter and cat-file (and
> even --pretty=format) then it would probably worth doing this kind of
> pre-parsing.
It could be worth it for cat-file alone if we find the right balance, as
it already does do a separate parsing step, but that is awkward with its
mark_query checks all over the place and remembers only object property
requirements and no other format string details.

Making the opcodes small should be beneficial.  We need only a handful
of them, so a byte each should suffice.  We can use a strbuf for that.

We can also store literal characters in there.  An opcode plus with a
payload char incurs an overhead of 50%, which sounds high, but at least
the default format only has two of them and it's much better than
storing pointer plus size for an overhead of more than 90% in case of a
single char.

That gets us closer to native speed, at least on an Apple M1:

Benchmark 1: ./git_fp cat-file --batch-all-objects --batch-check='%(objectname)-%(objecttype)-%(objectsize)'
  Time (mean ± σ):     992.7 ms ±   3.2 ms    [User: 967.5 ms, System: 23.8 ms]
  Range (min … max):   990.1 ms … 1000.7 ms    10 runs

Benchmark 2: ./git_switch cat-file --batch-all-objects --batch-check='%(objectname)-%(objecttype)-%(objectsize)'
  Time (mean ± σ):     991.8 ms ±   1.6 ms    [User: 967.0 ms, System: 23.3 ms]
  Range (min … max):   989.3 ms … 994.4 ms    10 runs

Benchmark 3: ./git cat-file --batch-all-objects --batch-check='%(objectname)-%(objecttype)-%(objectsize)'
  Time (mean ± σ):     985.8 ms ±   2.9 ms    [User: 960.5 ms, System: 23.6 ms]
  Range (min … max):   982.9 ms … 993.0 ms    10 runs

Benchmark 4: ./git cat-file --batch-all-objects --batch-check='%(objectname) %(objecttype) %(objectsize)'
  Time (mean ± σ):     982.1 ms ±   3.2 ms    [User: 956.7 ms, System: 23.6 ms]
  Range (min … max):   979.2 ms … 989.2 ms    10 runs

Summary
  ./git cat-file --batch-all-objects --batch-check='%(objectname) %(objecttype) %(objectsize)' ran
    1.00 ± 0.00 times faster than ./git cat-file --batch-all-objects --batch-check='%(objectname)-%(objecttype)-%(objectsize)'
    1.01 ± 0.00 times faster than ./git_switch cat-file --batch-all-objects --batch-check='%(objectname)-%(objecttype)-%(objectsize)'
    1.01 ± 0.00 times faster than ./git_fp cat-file --batch-all-objects --batch-check='%(objectname)-%(objecttype)-%(objectsize)'


A Ryzen laptop gives me noisy numbers that seem to suggest your
switch-based code already won, but the more compact representation is at
least not worse:

Benchmark 1: ./git_fp cat-file --batch-all-objects --batch-check='%(objectname)-%(objecttype)-%(objectsize)'
  Time (mean ± σ):     397.5 ms ±   8.0 ms    [User: 326.9 ms, System: 39.4 ms]
  Range (min … max):   388.1 ms … 410.0 ms    10 runs

Benchmark 2: ./git_switch cat-file --batch-all-objects --batch-check='%(objectname)-%(objecttype)-%(objectsize)'
  Time (mean ± σ):     388.2 ms ±   4.2 ms    [User: 318.2 ms, System: 39.2 ms]
  Range (min … max):   382.8 ms … 395.7 ms    10 runs

Benchmark 3: ./git cat-file --batch-all-objects --batch-check='%(objectname)-%(objecttype)-%(objectsize)'
  Time (mean ± σ):     385.5 ms ±   5.7 ms    [User: 311.2 ms, System: 43.2 ms]
  Range (min … max):   377.0 ms … 392.9 ms    10 runs

Benchmark 4: ./git cat-file --batch-all-objects --batch-check='%(objectname) %(objecttype) %(objectsize)'
  Time (mean ± σ):     397.5 ms ±   8.4 ms    [User: 321.9 ms, System: 45.2 ms]
  Range (min … max):   382.1 ms … 406.5 ms    10 runs

Summary
  ./git cat-file --batch-all-objects --batch-check='%(objectname)-%(objecttype)-%(objectsize)' ran
    1.01 ± 0.02 times faster than ./git_switch cat-file --batch-all-objects --batch-check='%(objectname)-%(objecttype)-%(objectsize)'
    1.03 ± 0.03 times faster than ./git_fp cat-file --batch-all-objects --batch-check='%(objectname)-%(objecttype)-%(objectsize)'
    1.03 ± 0.03 times faster than ./git cat-file --batch-all-objects --batch-check='%(objectname) %(objecttype) %(objectsize)'

René


diff --git a/builtin/cat-file.c b/builtin/cat-file.c
index 0d1998784c..5667a13e93 100644
--- a/builtin/cat-file.c
+++ b/builtin/cat-file.c
@@ -36,8 +36,6 @@ enum batch_mode {
 	BATCH_MODE_QUEUE_AND_DISPATCH,
 };
 
-struct format_item;
-
 struct batch_options {
 	struct list_objects_filter_options objects_filter;
 	int enabled;
@@ -50,7 +48,7 @@ struct batch_options {
 	char input_delim;
 	char output_delim;
 	const char *format;
-	struct format_item *parsed_format;
+	struct strbuf parsed_format;
 };
 
 static const char *force_path;
@@ -320,30 +318,16 @@ struct expand_data {
 };
 #define EXPAND_DATA_INIT  { .mode = S_IFINVALID }
 
-struct format_item {
-	enum {
-		FORMAT_TYPE_END = 0,
-		FORMAT_TYPE_LITERAL,
-		FORMAT_TYPE_OBJECTNAME,
-		FORMAT_TYPE_OBJECTTYPE,
-		FORMAT_TYPE_OBJECTSIZE,
-		FORMAT_TYPE_OBJECTSIZE_DISK,
-		FORMAT_TYPE_REST,
-		FORMAT_TYPE_DELTABASE,
-		FORMAT_TYPE_OBJECTMODE,
-	} type;
-	union {
-		struct {
-			const char *p;
-			size_t len;
-		} literal;
-	} u;
-	/*
-	 * We could make a true tree here with child/next pointers, which would
-	 * be necessary if we had recursive formats, like %(if). But for our
-	 * simple formats for now it is enough to have a linear set of items,
-	 * so we'll just allocate an array and terminate it with a NULL entry.
-	 */
+
+enum item_type {
+	FORMAT_TYPE_LITERAL,
+	FORMAT_TYPE_OBJECTNAME,
+	FORMAT_TYPE_OBJECTTYPE,
+	FORMAT_TYPE_OBJECTSIZE,
+	FORMAT_TYPE_OBJECTSIZE_DISK,
+	FORMAT_TYPE_REST,
+	FORMAT_TYPE_DELTABASE,
+	FORMAT_TYPE_OBJECTMODE,
 };
 
 static int is_atom(const char *atom, const char *s, int slen)
@@ -352,84 +336,66 @@ static int is_atom(const char *atom, const char *s, int slen)
 	return alen == slen && !memcmp(atom, s, alen);
 }
 
-static int parse_atom(struct format_item *fmt, const char *atom, int len,
+static int parse_atom(struct strbuf *parsed_format, const char *atom, int len,
 		      struct expand_data *data)
 {
 	if (is_atom("objectname", atom, len)) {
-		fmt->type = FORMAT_TYPE_OBJECTNAME;
+		strbuf_addch(parsed_format, FORMAT_TYPE_OBJECTNAME);
 	} else if (is_atom("objecttype", atom, len)) {
 		data->info.typep = &data->type;
-		fmt->type = FORMAT_TYPE_OBJECTTYPE;
+		strbuf_addch(parsed_format, FORMAT_TYPE_OBJECTTYPE);
 	} else if (is_atom("objectsize", atom, len)) {
 		data->info.sizep = &data->size;
-		fmt->type = FORMAT_TYPE_OBJECTSIZE;
+		strbuf_addch(parsed_format, FORMAT_TYPE_OBJECTSIZE);
 	} else if (is_atom("objectsize:disk", atom, len)) {
 		data->info.disk_sizep = &data->disk_size;
-		fmt->type = FORMAT_TYPE_OBJECTSIZE_DISK;
+		strbuf_addch(parsed_format, FORMAT_TYPE_OBJECTSIZE_DISK);
 	} else if (is_atom("rest", atom, len)) {
 		data->split_on_whitespace = 1;
-		fmt->type = FORMAT_TYPE_REST;
+		strbuf_addch(parsed_format, FORMAT_TYPE_REST);
 	} else if (is_atom("deltabase", atom, len)) {
 		data->info.delta_base_oid = &data->delta_base_oid;
-		fmt->type = FORMAT_TYPE_DELTABASE;
+		strbuf_addch(parsed_format, FORMAT_TYPE_DELTABASE);
 	} else if (is_atom("objectmode", atom, len)) {
-		fmt->type = FORMAT_TYPE_OBJECTMODE;
+		strbuf_addch(parsed_format, FORMAT_TYPE_OBJECTMODE);
 	} else
 		return 0;
 	return 1;
 }
 
-static struct format_item *parse_format(const char *start,
-					struct expand_data *data)
+static void parse_format(struct strbuf *parsed_format,
+			 const char *start, struct expand_data *data)
 {
-	struct format_item *ret = NULL;
-	size_t nr = 0, alloc = 0;
-
 	while (1) {
-		const char *percent = strchrnul(start, '%');
 		const char *end;
 
-		if (percent != start) {
-			ALLOC_GROW(ret, nr + 1, alloc);
-			ret[nr].type = FORMAT_TYPE_LITERAL;
-			ret[nr].u.literal.p = start;
-			ret[nr].u.literal.len = percent - start;
-			nr++;
+		while (*start && *start != '%') {
+			strbuf_addch(parsed_format, FORMAT_TYPE_LITERAL);
+			strbuf_addch(parsed_format, *start++);
 		}
 
-		if (!*percent)
+		if (!*start)
 			break;
 
-		start = percent + 1;
+		start++;
 
-		ALLOC_GROW(ret, nr + 1, alloc);
 		if (skip_prefix(start, "%", &start) || *start != '(') {
-			ret[nr].type = FORMAT_TYPE_LITERAL;
-			ret[nr].u.literal.p = "%";
-			ret[nr].u.literal.len = 1;
+			strbuf_addch(parsed_format, FORMAT_TYPE_LITERAL);
+			strbuf_addch(parsed_format, '%');
 		} else if ((end = strchr(start + 1, ')')) &&
-			   parse_atom(&ret[nr], start + 1, end - start - 1, data)) {
+			   parse_atom(parsed_format, start + 1, end - start - 1, data)) {
 			start = end + 1;
 		} else {
 			strbuf_expand_bad_format(start, "cat-file");
 		}
-		nr++;
 	}
-
-	ALLOC_GROW(ret, nr + 1, alloc);
-	ret[nr].type = FORMAT_TYPE_END;
-
-	return ret;
 }
 
-static void expand_format(struct strbuf *sb, struct format_item *fmt,
+static void expand_format(struct strbuf *sb, struct strbuf *parsed_format,
 			  struct expand_data *data)
 {
-	for (; fmt->type; fmt++)
-		switch (fmt->type) {
-		case FORMAT_TYPE_END:
-			BUG("we should have already left the loop!");
-			break;
+	for (size_t i = 0; i < parsed_format->len; i++)
+		switch (parsed_format->buf[i]) {
 		case FORMAT_TYPE_OBJECTNAME:
 			strbuf_add_oid_hex(sb, &data->oid);
 			break;
@@ -453,7 +419,7 @@ static void expand_format(struct strbuf *sb, struct format_item *fmt,
 				strbuf_addf(sb, "%06o", data->mode);
 			break;
 		case FORMAT_TYPE_LITERAL:
-			strbuf_add(sb, fmt->u.literal.p, fmt->u.literal.len);
+			strbuf_addch(sb, parsed_format->buf[++i]);
 		}
 }
 
@@ -641,7 +607,7 @@ static void batch_object_write(const char *obj_name,
 	if (!opt->format) {
 		print_default_format(scratch, data, opt);
 	} else {
-		expand_format(scratch, opt->parsed_format, data);
+		expand_format(scratch, &opt->parsed_format, data);
 		strbuf_addch(scratch, opt->output_delim);
 	}
 
@@ -1010,9 +976,8 @@ static int batch_objects(struct batch_options *opt)
 	int save_warning;
 	int retval = 0;
 
-	opt->parsed_format = parse_format(opt->format ?
-					  opt->format : DEFAULT_FORMAT,
-					  &data);
+	parse_format(&opt->parsed_format,
+		     opt->format ? opt->format : DEFAULT_FORMAT, &data);
 	if (opt->transform_mode)
 		data.split_on_whitespace = 1;
 
@@ -1152,6 +1117,7 @@ int cmd_cat_file(int argc,
 	const char *exp_type = NULL, *obj_name = NULL;
 	struct batch_options batch = {
 		.objects_filter = LIST_OBJECTS_FILTER_INIT,
+		.parsed_format = STRBUF_INIT,
 	};
 	int unknown_type = 0;
 	int input_nul_terminated = 0;


^ permalink raw reply related

* Re: [PATCH] cat-file: speed up default format
From: René Scharfe @ 2026-06-15 21:53 UTC (permalink / raw)
  To: Jeff King; +Cc: Git List
In-Reply-To: <20260615165326.GA91269@coredump.intra.peff.net>

On 6/15/26 6:53 PM, Jeff King wrote:
> 
> +static void rest_add(struct format_item *item UNUSED,
> +		     struct strbuf *sb, struct expand_data *data)
> +{
> +	strbuf_addstr(sb, data->rest);
> +}

>  	} else if (is_atom("rest", atom, len)) {
> -		if (data->mark_query)
> -			data->split_on_whitespace = 1;
> -		else if (data->rest)

This removes support for rest being NULL, breaking t1006.381.

> -			strbuf_addstr(sb, data->rest);
> +		data->split_on_whitespace = 1;
> +		fmt->add = rest_add;
René


^ permalink raw reply

* Re: [PREVIEW v4 0/6] [RFC] diff: add diff.<driver>.process for external hunk providers
From: Michael Montalbo @ 2026-06-15 21:14 UTC (permalink / raw)
  To: Michael Montalbo via GitGitGadget; +Cc: git, Johannes Schindelin
In-Reply-To: <pull.2120.v4.git.1781463332.gitgitgadget@gmail.com>

On Sun, Jun 14, 2026 at 11:55 AM Michael Montalbo via GitGitGadget
<gitgitgadget@gmail.com> wrote:
>
> This series adds diff.<driver>.process, a long-running subprocess protocol
> that lets an external tool control which lines Git considers changed while
> Git handles all output formatting...

Now I'm realizing there are some diff flags that are not properly supported
yet with an external diff provider. Flags like -L and the stat-related
flags should probably behave like blame and consult the external diff
process. In general, there should be a more explicit and comprehensive
explanation for which flags interact with external diff processes and
which do not, included principled reasons for why that is the case.

^ permalink raw reply

* Re: [PATCH v2] commit-reach: remove get_reachable_subset()
From: Kristofer Karlsson @ 2026-06-15 20:58 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Derrick Stolee, Kristofer Karlsson via GitGitGadget, git
In-Reply-To: <xmqq7bo5nf31.fsf@gitster.g>

I think we should park or abandon this patch for now; I initially thought
it would be a somewhat cheap win in code reduction but the risk of
introducing performance regressions for repos without commit graphs
means it's not really worth the time investment and I don't want to
add more maintainer burden for tracking it.

Thanks for looking at it though, I appreciate it!
Kristofer

On Thu, 11 Jun 2026 at 19:48, Junio C Hamano <gitster@pobox.com> wrote:
>
> Derrick Stolee <stolee@gmail.com> writes:
>
> > Finally, a commentary: You seem to have a habit of responding to
> > review feedback only through new patch versions, but I'd rather see
> > some thoughts in the discussion thread as direct replies to the review,
> > especially if you think you will change direction like this. Saying
> > something like "Maybe I should update the method to have two walk modes"
> > in a reply would have given me an opportunity to respond and perhaps
> > avoided a new version that went in this direction.
>
> Thanks for saying this.
>
> I haven't (yet) found it in my exchange with Kristofer, but I did
> find similar irritations during review sessions with other
> contributors.
>
> I wonder if we should talk about it in the SubmittingPatches and/or
> MyFirstContribution document?

^ permalink raw reply

* Re: [PATCH v2 2/4] pack-objects: support reachability bitmaps with `--path-walk`
From: Junio C Hamano @ 2026-06-15 20:57 UTC (permalink / raw)
  To: Taylor Blau
  Cc: git, Michael Montalbo, Derrick Stolee, Jeff King, Elijah Newren
In-Reply-To: <ffad584a43ebf3cb2138e8dce7daef84ab72712f.1780438896.git.me@ttaylorr.com>

Taylor Blau <me@ttaylorr.com> writes:

> diff --git a/t/t5310-pack-bitmaps.sh b/t/t5310-pack-bitmaps.sh
> index f693cb56691..69c5da1580a 100755
> --- a/t/t5310-pack-bitmaps.sh
> +++ b/t/t5310-pack-bitmaps.sh
> ...
> +		for reuse in true false
> +		do
> +			: >trace.txt &&
> +
> +			GIT_TRACE2_EVENT="$(pwd)/trace.txt" \
> +			git -c pack.allowPackReuse=$reuse pack-objects \
> +				--stdout --revs --path-walk --use-bitmap-index \
> +				<in >out.pack &&
> +			grep "\"category\":\"bitmap\",\"key\":\"bitmap/hits\"" trace.txt &&

This gets flagged by updated test linter X-<.  Use test_grep to
pacify it.


^ permalink raw reply

* Re: [PATCH v4] ref-filter: restore prefix-scoped iteration
From: Tamir Duberstein @ 2026-06-15 20:47 UTC (permalink / raw)
  To: git
  Cc: Karthik Nayak, Patrick Steinhardt, Junio C Hamano, Victoria Dye,
	ZheNing Hu
In-Reply-To: <20260612-fix-git-branch-regression-v4-1-f150038c02f4@gmail.com>

On Fri, Jun 12, 2026 at 5:27 PM Tamir Duberstein <tamird@gmail.com> wrote:
>
> dabecb9db2 (for-each-ref: introduce a '--start-after' option,
> 2025-07-15) changed branch, remote-tracking branch, and tag enumeration
> from constructing an iterator with the namespace prefix to constructing
> an unscoped iterator and seeking to the prefix.
>
> Review of --start-after noted that the construction prefix and seek
> position represent different state and are easy to conflate [1]. It also
> noted that future branch or tag support would need to retain the
> namespace prefix while moving the cursor [2].
>
> The files backend constructs its loose-ref iterator with cache priming
> enabled. cache_ref_iterator_begin() immediately applies the construction
> prefix through cache_ref_iterator_set_prefix(), reading loose refs
> beneath it before packed refs are opened. An empty prefix therefore
> reads every loose ref, and a later seek cannot undo that I/O.
>
> For the current single-kind filters, construct the iterator with the
> namespace prefix when start_after is not set. Leave the existing
> start_after path unchanged; no current command combines it with these
> filters, and future support must carry the prefix separately from the
> cursor.
>
> With 10,000 unrelated loose refs in the files backend, the p6300 tests
> improve as follows:
>
>                          before   after
>   branch                  2.74 s   0.11 s
>   branch --remotes        2.81 s   0.12 s
>   tag                     3.01 s   0.11 s
>
> [1] https://lore.kernel.org/r/aGZidwwlToWThkn8@pks.im/
> [2] https://lore.kernel.org/r/xmqqikjq7s16.fsf@gitster.g/
>
> Fixes: dabecb9db2b2 ("for-each-ref: introduce a '--start-after' option")
> Suggested-by: Karthik Nayak <karthik.188@gmail.com>
> Signed-off-by: Tamir Duberstein <tamird@gmail.com>
> ---
> The series is based on a89346e34a (maint) because the regression has
> been present in released versions since Git 2.51.0.
> ---
> Changes in v4:
> - Explain the historical references in the commit message.
> - Run the new performance cases with both ref backends.
> - Drop the Assisted-by trailer.
> - Link to v3: https://patch.msgid.link/20260610-fix-git-branch-regression-v3-1-6fd48fad7a53@gmail.com
>
> Changes in v3:
> - Construct the iterator directly with the namespace prefix.
> - Explain when the files backend primes its loose-ref cache.
> - Condense the commit message and performance results.
> - Link to v2: https://patch.msgid.link/20260608-fix-git-branch-regression-v2-1-fd82075a8520@gmail.com
>
> Changes in v2:
> - Extract local variable `store`.
> - Link to v1: https://patch.msgid.link/20260605-fix-git-branch-regression-v1-1-02f40ad40929@gmail.com
> ---

Hi folks, does this look reasonable?

^ permalink raw reply

* Re: [PATCH 0/2] Silence po catalog output under "make -s"
From: Harald Nordgren @ 2026-06-15 19:25 UTC (permalink / raw)
  To: Harald Nordgren via GitGitGadget; +Cc: git
In-Reply-To: <pull.2339.git.git.1781459539.gitgitgadget@gmail.com>

It's this output that currently is not silent:

$ make -s
...
579 translated messages.
558 translated messages.
514 translated messages.
381 translated messages, 4 fuzzy translations, 6 untranslated messages.
520 translated messages.
519 translated messages, 1 untranslated message.
546 translated messages, 1 untranslated message.
474 translated messages, 39 untranslated messages.
520 translated messages.
550 translated messages.
579 translated messages.
576 translated messages.
366 translated messages, 7 fuzzy translations, 17 untranslated messages.
543 translated messages.
Generating catalog po/bg.msg
322 translated messages.
Generating catalog po/ca.msg
307 translated messages.
Generating catalog po/de.msg
307 translated messages.
Generating catalog po/es.msg
184 translated messages, 46 fuzzy translations, 77 untranslated messages.
Generating catalog po/fr.msg
311 translated messages.
Generating catalog po/hu.msg
277 translated messages, 18 fuzzy translations, 12 untranslated messages.
Generating catalog po/it.msg
274 translated messages, 17 fuzzy translations, 16 untranslated messages.
Generating catalog po/ja.msg
311 translated messages.
Generating catalog po/pt_br.msg
279 translated messages, 16 fuzzy translations, 12 untranslated messages.
Generating catalog po/pt_pt.msg
311 translated messages.
Generating catalog po/ru.msg
317 translated messages.
Generating catalog po/sv.msg
323 translated messages.
Generating catalog po/ta.msg
Generating catalog po/vi.msg
327 translated messages.
Generating catalog po/zh_cn.msg
307 translated messages.
    GEN gitk-wish
317 translated messages.




On Sun, Jun 14, 2026 at 7:52 PM Harald Nordgren via GitGitGadget
<gitgitgadget@gmail.com> wrote:
>
> The gitk and git-gui catalog rules sent msgfmt --statistics output (and a
> "Generating catalog" line) to stderr, so it survived "make -s". Emit it only
> when "-s" is absent, keeping a quiet build silent and a verbose build
> unchanged.
>
> Harald Nordgren (2):
>   gitk: silence catalog output under "make -s"
>   git-gui: silence statistics under "make -s"
>
>  git-gui/Makefile  |  3 ++-
>  gitk-git/Makefile | 10 ++++++++--
>  2 files changed, 10 insertions(+), 3 deletions(-)
>
>
> base-commit: ea97ad8d017de0c9037451a78008a0fd60abea0c
> Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2339%2FHaraldNordgren%2Fsilence-catalog-output-under-make-s-v1
> Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2339/HaraldNordgren/silence-catalog-output-under-make-s-v1
> Pull-Request: https://github.com/git/git/pull/2339
> --
> gitgitgadget

^ permalink raw reply

* [PATCH] rebase: mention --abort alongside --continue
From: Harald Nordgren via GitGitGadget @ 2026-06-15 19:19 UTC (permalink / raw)
  To: git; +Cc: Harald Nordgren, Harald Nordgren

From: Harald Nordgren <haraldnordgren@gmail.com>

The warning shown when an "exec" step fails and the "git status"
advice while splitting or editing a commit pointed users at "git
rebase --continue" but not "--abort". Mention it in both, matching
the conflict case.

Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
---
    rebase: mention --abort when an exec step fails
    
    Mention git rebase --abort both in the warning shown when an exec step
    fails and in the git status advice while splitting or editing a commit,
    since rebase pointed users at --continue there without saying how to
    bail out, unlike every comparable command.

Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2330%2FHaraldNordgren%2Frebase-exec-abort-hint-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2330/HaraldNordgren/rebase-exec-abort-hint-v1
Pull-Request: https://github.com/git/git/pull/2330

 sequencer.c            |  8 ++++++--
 t/t7512-status-help.sh | 17 +++++++++++++++++
 wt-status.c            |  7 ++++++-
 3 files changed, 29 insertions(+), 3 deletions(-)

diff --git a/sequencer.c b/sequencer.c
index 57855b0066..c46e5b95bc 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -3884,7 +3884,9 @@ static int do_exec(struct repository *r, const char *command_line, int quiet)
 			  "You can fix the problem, and then run\n"
 			  "\n"
 			  "  git rebase --continue\n"
-			  "\n"),
+			  "\n"
+			  "To abort and get back to the state before \"git rebase\", run "
+			  "\"git rebase --abort\".\n"),
 			command_line,
 			dirty ? _("and made changes to the index and/or the "
 				"working tree.\n") : "");
@@ -3897,7 +3899,9 @@ static int do_exec(struct repository *r, const char *command_line, int quiet)
 			  "Commit or stash your changes, and then run\n"
 			  "\n"
 			  "  git rebase --continue\n"
-			  "\n"), command_line);
+			  "\n"
+			  "To abort and get back to the state before \"git rebase\", run "
+			  "\"git rebase --abort\".\n"), command_line);
 		status = 1;
 	}
 
diff --git a/t/t7512-status-help.sh b/t/t7512-status-help.sh
index 08e82f7914..ca7ef66ae3 100755
--- a/t/t7512-status-help.sh
+++ b/t/t7512-status-help.sh
@@ -206,6 +206,7 @@ No commands remaining.
 You are currently editing a commit while rebasing branch '\''rebase_i_edit'\'' on '\''$ONTO'\''.
   (use "git commit --amend" to amend the current commit)
   (use "git rebase --continue" once you are satisfied with your changes)
+  (use "git rebase --abort" to check out the original branch)
 
 nothing to commit (use -u to show untracked files)
 EOF
@@ -240,6 +241,7 @@ Next command to do (1 remaining command):
   (use "git rebase --edit-todo" to view and edit)
 You are currently splitting a commit while rebasing branch '\''split_commit'\'' on '\''$ONTO'\''.
   (Once your working directory is clean, run "git rebase --continue")
+  (use "git rebase --abort" to check out the original branch)
 
 Changes not staged for commit:
   (use "git add <file>..." to update what will be committed)
@@ -278,6 +280,7 @@ No commands remaining.
 You are currently editing a commit while rebasing branch '\''amend_last'\'' on '\''$ONTO'\''.
   (use "git commit --amend" to amend the current commit)
   (use "git rebase --continue" once you are satisfied with your changes)
+  (use "git rebase --abort" to check out the original branch)
 
 nothing to commit (use -u to show untracked files)
 EOF
@@ -317,6 +320,7 @@ Next command to do (1 remaining command):
 You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
   (use "git commit --amend" to amend the current commit)
   (use "git rebase --continue" once you are satisfied with your changes)
+  (use "git rebase --abort" to check out the original branch)
 
 nothing to commit (use -u to show untracked files)
 EOF
@@ -347,6 +351,7 @@ Next command to do (1 remaining command):
   (use "git rebase --edit-todo" to view and edit)
 You are currently splitting a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
   (Once your working directory is clean, run "git rebase --continue")
+  (use "git rebase --abort" to check out the original branch)
 
 Changes not staged for commit:
   (use "git add <file>..." to update what will be committed)
@@ -383,6 +388,7 @@ Next command to do (1 remaining command):
 You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
   (use "git commit --amend" to amend the current commit)
   (use "git rebase --continue" once you are satisfied with your changes)
+  (use "git rebase --abort" to check out the original branch)
 
 nothing to commit (use -u to show untracked files)
 EOF
@@ -414,6 +420,7 @@ Next command to do (1 remaining command):
 You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
   (use "git commit --amend" to amend the current commit)
   (use "git rebase --continue" once you are satisfied with your changes)
+  (use "git rebase --abort" to check out the original branch)
 
 nothing to commit (use -u to show untracked files)
 EOF
@@ -445,6 +452,7 @@ Next command to do (1 remaining command):
   (use "git rebase --edit-todo" to view and edit)
 You are currently splitting a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
   (Once your working directory is clean, run "git rebase --continue")
+  (use "git rebase --abort" to check out the original branch)
 
 Changes not staged for commit:
   (use "git add <file>..." to update what will be committed)
@@ -482,6 +490,7 @@ Next command to do (1 remaining command):
 You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
   (use "git commit --amend" to amend the current commit)
   (use "git rebase --continue" once you are satisfied with your changes)
+  (use "git rebase --abort" to check out the original branch)
 
 nothing to commit (use -u to show untracked files)
 EOF
@@ -515,6 +524,7 @@ Next command to do (1 remaining command):
 You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
   (use "git commit --amend" to amend the current commit)
   (use "git rebase --continue" once you are satisfied with your changes)
+  (use "git rebase --abort" to check out the original branch)
 
 nothing to commit (use -u to show untracked files)
 EOF
@@ -548,6 +558,7 @@ Next command to do (1 remaining command):
   (use "git rebase --edit-todo" to view and edit)
 You are currently splitting a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
   (Once your working directory is clean, run "git rebase --continue")
+  (use "git rebase --abort" to check out the original branch)
 
 Changes not staged for commit:
   (use "git add <file>..." to update what will be committed)
@@ -587,6 +598,7 @@ Next command to do (1 remaining command):
 You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
   (use "git commit --amend" to amend the current commit)
   (use "git rebase --continue" once you are satisfied with your changes)
+  (use "git rebase --abort" to check out the original branch)
 
 nothing to commit (use -u to show untracked files)
 EOF
@@ -718,6 +730,7 @@ No commands remaining.
 You are currently editing a commit while rebasing branch '\''bisect'\'' on '\''$ONTO'\''.
   (use "git commit --amend" to amend the current commit)
   (use "git rebase --continue" once you are satisfied with your changes)
+  (use "git rebase --abort" to check out the original branch)
 
 You are currently bisecting, started from branch '\''bisect_while_rebasing'\''.
   (use "git bisect reset" to get back to the original branch)
@@ -987,6 +1000,7 @@ No commands remaining.
 You are currently editing a commit while rebasing branch '\''several_commits'\'' on '\''$ONTO'\''.
   (use "git commit --amend" to amend the current commit)
   (use "git rebase --continue" once you are satisfied with your changes)
+  (use "git rebase --abort" to check out the original branch)
 
 nothing to commit (use -u to show untracked files)
 EOF
@@ -1015,6 +1029,7 @@ Next commands to do (2 remaining commands):
 You are currently editing a commit while rebasing branch '\''several_commits'\'' on '\''$ONTO'\''.
   (use "git commit --amend" to amend the current commit)
   (use "git rebase --continue" once you are satisfied with your changes)
+  (use "git rebase --abort" to check out the original branch)
 
 nothing to commit (use -u to show untracked files)
 EOF
@@ -1044,6 +1059,7 @@ Next commands to do (2 remaining commands):
 You are currently editing a commit while rebasing branch '\''several_commits'\'' on '\''$ONTO'\''.
   (use "git commit --amend" to amend the current commit)
   (use "git rebase --continue" once you are satisfied with your changes)
+  (use "git rebase --abort" to check out the original branch)
 
 nothing to commit (use -u to show untracked files)
 EOF
@@ -1064,6 +1080,7 @@ Next command to do (1 remaining command):
 You are currently editing a commit while rebasing branch '\''several_commits'\'' on '\''$ONTO'\''.
   (use "git commit --amend" to amend the current commit)
   (use "git rebase --continue" once you are satisfied with your changes)
+  (use "git rebase --abort" to check out the original branch)
 
 nothing to commit (use -u to show untracked files)
 EOF
diff --git a/wt-status.c b/wt-status.c
index b17372390c..94fd14a058 100644
--- a/wt-status.c
+++ b/wt-status.c
@@ -1527,9 +1527,12 @@ static void show_rebase_in_progress(struct wt_status *s,
 		else
 			status_printf_ln(s, color,
 					 _("You are currently splitting a commit during a rebase."));
-		if (s->hints)
+		if (s->hints) {
 			status_printf_ln(s, color,
 				_("  (Once your working directory is clean, run \"git rebase --continue\")"));
+			status_printf_ln(s, color,
+				_("  (use \"git rebase --abort\" to check out the original branch)"));
+		}
 	} else {
 		if (s->state.branch)
 			status_printf_ln(s, color,
@@ -1544,6 +1547,8 @@ static void show_rebase_in_progress(struct wt_status *s,
 				_("  (use \"git commit --amend\" to amend the current commit)"));
 			status_printf_ln(s, color,
 				_("  (use \"git rebase --continue\" once you are satisfied with your changes)"));
+			status_printf_ln(s, color,
+				_("  (use \"git rebase --abort\" to check out the original branch)"));
 		}
 	}
 	wt_longstatus_print_trailer(s);

base-commit: ea97ad8d017de0c9037451a78008a0fd60abea0c
-- 
gitgitgadget

^ permalink raw reply related

* Re: [PATCH] doc: fix a small, old release notes typo
From: D. Ben Knoble @ 2026-06-15 19:10 UTC (permalink / raw)
  To: Jeff King; +Cc: git, Elijah Newren
In-Reply-To: <20260615171416.GC91269@coredump.intra.peff.net>

On Mon, Jun 15, 2026 at 1:14 PM Jeff King <peff@peff.net> wrote:
>
> On Sun, Jun 14, 2026 at 01:28:31PM -0400, D. Ben Knoble wrote:
>
> > No harm done if you choose not to keep this, I think. Stumbled upon it when
> > trying to understand Elijah's message [1] about timestamp_t overflowing in 2106
> > (I though 32-bit time_t overflowed in 2038, but timestamp_t is something
> > different… except maybe when it's not? Anyway…)
>
> Leaving aside the patch for a moment, the answer to your timestamp
> question is: signed 32-bit takes us to 2038 (and back to 1902), but
> unsigned goes to 2106 (but only back to 1970).
>
> Usually time_t is signed, but our timestamp_t is not, mostly for
> historical reasons. And timestamp_t itself is our local invention
> because we have no control over the definition of time_t (but we still
> end up needing it to call system date functions).

Doh, that's the difference I missed. Thanks!

> I have some patches to allow negative timestamps, but I ran into
> portability issues. IIRC, Windows gmtime() chokes on negative
> timestamps.
>
> It hasn't been a big deal in practice since new commits made today will
> always have a positive epoch. But negative timestamps would allow
> importing some historical projects (like Apollo mission code), as well
> as weird (ab)uses of Git to store historical documents (like legal code
> going back centuries).
>
> -Peff

Reminded me of https://williamzujkowski.github.io/posts/2026-04-02-building-us-code-tracker-law-as-git-history/

Thanks again,
Ben

^ permalink raw reply

* What's cooking in git.git (Jun 2026, #05)
From: Junio C Hamano @ 2026-06-15 18:55 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).

A preview release Git 2.55-rc0 has been tagged and pushed out.  There
may be a few more topics in 'next' and possibly outside 'next' that
we may want to include in the release that I didn't manage or I
forgot (please let me know).

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']

* ak/typofixes (2026-05-31) 1 commit
  (merged to 'next' on 2026-06-09 at 40de2e7b90)
 + doc: fix typos via codespell

 Typofixes.
  cf. <3398ef40-1547-4324-2cfc-97b9e2b24854@gmx.de>
 cf. <xmqq8q8p1ese.fsf@gitster.g>
 source: <20260531184428.55905-1-algonell@gmail.com>
 source: <20260506101631.18127-1-algonell@gmail.com>
 source: <3398ef40-1547-4324-2cfc-97b9e2b24854@gmx.de>


* am/doc-tech-hash-typofix (2026-06-05) 1 commit
  (merged to 'next' on 2026-06-09 at aeaf2363f8)
 + doc: fix typo in GIT_ALTERNATE_OBJECT_DIRECTORIES

 Typofix.
  cf. <aiZo9FqsdKrhz0gA@pks.im>
 source: <20260605172643.8796-1-amonakov@ispras.ru>


* hn/config-typo-advice (2026-06-02) 2 commits
  (merged to 'next' on 2026-06-09 at 5149e69e3e)
 + config: improve diagnostic for "set" with missing value
 + config: add git_config_key_is_valid() for quiet validation

 "git config foo.bar=baz" is not likely to be a request to read the
 value of such a variable with '=' in its name; rather it is plausible
 that the user meant "git config set foo.bar baz".  Give advice when
 giving an error message.
  cf. <xmqq1penqfg2.fsf@gitster.g>
 source: <pull.2302.v6.git.git.1780425808.gitgitgadget@gmail.com>


* jc/submitting-patches-cover-letter (2026-06-02) 2 commits
  (merged to 'next' on 2026-06-09 at 42b2538a2a)
 + SubmittingPatches: describe cover letter
 + SubmittingPatches: separate typofixes section

 Guidelines on how to write a cover letter for a multi-patch series
 have been added to SubmittingPatches, which also got a new marker
 to separate the section for typofixes.
  cf. <c54f3571-ff7b-4caa-b75d-a739ed87ec9d@gmail.com>
 cf. <aiEgUdnL8dkszKFn@pks.im>
 source: <20260602144304.3341000-1-gitster@pobox.com>


* lo/doc-format-patch-subject-prefix (2026-06-04) 1 commit
  (merged to 'next' on 2026-06-09 at 58b2a20f6d)
 + Documentation: remove redundant 'instead' in --subject-prefix

 Wording used in "format-patch --subject-prefix" documentation
 has been improved.
 
 source: <20260604163510.36687-2-lucasseikioshiro@gmail.com>


* ls/doc-raw-timestamp-prefix (2026-06-02) 1 commit
  (merged to 'next' on 2026-06-09 at 7198b6bb9d)
 + doc: document and test `@` prefix for raw timestamps

 Documentation and tests have been added to clarify that Git's internal
 raw timestamp format requires a `@` prefix for values less than
 100,000,000 to prevent ambiguity with other formats like YYYYMMDD.
  cf. xmqqmrxdxq1r.fsf@gitster.g>
 source: <20260602081924.673763-2-dev@luna.gl>


* ob/more-repo-config-values (2026-06-02) 8 commits
  (merged to 'next' on 2026-06-09 at 3d0b057aee)
 + environment: move "warn_on_object_refname_ambiguity" into `struct repo_config_values`
 + environment: move "sparse_expect_files_outside_of_patterns" into `struct repo_config_values`
 + environment: move "core_sparse_checkout_cone" into `struct repo_config_values`
 + environment: move "precomposed_unicode" into `struct repo_config_values`
 + environment: move "pack_compression_level" into `struct repo_config_values`
 + environment: move `zlib_compression_level` into `struct repo_config_values`
 + environment: move "check_stat" into `struct repo_config_values`
 + environment: move "trust_ctime" into `struct repo_config_values`

 Many core configuration variables have been migrated from global
 variables into 'repo_config_values' to tie them to a specific
 repository instance, avoiding cross-repository state leakage.
 
 source: <20260602170921.35869-1-belkid98@gmail.com>


* ps/setup-centralize-odb-creation (2026-06-04) 9 commits
  (merged to 'next' on 2026-06-09 at a1f23cb38c)
 + setup: construct object database in `apply_repository_format()`
 + repository: stop reading loose object map twice on repo init
 + setup: stop initializing object database without repository
 + setup: stop creating the object database in `setup_git_env()`
 + repository: stop initializing the object database in `repo_set_gitdir()`
 + setup: deduplicate logic to apply repository format
 + setup: drop `setup_git_env()`
 + t0001: plug test gaps for git-init(1) with GIT_OBJECT_DIRECTORY
 + Merge branch 'ps/setup-wo-the-repository' into ps/setup-centralize-odb-creation
 (this branch is used by ps/refs-avoid-chdir-notify-reparent and ps/setup-drop-global-state.)

 The setup logic to discover and configure repositories has been
 refactored, and the initialization of the object database has been
 centralized.
  cf. <CAOLa=ZQwVbLsOcajaxQwtkTPm=4St7EiGEEyL6_B0o3Tt1v1pw@mail.gmail.com>
 source: <20260604-b4-pks-setup-centralize-odb-creation-v3-0-0691834f318a@pks.im>

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

* kh/submittingpatches-trailers (2026-06-10) 6 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: discuss non-ident trailers
 - SubmittingPatches: encourage trailer use for substantial help

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

 Waiting for response(s) to review comment(s).
 cf. <xmqqse6tnho1.fsf@gitster.g>
 source: <CV_SubPatches_trailers.8f3@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>


* 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.

 Will merge to 'master'.
 cf. <xmqqfr2tnfk0.fsf@gitster.g>
 source: <20260611-ls-files-pathspec-lstat-v3-1-f967e1a00c13@gmail.com>


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

 The `git multi-pack-index write --incremental` command has been
 corrected to properly honor the `--base` option. Previously, the
 custom base was ignored by the normal write path, 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>


* en/commit-graph-timestamp-fix (2026-06-13) 1 commit
 - 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.
 Will merge to 'next'?
 cf. <ai-zzWn9Ls6-j9h8@pks.im>
 source: <pull.2148.git.1781420271100.gitgitgadget@gmail.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
 - 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

 source: <pull.2137.v2.git.1781524349.gitgitgadget@gmail.com>


* kw/gitattributes-typofix (2026-06-15) 1 commit
 - gitattributes: fix eol attribute for Perl scripts

 source: <pull.2151.v2.git.1781510039164.gitgitgadget@gmail.com>


* rs/cat-file-default-format-optim (2026-06-14) 1 commit
 - cat-file: speed up default format

 source: <5a7ed929-6fe0-496c-83bd-65dee57c2241@web.de>

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

* 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 for too long. stalled.
 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.

 Comments?
 source: <pull.2140.v4.git.1780945851.gitgitgadget@gmail.com>


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

 API clean-up.

 Waiting for response(s) to review comment(s).
 cf. <3a3d1dc4-341f-4276-a1ee-2972a885db84@gmail.com>
 source: <pull.2144.v2.git.1781178567862.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-10) 3 commits
 - 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.

 Waiting for response(s) to review comment(s).
 cf. <xmqqjys6wcpo.fsf@gitster.g>
 cf. <CABPp-BGRi2obnqRGEY9pSMyvRbNGs8AdVUpZmr0C6vZSgHb=cg@mail.gmail.com>
 source: <20260610-toon-git-replay-drop-merges-v2-0-5714a71c6d83@iotcl.com>


* 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.

 Will merge to 'master'.
 cf. <20260611064912.GC2191159@coredump.intra.peff.net>
 source: <20260610-describe-tag-ref-scope-v3-1-5aa63ab279f7@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.

 Will merge to 'master'.
 cf. <20260612045329.GA593075@coredump.intra.peff.net>
 source: <20260611161946.12166-1-taahol@utu.fi>


* 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 merge to 'master'.
 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-15) 9 commits
 - refs: drop local buffer in `refs_compute_filesystem_location()`
 - refs: fix recursing `get_main_ref_store()` with "onbranch" config
 - 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: <20260615-b4-pks-refs-avoid-chdir-notify-reparent-v2-0-f4854aa99859@pks.im>


* 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.

 Will merge to 'master'.
 cf. <aiqs5Wq2Di-6yW0D@pks.im>
 source: <xmqqo6hit6rn.fsf@gitster.g>


* jd/unpack-trees-wo-the-repository (2026-03-31) 1 commit
  (merged to 'next' on 2026-06-11 at 3d7788721e)
 + unpack-trees: use repository from index instead of global

 A handful of inappropriate uses of the_repository have been
 rewritten to use the right repository structure instance in the
 unpack-trees.c codepath.

 Will merge to 'master'.
 cf. <xmqqqzmfz91r.fsf@gitster.g>
 source: <pull.2258.v2.git.git.1774971267.gitgitgadget@gmail.com>


* ps/odb-source-packed (2026-06-09) 18 commits
 - 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.

 Needs review.
 source: <20260609-pks-odb-source-packed-v2-0-839089132c8b@pks.im>


* 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.

 Will merge to 'master'.
 cf. <20260609002833.GE358144@coredump.intra.peff.net>
 cf. <20260611083320.GI2191159@coredump.intra.peff.net>
 source: <20260609134741.4727-2-pushkarkumarsingh1970@gmail.com>


* ta/typofixes (2026-06-04) 1 commit
  (merged to 'next' on 2026-06-11 at dfb63ded01)
 + docs: fix typos

 Typofixes

 Will merge to 'master'.
 cf. <xmqqh5ncvfsu.fsf@gitster.g>
 source: <20260604131457.19215-1-taahol@utu.fi>


* js/win-kill-child-more-gently (2026-06-04) 2 commits
  (merged to 'next' on 2026-06-11 at b4a2299e7e)
 + mingw: really handle SIGINT
 + mingw: kill child processes in a gentler way

 Advanced emulation of kill() used on Windows in GfW has been
 upstreamed to improve the symptoms like left-behind .lock files and
 that fails to let the child clean-up itself when it gets killed.

 Will merge to 'master'.
 source: <pull.2130.git.1780590261.gitgitgadget@gmail.com>


* 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.

 Will merge to 'master'.
 cf. <ai-8Y1r9zbWfdY8p@pks.im>
 source: <20260613122711.38662-1-dominik.loidolt@univie.ac.at>


* td/ref-filter-restore-prefix-iteration (2026-06-12) 1 commit
 - 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.

 Needs review.
 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 merge to 'master'.
 cf. <CAP8UFD35Tiy1_fqpjq8P-z=ZhzR3MTiThqfCs977652umRoSEQ@mail.gmail.com>
 cf. <xmqqse6uwdnz.fsf@gitster.g>
 source: <20260610124353.149874-2-cat@malon.dev>


* 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>


* ps/cat-file-remote-object-info (2026-06-08) 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: add declaration of variable i inside its for loop
 - 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.

 Expecting a reroll.
 cf. <CAN5EUNQQBRoHUbZtkhLoBX-K7_4Carsxws_fyh1Ac7Lmd_FjKg@mail.gmail.com>
 source: <20260608-ps-eric-work-rebase-v12-0-5338b766e658@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
 - 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.

 Needs review.
 source: <20260615-pks-b4-v4-0-22cfca8f19c5@pks.im>


* kk/streaming-walk-pqueue (2026-05-27) 3 commits
  (merged to 'next' on 2026-06-11 at 1466219fc9)
 + revision: use priority queue for non-limited streaming walks
 + revision: introduce rev_walk_mode to clarify get_revision_1()
 + pack-objects: call release_revisions() after cruft traversal

 Streaming revision walks have been optimized by using a priority queue
 for date-sorting commits, speeding up walks repositories with many
 merges.

 Will merge to 'master'.
 source: <pull.2127.git.1779897003.gitgitgadget@gmail.com>


* 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>


* jk/describe-contains-all-match-fix (2026-06-01) 1 commit
  (merged to 'next' on 2026-06-11 at a95871538b)
 + describe: fix --exclude, --match with --contains and --all

 The 'git describe --contains --all' command has been fixed to
 properly honor the '--match' and '--exclude' options by passing
 them down to 'git name-rev' with the appropriate reference
 prefixes.

 Will merge to 'master'.
 source: <20260601233727.43558-1-jacob.e.keller@intel.com>


* wy/docs-typofixes (2026-05-29) 1 commit
  (merged to 'next' on 2026-06-11 at bd53c91110)
 + docs: fix typos and grammar

 Various typos, grammatical errors, and duplicated words in both
 documentation and code comments have been corrected.

 Will merge to 'master'.
 source: <7b502e20e9495cd4720496bd6738a1fbeb453410.1780041658.git.wy@wyuan.org>


* ab/index-pack-retain-child-bases (2026-06-01) 1 commit
  (merged to 'next' on 2026-06-12 at 625f76ac4c)
 + index-pack: retain child bases in delta cache

 "git index-pack" has been optimized by retaining child bases in the
 delta cache instead of immediately freeing them, letting the existing
 cache limit policy decide eviction.

 Will merge to 'master'.
 source: <pull.2131.v2.git.1780330402264.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.

 Needs review.
 source: <pull.2120.v4.git.1781463564.gitgitgadget@gmail.com>


* tb/pack-path-walk-bitmap-delta-islands (2026-06-02) 5 commits
 - 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>
 source: <cover.1780438896.git.me@ttaylorr.com>


* ty/migrate-trust-executable-bit (2026-06-12) 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: <20260612160527.167203-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>


* mm/subprocess-handshake-fix (2026-06-01) 1 commit
  (merged to 'next' on 2026-06-11 at b649c3a97c)
 + sub-process: use gentle handshake to avoid die() on startup failure

 The subprocess handshake during startup has been made gentler by using
 packet_read_line_gently() instead of packet_read_line() to prevent the
 parent Git process from dying abruptly when a configured subprocess
 (e.g., a clean/smudge filter) fails to start.

 Will merge to 'master'.
 source: <pull.2133.v2.git.1780348848489.gitgitgadget@gmail.com>


* jk/repo-info-path-keys (2026-06-14) 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".

 Needs review.
 source: <20260615045112.50686-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-01) 1 commit
 - read_gitfile_gently(): return non-repo path on error

 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.

 Waiting for response(s) to review comment(s).
 cf. <xmqqeciezh0w.fsf@gitster.g>
 source: <20260602061159.GA693928@coredump.intra.peff.net>


* ps/t7527-fix-tap-output (2026-06-04) 8 commits
  (merged to 'next' on 2026-06-11 at b5a4cd26ee)
 + t: let prove fail when parsing invalid TAP output
 + t/lib-git-p4: silence output when killing p4d and its watchdog
 + t/test-lib: silence EBUSY errors on Windows during test cleanup
 + t7810: turn MB_REGEX check into a lazy prereq
 + t7527: fix broken TAP output
 + ci: unify Linux images across GitLab and GitHub
 + gitlab-ci: add missing Linux jobs
 + gitlab-ci: rearrange Linux jobs to match GitHub's order

 A recent regression in t7527 that broke TAP output has been fixed,
 some other test noise that also broke TAP output has been silenced,
 and 'prove' is now configured to fail on invalid TAP output to
 prevent future regressions.

 Will merge to 'master'.
 source: <20260604-pks-t7527-fix-tap-output-v3-0-7d766ed481e4@pks.im>


* 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.

 Waiting for response(s) to review comment(s).
 cf. <xmqqcxxyt4op.fsf@gitster.g>
 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 merge to 'master'.
 cf. <xmqq7bo6xuok.fsf@gitster.g>
 source: <pull.2301.v4.git.git.1779372367317.gitgitgadget@gmail.com>


* 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).
 cf. <agrIrGwSMFlKTx9x@pks.im>
 source: <20260517132111.1014901-1-joerg@thalheim.io>


* hn/branch-prune-merged (2026-06-15) 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 "--prune-merged" option to remove
 local branches that have already been merged to the remote-tracking
 branches they track.

 Needs review.
 source: <pull.2285.v15.git.git.1781542042.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 merge to 'master'.
 cf. <877bo7294j.fsf@emacs.iotcl.com>
 cf. <xmqqh5naxwfc.fsf@gitster.g>
 source: <20260527140820.1438165-1-christian.couder@gmail.com>


* hn/checkout-track-fetch (2026-05-23) 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.v13.git.git.1779565714.gitgitgadget@gmail.com>


* mf/revision-max-count-oldest (2026-06-10) 2 commits
  (merged to 'next' on 2026-06-11 at c89a71798a)
 + bash-completions: add --max-count-oldest
  (merged to 'next' on 2026-06-09 at 076600fa21)
 + revision.c: implement --max-count-oldest

 "git rev-list" (and "git log" family of commands) learned a new "--max-count-oldest"
 that picks oldest N commits in the range instead of the usual newest.

 Will merge to 'master'.
 source: <xmqq4ijm3p2x.fsf@gitster.g>


* en/ort-harden-against-corrupt-trees (2026-06-13) 5 commits
 - 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.

 Waiting for response(s) to review comment(s).
 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. <xmqqo6hdepgy.fsf@gitster.g>
 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>

^ permalink raw reply

* Re: [GSoC Patch v4 3/4] repo: add path.commondir with absolute and relative suffix formatting
From: Justin Tobler @ 2026-06-15 18:17 UTC (permalink / raw)
  To: K Jayatheerth
  Cc: git, a3205153416, gitster, kumarayushjha123, lucasseikioshiro,
	phillip.wood, sandals, kristofferhaugsbakk
In-Reply-To: <20260615045112.50686-4-jayatheerthkulkarni2005@gmail.com>

On 26/06/15 10:21AM, K Jayatheerth wrote:
> 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.
> 
> Add a test helper test_repo_info_path that creates isolated
> repositories per test case to prevent state leaks, captures the repo
> root before changing directories to avoid eval, and accepts an optional
> init_command to cover environment variable overrides such as
> GIT_COMMON_DIR and GIT_DIR.

I'm not sure this last paragraph in the log message provides much value.
To me it's a bit verbose and focuses mostly on what the test helper is
doing. Maybe we can just omit this section? If we want to have a note
though maybe we could say something like:

  Each path key is expected to have an absolute and relative form. To
  reduce duplication, a test_repo_info_path helper function is
  introduced to configure and exercise both cases.

> Mentored-by: Justin Tobler <jltobler@gmail.com>
> Mentored-by: Lucas Seiki Oshiro <lucasseikioshiro@gmail.com>
> Signed-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>
> ---
[snip]
> diff --git a/t/t1900-repo-info.sh b/t/t1900-repo-info.sh
> index 39bb77dda0..0c0228687f 100755
> --- a/t/t1900-repo-info.sh
> +++ b/t/t1900-repo-info.sh
> @@ -155,4 +155,65 @@ 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: unique repo name for isolation
> +# $4: expect_absolute (suffix appended to repo root)
> +# $5: expect_relative (the relative path string expected)
> +# $6: init_command (extra setup like exporting env vars)
> +test_repo_info_path () {
> +	label=$1
> +	field_name=$2
> +	repo_name=$3
> +	expect_absolute_suffix=$4
> +	expect_relative=$5
> +	init_command=$6

I may be overthinking it, but I can't help but feel this test helper is
overly complicated. I wonder if we can simlify and reduce the number of
arguments. For example, could we programatically construct the label
from the field name and init_command instead of explicitly passing it?

> +	absolute_root="$repo_name-absolute"
> +	relative_root="$repo_name-relative"
> +
> +	test_expect_success "setup: $label" '
> +		git init "$absolute_root" &&
> +		git init "$relative_root" &&
> +		mkdir -p "$absolute_root/sub" "$relative_root/sub"
> +	'

Do really need this setup test case? Could we instead embed the setup in
both test cases below? Something like:

	test_when_finished rm -rf repo &&
	git init repo &&
	(
	  mkdir repo/sub &&
	  cd repo/sub &&
	  ...
	)

With something like this, each test case is responsible to creating its
own repo and cleaning it up when finished. Then we could avoid have to
provide a separate repo name for each set of test cases and remove the
repo_name argument.

> +	test_expect_success "absolute: $label" '
> +		(
> +			cd "$absolute_root/sub" &&
> +			ROOT="$(test-tool path-utils real_path ..)" && export ROOT &&
> +			eval "$init_command" &&
> +			expect_path="$ROOT${expect_absolute_suffix:+/$expect_absolute_suffix}" &&
> +			echo "path.$field_name.absolute=$expect_path" >expect &&
> +			git repo info "path.$field_name.absolute" >actual &&
> +			test_cmp expect actual
> +		)
> +	'
> +
> +	test_expect_success "relative: $label" '
> +		(
> +			cd "$relative_root/sub" &&
> +			ROOT="$(test-tool path-utils real_path ..)" && export ROOT &&
> +			eval "$init_command" &&
> +			echo "path.$field_name.relative=$expect_relative" >expect &&
> +			git repo info "path.$field_name.relative" >actual &&
> +			test_cmp expect actual
> +		)
> +	'
> +}
> +
> +test_repo_info_path 'commondir standard' 'commondir' 'commondir-std' \
> +	'.git' '../.git'
> +
> +test_repo_info_path 'commondir with GIT_COMMON_DIR and GIT_DIR' 'commondir' \
> +	'commondir-envs' 'custom-common' '../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' \
> +	'commondir-only-gitdir' '.git' '../.git' \

For each of these test cases, the `expect_absolute_suffix` and
`expect_relative` and exactly the same. This also appears to be the case
for the test cases in the next patch. Do these really need to be
configurable at all? Can we just embed them directly in each test case
assertion? Or maybe future keys will need this to be configurable?

> +	'GIT_DIR="../.git" && export GIT_DIR'
> +
>  test_done

Overall the rest of the patch looks good to me.

-Justin

^ permalink raw reply

* Re: [PATCH] builtin/history: unuse the commit buffer after use
From: Jeff King @ 2026-06-15 17:29 UTC (permalink / raw)
  To: Patrick Steinhardt; +Cc: Kaartic Sivaraam, Git mailing list
In-Reply-To: <ai_KWo9o1Fhc6OFs@pks.im>

On Mon, Jun 15, 2026 at 11:48:10AM +0200, Patrick Steinhardt wrote:

> On Sun, Jun 14, 2026 at 02:15:40PM +0000, Kaartic Sivaraam wrote:
> > While running `git history reword` using a Git built with `SANITIZE` flag set
> > to `address,leak`, we could observe the following leak being reported:
> 
> Huh, curious. That seems to hint that we're missing test coverage for
> this specific scenario, as our test suite doesn't detect this leak.

I think it will only leak when the commit object has an "encoding"
header. See below.

> > As part of rewording a commit in `git history`, we get the commit message
> > buffer in the `commit_tree_ext` function. This in turn obtains the buffer
> > from `repo_logmsg_reencode`. Given how `commit_tree_ext` is invoking the
> > function with the last two parameters as NULL, we are clearly not expecting
> > a reencode to happen. In this case, the buffer that we receive from
> > `repo_logmsg_reencode` ends up always being obtained from a call to
> > `repo_get_commit_buffer`.
> > 
> > This buffer is expected to be released with an accompanying call to
> > `repo_unuse_commit_buffer` which takes care of freeing it. This call
> > is missing in the `commit_tree_ext` flow thus resulting in the leak.
> 
> So this doesn't really read specific at all, and I would have expected
> us to hit this leak. Puzzling.

The first paragraph is accurate here. We'd generally just get a pointer
to the buffer cached in the slab, because no re-encoding occurs. And in
that case you _don't_ need to call unuse_commit_buffer(), because you
have a read-only copy, and the slab cache will hold it forever[1].
Calling the unuse function will be a noop.

But when we _do_ re-encode, then you get a new buffer which must be
freed. And that is when you have to call the unuse function. And the
reason it is "unuse" and not just "free" is that you don't necessarily
know which you have, but that function figures it out (and frees it only
if necessary).

So what the patch is doing is correct, but the explanation is a little
confused. We see the leak only when re-encoding, so we'd probably want a
test case that triggers that. Which I assume implies rewriting a commit
that was previously generated with an encoding header.

Now back to that [1] note. Even if we didn't re-encode, we'll still hold
onto that buffer forever. It's not a "leak" in the traditional sense
because it's still referenced in the commit slab cache. But if you are
going to walk over a million commits (like git-log does), you probably
don't want to hold a million commit messages in memory at once.

For that you'd want to call free_commit_buffer() when you know you're
totally done with it (again, like git-log does after it finishes showing
the commit). That might be the case here in commit_tree_ext(), or it
might happen later (I'm not familiar with the git-history code).

But note that you need to do _both_ the unuse and free calls. If we did
re-encode, the former is needed to free the newly allocated buffer. The
latter only drops the original buffer in the cache.

-Peff

^ permalink raw reply

* Re: [GSoC Patch v4 2/4] rev-parse: use append_formatted_path() for path formatting
From: Justin Tobler @ 2026-06-15 17:18 UTC (permalink / raw)
  To: K Jayatheerth
  Cc: git, a3205153416, gitster, kumarayushjha123, lucasseikioshiro,
	phillip.wood, sandals, kristofferhaugsbakk
In-Reply-To: <20260615045112.50686-3-jayatheerthkulkarni2005@gmail.com>

On 26/06/15 10:21AM, K Jayatheerth wrote:
[snip]
> -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)
>  {
> -	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;
> -		}
> -		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);
> +	struct strbuf sb = STRBUF_INIT;
> +	enum path_format fmt = (arg_path_format != PATH_FORMAT_DEFAULT) ? arg_path_format : def_format;

Without context, it might be a bit confusing to readers as to why we
override PATH_FORMAT_DEFAULT without our own provided default. It may be
worth leaving a comment to provide some breadcrumbs.

The rest of this patch looks good to me.

-Justin

^ permalink raw reply

* Re: [PATCH] doc: fix a small, old release notes typo
From: Jeff King @ 2026-06-15 17:14 UTC (permalink / raw)
  To: D. Ben Knoble; +Cc: git, Elijah Newren
In-Reply-To: <645638cd87d6d919af6d4310be8176d49fba326e.1781456960.git.ben.knoble+github@gmail.com>

On Sun, Jun 14, 2026 at 01:28:31PM -0400, D. Ben Knoble wrote:

> No harm done if you choose not to keep this, I think. Stumbled upon it when
> trying to understand Elijah's message [1] about timestamp_t overflowing in 2106
> (I though 32-bit time_t overflowed in 2038, but timestamp_t is something
> different… except maybe when it's not? Anyway…)

Leaving aside the patch for a moment, the answer to your timestamp
question is: signed 32-bit takes us to 2038 (and back to 1902), but
unsigned goes to 2106 (but only back to 1970).

Usually time_t is signed, but our timestamp_t is not, mostly for
historical reasons. And timestamp_t itself is our local invention
because we have no control over the definition of time_t (but we still
end up needing it to call system date functions).

I have some patches to allow negative timestamps, but I ran into
portability issues. IIRC, Windows gmtime() chokes on negative
timestamps.

It hasn't been a big deal in practice since new commits made today will
always have a positive epoch. But negative timestamps would allow
importing some historical projects (like Apollo mission code), as well
as weird (ab)uses of Git to store historical documents (like legal code
going back centuries).

-Peff

^ permalink raw reply

* Re: [PATCH] cat-file: speed up default format
From: Jeff King @ 2026-06-15 17:06 UTC (permalink / raw)
  To: René Scharfe; +Cc: Git List
In-Reply-To: <20260615165326.GA91269@coredump.intra.peff.net>

On Mon, Jun 15, 2026 at 12:53:26PM -0400, Jeff King wrote:

> It uses per-atom callback functions which is nice and clean, though we
> might be able to do even better with a big ugly switch() statement.

Being the curious sort, I swapped it out for a big switch statement.
Patch below, but it does not seem to be any faster.

So the bottom line is I think you could gain a little bit of performance
by pre-parsing (versus strbuf_expand() on each object). Around 3% for
something that actually looks at the objects, though more like 15% if
for just dumping the objectnames.

IMHO that is probably not worth it for a custom parsing system just for
cat-file.  But if we were to finally unify ref-filter and cat-file (and
even --pretty=format) then it would probably worth doing this kind of
pre-parsing.

---
diff --git a/builtin/cat-file.c b/builtin/cat-file.c
index 9cc7ec7a6f..da6ecc61f9 100644
--- a/builtin/cat-file.c
+++ b/builtin/cat-file.c
@@ -321,7 +321,17 @@ struct expand_data {
 #define EXPAND_DATA_INIT  { .mode = S_IFINVALID }
 
 struct format_item {
-	void (*add)(struct format_item *item, struct strbuf *sb, struct expand_data *data);
+	enum {
+		FORMAT_TYPE_END = 0,
+		FORMAT_TYPE_LITERAL,
+		FORMAT_TYPE_OBJECTNAME,
+		FORMAT_TYPE_OBJECTTYPE,
+		FORMAT_TYPE_OBJECTSIZE,
+		FORMAT_TYPE_OBJECTSIZE_DISK,
+		FORMAT_TYPE_REST,
+		FORMAT_TYPE_DELTABASE,
+		FORMAT_TYPE_OBJECTMODE,
+	} type;
 	union {
 		struct {
 			const char *p;
@@ -336,55 +346,6 @@ struct format_item {
 	 */
 };
 
-static void objectname_add(struct format_item *item UNUSED,
-			   struct strbuf *sb, struct expand_data *data)
-{
-	strbuf_add_oid_hex(sb, &data->oid);
-}
-
-static void objecttype_add(struct format_item *item UNUSED,
-			   struct strbuf *sb, struct expand_data *data)
-{
-	strbuf_addstr(sb, type_name(data->type));
-}
-
-static void objectsize_add(struct format_item *item UNUSED,
-			   struct strbuf *sb, struct expand_data *data)
-{
-	strbuf_add_uint(sb, data->size);
-}
-
-static void objectsize_disk_add(struct format_item *item UNUSED,
-				struct strbuf *sb, struct expand_data *data)
-{
-	strbuf_add_uint(sb, data->disk_size);
-}
-
-static void rest_add(struct format_item *item UNUSED,
-		     struct strbuf *sb, struct expand_data *data)
-{
-	strbuf_addstr(sb, data->rest);
-}
-
-static void deltabase_add(struct format_item *item UNUSED,
-			  struct strbuf *sb, struct expand_data *data)
-{
-	strbuf_add_oid_hex(sb, &data->delta_base_oid);
-}
-
-static void objectmode_add(struct format_item *item UNUSED,
-			   struct strbuf *sb, struct expand_data *data)
-{
-	if (data->mode != S_IFINVALID)
-		strbuf_addf(sb, "%06o", data->mode);
-}
-
-static void literal_add(struct format_item *item,
-			struct strbuf *sb, struct expand_data *data UNUSED)
-{
-	strbuf_add(sb, item->u.literal.p, item->u.literal.len);
-}
-
 static int is_atom(const char *atom, const char *s, int slen)
 {
 	int alen = strlen(atom);
@@ -395,24 +356,24 @@ static int parse_atom(struct format_item *fmt, const char *atom, int len,
 		      struct expand_data *data)
 {
 	if (is_atom("objectname", atom, len)) {
-		fmt->add = objectname_add;
+		fmt->type = FORMAT_TYPE_OBJECTNAME;
 	} else if (is_atom("objecttype", atom, len)) {
 		data->info.typep = &data->type;
-		fmt->add = objecttype_add;
+		fmt->type = FORMAT_TYPE_OBJECTTYPE;
 	} else if (is_atom("objectsize", atom, len)) {
 		data->info.sizep = &data->size;
-		fmt->add = objectsize_add;
+		fmt->type = FORMAT_TYPE_OBJECTSIZE;
 	} else if (is_atom("objectsize:disk", atom, len)) {
 		data->info.disk_sizep = &data->disk_size;
-		fmt->add = objectsize_disk_add;
+		fmt->type = FORMAT_TYPE_OBJECTSIZE_DISK;
 	} else if (is_atom("rest", atom, len)) {
 		data->split_on_whitespace = 1;
-		fmt->add = rest_add;
+		fmt->type = FORMAT_TYPE_REST;
 	} else if (is_atom("deltabase", atom, len)) {
 		data->info.delta_base_oid = &data->delta_base_oid;
-		fmt->add = deltabase_add;
+		fmt->type = FORMAT_TYPE_DELTABASE;
 	} else if (is_atom("objectmode", atom, len)) {
-		fmt->add = objectmode_add;
+		fmt->type = FORMAT_TYPE_OBJECTMODE;
 	} else
 		return 0;
 	return 1;
@@ -430,7 +391,7 @@ static struct format_item *parse_format(const char *start,
 
 		if (percent != start) {
 			ALLOC_GROW(ret, nr + 1, alloc);
-			ret[nr].add = literal_add;
+			ret[nr].type = FORMAT_TYPE_LITERAL;
 			ret[nr].u.literal.p = start;
 			ret[nr].u.literal.len = percent - start;
 			nr++;
@@ -443,7 +404,7 @@ static struct format_item *parse_format(const char *start,
 
 		ALLOC_GROW(ret, nr + 1, alloc);
 		if (skip_prefix(start, "%", &start) || *start != '(') {
-			ret[nr].add = literal_add;
+			ret[nr].type = FORMAT_TYPE_LITERAL;
 			ret[nr].u.literal.p = "%";
 			ret[nr].u.literal.len = 1;
 		} else if ((end = strchr(start + 1, ')')) &&
@@ -456,16 +417,44 @@ static struct format_item *parse_format(const char *start,
 	}
 
 	ALLOC_GROW(ret, nr + 1, alloc);
-	ret[nr].add = NULL;
+	ret[nr].type = FORMAT_TYPE_END;
 
 	return ret;
 }
 
 static void expand_format(struct strbuf *sb, struct format_item *fmt,
 			  struct expand_data *data)
 {
-	for (; fmt->add; fmt++)
-		fmt->add(fmt, sb, data);
+	for (; fmt->type; fmt++)
+		switch (fmt->type) {
+		case FORMAT_TYPE_END:
+			BUG("we should have already left the loop!");
+			break;
+		case FORMAT_TYPE_OBJECTNAME:
+			strbuf_add_oid_hex(sb, &data->oid);
+			break;
+		case FORMAT_TYPE_OBJECTTYPE:
+			strbuf_addstr(sb, type_name(data->type));
+			break;
+		case FORMAT_TYPE_OBJECTSIZE:
+			strbuf_add_uint(sb, data->size);
+			break;
+		case FORMAT_TYPE_OBJECTSIZE_DISK:
+			strbuf_add_uint(sb, data->disk_size);
+			break;
+		case FORMAT_TYPE_REST:
+			strbuf_addstr(sb, data->rest);
+			break;
+		case FORMAT_TYPE_DELTABASE:
+			strbuf_add_oid_hex(sb, &data->delta_base_oid);
+			break;
+		case FORMAT_TYPE_OBJECTMODE:
+			if (data->mode != S_IFINVALID)
+				strbuf_addf(sb, "%06o", data->mode);
+			break;
+		case FORMAT_TYPE_LITERAL:
+			strbuf_add(sb, fmt->u.literal.p, fmt->u.literal.len);
+		}
 }
 
 static void batch_write(struct batch_options *opt, const void *data, int len)

^ permalink raw reply related

* Re: [PATCH v3] update-ref: add --rename option
From: Junio C Hamano @ 2026-06-15 17:03 UTC (permalink / raw)
  To: Patrick Steinhardt; +Cc: git
In-Reply-To: <ai--jEk23E7RJPnc@pks.im>

Patrick Steinhardt <ps@pks.im> writes:

> On Fri, Jun 12, 2026 at 08:41:48AM -0700, Junio C Hamano wrote:
>> Patrick Steinhardt <ps@pks.im> writes:
>> 
>> > A slight tangent: this is part of why I really don't like commands that
>> > determine their mode via flags: you now have to worry about every
>> > combination of flags and whether they even make sense. With subcommands
>> > we at least only have to worry about the set of flags that directly
>> > apply to that given subcommand.
>> >
>> > Makes me wonder whether I should have a look at extending git-refs(1)
>> > further:
>> >
>> >     git refs delete <ref> [<oldvalue>]
>> >     git refs update <ref> <newvalue> [<oldvalue>]
>> >     git refs rename <ref> <oldname> <newname>
>> >
>> > I always wanted to do this eventually so that we have one top-level
>> > command that knows how to do "everything refs".
>> 
>> That may indeed be a better direction to go, but isn't update-ref
>> the "everything refs" command already?
>
> Well, it doesn't handle reading references, which is something that
> git-refs(1) already knows to do.

OK, fair enough.  "git refs" should become superset of "git
show-ref" and "git update-ref", I guess.

So do you want to take the topic over and add it to "git refs"?


^ permalink raw reply

* Re: [PATCH] cat-file: speed up default format
From: Jeff King @ 2026-06-15 16:53 UTC (permalink / raw)
  To: René Scharfe; +Cc: Git List
In-Reply-To: <5a7ed929-6fe0-496c-83bd-65dee57c2241@web.de>

On Sun, Jun 14, 2026 at 06:28:34PM +0200, René Scharfe wrote:

> eb54a3391b (cat-file: skip expanding default format, 2022-03-15) added
> special handling for the default batch format.  In the meantime it has
> fallen behind the code path for handling arbitrary formats.  Bring it up
> to speed by using the new and more efficient strbuf_add_oid_hex() and
> strbuf_add_uint() instead of strbuf_addf():
> 
> Benchmark 1: ./git_main cat-file --batch-all-objects --batch-check='%(objectname) %(objecttype) %(objectsize)'
>   Time (mean ± σ):      1.051 s ±  0.003 s    [User: 1.027 s, System: 0.023 s]
>   Range (min … max):    1.049 s …  1.058 s    10 runs
> 
> Benchmark 2: ./git_main cat-file --batch-all-objects --batch-check='%(objectname)-%(objecttype)-%(objectsize)'
>   Time (mean ± σ):      1.012 s ±  0.002 s    [User: 0.988 s, System: 0.023 s]
>   Range (min … max):    1.010 s …  1.018 s    10 runs
> 
> Benchmark 3: ./git cat-file --batch-all-objects --batch-check='%(objectname) %(objecttype) %(objectsize)'
>   Time (mean ± σ):     979.0 ms ±   1.1 ms    [User: 954.1 ms, System: 23.2 ms]
>   Range (min … max):   977.7 ms … 980.8 ms    10 runs

Interesting that it was actually slower than a custom format.  Using the
default format saves the cost of strbuf_expand(), but it was paying the
price of strbuf_addf(), which the custom path no longer used. So the
cost of strbuf_addf() is more than strbuf_expand(), which is not all
that surprising.

Your patch seems obviously right, and everything below is idle
speculation / nerd-sniping.

I have long wondered if we could do better with a separate initial parse
step, which would let us walk the parse tree for each object. In theory
that tree is more compact.

I think it would be a huge improvement for ref-filter, whose parser is
complicated and slow (though its biggest sin is that it allocates a
separate string for each atom before assembling the final output). But
could it help even cat-file, which is using a pretty tight loop over
strbuf_expand()? I sketched out a rough draft below.

It uses per-atom callback functions which is nice and clean, though we
might be able to do even better with a big ugly switch() statement.

The timings I got are below (git.old is master with your patch here
applied, and git.new is my patch on top). It looks like it does make a
custom format ~3% faster. But it's still a shade slower than the default
format. Not sure if it's the extra function calls, or if the static
print_default_format() function gives the compiler more opportunities
for optimization.

  Benchmark 1: ./git.old cat-file --batch-all-objects --batch-check='%(objectname) %(objecttype) %(objectsize)'
    Time (mean ± σ):     580.2 ms ±   5.0 ms    [User: 558.7 ms, System: 21.5 ms]
    Range (min … max):   569.9 ms … 585.6 ms    10 runs
  
  Benchmark 2: ./git.new cat-file --batch-all-objects --batch-check='%(objectname) %(objecttype) %(objectsize)'
    Time (mean ± σ):     580.4 ms ±   5.1 ms    [User: 562.7 ms, System: 17.8 ms]
    Range (min … max):   571.8 ms … 587.0 ms    10 runs
  
  Benchmark 3: ./git.old cat-file --batch-all-objects --batch-check='%(objectname)-%(objecttype)-%(objectsize)'
    Time (mean ± σ):     618.6 ms ±   5.0 ms    [User: 598.9 ms, System: 19.7 ms]
    Range (min … max):   613.6 ms … 628.3 ms    10 runs
  
  Benchmark 4: ./git.new cat-file --batch-all-objects --batch-check='%(objectname)-%(objecttype)-%(objectsize)'
    Time (mean ± σ):     600.2 ms ±   4.2 ms    [User: 581.2 ms, System: 19.0 ms]
    Range (min … max):   595.2 ms … 608.8 ms    10 runs
  
  Summary
    ./git.old cat-file --batch-all-objects --batch-check='%(objectname) %(objecttype) %(objectsize)' ran
      1.00 ± 0.01 times faster than ./git.new cat-file --batch-all-objects --batch-check='%(objectname) %(objecttype) %(objectsize)'
      1.03 ± 0.01 times faster than ./git.new cat-file --batch-all-objects --batch-check='%(objectname)-%(objecttype)-%(objectsize)'
      1.07 ± 0.01 times faster than ./git.old cat-file --batch-all-objects --batch-check='%(objectname)-%(objecttype)-%(objectsize)'

Patch below, only lightly tested.

---
diff --git a/builtin/cat-file.c b/builtin/cat-file.c
index d7f7895e30..9cc7ec7a6f 100644
--- a/builtin/cat-file.c
+++ b/builtin/cat-file.c
@@ -36,6 +36,8 @@ enum batch_mode {
 	BATCH_MODE_QUEUE_AND_DISPATCH,
 };
 
+struct format_item;
+
 struct batch_options {
 	struct list_objects_filter_options objects_filter;
 	int enabled;
@@ -48,6 +50,7 @@ struct batch_options {
 	char input_delim;
 	char output_delim;
 	const char *format;
+	struct format_item *parsed_format;
 };
 
 static const char *force_path;
@@ -294,12 +297,6 @@ struct expand_data {
 	const char *rest;
 	struct object_id delta_base_oid;
 
-	/*
-	 * If mark_query is true, we do not expand anything, but rather
-	 * just mark the object_info with items we wish to query.
-	 */
-	int mark_query;
-
 	/*
 	 * Whether to split the input on whitespace before feeding it to
 	 * get_sha1; this is decided during the mark_query phase based on
@@ -323,65 +320,152 @@ struct expand_data {
 };
 #define EXPAND_DATA_INIT  { .mode = S_IFINVALID }
 
+struct format_item {
+	void (*add)(struct format_item *item, struct strbuf *sb, struct expand_data *data);
+	union {
+		struct {
+			const char *p;
+			size_t len;
+		} literal;
+	} u;
+	/*
+	 * We could make a true tree here with child/next pointers, which would
+	 * be necessary if we had recursive formats, like %(if). But for our
+	 * simple formats for now it is enough to have a linear set of items,
+	 * so we'll just allocate an array and terminate it with a NULL entry.
+	 */
+};
+
+static void objectname_add(struct format_item *item UNUSED,
+			   struct strbuf *sb, struct expand_data *data)
+{
+	strbuf_add_oid_hex(sb, &data->oid);
+}
+
+static void objecttype_add(struct format_item *item UNUSED,
+			   struct strbuf *sb, struct expand_data *data)
+{
+	strbuf_addstr(sb, type_name(data->type));
+}
+
+static void objectsize_add(struct format_item *item UNUSED,
+			   struct strbuf *sb, struct expand_data *data)
+{
+	strbuf_add_uint(sb, data->size);
+}
+
+static void objectsize_disk_add(struct format_item *item UNUSED,
+				struct strbuf *sb, struct expand_data *data)
+{
+	strbuf_add_uint(sb, data->disk_size);
+}
+
+static void rest_add(struct format_item *item UNUSED,
+		     struct strbuf *sb, struct expand_data *data)
+{
+	strbuf_addstr(sb, data->rest);
+}
+
+static void deltabase_add(struct format_item *item UNUSED,
+			  struct strbuf *sb, struct expand_data *data)
+{
+	strbuf_add_oid_hex(sb, &data->delta_base_oid);
+}
+
+static void objectmode_add(struct format_item *item UNUSED,
+			   struct strbuf *sb, struct expand_data *data)
+{
+	if (data->mode != S_IFINVALID)
+		strbuf_addf(sb, "%06o", data->mode);
+}
+
+static void literal_add(struct format_item *item,
+			struct strbuf *sb, struct expand_data *data UNUSED)
+{
+	strbuf_add(sb, item->u.literal.p, item->u.literal.len);
+}
+
 static int is_atom(const char *atom, const char *s, int slen)
 {
 	int alen = strlen(atom);
 	return alen == slen && !memcmp(atom, s, alen);
 }
 
-static int expand_atom(struct strbuf *sb, const char *atom, int len,
-		       struct expand_data *data)
+static int parse_atom(struct format_item *fmt, const char *atom, int len,
+		      struct expand_data *data)
 {
 	if (is_atom("objectname", atom, len)) {
-		if (!data->mark_query)
-			strbuf_add_oid_hex(sb, &data->oid);
+		fmt->add = objectname_add;
 	} else if (is_atom("objecttype", atom, len)) {
-		if (data->mark_query)
-			data->info.typep = &data->type;
-		else
-			strbuf_addstr(sb, type_name(data->type));
+		data->info.typep = &data->type;
+		fmt->add = objecttype_add;
 	} else if (is_atom("objectsize", atom, len)) {
-		if (data->mark_query)
-			data->info.sizep = &data->size;
-		else
-			strbuf_add_uint(sb, data->size);
+		data->info.sizep = &data->size;
+		fmt->add = objectsize_add;
 	} else if (is_atom("objectsize:disk", atom, len)) {
-		if (data->mark_query)
-			data->info.disk_sizep = &data->disk_size;
-		else
-			strbuf_add_uint(sb, data->disk_size);
+		data->info.disk_sizep = &data->disk_size;
+		fmt->add = objectsize_disk_add;
 	} else if (is_atom("rest", atom, len)) {
-		if (data->mark_query)
-			data->split_on_whitespace = 1;
-		else if (data->rest)
-			strbuf_addstr(sb, data->rest);
+		data->split_on_whitespace = 1;
+		fmt->add = rest_add;
 	} else if (is_atom("deltabase", atom, len)) {
-		if (data->mark_query)
-			data->info.delta_base_oid = &data->delta_base_oid;
-		else
-			strbuf_add_oid_hex(sb, &data->delta_base_oid);
+		data->info.delta_base_oid = &data->delta_base_oid;
+		fmt->add = deltabase_add;
 	} else if (is_atom("objectmode", atom, len)) {
-		if (!data->mark_query && !(S_IFINVALID == data->mode))
-			strbuf_addf(sb, "%06o", data->mode);
+		fmt->add = objectmode_add;
 	} else
 		return 0;
 	return 1;
 }
 
-static void expand_format(struct strbuf *sb, const char *start,
-			  struct expand_data *data)
+static struct format_item *parse_format(const char *start,
+					struct expand_data *data)
 {
-	while (strbuf_expand_step(sb, &start)) {
+	struct format_item *ret = NULL;
+	size_t nr = 0, alloc = 0;
+
+	while (1) {
+		const char *percent = strchrnul(start, '%');
 		const char *end;
 
-		if (skip_prefix(start, "%", &start) || *start != '(')
-			strbuf_addch(sb, '%');
-		else if ((end = strchr(start + 1, ')')) &&
-			 expand_atom(sb, start + 1, end - start - 1, data))
+		if (percent != start) {
+			ALLOC_GROW(ret, nr + 1, alloc);
+			ret[nr].add = literal_add;
+			ret[nr].u.literal.p = start;
+			ret[nr].u.literal.len = percent - start;
+			nr++;
+		}
+
+		if (!*percent)
+			break;
+
+		start = percent + 1;
+
+		ALLOC_GROW(ret, nr + 1, alloc);
+		if (skip_prefix(start, "%", &start) || *start != '(') {
+			ret[nr].add = literal_add;
+			ret[nr].u.literal.p = "%";
+			ret[nr].u.literal.len = 1;
+		} else if ((end = strchr(start + 1, ')')) &&
+			   parse_atom(&ret[nr], start + 1, end - start - 1, data)) {
 			start = end + 1;
-		else
+		} else {
 			strbuf_expand_bad_format(start, "cat-file");
+		}
+		nr++;
 	}
+
+	ALLOC_GROW(ret, nr + 1, alloc);
+	ret[nr].add = NULL;
+
+	return ret;
+}
+
+static void expand_format(struct strbuf *sb, struct format_item *fmt,
+			  struct expand_data *data)
+{
+	for (; fmt->add; fmt++)
+		fmt->add(fmt, sb, data);
 }
 
 static void batch_write(struct batch_options *opt, const void *data, int len)
@@ -568,7 +652,7 @@ static void batch_object_write(const char *obj_name,
 	if (!opt->format) {
 		print_default_format(scratch, data, opt);
 	} else {
-		expand_format(scratch, opt->format, data);
+		expand_format(scratch, opt->parsed_format, data);
 		strbuf_addch(scratch, opt->output_delim);
 	}
 
@@ -936,17 +1020,9 @@ static int batch_objects(struct batch_options *opt)
 	int save_warning;
 	int retval = 0;
 
-	/*
-	 * Expand once with our special mark_query flag, which will prime the
-	 * object_info to be handed to odb_read_object_info_extended for each
-	 * object.
-	 */
-	data.mark_query = 1;
-	expand_format(&output,
-		      opt->format ? opt->format : DEFAULT_FORMAT,
-		      &data);
-	data.mark_query = 0;
-	strbuf_release(&output);
+	opt->parsed_format = parse_format(opt->format ?
+					  opt->format : DEFAULT_FORMAT,
+					  &data);
 	if (opt->transform_mode)
 		data.split_on_whitespace = 1;
 

^ permalink raw reply related

* [PATCH v15 7/7] branch: add --dry-run for --delete-merged
From: Harald Nordgren via GitGitGadget @ 2026-06-15 16:47 UTC (permalink / raw)
  To: git
  Cc: Kristoffer Haugsbakk, Johannes Sixt, Phillip Wood,
	Harald Nordgren, Harald Nordgren
In-Reply-To: <pull.2285.v15.git.git.1781542042.gitgitgadget@gmail.com>

From: Harald Nordgren <haraldnordgren@gmail.com>

With --dry-run, --delete-merged prints the local branches it would
delete, one "Would delete branch <name>" line each, and exits
without touching any ref. The same filtering applies, so the output
is exactly the set that the real run would delete.

--dry-run is only meaningful together with --delete-merged and is
rejected otherwise.

Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
---
 Documentation/git-branch.adoc |  8 ++++++-
 builtin/branch.c              | 13 ++++++++---
 t/t3200-branch.sh             | 44 +++++++++++++++++++++++++++++++++++
 3 files changed, 61 insertions(+), 4 deletions(-)

diff --git a/Documentation/git-branch.adoc b/Documentation/git-branch.adoc
index 91700f2e8a..09063d74f2 100644
--- a/Documentation/git-branch.adoc
+++ b/Documentation/git-branch.adoc
@@ -25,7 +25,7 @@ git branch (-m|-M) [<old-branch>] <new-branch>
 git branch (-c|-C) [<old-branch>] <new-branch>
 git branch (-d|-D) [-r] <branch-name>...
 git branch --edit-description [<branch-name>]
-git branch --delete-merged <branch>...
+git branch [--dry-run] --delete-merged <branch>...
 
 DESCRIPTION
 -----------
@@ -226,6 +226,12 @@ A branch whose work has not yet been merged into its upstream is
 silently skipped. Delete it with `git branch -D` if you want to
 remove it anyway.
 
+`--dry-run`::
+	With `--delete-merged`, print which branches would be
+	deleted and exit without touching any ref.  Useful for
+	sanity-checking a wide pattern like `'origin/*'` before
+	committing to the deletion.
+
 `-v`::
 `-vv`::
 `--verbose`::
diff --git a/builtin/branch.c b/builtin/branch.c
index 0e1e7c2e6f..d18a830249 100644
--- a/builtin/branch.c
+++ b/builtin/branch.c
@@ -716,7 +716,7 @@ static int parse_opt_forked(const struct option *opt, const char *arg, int unset
 }
 
 static int delete_merged_branches(int argc, const char **argv,
-				 int quiet)
+				 int quiet, int dry_run)
 {
 	struct ref_store *refs = get_main_ref_store(the_repository);
 	struct ref_filter filter = REF_FILTER_INIT;
@@ -775,7 +775,8 @@ static int delete_merged_branches(int argc, const char **argv,
 				      FILTER_REFS_BRANCHES,
 				      DELETE_BRANCH_SKIP_UNMERGED |
 				      DELETE_BRANCH_NO_HEAD_FALLBACK |
-				      (quiet ? DELETE_BRANCH_QUIET : 0));
+				      (quiet ? DELETE_BRANCH_QUIET : 0) |
+				      (dry_run ? DELETE_BRANCH_DRY_RUN : 0));
 
 	strvec_clear(&deletable);
 	ref_array_clear(&candidates);
@@ -825,6 +826,7 @@ int cmd_branch(int argc,
 	int delete = 0, rename = 0, copy = 0, list = 0,
 	    unset_upstream = 0, show_current = 0, edit_description = 0;
 	int delete_merged = 0;
+	int dry_run = 0;
 	const char *new_upstream = NULL;
 	int noncreate_actions = 0;
 	/* possible options */
@@ -880,6 +882,8 @@ int cmd_branch(int argc,
 			 N_("edit the description for the branch")),
 		OPT_BOOL(0, "delete-merged", &delete_merged,
 			N_("delete local branches whose upstream matches <branch> and are merged")),
+		OPT_BOOL(0, "dry-run", &dry_run,
+			N_("with --delete-merged, only print which branches would be deleted")),
 		OPT__FORCE(&force, N_("force creation, move/rename, deletion"), PARSE_OPT_NOCOMPLETE),
 		OPT_MERGED(&filter, N_("print only branches that are merged")),
 		OPT_NO_MERGED(&filter, N_("print only branches that are not merged")),
@@ -942,6 +946,9 @@ int cmd_branch(int argc,
 	if (noncreate_actions > 1)
 		usage_with_options(builtin_branch_usage, options);
 
+	if (dry_run && !delete_merged)
+		die(_("--dry-run requires --delete-merged"));
+
 	if (recurse_submodules_explicit) {
 		if (!submodule_propagate_branches)
 			die(_("branch with --recurse-submodules can only be used if submodule.propagateBranches is enabled"));
@@ -981,7 +988,7 @@ int cmd_branch(int argc,
 				      (quiet ? DELETE_BRANCH_QUIET : 0));
 		goto out;
 	} else if (delete_merged) {
-		ret = delete_merged_branches(argc, argv, quiet);
+		ret = delete_merged_branches(argc, argv, quiet, dry_run);
 		goto out;
 	} else if (show_current) {
 		print_current_branch_name();
diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh
index 5ac3c2bb5d..1cb32497b8 100755
--- a/t/t3200-branch.sh
+++ b/t/t3200-branch.sh
@@ -2060,4 +2060,48 @@ test_expect_success 'branch -d still deletes a deleteMerged=false branch' '
 	test_must_fail git -C pm-optout-d rev-parse --verify refs/heads/one
 '
 
+test_expect_success '--delete-merged --dry-run lists but does not delete' '
+	test_when_finished "rm -rf pm-dry" &&
+	git clone pm-upstream pm-dry &&
+	git -C pm-dry remote add fork ../pm-fork &&
+	test_config -C pm-dry remote.pushDefault fork &&
+	test_config -C pm-dry push.default current &&
+	git -C pm-dry branch one one-commit &&
+	git -C pm-dry branch --set-upstream-to=origin/next one &&
+	git -C pm-dry branch two two-commit &&
+	git -C pm-dry branch --set-upstream-to=origin/next two &&
+
+	git -C pm-dry branch --dry-run --delete-merged "origin/*" >actual &&
+	test_grep "Would delete branch one " actual &&
+	test_grep "Would delete branch two " actual &&
+
+	git -C pm-dry rev-parse --verify refs/heads/one &&
+	git -C pm-dry rev-parse --verify refs/heads/two
+'
+
+test_expect_success '--delete-merged --dry-run only lists branches the live run would delete' '
+	test_when_finished "rm -rf pm-dry-mixed" &&
+	git clone pm-upstream pm-dry-mixed &&
+	git -C pm-dry-mixed remote add fork ../pm-fork &&
+	test_config -C pm-dry-mixed remote.pushDefault fork &&
+	test_config -C pm-dry-mixed push.default current &&
+	git -C pm-dry-mixed checkout -b wip origin/next &&
+	git -C pm-dry-mixed branch --set-upstream-to=origin/next wip &&
+	test_commit -C pm-dry-mixed local-only &&
+	git -C pm-dry-mixed checkout - &&
+	git -C pm-dry-mixed branch merged one-commit &&
+	git -C pm-dry-mixed branch --set-upstream-to=origin/next merged &&
+
+	git -C pm-dry-mixed branch --dry-run --delete-merged "origin/*" >out &&
+	test_grep "Would delete branch merged" out &&
+	test_grep ! "Would delete branch wip" out &&
+	git -C pm-dry-mixed rev-parse --verify refs/heads/wip &&
+	git -C pm-dry-mixed rev-parse --verify refs/heads/merged
+'
+
+test_expect_success '--dry-run without --delete-merged is rejected' '
+	test_must_fail git -C forked branch --dry-run 2>err &&
+	test_grep "requires --delete-merged" err
+'
+
 test_done
-- 
gitgitgadget

^ 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