Volume XXII, number 280Wednesday, October 7, 2026Latest message 3 hours ago

The Git List

News and archive of git@vger.kernel.org, since April 2005

patch, 4 partsteach git repo info to handle path keys

82 messages between Jun 1, 2026 and Jun 24, 2026, from K Jayatheerth, Lucas Seiki Oshiro, Junio C Hamano, Phillip Wood, Kristoffer Haugsbakk, Justin Tobler.

Plain Markdown or JSON for tools and agents. Diffs are folded; open one to read it.

K JayatheerthJun 1, 2026, 15:19 UTC on lore
Hi!

The first and second patches are self-explanatory, so I will focus more on the third and fourth patches, which introduce the path-related fields to `git repo info`.

In the last discussion [1] we had on the mailing list about paths in repo info, we didn't reach a definitive conclusion, but adding both options made the most sense based on the feedback.

So in patches 3 and 4, we add both `path.<field>.absolute` and `path.<field>.relative` for `gitdir` and `commondir`. Initially, it was proposed by Ayush to use `path.absolute.<field>`, but this would break the lexicographical order of the internal field array. I tweaked it to place the variant at the end as a suffix instead.

There are still a few open questions that should be addressed by the community. I am tagging members who were involved in the previous discussions:

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

Apologies if I missed anyone; I included everyone who reviewed or participated in the discussions of Eslam's and Lucas's patches.

Questions:
1. Should there still be a --path-format flag?
2. Should we consider a default option?
   Currently we have path.gitdir.absolute; should we consider
   an option where a plain path.gitdir returns some default?
   If yes:
     2.1 Should we keep the default the same as rev-parse? Or
         should either relative or absolute be the default?
     2.2 When printing using --all, should the default be
         printed, or should we print both absolute and
         relative?
3. Is printing both absolute and relative in a single call
   using --all acceptable?
   If no:
     3.1 What's a better approach?

I have discussed these changes with both Justin and Lucas internally. This series is presented to gather opinions from the wider community before moving forward.

K Jayatheerth (4):
  path: add strbuf_add_path for formatting paths
  rev-parse: use strbuf_add_path for path formatting
  repo: add path.gitdir with absolute and relative suffix formatting
  repo: add path.commondir with absolute and relative suffix formatting
 Documentation/git-repo.adoc |  15 ++++++
 builtin/repo.c              |  50 ++++++++++++++++++
 builtin/rev-parse.c         | 100 ++++++++----------------------------
 path.c                      |  58 +++++++++++++++++++++
 path.h                      |  16 ++++++
 t/t1900-repo-info.sh        |  32 ++++++++++++
 6 files changed, 192 insertions(+), 79 deletions(-)
-- 
2.54.0
K JayatheerthJun 1, 2026, 15:19 UTC in reply to K Jayatheerth on lore

[GSoC][PATCH 1/4] path: add strbuf_add_path for formatting paths

The `print_path()` function in `builtin/rev-parse.c` contains logic for formatting paths as either absolute or relative based on user preferences and default behaviors. However, this logic is currently locked inside `rev-parse` and writes directly to stdout using `puts()`.

To allow other builtins (such as the new `git repo` command) to utilize this same path-formatting logic, extract the core algorithm into a new string-builder function, `strbuf_add_path()`, in `path.c`.

Additionally, extract the associated enums (`format_type` and `default_type`), and prefix them with `path_` (e.g., `path_format_type`) to safely expose them in `path.h` without polluting the global namespace.

Signed-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>
Mentored-by: Justin Tobler <jltobler@gmail.com>
Mentored-by: Lucas Seiki Oshiro <lucasseikioshiro@gmail.com>
---
 path.c | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 path.h | 16 ++++++++++++++++
 2 files changed, 74 insertions(+)
Show changes to 2 files +74 −0

path.c, path.h

diff --git a/path.c b/path.c
index d7e17bf174..914812320f 100644
--- a/path.c
+++ b/path.c
@@ -1579,6 +1579,64 @@ char *xdg_cache_home(const char *filename)
 	return NULL;
 }
 
+void strbuf_add_path(struct strbuf *sb, const char *path, const char *prefix,
+		     enum path_format_type format, enum path_default_type def)
+{
+	char *cwd = NULL;
+
+	/*
+	 * We don't ever produce a relative path if prefix is NULL, so set the
+	 * prefix to the current directory so that we can produce a relative
+	 * path whenever possible. If we're using RELATIVE_IF_SHARED mode, then
+	 * we want an absolute path unless the two share a common prefix, so don't
+	 * set it in that case, since doing so causes a relative path to always
+	 * be produced if possible.
+	 */
+	if (!prefix && (format != PATH_FORMAT_DEFAULT || def != PATH_DEFAULT_RELATIVE_IF_SHARED))
+		prefix = cwd = xgetcwd();
+
+	if (format == PATH_FORMAT_DEFAULT && def == PATH_DEFAULT_UNMODIFIED) {
+		/* Case 1: Return the path exactly as-is without modifications */
+		strbuf_addstr(sb, path);
+	} else if (format == PATH_FORMAT_RELATIVE ||
+		   (format == PATH_FORMAT_DEFAULT && def == PATH_DEFAULT_RELATIVE)) {
+		/*
+		 * Case 2: Explicitly or implicitly relative.
+		 * inside relative_path(), both targets must be absolute paths
+		 * to compute a reliable relative tracking offset.
+		 */
+		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;
+		}
+
+		strbuf_addstr(sb, relative_path(path, prefix, &buf));
+
+		strbuf_release(&buf);
+		strbuf_release(&realbuf);
+		strbuf_release(&prefixbuf);
+	} else if (format == PATH_FORMAT_DEFAULT && def == PATH_DEFAULT_RELATIVE_IF_SHARED) {
+		/* Case 3: Relative format if they share a common root pathway */
+		struct strbuf buf = STRBUF_INIT;
+		strbuf_addstr(sb, relative_path(path, prefix, &buf));
+		strbuf_release(&buf);
+	} else {
+		/* Case 4: Forced absolute / canonical format optimization */
+		struct strbuf buf = STRBUF_INIT;
+		strbuf_realpath_forgiving(&buf, path, 1);
+		strbuf_addbuf(sb, &buf);
+		strbuf_release(&buf);
+	}
+
+	free(cwd);
+}
+
 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..b9b626ce4a 100644
--- a/path.h
+++ b/path.h
@@ -262,6 +262,22 @@ enum scld_error safe_create_leading_directories_no_share(char *path);
 int safe_create_file_with_leading_directories(struct repository *repo,
 					      const char *path);
 
+enum path_format_type {
+	PATH_FORMAT_DEFAULT,
+	PATH_FORMAT_RELATIVE,
+	PATH_FORMAT_CANONICAL
+};
+
+enum path_default_type {
+	PATH_DEFAULT_RELATIVE,
+	PATH_DEFAULT_RELATIVE_IF_SHARED,
+	PATH_DEFAULT_CANONICAL,
+	PATH_DEFAULT_UNMODIFIED
+};
+
+void strbuf_add_path(struct strbuf *buf, const char *path, const char *prefix,
+		     enum path_format_type format, enum path_default_type def);
+
 # ifdef USE_THE_REPOSITORY_VARIABLE
 #  include "strbuf.h"
 #  include "repository.h"
-- 
2.54.0
K JayatheerthJun 1, 2026, 15:19 UTC in reply to K Jayatheerth on lore

[GSoC][PATCH 2/4] rev-parse: use strbuf_add_path for path formatting

Now that the core path-formatting logic has been abstracted into strbuf_add_path() inside path.c, remove the duplicate localized implementation from builtin/rev-parse.c.

Drop the local format_type and default_type enums from the builtin, and update print_path() to act as a light wrapper around the new shared strbuf engine. Update cmd_rev_parse() to use the new path_ format and default enum types exposed via path.h.

Signed-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>
Mentored-by: Justin Tobler <jltobler@gmail.com>
Mentored-by: Lucas Seiki Oshiro <lucasseikioshiro@gmail.com>
---
 builtin/rev-parse.c | 100 ++++++++++----------------------------------
 1 file changed, 21 insertions(+), 79 deletions(-)
Show changes to builtin/rev-parse.c +21 −79
diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c
index 218b5f34d6..812cfd55ad 100644
--- a/builtin/rev-parse.c
+++ b/builtin/rev-parse.c
@@ -632,73 +632,15 @@ 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_type format, enum path_default_type def)
 {
-	char *cwd = NULL;
-	/*
-	 * We don't ever produce a relative path if prefix is NULL, so set the
-	 * prefix to the current directory so that we can produce a relative
-	 * path whenever possible.  If we're using RELATIVE_IF_SHARED mode, then
-	 * we want an absolute path unless the two share a common prefix, so don't
-	 * set it in that case, since doing so causes a relative path to always
-	 * be produced if possible.
-	 */
-	if (!prefix && (format != FORMAT_DEFAULT || def != DEFAULT_RELATIVE_IF_SHARED))
-		prefix = cwd = xgetcwd();
-	if (format == FORMAT_DEFAULT && def == DEFAULT_UNMODIFIED) {
-		puts(path);
-	} else if (format == FORMAT_RELATIVE ||
-		  (format == FORMAT_DEFAULT && def == DEFAULT_RELATIVE)) {
-		/*
-		 * In order for relative_path to work as expected, we need to
-		 * make sure that both paths are absolute paths.  If we don't,
-		 * we can end up with an unexpected absolute path that the user
-		 * didn't want.
-		 */
-		struct strbuf buf = STRBUF_INIT, realbuf = STRBUF_INIT, prefixbuf = STRBUF_INIT;
-		if (!is_absolute_path(path)) {
-			strbuf_realpath_forgiving(&realbuf, path,  1);
-			path = realbuf.buf;
-		}
-		if (!is_absolute_path(prefix)) {
-			strbuf_realpath_forgiving(&prefixbuf, prefix, 1);
-			prefix = prefixbuf.buf;
-		}
-		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;
+
+	strbuf_add_path(&sb, path, prefix, format, def);
+	puts(sb.buf);
+
+	strbuf_release(&sb);
 }
 
 int cmd_rev_parse(int argc,
@@ -717,7 +659,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_type format = PATH_FORMAT_DEFAULT;
 
 	show_usage_if_asked(argc, argv, builtin_rev_parse_usage);
 
@@ -798,7 +740,7 @@ int cmd_rev_parse(int argc,
 				print_path(repo_git_path_replace(the_repository, &buf,
 								 "%s", argv[i + 1]), prefix,
 						format,
-						DEFAULT_RELATIVE_IF_SHARED);
+						PATH_DEFAULT_RELATIVE_IF_SHARED);
 				i++;
 				continue;
 			}
@@ -820,9 +762,9 @@ int cmd_rev_parse(int argc,
 				if (!arg)
 					die(_("--path-format requires an argument"));
 				if (!strcmp(arg, "absolute")) {
-					format = FORMAT_CANONICAL;
+					format = PATH_FORMAT_CANONICAL;
 				} else if (!strcmp(arg, "relative")) {
-					format = FORMAT_RELATIVE;
+					format = PATH_FORMAT_RELATIVE;
 				} else {
 					die(_("unknown argument to --path-format: %s"), arg);
 				}
@@ -985,7 +927,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, format, PATH_DEFAULT_UNMODIFIED);
 				else
 					die(_("this operation must be run in a work tree"));
 				continue;
@@ -993,7 +935,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, format, PATH_DEFAULT_UNMODIFIED);
 				strbuf_release(&superproject);
 				continue;
 			}
@@ -1028,18 +970,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_type wanted = format;
 				if (arg[2] == 'g') {	/* --git-dir */
 					if (gitdir) {
-						print_path(gitdir, prefix, format, DEFAULT_UNMODIFIED);
+						print_path(gitdir, prefix, format, PATH_DEFAULT_UNMODIFIED);
 						continue;
 					}
 					if (!prefix) {
-						print_path(".git", prefix, format, DEFAULT_UNMODIFIED);
+						print_path(".git", prefix, format, PATH_DEFAULT_UNMODIFIED);
 						continue;
 					}
 				} else {		/* --absolute-git-dir */
-					wanted = FORMAT_CANONICAL;
+					wanted = PATH_FORMAT_CANONICAL;
 					if (!gitdir && !prefix)
 						gitdir = ".git";
 					if (gitdir) {
@@ -1055,11 +997,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_DEFAULT_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, format, PATH_DEFAULT_RELATIVE_IF_SHARED);
 				continue;
 			}
 			if (!strcmp(arg, "--is-inside-git-dir")) {
@@ -1089,7 +1031,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, format, PATH_DEFAULT_RELATIVE);
 				}
 				continue;
 			}
-- 
2.54.0
K JayatheerthJun 1, 2026, 15:19 UTC in reply to K Jayatheerth on lore

[GSoC][PATCH 3/4] repo: add path.gitdir with absolute and relative suffix formatting

Introduce path-related metadata fields to `git repo info` by adding explicit `path.gitdir.absolute` and `path.gitdir.relative` keys. This replaces dynamic prefix parsing machinery with individual, predictable lexicographically-sorted keys that map directly to dedicated formatting callbacks.

To calculate paths relative to the current working directory, update `builtin/repo.c` to include `setup.h` and supply `startup_info->prefix` to the path-formatting engine. Both explicit variants automatically populate bulk dumps via `--all` and output predictably under `--keys`.

Update `t/t1900-repo-info.sh` to use a modernized, function-based loop helper (`test_repo_info_path`) and `test_grep` to cleanly assert separate path variation lookups.

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

Documentation/git-repo.adoc, builtin/repo.c, t/t1900-repo-info.sh

diff --git a/Documentation/git-repo.adoc b/Documentation/git-repo.adoc
index 42262c1983..a0dca7ce88 100644
--- a/Documentation/git-repo.adoc
+++ b/Documentation/git-repo.adoc
@@ -104,6 +104,12 @@ values that they return:
 `object.format`::
 	The object format (hash algorithm) used in the repository.
 
+`path.gitdir.absolute`::
+	The canonical absolute path to the Git repository directory (the `.git` directory).
+
+`path.gitdir.relative`::
+	The path to the Git repository directory relative to the current working directory.
+
 `references.format`::
 	The reference storage format. The valid values are:
 +
diff --git a/builtin/repo.c b/builtin/repo.c
index 71a5c1c29c..c141ef892a 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_gitdir_absolute(struct repository *repo, struct strbuf *buf)
+{
+	const char *git_dir = repo_get_git_dir(repo);
+
+	if (!git_dir)
+		return error(_("unable to get git directory"));
+
+	strbuf_add_path(buf, git_dir, startup_info->prefix, PATH_FORMAT_CANONICAL, PATH_DEFAULT_UNMODIFIED);
+	return 0;
+}
+
+static int get_path_gitdir_relative(struct repository *repo, struct strbuf *buf)
+{
+	const char *git_dir = repo_get_git_dir(repo);
+
+	if (!git_dir)
+		return error(_("unable to get git directory"));
+
+	strbuf_add_path(buf, git_dir, startup_info->prefix, PATH_FORMAT_RELATIVE, PATH_DEFAULT_UNMODIFIED);
+	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.gitdir.absolute", get_path_gitdir_absolute },
+	{ "path.gitdir.relative", get_path_gitdir_relative },
 	{ "references.format", get_references_format },
 };
 
diff --git a/t/t1900-repo-info.sh b/t/t1900-repo-info.sh
index 39bb77dda0..7c7dfbb052 100755
--- a/t/t1900-repo-info.sh
+++ b/t/t1900-repo-info.sh
@@ -155,4 +155,35 @@ test_expect_success 'git repo info -h shows only repo info usage' '
 	test_grep ! "git repo structure" actual
 '
 
+test_repo_info_path () {
+	field_name=$1
+	expect_relative=$2
+
+	test_expect_success "query individual key: path.$field_name.absolute" '
+		(
+			cd test-repo/sub &&
+			expect_absolute=$(cd .. && pwd)/.git &&
+			echo "path.$field_name.absolute=$expect_absolute" >expect &&
+			git repo info path.$field_name.absolute >actual &&
+			test_cmp expect actual
+		)
+	'
+
+	test_expect_success "query individual key: path.$field_name.relative" '
+		(
+			cd test-repo/sub &&
+			echo "path.$field_name.relative=$expect_relative" >expect &&
+			git repo info path.$field_name.relative >actual &&
+			test_cmp expect actual
+		)
+	'
+}
+
+test_expect_success 'setup test repository layout for path fields' '
+	git init test-repo &&
+	mkdir -p test-repo/sub
+'
+
+test_repo_info_path 'gitdir' '../.git'
+
 test_done
-- 
2.54.0
K JayatheerthJun 1, 2026, 15:19 UTC in reply to K Jayatheerth on lore

[GSoC][PATCH 4/4] repo: add path.commondir with absolute and relative suffix formatting

Introduce `path.commondir.absolute` and `path.commondir.relative` keys to `git repo info`. These track the repository's common directory path, extending the path metadata engine alongside the existing `gitdir` fields.

Update `repo_info_field` to store the new keys in proper lexicographical order to protect binary search operations, and expand the test matrix in `t/t1900-repo-info.sh` to validate separate queries, bulk dumps, and key listings.

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

Documentation/git-repo.adoc, builtin/repo.c, t/t1900-repo-info.sh

diff --git a/Documentation/git-repo.adoc b/Documentation/git-repo.adoc
index a0dca7ce88..ed7d80c690 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.
+
 `path.gitdir.absolute`::
 	The canonical absolute path to the Git repository directory (the `.git` directory).
 
diff --git a/builtin/repo.c b/builtin/repo.c
index c141ef892a..be24a5a8e8 100644
--- a/builtin/repo.c
+++ b/builtin/repo.c
@@ -77,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"));
+
+	strbuf_add_path(buf, common_dir, startup_info->prefix, PATH_FORMAT_CANONICAL, PATH_DEFAULT_UNMODIFIED);
+	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"));
+
+	strbuf_add_path(buf, common_dir, startup_info->prefix, PATH_FORMAT_RELATIVE, PATH_DEFAULT_UNMODIFIED);
+	return 0;
+}
+
 static int get_path_gitdir_absolute(struct repository *repo, struct strbuf *buf)
 {
 	const char *git_dir = repo_get_git_dir(repo);
@@ -111,6 +133,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 },
 	{ "path.gitdir.absolute", get_path_gitdir_absolute },
 	{ "path.gitdir.relative", get_path_gitdir_relative },
 	{ "references.format", get_references_format },
diff --git a/t/t1900-repo-info.sh b/t/t1900-repo-info.sh
index 7c7dfbb052..dd2706e1f7 100755
--- a/t/t1900-repo-info.sh
+++ b/t/t1900-repo-info.sh
@@ -184,6 +184,7 @@ test_expect_success 'setup test repository layout for path fields' '
 	mkdir -p test-repo/sub
 '
 
+test_repo_info_path 'commondir' '../.git'
 test_repo_info_path 'gitdir' '../.git'
 
 test_done
-- 
2.54.0
Lucas Seiki OshiroJun 1, 2026, 16:25 UTC in reply to K Jayatheerth on lore

Re: [GSoC][PATCH 0/4] teach git repo info to handle path keys

> 1. Should there still be a --path-format flag?

If you specify "absolute" and "relative" in the keys, it won't make sense to use it.

> 2. Should we consider a default option?
Some pros and cons:
- Pro: some values make more sense to be in absolute or relative
  format
- Pro: it's boring to always add `.(relative|absolute)` to the
  paths
- Con: it will be perpetuating what git-rev-parse does, and we
  don't git-repo-info to be git-rev-parse with a different
  interface. It's our chance to learn with [1] for example.
- Con: the user will need if the value is relative or absolute
> 3. Is printing both absolute and relative in a single call
>   using --all acceptable?

If you're providing both keys, I think it's not only acceptable but mandatory. `--all` should mean "all", not "all, but ...".

> I have discussed these changes with both Justin and Lucas
> internally. This series is presented to gather opinions from the
> wider community before moving forward.

I probably sent the same comments internally, but I'm sending here to share my opinions with the rest of the community ;-)

[1] fac60b8925 (rev-parse: add option for absolute or relative path formatting, 2020-12-13)
Lucas Seiki OshiroJun 1, 2026, 16:28 UTC in reply to K Jayatheerth on lore

Re: [GSoC][PATCH 3/4] repo: add path.gitdir with absolute and relative suffix formatting

Show 8 quoted lines
> +test_repo_info_path () {
> + field_name=$1
> + expect_relative=$2
> +
> + test_expect_success "query individual key: path.$field_name.absolute" '
> + (
> + cd test-repo/sub &&
> + expect_absolute=$(cd .. && pwd)/.git &&

Note that this semi-hardcoded path won't work for other values (e.g. top level dir, superproject working tree). This needs to be a parameter just like `expect_relative`

Lucas Seiki OshiroJun 1, 2026, 16:34 UTC in reply to K Jayatheerth on lore

Re: [GSoC][PATCH 4/4] repo: add path.commondir with absolute and relative suffix formatting

Show 10 quoted lines
> diff --git a/t/t1900-repo-info.sh b/t/t1900-repo-info.sh
> index 7c7dfbb052..dd2706e1f7 100755
> --- a/t/t1900-repo-info.sh
> +++ b/t/t1900-repo-info.sh
> @@ -184,6 +184,7 @@ test_expect_success 'setup test repository layout for path fields' '
> mkdir -p test-repo/sub
> '
> 
> +test_repo_info_path 'commondir' '../.git'
> test_repo_info_path 'gitdir' '../.git'

I was thinking here, maybe you need to take a look at git-rev-parse's tests and check what are the corner cases.

For example, `git rev-parse --git-common-dir` documentation says:

    --git-common-dir:
        Show $GIT_COMMON_DIR if defined, else $GIT_DIR

This way, you should take a look on how git-rev-parse tests test those two branches (GIT_COMMON_DIR and GIT_DIR).

Lucas Seiki OshiroJun 1, 2026, 21:58 UTC in reply to K Jayatheerth on lore

Re: [GSoC][PATCH 4/4] repo: add path.commondir with absolute and relative suffix formatting

Show 10 quoted lines
> diff --git a/t/t1900-repo-info.sh b/t/t1900-repo-info.sh
> index 7c7dfbb052..dd2706e1f7 100755
> --- a/t/t1900-repo-info.sh
> +++ b/t/t1900-repo-info.sh
> @@ -184,6 +184,7 @@ test_expect_success 'setup test repository layout for path fields' '
> mkdir -p test-repo/sub
> '
> 
> +test_repo_info_path 'commondir' '../.git'
> test_repo_info_path 'gitdir' '../.git'

I was thinking here, maybe you need to take a look at git-rev-parse's tests and check what are the corner cases.

For example, `git rev-parse --git-common-dir` documentation says:

    --git-common-dir:
        Show $GIT_COMMON_DIR if defined, else $GIT_DIR

This way, you should take a look on how git-rev-parse tests test those two cases (GIT_COMMON_DIR and GIT_DIR) and do something similar here.

Lucas Seiki OshiroJun 1, 2026, 22:04 UTC in reply to K Jayatheerth on lore

Re: [GSoC][PATCH 0/4] teach git repo info to handle path keys

Nitpick: use [GSoC PATCH] instead of [GSoC][PATCH] as prefix.
Use --subject-prefix='GSoC PATCH' in git-send-email or
git-format-patch or set the configuration variable
`format.subjectPrefix` to that until you finish your GSoC:
$ git config --local format.subjectPrefix 'GSoC PATCH'
Junio C HamanoJun 1, 2026, 23:05 UTC in reply to K Jayatheerth on lore

Re: [GSoC][PATCH 0/4] teach git repo info to handle path keys

K Jayatheerth <jayatheerthkulkarni2005@gmail.com> writes:
Show 5 quoted lines
> Hi!
>
> The first and second patches are self-explanatory, so I will
> focus more on the third and fourth patches, which introduce the
> path-related fields to `git repo info`.

I sense that a paragraph or two is missing before that. The purpose of your cover letter is to _sell_ your changes, explain what it is about, and get your target audience interested enough to read the patches. The above goes totally backwards---your readers do not yet know what the series is about, they haven't decided if it is worth their time to read it, and you are telling "go read first two yourself, I am not going to tell you what they are about"?

> In the last discussion [1] we had on the mailing list about paths
> in repo info, we didn't reach a definitive conclusion, but
> adding both options made the most sense based on the feedback.

And again, you are excluding those who were not in, or do not remember, what was discussed in "the last discussion".

Junio C HamanoJun 1, 2026, 23:09 UTC in reply to Lucas Seiki Oshiro on lore

Re: [GSoC][PATCH 3/4] repo: add path.gitdir with absolute and relative suffix formatting

Lucas Seiki Oshiro <lucasseikioshiro@gmail.com> writes:
Show 12 quoted lines
>> +test_repo_info_path () {
>> + field_name=$1
>> + expect_relative=$2
>> +
>> + test_expect_success "query individual key: path.$field_name.absolute" '
>> + (
>> + cd test-repo/sub &&
>> + expect_absolute=$(cd .. && pwd)/.git &&
>
> Note that this semi-hardcoded path won't work for other values (e.g.
> top level dir, superproject working tree). This needs to be a parameter
> just like `expect_relative`
Good thinking.  Thanks for carefully reading.
Phillip WoodJun 2, 2026, 13:00 UTC in reply to K Jayatheerth on lore

Re: [GSoC][PATCH 1/4] path: add strbuf_add_path for formatting paths

On 01/06/2026 16:19, K Jayatheerth wrote:
Show 24 quoted lines
> 
> diff --git a/path.h b/path.h
> index 0434ba5e07..b9b626ce4a 100644
> --- a/path.h
> +++ b/path.h
> @@ -262,6 +262,22 @@ enum scld_error safe_create_leading_directories_no_share(char *path);
>   int safe_create_file_with_leading_directories(struct repository *repo,
>   					      const char *path);
>   
> +enum path_format_type {
> +	PATH_FORMAT_DEFAULT,
> +	PATH_FORMAT_RELATIVE,
> +	PATH_FORMAT_CANONICAL
> +};
> +
> +enum path_default_type {
> +	PATH_DEFAULT_RELATIVE,
> +	PATH_DEFAULT_RELATIVE_IF_SHARED,
> +	PATH_DEFAULT_CANONICAL,
> +	PATH_DEFAULT_UNMODIFIED
> +};
> +
> +void strbuf_add_path(struct strbuf *buf, const char *path, const char *prefix,
> +		     enum path_format_type format, enum path_default_type def);

This API is very specific to rev-parse and to me at least it is hard to understand. I think it would be clearer if we had a single enum describing the desired format and let the rev-parse code worry about passing the appropriate value based on the options the user passed.

enum path_format {
	PATH_FORMAT_ABSOLUTE,
	PATH_FORMAT_CANONICAL,
	PATH_FORMAT_RELATIVE,
	PATH_FORMAT_RELATIVE_IF_SHARED	PATH_FORMAT_UNMODIFIED,
};
void format_path(struct strbuf *buf, const char *path,
		 const char *prefix, enum path_format format);

We tend to avoid adding "strbuf_" to the beginning of functions these days when they're adding things to a strbuf. This function also needs some documentation explaining what the arguments are.

Thanks
Phillip
Phillip WoodJun 2, 2026, 13:03 UTC in reply to K Jayatheerth on lore

Re: [GSoC][PATCH 0/4] teach git repo info to handle path keys

On 01/06/2026 16:19, K Jayatheerth wrote:
Show 6 quoted lines
> 
> So in patches 3 and 4, we add both `path.<field>.absolute` and
> `path.<field>.relative` for `gitdir` and `commondir`. Initially,
> it was proposed by Ayush to use `path.absolute.<field>`, but
> this would break the lexicographical order of the internal field
> array. I tweaked it to place the variant at the end as a suffix instead.

I don't understand the comment about breaking the lexicographical order, surely it only breaks if the new items are added out of order? Why can't we have

	path.absolute.commondir
	path.absolute.gitdir
	path.relative.commondir
	path.relative.gitdir
?
Thanks
Phillip
Show 46 quoted lines
> There are still a few open questions that should be addressed
> by the community. I am tagging members who were involved in the
> previous discussions:
> 
> Justin Tobler, Lucas Seiki Oshiro, Junio, Phillip Wood,
> brian m. carlson, and Ayush Jha.
> 
> Apologies if I missed anyone; I included everyone who reviewed
> or participated in the discussions of Eslam's and Lucas's
> patches.
> 
> Questions:
> 
> 1. Should there still be a --path-format flag?
> 2. Should we consider a default option?
>     Currently we have path.gitdir.absolute; should we consider
>     an option where a plain path.gitdir returns some default?
>     If yes:
>       2.1 Should we keep the default the same as rev-parse? Or
>           should either relative or absolute be the default?
>       2.2 When printing using --all, should the default be
>           printed, or should we print both absolute and
>           relative?
> 3. Is printing both absolute and relative in a single call
>     using --all acceptable?
>     If no:
>       3.1 What's a better approach?
> 
> I have discussed these changes with both Justin and Lucas
> internally. This series is presented to gather opinions from the
> wider community before moving forward.
> 
> K Jayatheerth (4):
>    path: add strbuf_add_path for formatting paths
>    rev-parse: use strbuf_add_path for path formatting
>    repo: add path.gitdir with absolute and relative suffix formatting
>    repo: add path.commondir with absolute and relative suffix formatting
> 
>   Documentation/git-repo.adoc |  15 ++++++
>   builtin/repo.c              |  50 ++++++++++++++++++
>   builtin/rev-parse.c         | 100 ++++++++----------------------------
>   path.c                      |  58 +++++++++++++++++++++
>   path.h                      |  16 ++++++
>   t/t1900-repo-info.sh        |  32 ++++++++++++
>   6 files changed, 192 insertions(+), 79 deletions(-)
> 
K JayatheerthJun 5, 2026, 16:30 UTC in reply to K Jayatheerth on lore

[GSoC PATCH v2 0/4] teach git repo info to handle path keys

Hi everyone,

This series teaches `git repo info` to handle `path.*` keys, so scripts can easily discover repository paths.

The commits are divided into 4 parts:
  1. path: extract the path-formatting logic from rev-parse and
     expose it via path.h with a better naming convention.
  2. rev-parse: refactor the command to use the exported function
     and enum.
  3. repo: introduce path.gitdir with standardized tests and docs.
  4. repo: introduce path.commondir.

About patches 3 and 4: In our last discussion [1], we didn't reach a definitive conclusion about paths in repo info, but based on the feedback, explicitly offering both relative and absolute options made the most sense. So, patches 3 and 4 add both `path.<field>.absolute` and `path.<field>.relative` for `gitdir` and `commondir`.

There are still a few open questions. Tagging Justin, Lucas, Junio, Phillip, brian, and Ayush.

Questions:
1. Should there still be a --path-format flag?
2. Should we consider a default option?
   Currently we have path.gitdir.absolute. Should we consider an
   option where a plain `path.gitdir` returns some default?
   If yes:
     2.1 Should we keep the default the same as rev-parse? Or should
         either relative or absolute be the default?
     2.2 When printing using --all, should the default be printed,
         or should we print both absolute and relative?
3. Is printing both absolute and relative in a single call using
   --all acceptable? If no, what's a better approach?

I have discussed these changes with both Justin and Lucas internally and wanted to gather opinions from the wider community before moving forward.

Changes since v1:
* Lucas's feedback: Added corner cases covering GIT_COMMON_DIR and
  GIT_DIR. Parameterized the test helper fields instead of hardcoding
  them. Also fixed the subject prefix to [GSoC PATCH v2].
* Junio's feedback: Added a clearer description of what the series
  does up front. I also realized the commit messages for patches 3
  and 4 explained the "what" and not the "why", so I have (hopefully)
  improved them :)
* Phillip's feedback: Changed the helper function name and combined
  the two enums into one, which made a lot of sense.
  I have also added comments within the path.h files to document
  the API.
* About lexicographical order: "Breaking" wasn't the right term
  before, but I do believe keeping .absolute and .relative as
  suffixes is a better choice. I prefer having the two choices
  side-by-side grouped by entity, rather than a cluster of absolute
  keys followed by relative ones. Open to hearing if the latter is
  preferred!
Thanks for this round of feedback guys, this has been fruitful!

P.S - I realized that I didn't add the link to Lucas's patch thread last time :) sorry bout that!

[1] https://public-inbox.org/git/041DCF2E-75FB-4B0A-9128-FDBB1A6DAC3C@gmail.com/T/#t
K Jayatheerth (4):
  path: introduce format_path() for centralized path formatting
  rev-parse: use format_path for path formatting
  repo: add path.gitdir with absolute and relative suffix formatting
  repo: add path.commondir with absolute and relative suffix formatting
 Documentation/git-repo.adoc |  15 ++++++
 builtin/repo.c              |  50 +++++++++++++++++
 builtin/rev-parse.c         | 103 ++++++++----------------------------
 path.c                      |  58 ++++++++++++++++++++
 path.h                      |  30 +++++++++++
 t/t1900-repo-info.sh        |  40 ++++++++++++++
 6 files changed, 216 insertions(+), 80 deletions(-)
-- 
2.54.0
K JayatheerthJun 5, 2026, 16:30 UTC in reply to K Jayatheerth on lore

[GSoC PATCH v2 1/4] path: introduce format_path() for centralized path formatting

The path-formatting logic inside `builtin/rev-parse.c` handles absolute, canonical, and relative formatting rules based on user-supplied options. However, this logic is tightly coupled to `rev-parse` and writes directly to stdout.

To allow other builtins (such as the upcoming `git repo` path keys) to re-use this logic, extract the core path-formatting algorithm into a centralized helper function, `format_path()`, in `path.c`.

Expose a single, streamlined `path_format` enum in `path.h` to let callers explicitly declare their formatting strategy (UNMODIFIED, RELATIVE, RELATIVE_IF_SHARED, or CANONICAL). This decouples the core algorithm from the localized fallback mechanics specific to `rev-parse`.

Signed-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>
Mentored-by: Justin Tobler <jltobler@gmail.com>
Mentored-by: Lucas Seiki Oshiro <lucasseikioshiro@gmail.com>
---
 path.c | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 path.h | 30 ++++++++++++++++++++++++++++++
 2 files changed, 88 insertions(+)
Show changes to 2 files +88 −0

path.c, path.h

diff --git a/path.c b/path.c
index d7e17bf174..2fcd24c5eb 100644
--- a/path.c
+++ b/path.c
@@ -1579,6 +1579,64 @@ char *xdg_cache_home(const char *filename)
 	return NULL;
 }
 
+void format_path(struct strbuf *buf, const char *path,
+		 const char *prefix, enum path_format format)
+{
+	if (format == PATH_FORMAT_UNMODIFIED) {
+		strbuf_addstr(buf, path);
+		return;
+	}
+
+	if (format == 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(buf, relative_path(path, prefix, &relative_buf));
+
+		strbuf_release(&relative_buf);
+		strbuf_release(&real_path);
+		strbuf_release(&real_prefix);
+		free(cwd);
+	} else if (format == 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(buf, relative_path(path, prefix, &relative_buf));
+		strbuf_release(&relative_buf);
+	} else if (format == PATH_FORMAT_CANONICAL) {
+		struct strbuf canonical_buf = STRBUF_INIT;
+
+		strbuf_realpath_forgiving(&canonical_buf, path, 1);
+		strbuf_addbuf(buf, &canonical_buf);
+
+		strbuf_release(&canonical_buf);
+	}
+}
+
 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..a78e0fc141 100644
--- a/path.h
+++ b/path.h
@@ -262,6 +262,36 @@ enum scld_error safe_create_leading_directories_no_share(char *path);
 int safe_create_file_with_leading_directories(struct repository *repo,
 					      const char *path);
 
+/**
+ * The formatting strategy to apply when writing a path into a buffer.
+ */
+enum path_format {
+	/* Output the path exactly as-is without any modifications. */
+	PATH_FORMAT_UNMODIFIED,
+
+	/* Output a path relative to the provided directory prefix. */
+	PATH_FORMAT_RELATIVE,
+
+	/* Output a relative path only if the path shares a root with the prefix. */
+	PATH_FORMAT_RELATIVE_IF_SHARED,
+
+	/* Output a fully resolved, absolute canonical path. */
+	PATH_FORMAT_CANONICAL
+};
+
+/**
+ * Format a path according to the specified formatting strategy and append
+ * the result to the given strbuf.
+ *
+ * `buf`    : 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 format_path(struct strbuf *buf, const char *path,
+		 const char *prefix, enum path_format format);
+
 # ifdef USE_THE_REPOSITORY_VARIABLE
 #  include "strbuf.h"
 #  include "repository.h"
-- 
2.54.0
K JayatheerthJun 5, 2026, 16:30 UTC in reply to K Jayatheerth on lore

[GSoC PATCH v2 2/4] rev-parse: use format_path for path formatting

Now that the core path-formatting logic has been abstracted into format_path() inside path.c, remove the localized duplicate formatting mechanics from builtin/rev-parse.c.

Drop the usage of the old local format_type and default_type enums, and update print_path() to act as a light wrapper around the new shared engine. Resolve user-provided formatting flags directly within rev-parse to pass the final determined path_format to format_path().

Signed-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>
Mentored-by: Justin Tobler <jltobler@gmail.com>
Mentored-by: Lucas Seiki Oshiro <lucasseikioshiro@gmail.com>
---
 builtin/rev-parse.c | 103 ++++++++++----------------------------------
 1 file changed, 23 insertions(+), 80 deletions(-)
Show changes to builtin/rev-parse.c +23 −80
diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c
index 218b5f34d6..c78bdc04c1 100644
--- a/builtin/rev-parse.c
+++ b/builtin/rev-parse.c
@@ -632,73 +632,16 @@ 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,
+		       int 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 != -1) ? arg_path_format : def_format;
+
+	format_path(&sb, path, prefix, fmt);
+	puts(sb.buf);
+
+	strbuf_release(&sb);
 }
 
 int cmd_rev_parse(int argc,
@@ -717,7 +660,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;
+	int arg_path_format = -1;
 
 	show_usage_if_asked(argc, argv, builtin_rev_parse_usage);
 
@@ -797,8 +740,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 +763,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 +928,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 +936,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 +971,18 @@ int cmd_rev_parse(int argc,
 				const char *gitdir = getenv(GIT_DIR_ENVIRONMENT);
 				char *cwd;
 				int len;
-				enum format_type wanted = format;
+				int 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 +998,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 +1032,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
K JayatheerthJun 5, 2026, 16:30 UTC in reply to K Jayatheerth on lore

[GSoC PATCH v2 3/4] repo: add path.gitdir with absolute and relative suffix formatting

Scripts often need to locate the `.git` directory. While `git rev-parse` provides this, it relies on command-line flags to dictate path formatting.

Introduce `path.gitdir.absolute` and `path.gitdir.relative` keys to `git repo info`. Exposing separate format-specific keys instead of a base `path.gitdir` key avoids default fallbacks and requires callers to state their format requirements explicitly. Both keys use `format_path()` to resolve paths.

To test these keys, introduce the `test_repo_info_path` helper in `t/t1900-repo-info.sh`. The helper evaluates paths dynamically and accepts environment variable prefixes. This prepares the test suite for future path keys that depend on environment overrides, such as `commondir`.

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

Documentation/git-repo.adoc, builtin/repo.c, t/t1900-repo-info.sh

diff --git a/Documentation/git-repo.adoc b/Documentation/git-repo.adoc
index 42262c1983..a0dca7ce88 100644
--- a/Documentation/git-repo.adoc
+++ b/Documentation/git-repo.adoc
@@ -104,6 +104,12 @@ values that they return:
 `object.format`::
 	The object format (hash algorithm) used in the repository.
 
+`path.gitdir.absolute`::
+	The canonical absolute path to the Git repository directory (the `.git` directory).
+
+`path.gitdir.relative`::
+	The path to the Git repository directory relative to the current working directory.
+
 `references.format`::
 	The reference storage format. The valid values are:
 +
diff --git a/builtin/repo.c b/builtin/repo.c
index 71a5c1c29c..6e97f6a0e4 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_gitdir_absolute(struct repository *repo, struct strbuf *buf)
+{
+	const char *git_dir = repo_get_git_dir(repo);
+
+	if (!git_dir)
+		return error(_("unable to get git directory"));
+
+	format_path(buf, git_dir, startup_info->prefix, PATH_FORMAT_CANONICAL);
+	return 0;
+}
+
+static int get_path_gitdir_relative(struct repository *repo, struct strbuf *buf)
+{
+	const char *git_dir = repo_get_git_dir(repo);
+
+	if (!git_dir)
+		return error(_("unable to get git directory"));
+
+	format_path(buf, git_dir, startup_info->prefix, PATH_FORMAT_RELATIVE);
+	return 0;
+}
+
 static int get_references_format(struct repository *repo, struct strbuf *buf)
 {
 	strbuf_addstr(buf,
@@ -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.gitdir.absolute", get_path_gitdir_absolute },
+	{ "path.gitdir.relative", get_path_gitdir_relative },
 	{ "references.format", get_references_format },
 };
 
diff --git a/t/t1900-repo-info.sh b/t/t1900-repo-info.sh
index 39bb77dda0..0660b00bbc 100755
--- a/t/t1900-repo-info.sh
+++ b/t/t1900-repo-info.sh
@@ -155,4 +155,37 @@ test_expect_success 'git repo info -h shows only repo info usage' '
 	test_grep ! "git repo structure" actual
 '
 
+test_repo_info_path () {
+	field_name=$1
+	expect_absolute_eval=$2
+	expect_relative=$3
+	env_prefix=$4
+
+	test_expect_success "query individual key: path.$field_name.absolute${env_prefix:+ ($env_prefix)}" '
+		(
+			cd test-repo/sub &&
+			expect_absolute=$(eval "$expect_absolute_eval") &&
+			echo "path.$field_name.absolute=$expect_absolute" >expect &&
+			eval "${env_prefix:+$env_prefix }git repo info \"path.$field_name.absolute\"" >actual &&
+			test_cmp expect actual
+		)
+	'
+
+	test_expect_success "query individual key: path.$field_name.relative${env_prefix:+ ($env_prefix)}" '
+		(
+			cd test-repo/sub &&
+			echo "path.$field_name.relative=$expect_relative" >expect &&
+			eval "${env_prefix:+$env_prefix }git repo info \"path.$field_name.relative\"" >actual &&
+			test_cmp expect actual
+		)
+	'
+}
+
+test_expect_success 'setup test repository layout for path fields' '
+	git init test-repo &&
+	mkdir -p test-repo/sub
+'
+
+test_repo_info_path 'gitdir' 'echo "$(cd .. && pwd)/.git"' '../.git'
+
 test_done
-- 
2.54.0
K JayatheerthJun 5, 2026, 16:30 UTC in reply to K Jayatheerth on lore

[GSoC PATCH v2 4/4] repo: add path.commondir with absolute and relative suffix formatting

In standard Git repositories, the Git directory and the common directory are identical. However, in environments utilizing multiple worktrees, the local working state ($GIT_DIR) is separated from the shared central data ($GIT_COMMON_DIR). Scripts require a reliable way to discover this shared path.

Introduce `path.commondir.absolute` and `path.commondir.relative` keys to `git repo info`. Similar to the `path.gitdir` keys, exposing explicit format variants removes the ambiguity of default fallbacks. Both keys are evaluated via the `format_path()` engine.

Insert the new keys into the `repo_info_field` array in lexicographical order to maintain the integrity of binary search lookups.

Utilize the parameterized `test_repo_info_path` helper to validate the worktree edge cases. This ensures that path resolution correctly respects $GIT_COMMON_DIR when defined and safely falls back to $GIT_DIR otherwise.

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

Documentation/git-repo.adoc, builtin/repo.c, t/t1900-repo-info.sh

diff --git a/Documentation/git-repo.adoc b/Documentation/git-repo.adoc
index a0dca7ce88..ed7d80c690 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.
+
 `path.gitdir.absolute`::
 	The canonical absolute path to the Git repository directory (the `.git` directory).
 
diff --git a/builtin/repo.c b/builtin/repo.c
index 6e97f6a0e4..27c8caff38 100644
--- a/builtin/repo.c
+++ b/builtin/repo.c
@@ -77,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"));
+
+	format_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"));
+
+	format_path(buf, common_dir, startup_info->prefix, PATH_FORMAT_RELATIVE);
+	return 0;
+}
+
 static int get_path_gitdir_absolute(struct repository *repo, struct strbuf *buf)
 {
 	const char *git_dir = repo_get_git_dir(repo);
@@ -111,6 +133,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 },
 	{ "path.gitdir.absolute", get_path_gitdir_absolute },
 	{ "path.gitdir.relative", get_path_gitdir_relative },
 	{ "references.format", get_references_format },
diff --git a/t/t1900-repo-info.sh b/t/t1900-repo-info.sh
index 0660b00bbc..21755d9d14 100755
--- a/t/t1900-repo-info.sh
+++ b/t/t1900-repo-info.sh
@@ -186,6 +186,13 @@ test_expect_success 'setup test repository layout for path fields' '
 	mkdir -p test-repo/sub
 '
 
+test_expect_success 'setup custom-common for commondir tests' '
+	git init --bare test-repo/custom-common
+'
+
+test_repo_info_path 'commondir' 'echo "$(cd .. && pwd)/.git"' '../.git'
+test_repo_info_path 'commondir' 'echo "$(cd .. && pwd)/custom-common"' '../custom-common' 'GIT_COMMON_DIR="$(cd .. && pwd)/custom-common" GIT_DIR=../.git'
+test_repo_info_path 'commondir' 'echo "$(cd .. && pwd)/.git"' '../.git' 'GIT_DIR=../.git'
 test_repo_info_path 'gitdir' 'echo "$(cd .. && pwd)/.git"' '../.git'
 
 test_done
-- 
2.54.0
Kristoffer HaugsbakkJun 5, 2026, 16:55 UTC in reply to K Jayatheerth on lore

Re: [GSoC PATCH v2 1/4] path: introduce format_path() for centralized path formatting

On Fri, Jun 5, 2026, at 18:30, K Jayatheerth wrote:
Show 13 quoted lines
> The path-formatting logic inside `builtin/rev-parse.c` handles absolute,
> canonical, and relative formatting rules based on user-supplied options.
> However, this logic is tightly coupled to `rev-parse` and writes directly
> to stdout.
>
> To allow other builtins (such as the upcoming `git repo` path keys) to
> re-use this logic, extract the core path-formatting algorithm into a centralized
> helper function, `format_path()`, in `path.c`.
>
> Expose a single, streamlined `path_format` enum in `path.h` to let callers
> explicitly declare their formatting strategy (UNMODIFIED, RELATIVE,
> RELATIVE_IF_SHARED, or CANONICAL). This decouples the core algorithm from
> the localized fallback mechanics specific to `rev-parse`.
This looks very well explained to my naive eyes.
>
> Signed-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>
> Mentored-by: Justin Tobler <jltobler@gmail.com>
> Mentored-by: Lucas Seiki Oshiro <lucasseikioshiro@gmail.com>

Nitpick. You are supposed to add your `Signed-off-by` at the end. You are saying with that line that you are signing off on the changes and the commit message, including the trailers (mentors) you’ve decided to add. Imagine if the maintainer applies this patch and fixes a typo and the commit becomes:

    Mentored-by: Justin Tobler <jltobler@gmail.com>
    Mentored-by: Lucas Seiki Oshiro <lucasseikioshiro@gmail.com>
    Signed-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>
    [jc: typo fix]
    Signed-off-by: Junio ...
The chain of custody is then very clear.
> ---
>[snip]
Lucas Seiki OshiroJun 5, 2026, 17:35 UTC in reply to K Jayatheerth on lore

Re: [GSoC PATCH v2 0/4] teach git repo info to handle path keys

Show 6 quoted lines
> * About lexicographical order: "Breaking" wasn't the right term
>  before, but I do believe keeping .absolute and .relative as
>  suffixes is a better choice. I prefer having the two choices
>  side-by-side grouped by entity, rather than a cluster of absolute
>  keys followed by relative ones. Open to hearing if the latter is
>  preferred!

I prefer `.(absolute|relative)` at the end. `path.gitdir.relative` means that we have a collection of paths, in those collections we have gitdir that can be relative or absolute, and we want the relative. `path.relative.gitdir` means that we have a collection of relative paths and from those we're picking gitdir. The first feels more natural.

> Thanks for this round of feedback guys, this has been fruitful!

Thanks again for your interest in improving `git repo info`! I'll review your patchset with more attention later.

PS: this is a nitpick, but it would be really helpful if you provide
a range-diff in the cover letter. Check the usage of `--range-diff`
in git-format-patch documentation (this flag also works for
git-send-email). Or, if you prefer, you can generate it by running
`git range-diff` and copying the output.
Lucas Seiki OshiroJun 8, 2026, 15:05 UTC in reply to K Jayatheerth on lore

Re: [GSoC PATCH v2 1/4] path: introduce format_path() for centralized path formatting

Show 34 quoted lines
> +++ b/path.h
> @@ -262,6 +262,36 @@ enum scld_error safe_create_leading_directories_no_share(char *path);
> int safe_create_file_with_leading_directories(struct repository *repo,
>      const char *path);
> 
> +/**
> + * The formatting strategy to apply when writing a path into a buffer.
> + */
> +enum path_format {
> + /* Output the path exactly as-is without any modifications. */
> + PATH_FORMAT_UNMODIFIED,
> +
> + /* Output a path relative to the provided directory prefix. */
> + PATH_FORMAT_RELATIVE,
> +
> + /* Output a relative path only if the path shares a root with the prefix. */
> + PATH_FORMAT_RELATIVE_IF_SHARED,
> +
> + /* Output a fully resolved, absolute canonical path. */
> + PATH_FORMAT_CANONICAL
> +};
> +
> +/**
> + * Format a path according to the specified formatting strategy and append
> + * the result to the given strbuf.
> + *
> + * `buf`    : 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 format_path(struct strbuf *buf, const char *path,
> + const char *prefix, enum path_format format);
Nitpick: the documentation is clear to me, but maybe the function name
"format" and the parameter name "buf" can mislead the user to think
that it only formats the path without appending to the existing string
in `buf`. My suggestion is to rename them to something like 
`append_formatted_path` and `dest`, respectively.
Justin ToblerJun 8, 2026, 17:28 UTC in reply to K Jayatheerth on lore

Re: [GSoC PATCH v2 1/4] path: introduce format_path() for centralized path formatting

On 26/06/05 10:00PM, K Jayatheerth wrote:
Show 8 quoted lines
> The path-formatting logic inside `builtin/rev-parse.c` handles absolute,
> canonical, and relative formatting rules based on user-supplied options.
> However, this logic is tightly coupled to `rev-parse` and writes directly
> to stdout.
> 
> To allow other builtins (such as the upcoming `git repo` path keys) to
> re-use this logic, extract the core path-formatting algorithm into a centralized
> helper function, `format_path()`, in `path.c`.
Makes sense.
> Expose a single, streamlined `path_format` enum in `path.h` to let callers
> explicitly declare their formatting strategy (UNMODIFIED, RELATIVE,
> RELATIVE_IF_SHARED, or CANONICAL). This decouples the core algorithm from
> the localized fallback mechanics specific to `rev-parse`.

Ok, so rev-parse has its own logic to select the formatting strategy used when printing paths that either relies on what the user provides or a designated fallback format that is specific to the type of path. Since that is specific to rev-parse, it makes to factor it out of the generic helper function here.

Show 25 quoted lines
> Signed-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>
> Mentored-by: Justin Tobler <jltobler@gmail.com>
> Mentored-by: Lucas Seiki Oshiro <lucasseikioshiro@gmail.com>
> ---
>  path.c | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
>  path.h | 30 ++++++++++++++++++++++++++++++
>  2 files changed, 88 insertions(+)
> 
> diff --git a/path.c b/path.c
> index d7e17bf174..2fcd24c5eb 100644
> --- a/path.c
> +++ b/path.c
> @@ -1579,6 +1579,64 @@ char *xdg_cache_home(const char *filename)
>  	return NULL;
>  }
>  
> +void format_path(struct strbuf *buf, const char *path,
> +		 const char *prefix, enum path_format format)
> +{
> +	if (format == PATH_FORMAT_UNMODIFIED) {
> +		strbuf_addstr(buf, path);
> +		return;
> +	}
> +
> +	if (format == PATH_FORMAT_RELATIVE) {

nit: we could just continue the "else if" chain here instead of restarting it.

Show 44 quoted lines
> +		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(buf, relative_path(path, prefix, &relative_buf));
> +
> +		strbuf_release(&relative_buf);
> +		strbuf_release(&real_path);
> +		strbuf_release(&real_prefix);
> +		free(cwd);
> +	} else if (format == 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(buf, relative_path(path, prefix, &relative_buf));
> +		strbuf_release(&relative_buf);
> +	} else if (format == PATH_FORMAT_CANONICAL) {
> +		struct strbuf canonical_buf = STRBUF_INIT;
> +
> +		strbuf_realpath_forgiving(&canonical_buf, path, 1);
> +		strbuf_addbuf(buf, &canonical_buf);

Do we need `canonical_buf` here? Can we just add the path to `buf` directly?

Show 46 quoted lines
> +
> +		strbuf_release(&canonical_buf);
> +	}
> +}
> +
>  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..a78e0fc141 100644
> --- a/path.h
> +++ b/path.h
> @@ -262,6 +262,36 @@ enum scld_error safe_create_leading_directories_no_share(char *path);
>  int safe_create_file_with_leading_directories(struct repository *repo,
>  					      const char *path);
>  
> +/**
> + * The formatting strategy to apply when writing a path into a buffer.
> + */
> +enum path_format {
> +	/* Output the path exactly as-is without any modifications. */
> +	PATH_FORMAT_UNMODIFIED,
> +
> +	/* Output a path relative to the provided directory prefix. */
> +	PATH_FORMAT_RELATIVE,
> +
> +	/* Output a relative path only if the path shares a root with the prefix. */
> +	PATH_FORMAT_RELATIVE_IF_SHARED,
> +
> +	/* Output a fully resolved, absolute canonical path. */
> +	PATH_FORMAT_CANONICAL
> +};
> +
> +/**
> + * Format a path according to the specified formatting strategy and append
> + * the result to the given strbuf.
> + *
> + * `buf`    : 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 format_path(struct strbuf *buf, const char *path,
> +		 const char *prefix, enum path_format format);
> +

Ok so in this patch we are just adding the new path formatting interface and will integrate it in the next one. Overall the direction of this patch looks good to me.

-Justin
Justin ToblerJun 8, 2026, 17:54 UTC in reply to K Jayatheerth on lore

Re: [GSoC PATCH v2 2/4] rev-parse: use format_path for path formatting

On 26/06/05 10:00PM, K Jayatheerth wrote:
Show 8 quoted lines
> Now that the core path-formatting logic has been abstracted into
> format_path() inside path.c, remove the localized duplicate formatting
> mechanics from builtin/rev-parse.c.
> 
> Drop the usage of the old local format_type and default_type enums,
> and update print_path() to act as a light wrapper around the new shared
> engine. Resolve user-provided formatting flags directly within rev-parse
> to pass the final determined path_format to format_path().

So if the format isn't explicitly set by the user via the `--path-format` option, the default formatting strategy used depends on the path being printed. IOW, there is no consistent default path format here.

Show 39 quoted lines
> Signed-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>
> Mentored-by: Justin Tobler <jltobler@gmail.com>
> Mentored-by: Lucas Seiki Oshiro <lucasseikioshiro@gmail.com>
> ---
>  builtin/rev-parse.c | 103 ++++++++++----------------------------------
>  1 file changed, 23 insertions(+), 80 deletions(-)
> 
> diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c
> index 218b5f34d6..c78bdc04c1 100644
> --- a/builtin/rev-parse.c
> +++ b/builtin/rev-parse.c
> @@ -632,73 +632,16 @@ 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,
> +		       int arg_path_format, enum path_format def_format)
>  {
[snip]
> +	struct strbuf sb = STRBUF_INIT;
> +	enum path_format fmt = (arg_path_format != -1) ? arg_path_format : def_format;

hmmm, so `arg_path_format` specifies what the user-provided format and acts as a sentinel to signal there is no value provided and the fallback format needs to be used. This feels a tad bit awkward to me.

I wonder if we should introduce a PATH_FORMAT_DEFAULT to the `path_format` enum that maps to one of the existing enum values in `path.c:format_path()`. Here in `print_path()`, we could then intercept a PATH_FORMAT_DEFAULT value and override it to the specified `def_format`. I'm not sure if this is ultimately that much better though.

-Justin
Justin ToblerJun 8, 2026, 18:50 UTC in reply to K Jayatheerth on lore

Re: [GSoC PATCH v2 3/4] repo: add path.gitdir with absolute and relative suffix formatting

On 26/06/05 10:00PM, K Jayatheerth wrote:
Show 8 quoted lines
> Scripts often need to locate the `.git` directory. While `git rev-parse`
> provides this, it relies on command-line flags to dictate path formatting.
> 
> Introduce `path.gitdir.absolute` and `path.gitdir.relative` keys to
> `git repo info`. Exposing separate format-specific keys instead of a base
> `path.gitdir` key avoids default fallbacks and requires callers to state
> their format requirements explicitly. Both keys use `format_path()` to
> resolve paths.
Makes sense.
Show 62 quoted lines
> To test these keys, introduce the `test_repo_info_path` helper in
> `t/t1900-repo-info.sh`. The helper evaluates paths dynamically and accepts
> environment variable prefixes. This prepares the test suite for future path
> keys that depend on environment overrides, such as `commondir`.
> 
> Signed-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>
> Mentored-by: Justin Tobler <jltobler@gmail.com>
> Mentored-by: Lucas Seiki Oshiro <lucasseikioshiro@gmail.com>
> ---
>  Documentation/git-repo.adoc |  6 ++++++
>  builtin/repo.c              | 26 ++++++++++++++++++++++++++
>  t/t1900-repo-info.sh        | 33 +++++++++++++++++++++++++++++++++
>  3 files changed, 65 insertions(+)
> 
> diff --git a/Documentation/git-repo.adoc b/Documentation/git-repo.adoc
> index 42262c1983..a0dca7ce88 100644
> --- a/Documentation/git-repo.adoc
> +++ b/Documentation/git-repo.adoc
> @@ -104,6 +104,12 @@ values that they return:
>  `object.format`::
>  	The object format (hash algorithm) used in the repository.
>  
> +`path.gitdir.absolute`::
> +	The canonical absolute path to the Git repository directory (the `.git` directory).
> +
> +`path.gitdir.relative`::
> +	The path to the Git repository directory relative to the current working directory.
> +
>  `references.format`::
>  	The reference storage format. The valid values are:
>  +
> diff --git a/builtin/repo.c b/builtin/repo.c
> index 71a5c1c29c..6e97f6a0e4 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_gitdir_absolute(struct repository *repo, struct strbuf *buf)
> +{
> +	const char *git_dir = repo_get_git_dir(repo);
> +
> +	if (!git_dir)
> +		return error(_("unable to get git directory"));
> +
> +	format_path(buf, git_dir, startup_info->prefix, PATH_FORMAT_CANONICAL);

For absolute paths, I don't think we actually need the prefix, but providing it doesn't probably matter too much either way.

Show 13 quoted lines
> +	return 0;
> +}
> +
> +static int get_path_gitdir_relative(struct repository *repo, struct strbuf *buf)
> +{
> +	const char *git_dir = repo_get_git_dir(repo);
> +
> +	if (!git_dir)
> +		return error(_("unable to get git directory"));
> +
> +	format_path(buf, git_dir, startup_info->prefix, PATH_FORMAT_RELATIVE);
> +	return 0;
> +}
Looks good.
Show 26 quoted lines
> +
>  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.gitdir.absolute", get_path_gitdir_absolute },
> +	{ "path.gitdir.relative", get_path_gitdir_relative },
>  	{ "references.format", get_references_format },
>  };
>  
> diff --git a/t/t1900-repo-info.sh b/t/t1900-repo-info.sh
> index 39bb77dda0..0660b00bbc 100755
> --- a/t/t1900-repo-info.sh
> +++ b/t/t1900-repo-info.sh
> @@ -155,4 +155,37 @@ test_expect_success 'git repo info -h shows only repo info usage' '
>  	test_grep ! "git repo structure" actual
>  '
>  
> +test_repo_info_path () {
> +	field_name=$1
> +	expect_absolute_eval=$2
> +	expect_relative=$3
> +	env_prefix=$4

nit: I was a bit uncertain regarding the purpose of env_prefix here. Since the env_prefix is not used by any tests yet, I wonder if it we should delay adding it until the next patch. If we want to reduce churn though, I think we could also swap the order of patch 3 and 4.

Show 5 quoted lines
> +
> +	test_expect_success "query individual key: path.$field_name.absolute${env_prefix:+ ($env_prefix)}" '
> +		(
> +			cd test-repo/sub &&
> +			expect_absolute=$(eval "$expect_absolute_eval") &&

Can we just compute `expect_absolute` prior to passing it instead of using eval here?

Show 22 quoted lines
> +			echo "path.$field_name.absolute=$expect_absolute" >expect &&
> +			eval "${env_prefix:+$env_prefix }git repo info \"path.$field_name.absolute\"" >actual &&
> +			test_cmp expect actual
> +		)
> +	'
> +
> +	test_expect_success "query individual key: path.$field_name.relative${env_prefix:+ ($env_prefix)}" '
> +		(
> +			cd test-repo/sub &&
> +			echo "path.$field_name.relative=$expect_relative" >expect &&
> +			eval "${env_prefix:+$env_prefix }git repo info \"path.$field_name.relative\"" >actual &&
> +			test_cmp expect actual
> +		)
> +	'
> +}
> +
> +test_expect_success 'setup test repository layout for path fields' '
> +	git init test-repo &&
> +	mkdir -p test-repo/sub
> +'
> +
> +test_repo_info_path 'gitdir' 'echo "$(cd .. && pwd)/.git"' '../.git'

hmmm, do we expect the path suffix to be the same between relative and absolute paths for all test cases? If so, we could just have a single `expect_path_suffix` argument and let the helper compute the appropriate absolute and relative paths internally.

-Justin
Lucas Seiki OshiroJun 8, 2026, 22:17 UTC in reply to K Jayatheerth on lore

Re: [GSoC PATCH v2 3/4] repo: add path.gitdir with absolute and relative suffix formatting

Show 5 quoted lines
> +test_repo_info_path () {
> + field_name=$1
> + expect_absolute_eval=$2
> + expect_relative=$3
> + env_prefix=$4
This helper function needs a documentation.
> + test_expect_success "query individual key: path.$field_name.absolute${env_prefix:+ ($env_prefix)}" '
This makes the output polluted. What about changing it by something like:
	test_expect_success "absolute: $label' '...'
	test_expect_success "relative: $label' '...'
with a custom label?
Show 5 quoted lines
> +
> +test_expect_success 'setup test repository layout for path fields' '
> + git init test-repo &&
> + mkdir -p test-repo/sub
> +'

The helper function `test_repo_info_path` is relying too much on the existence of the `test-repo`. I think it would be better to add a new parameter `repo_name` (or similar) because:

1. You could move this creation to the helper function and
   you won't need to place the test after that creation
2. You could use different for each (test_repo_info_path call, path format)
   pair. Currently, if more than one test fails, its result is overwritten
   and the `expect` and `actual` files from the trash directory will be
   the last of the broken tests.
3. You won't need to use the hacky 'echo "$(cd .. && pwd)' 
This applies my suggestions (feel free to use, adapt or discard it):
test_repo_info_path () {
        label=$1
	field_name=$2
	repo_name=$3
	expect_absolute=$4
	expect_relative=$5
	init_command=$6
	absolute_root="$repo_name"-absolute
	relative_root="$repo_name"-relative
	expect_absolute="$PWD"/"$absolute_root"/"$expect_absolute"
	test_expect_success 'setup test repository layout for path fields' '
		git init "$absolute_root" &&
		git init "$relative_root" &&
	       	mkdir -p "$absolute_root"/sub "$relative_root"/sub
	'
	test_expect_success "absolute: $label" '
		(
			export ROOT="$PWD"/"$absolute_root" &&
			cd "$absolute_root"/sub &&
			eval "$init_command" &&
			echo "path.$field_name.absolute=$expect_absolute" >expect &&
			git repo info path.$field_name.absolute >actual &&
			test_cmp expect actual
		)
	'
	test_expect_success "relative: $label" '
		(
			export ROOT="$PWD"/"$relative_root" &&
			cd "$relative_root"/sub &&
			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 'gitdir' 'gitdir' 'gitdir' '.git' '../.git'
Junio C HamanoJun 8, 2026, 22:36 UTC in reply to K Jayatheerth on lore

Re: [GSoC PATCH v2 0/4] teach git repo info to handle path keys

K Jayatheerth <jayatheerthkulkarni2005@gmail.com> writes:
> 2. Should we consider a default option?
>    Currently we have path.gitdir.absolute. Should we consider an
>    option where a plain `path.gitdir` returns some default?

Probably not. It will invite folks wanting to tweak the default between absolute and relative, rendering this feature useless for robust scripting. You do not necessarily want to save typing in plumbing interface. You want to reduce ambiguity by reducing more than one ways to do a thing down to just one way, and as long as that one way is not overly verbose, you are fine.

Lucas Seiki OshiroJun 8, 2026, 22:40 UTC in reply to K Jayatheerth on lore

Re: [GSoC PATCH v2 4/4] repo: add path.commondir with absolute and relative suffix formatting

This patch looks really straightforward after the previous one. I hope the rest of the path.* series will be just like that.

> +test_repo_info_path 'commondir' 'echo "$(cd .. && pwd)/.git"' '../.git'
> +test_repo_info_path 'commondir' 'echo "$(cd .. && pwd)/custom-common"' '../custom-common' 'GIT_COMMON_DIR="$(cd .. && pwd)/custom-common" GIT_DIR=../.git'
> +test_repo_info_path 'commondir' 'echo "$(cd .. && pwd)/.git"' '../.git' 'GIT_DIR=../.git'

If you use the test_repo_info_path that I suggested in the other answer, this would be:

test_repo_info_path 'commondir without env vars' 'commondir' 'common-no-env' \
                    '.git' '../.git'
test_repo_info_path 'commondir with GIT_COMMON_DIR and GIT_DIR' 'commondir' \
                    'commondir-envs' 'custom-common' '../custom-common'\
		    'export GIT_COMMON_DIR="$ROOT/custom-common" &&
		     export GIT_DIR="../.git" &&
		     git init --bare "$ROOT/custom-common"
		    '
test_repo_info_path 'commondir with only GIT_DIR' 'commondir' \
                    'commondir-only-gitdir' '.git' '../.git' 'GIT_DIR=../.git'
K JayatheerthJun 9, 2026, 02:27 UTC in reply to Kristoffer Haugsbakk on lore

Re: [GSoC PATCH v2 1/4] path: introduce format_path() for centralized path formatting

> > the localized fallback mechanics specific to `rev-parse`.
>
> This looks very well explained to my naive eyes.
Thank you!
Show 16 quoted lines
> Nitpick. You are supposed to add your `Signed-off-by` at the end. You
> are saying with that line that you are signing off on the changes and
> the commit message, including the trailers (mentors) you’ve decided to
> add. Imagine if the maintainer applies this patch and fixes a typo and
> the commit becomes:
>
>     Mentored-by: Justin Tobler <jltobler@gmail.com>
>     Mentored-by: Lucas Seiki Oshiro <lucasseikioshiro@gmail.com>
>     Signed-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>
>     [jc: typo fix]
>     Signed-off-by: Junio ...
>
> The chain of custody is then very clear.
>
> > ---
> >[snip]

Ok, I understand thanks for taking time to explain that

Will change it in the next patch series.
K JayatheerthJun 9, 2026, 02:30 UTC in reply to Lucas Seiki Oshiro on lore

Re: [GSoC PATCH v2 0/4] teach git repo info to handle path keys

Show 8 quoted lines
>
> I prefer `.(absolute|relative)` at the end. `path.gitdir.relative`
> means that we have a collection of paths, in those collections we
> have gitdir that can be relative or absolute, and we want the
> relative. `path.relative.gitdir` means that we have a collection
> of relative paths and from those we're picking gitdir. The first
> feels more natural.
>
Yes, I believe the same.
Show 5 quoted lines
> PS: this is a nitpick, but it would be really helpful if you provide
> a range-diff in the cover letter. Check the usage of `--range-diff`
> in git-format-patch documentation (this flag also works for
> git-send-email). Or, if you prefer, you can generate it by running
> `git range-diff` and copying the output.
Alright, I will add that as well in the next series.
Thank you!
K JayatheerthJun 9, 2026, 02:47 UTC in reply to Lucas Seiki Oshiro on lore

Re: [GSoC PATCH v2 1/4] path: introduce format_path() for centralized path formatting

Show 7 quoted lines
>
> Nitpick: the documentation is clear to me, but maybe the function name
> "format" and the parameter name "buf" can mislead the user to think
> that it only formats the path without appending to the existing string
> in `buf`. My suggestion is to rename them to something like
> `append_formatted_path` and `dest`, respectively.
>

Ok, that's a good point! I will add this in the next series!

Show 9 quoted lines
>
> > +test_repo_info_path () {
> > + field_name=$1
> > + expect_absolute_eval=$2
> > + expect_relative=$3
> > + env_prefix=$4
>
> This helper function needs a documentation.
>
Alright, I will add that.
Show 9 quoted lines
> > + test_expect_success "query individual key: path.$field_name.absolute${env_prefix:+ ($env_prefix)}" '
>
> This makes the output polluted. What about changing it by something like:
>
>         test_expect_success "absolute: $label' '...'
>         test_expect_success "relative: $label' '...'
>
> with a custom label?
>

Ahh, interesting. I agree, I will look into this!

Show 22 quoted lines
> > +
> > +test_expect_success 'setup test repository layout for path fields' '
> > + git init test-repo &&
> > + mkdir -p test-repo/sub
> > +'
>
> The helper function `test_repo_info_path` is relying too much on the
> existence of the `test-repo`. I think it would be better to add a new
> parameter `repo_name` (or similar) because
>
> 1. You could move this creation to the helper function and
>    you won't need to place the test after that creation
>
> 2. You could use different for each (test_repo_info_path call, path format)
>    pair. Currently, if more than one test fails, its result is overwritten
>    and the `expect` and `actual` files from the trash directory will be
>    the last of the broken tests.
>
> 3. You won't need to use the hacky 'echo "$(cd .. && pwd)'
>
> This applies my suggestions (feel free to use, adapt or discard it):
>

Thanks! That is helpful.

Regards,
- K Jayatheerth
K JayatheerthJun 9, 2026, 04:41 UTC in reply to Justin Tobler on lore

Re: [GSoC PATCH v2 3/4] repo: add path.gitdir with absolute and relative suffix formatting

Show 9 quoted lines
> > +     if (format == PATH_FORMAT_UNMODIFIED) {
> > +             strbuf_addstr(buf, path);
> > +             return;
> > +     }
> > +
> > +     if (format == PATH_FORMAT_RELATIVE) {
>
> nit: we could just continue the "else if" chain here instead of
> restarting it.

Ahh, good catch! True we can.

Show 6 quoted lines
> > +             strbuf_realpath_forgiving(&canonical_buf, path, 1);
> > +             strbuf_addbuf(buf, &canonical_buf);
>
> Do we need `canonical_buf` here? Can we just add the path to `buf`
> directly?
>

canonical_buf is necessary if I my understanding is correct. We can't pass buf directly to strbuf_realpath_forgiving() because it resets its destination buffer before writing. Since format_path() has append semantics, doing so would clobber any existing content in buf. The intermediate canonical_buf is needed to keep that safe.

Show 7 quoted lines
> > +void format_path(struct strbuf *buf, const char *path,
> > +              const char *prefix, enum path_format format);
> > +
>
> Ok so in this patch we are just adding the new path formatting
> interface and will integrate it in the next one. Overall the direction
> of this patch looks good to me.
Yup, that's correct!
Show 9 quoted lines
> > and update print_path() to act as a light wrapper around the new shared
> > engine. Resolve user-provided formatting flags directly within rev-parse
> > to pass the final determined path_format to format_path().
>
> So if the format isn't explicitly set by the user via the
> `--path-format` option, the default formatting strategy used depends on
> the path being printed. IOW, there is no consistent default path format
> here.
>
Yes, that's correct.
Show 15 quoted lines
> > +     struct strbuf sb = STRBUF_INIT;
> > +     enum path_format fmt = (arg_path_format != -1) ? arg_path_format : def_format;
>
> hmmm, so `arg_path_format` specifies what the user-provided format and
> acts as a sentinel to signal there is no value provided and the fallback
> format needs to be used. This feels a tad bit awkward to me.
>
> I wonder if we should introduce a PATH_FORMAT_DEFAULT to the
> `path_format` enum that maps to one of the existing enum values in
> `path.c:format_path()`. Here in `print_path()`, we could then intercept
> a PATH_FORMAT_DEFAULT value and override it to the specified
> `def_format`. I'm not sure if this is ultimately that much better
> though.
>
> -Justin

You're right that the -1 is awkward it forces arg_path_format to be an int rather than the enum type itself, which loses type safety.

PATH_FORMAT_DEFAULT is cleaner in that regard, but it pushes the "what does default mean?" question into format_path() which currently has no notion of a fallback. Since the fallback is call-site specific (each path type in rev-parse has its own default), I'd rather keep that logic in print_path() where the context lives.

A middle ground would be adding PATH_FORMAT_DEFAULT to the enum but not handling it in format_path().

--- enum path_format_type format = PATH_FORMAT_DEFAULT;

/* ... */
static void print_path(const char *path, const char *prefix,
                       enum path_format_type format,
                       enum path_format_type def_format)
{
        struct strbuf sb = STRBUF_INIT;
        enum path_format_type fmt =
            (format == PATH_FORMAT_DEFAULT) ? def_format : format;
        format_path(&sb, path, prefix, fmt);
        puts(sb.buf);
        strbuf_release(&sb);
}
---
Show 5 quoted lines
> > +     format_path(buf, git_dir, startup_info->prefix, PATH_FORMAT_CANONICAL);
>
> For absolute paths, I don't think we actually need the prefix, but
> providing it doesn't probably matter too much either way.
>

Yeah, true. Since the relative had the prefix I just added the prefix here too. It is consistent.

Show 12 quoted lines
> >
> > +test_repo_info_path () {
> > +     field_name=$1
> > +     expect_absolute_eval=$2
> > +     expect_relative=$3
> > +     env_prefix=$4
>
> nit: I was a bit uncertain regarding the purpose of env_prefix here.
> Since the env_prefix is not used by any tests yet, I wonder if it we
> should delay adding it until the next patch. If we want to reduce churn
> though, I think we could also swap the order of patch 3 and 4.
>

Good point I will actually swap 3 and 4 It is just better tbh.

       (
Show 6 quoted lines
> > +                     cd test-repo/sub &&
> > +                     expect_absolute=$(eval "$expect_absolute_eval") &&
>
> Can we just compute `expect_absolute` prior to passing it instead of
> using eval here?
>

Yes, I plan to follow Lucas's suggestion from his review. passing a repo_name parameter and capturing $PWD before the cd to construct the absolute path at helper-call time. That avoids eval entirely and also addresses his other concerns about test isolation. Will fix in v3.

Show 12 quoted lines
> > +test_expect_success 'setup test repository layout for path fields' '
> > +     git init test-repo &&
> > +     mkdir -p test-repo/sub
> > +'
> > +
> > +test_repo_info_path 'gitdir' 'echo "$(cd .. && pwd)/.git"' '../.git'
>
> hmmm, do we expect the path suffix to be the same between relative and
> absolute paths for all test cases? If so, we could just have a single
> `expect_path_suffix` argument and let the helper compute the appropriate
> absolute and relative paths internally.
>

Yes it is consistent between absolute and relative. This is a good suggestion. Also aligns with what Lucas said.

Thank you, This will help building v3 much smoother.

Regards,
- K Jayatheerth
K JayatheerthJun 9, 2026, 05:00 UTC in reply to Junio C Hamano on lore

Re: [GSoC PATCH v2 0/4] teach git repo info to handle path keys

On Tue, Jun 9, 2026 at 4:06 AM Junio C Hamano <gitster@pobox.com> wrote:
Show 13 quoted lines
>
> K Jayatheerth <jayatheerthkulkarni2005@gmail.com> writes:
>
> > 2. Should we consider a default option?
> >    Currently we have path.gitdir.absolute. Should we consider an
> >    option where a plain `path.gitdir` returns some default?
>
> Probably not.  It will invite folks wanting to tweak the default
> between absolute and relative, rendering this feature useless for
> robust scripting.  You do not necessarily want to save typing in
> plumbing interface.  You want to reduce ambiguity by reducing more
> than one ways to do a thing down to just one way, and as long as
> that one way is not overly verbose, you are fine.

Makes sense. Explicit keys like path.gitdir.absolute and path.gitdir.relative are unambiguous for scripting, and saving a few keystrokes isn't worth introducing a configurable default that would make the output unpredictable. I'll actually drop question 2 from the open questions in v3's cover letter.

Thanks!
Regards,
- K Jayatheerth
Justin ToblerJun 9, 2026, 14:31 UTC in reply to K Jayatheerth on lore

Re: [GSoC PATCH v2 3/4] repo: add path.gitdir with absolute and relative suffix formatting

On 26/06/09 10:11AM, K Jayatheerth wrote:
Show 46 quoted lines
> > > +     struct strbuf sb = STRBUF_INIT;
> > > +     enum path_format fmt = (arg_path_format != -1) ? arg_path_format : def_format;
> >
> > hmmm, so `arg_path_format` specifies what the user-provided format and
> > acts as a sentinel to signal there is no value provided and the fallback
> > format needs to be used. This feels a tad bit awkward to me.
> >
> > I wonder if we should introduce a PATH_FORMAT_DEFAULT to the
> > `path_format` enum that maps to one of the existing enum values in
> > `path.c:format_path()`. Here in `print_path()`, we could then intercept
> > a PATH_FORMAT_DEFAULT value and override it to the specified
> > `def_format`. I'm not sure if this is ultimately that much better
> > though.
> 
> You're right that the -1 is awkward
> it forces arg_path_format to be an int rather than the enum type
> itself, which loses type safety.
> 
> PATH_FORMAT_DEFAULT is cleaner in that regard, but it pushes the "what
> does default mean?" question into format_path()
> which currently has no notion of a fallback.
> Since the fallback is call-site specific (each path type in rev-parse
> has its own default),
> I'd rather keep that logic in print_path() where the context lives.
> 
> A middle ground would be adding PATH_FORMAT_DEFAULT to the enum but
> not handling it in format_path().
> 
> ---
> enum path_format_type format = PATH_FORMAT_DEFAULT;
> 
> /* ... */
> 
> static void print_path(const char *path, const char *prefix,
>                        enum path_format_type format,
>                        enum path_format_type def_format)
> {
>         struct strbuf sb = STRBUF_INIT;
>         enum path_format_type fmt =
>             (format == PATH_FORMAT_DEFAULT) ? def_format : format;
> 
>         format_path(&sb, path, prefix, fmt);
>         puts(sb.buf);
>         strbuf_release(&sb);
> }
> ---

Intercepting PATH_FORMAT_DEFAULT in print_path() and overriding it to the appropriate default needed for the specific path printed by git-rev-parse(1), as shown above, seems reasonable to me.

But I do think that PATH_FORMAT_DEFAULT should have an actual default in format_path(). Otherwise we would have an enum value that requires callers to explicitly handle prior to invoking format_path() which would also be rather awkward. IMO, it probably wouldn't be a big deal to just say PATH_FORMAT_DEFAULT is treated as PATH_FORMAT_UNMODIFIED when passed to format_path() and document it. In practice, our rev-parse use-case would always replace PATH_FORMAT_DEFAULT with the appropriate value prior to invoking format_path().

-Justin
K JayatheerthJun 10, 2026, 12:11 UTC in reply to Justin Tobler on lore

Re: [GSoC PATCH v2 3/4] repo: add path.gitdir with absolute and relative suffix formatting

Show 14 quoted lines
>
> Intercepting PATH_FORMAT_DEFAULT in print_path() and overriding it to
> the appropriate default needed for the specific path printed by
> git-rev-parse(1), as shown above, seems reasonable to me.
>
> But I do think that PATH_FORMAT_DEFAULT should have an actual default in
> format_path(). Otherwise we would have an enum value that requires
> callers to explicitly handle prior to invoking format_path() which would
> also be rather awkward. IMO, it probably wouldn't be a big deal to just
> say PATH_FORMAT_DEFAULT is treated as PATH_FORMAT_UNMODIFIED when passed
> to format_path() and document it. In practice, our rev-parse use-case
> would always replace PATH_FORMAT_DEFAULT with the appropriate value
> prior to invoking format_path().
>

Makes sense. I'll have format_path() treat PATH_FORMAT_DEFAULT as PATH_FORMAT_UNMODIFIED internally and document it, so there's no unhandled enum value. print_path() in rev-parse will still intercept PATH_FORMAT_DEFAULT and replace it with the path-specific default before calling format_path(), so in practice it won't fall through to the unmodified behavior. Almost done with v3 this was the only one left.

Thanks for taking time!
Lucas Seiki OshiroJun 10, 2026, 12:42 UTC in reply to K Jayatheerth on lore

Re: [GSoC PATCH v2 0/4] teach git repo info to handle path keys

Junio has a good point here.

This is a plumbing command and we should design it for machines instead of humans.

K JayatheerthJun 12, 2026, 18:28 UTC in reply to K Jayatheerth on lore

[GSoC Patch v3 0/4] teach git repo info to handle path keys

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.

Since all the questions were answered I have removed them from this cover letter.

Changes since v2:
* Renamed the shared helper from `format_path()` to
  `append_formatted_path()`, and renamed the `buf`
  parameter to `dest` to better reflect its
  append-style behavior (Lucas, Phillip).
* Introduced a dedicated `PATH_FORMAT_DEFAULT`
  enumerator. This removes the awkward `-1`
  sentinel in `print_path()` while preserving enum
  type safety (Phillip, Justin).
* Handled `PATH_FORMAT_DEFAULT` as
  `PATH_FORMAT_UNMODIFIED` inside
  `append_formatted_path()`, while intercepting it
  in `print_path()` for rev-parse-specific fallback
  behavior (Justin).
* Replaced the `else if` chain in `append_formatted_path()` with a 
  clean `switch` statement setup (Justin, Lucas).
* Reordered the `commondir` and `gitdir` patches so
  the parameterized test helper
  (`test_repo_info_path`) is introduced first,
  establishing the isolated test infrastructure up
  front (Justin).
* Reworked the test helper to accept a label,
  `repo_name`, and `path_suffix`; moved repository
  creation into the helper for isolation; and
  replaced `eval` by capturing `$PWD` before
  changing directories (Justin, Lucas).
* Corrected trailer ordering so `Signed-off-by`
  appears after `Mentored-by` (Kristoffer).
* Cleaned up minor trailing whitespace issues across
  the patch array declarations.

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

Thanks for taking another look!
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         | 103 ++++++++----------------------------
 path.c                      |  70 ++++++++++++++++++++++++
 path.h                      |  36 +++++++++++++
 t/t1900-repo-info.sh        |  68 ++++++++++++++++++++++++
 6 files changed, 262 insertions(+), 80 deletions(-)
Range-diff against v2:
1:  c1f1e87fe9 ! 1:  a396b4f8e6 path: introduce format_path() for centralized path formatting
    @@ Metadata
     Author: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>
     
      ## Commit message ##
    -    path: introduce format_path() for centralized path formatting
    +    path: introduce append_formatted_path() for shared path formatting
     
    -    The path-formatting logic inside `builtin/rev-parse.c` handles absolute,
    -    canonical, and relative formatting rules based on user-supplied options.
    -    However, this logic is tightly coupled to `rev-parse` and writes directly
    -    to stdout.
    +    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.
     
    -    To allow other builtins (such as the upcoming `git repo` path keys) to
    -    re-use this logic, extract the core path-formatting algorithm into a centralized
    -    helper function, `format_path()`, in `path.c`.
    -
    -    Expose a single, streamlined `path_format` enum in `path.h` to let callers
    -    explicitly declare their formatting strategy (UNMODIFIED, RELATIVE,
    -    RELATIVE_IF_SHARED, or CANONICAL). This decouples the core algorithm from
    -    the localized fallback mechanics specific to `rev-parse`.
    +    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>
    @@ path.c: char *xdg_cache_home(const char *filename)
      	return NULL;
      }
      
    -+void format_path(struct strbuf *buf, const char *path,
    -+		 const char *prefix, enum path_format format)
    ++void append_formatted_path(struct strbuf *dest, const char *path,
    ++			   const char *prefix, enum path_format format)
     +{
    -+	if (format == PATH_FORMAT_UNMODIFIED) {
    -+		strbuf_addstr(buf, path);
    -+		return;
    -+	}
    ++	switch (format) {
    ++	case PATH_FORMAT_DEFAULT:
    ++	case PATH_FORMAT_UNMODIFIED:
    ++		strbuf_addstr(dest, path);
    ++		break;
     +
    -+	if (format == PATH_FORMAT_RELATIVE) {
    ++	case PATH_FORMAT_RELATIVE: {
     +		struct strbuf relative_buf = STRBUF_INIT;
     +		struct strbuf real_path = STRBUF_INIT;
     +		struct strbuf real_prefix = STRBUF_INIT;
    @@ path.c: char *xdg_cache_home(const char *filename)
     +			prefix = real_prefix.buf;
     +		}
     +
    -+		strbuf_addstr(buf, relative_path(path, prefix, &relative_buf));
    ++		strbuf_addstr(dest, relative_path(path, prefix, &relative_buf));
     +
     +		strbuf_release(&relative_buf);
     +		strbuf_release(&real_path);
     +		strbuf_release(&real_prefix);
     +		free(cwd);
    -+	} else if (format == PATH_FORMAT_RELATIVE_IF_SHARED) {
    ++		break;
    ++	}
    ++
    ++	case PATH_FORMAT_RELATIVE_IF_SHARED: {
     +		struct strbuf relative_buf = STRBUF_INIT;
     +
     +		/*
    @@ path.c: char *xdg_cache_home(const char *filename)
     +		 * default the prefix to the current working directory. Doing so
     +		 * would cause a relative path to always be produced if possible.
     +		 */
    -+		strbuf_addstr(buf, relative_path(path, prefix, &relative_buf));
    ++		strbuf_addstr(dest, relative_path(path, prefix, &relative_buf));
     +		strbuf_release(&relative_buf);
    -+	} else if (format == PATH_FORMAT_CANONICAL) {
    ++		break;
    ++	}
    ++
    ++	case PATH_FORMAT_CANONICAL: {
     +		struct strbuf canonical_buf = STRBUF_INIT;
     +
     +		strbuf_realpath_forgiving(&canonical_buf, path, 1);
    -+		strbuf_addbuf(buf, &canonical_buf);
    ++		strbuf_addbuf(dest, &canonical_buf);
     +
     +		strbuf_release(&canonical_buf);
    ++		break;
    ++	}
    ++
    ++	default:
    ++		BUG("unknown path_format value %d", format);
     +	}
     +}
     +
    @@ path.h: enum scld_error safe_create_leading_directories_no_share(char *path);
     + * The formatting strategy to apply when writing a path into a buffer.
     + */
     +enum path_format {
    ++	/*
    ++	 * Represents the default formatting behavior. Treated as
    ++	 * PATH_FORMAT_UNMODIFIED by append_formatted_path().
    ++	 */
    ++	PATH_FORMAT_DEFAULT,
    ++
     +	/* Output the path exactly as-is without any modifications. */
     +	PATH_FORMAT_UNMODIFIED,
     +
    @@ path.h: enum scld_error safe_create_leading_directories_no_share(char *path);
     + * Format a path according to the specified formatting strategy and append
     + * the result to the given strbuf.
     + *
    -+ * `buf`    : The string buffer to append the formatted path to.
    ++ * `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 format_path(struct strbuf *buf, const char *path,
    -+		 const char *prefix, enum path_format format);
    ++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"
2:  2cc3e671af ! 2:  16198f96d1 rev-parse: use format_path for path formatting
    @@ Metadata
     Author: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>
     
      ## Commit message ##
    -    rev-parse: use format_path for path formatting
    +    rev-parse: use append_formatted_path() for path formatting
     
    -    Now that the core path-formatting logic has been abstracted into
    -    format_path() inside path.c, remove the localized duplicate formatting
    -    mechanics from builtin/rev-parse.c.
    +    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.
     
    -    Drop the usage of the old local format_type and default_type enums,
    -    and update print_path() to act as a light wrapper around the new shared
    -    engine. Resolve user-provided formatting flags directly within rev-parse
    -    to pass the final determined path_format to format_path().
    +    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>
    @@ builtin/rev-parse.c: static void handle_ref_opt(const char *pattern, const char
     -
     -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,
    -+		       int arg_path_format, enum path_format def_format)
    ++		       enum path_format arg_path_format, enum path_format def_format)
      {
     -	char *cwd = NULL;
     -	/*
    @@ builtin/rev-parse.c: static void handle_ref_opt(const char *pattern, const char
     -	}
     -	free(cwd);
     +	struct strbuf sb = STRBUF_INIT;
    -+	enum path_format fmt = (arg_path_format != -1) ? arg_path_format : def_format;
    ++	enum path_format fmt = (arg_path_format != PATH_FORMAT_DEFAULT) ? arg_path_format : def_format;
     +
    -+	format_path(&sb, path, prefix, fmt);
    ++	append_formatted_path(&sb, path, prefix, fmt);
     +	puts(sb.buf);
     +
     +	strbuf_release(&sb);
    @@ builtin/rev-parse.c: int cmd_rev_parse(int argc,
      	struct strbuf buf = STRBUF_INIT;
      	int seen_end_of_options = 0;
     -	enum format_type format = FORMAT_DEFAULT;
    -+	int arg_path_format = -1;
    ++	enum path_format arg_path_format = PATH_FORMAT_DEFAULT;
      
      	show_usage_if_asked(argc, argv, builtin_rev_parse_usage);
      
    @@ builtin/rev-parse.c: int cmd_rev_parse(int argc,
      				char *cwd;
      				int len;
     -				enum format_type wanted = format;
    -+				int wanted = arg_path_format;
    ++				enum path_format wanted = arg_path_format;
      				if (arg[2] == 'g') {	/* --git-dir */
      					if (gitdir) {
     -						print_path(gitdir, prefix, format, DEFAULT_UNMODIFIED);
4:  61b5d69306 ! 3:  7de41faa04 repo: add path.commondir with absolute and relative suffix formatting
    @@ Metadata
      ## Commit message ##
         repo: add path.commondir with absolute and relative suffix formatting
     
    -    In standard Git repositories, the Git directory and the common directory
    -    are identical. However, in environments utilizing multiple worktrees, the
    -    local working state ($GIT_DIR) is separated from the shared central data
    -    ($GIT_COMMON_DIR). Scripts require a reliable way to discover this shared
    -    path.
    +    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
    -    to `git repo info`. Similar to the `path.gitdir` keys, exposing explicit
    -    format variants removes the ambiguity of default fallbacks. Both keys are
    -    evaluated via the `format_path()` engine.
    +    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.
     
    -    Insert the new keys into the `repo_info_field` array in lexicographical
    -    order to maintain the integrity of binary search lookups.
    -
    -    Utilize the parameterized `test_repo_info_path` helper to validate the
    -    worktree edge cases. This ensures that path resolution correctly respects
    -    $GIT_COMMON_DIR when defined and safely falls back to $GIT_DIR otherwise.
    +    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>
    @@ Documentation/git-repo.adoc: values that they return:
     +	The path to the Git repository's common directory relative to
     +	the current working directory.
     +
    - `path.gitdir.absolute`::
    - 	The canonical absolute path to the Git repository directory (the `.git` directory).
    - 
    + `references.format`::
    + 	The reference storage format. The valid values are:
    + +
     
      ## builtin/repo.c ##
    +@@
    + #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"
     @@ builtin/repo.c: static int get_object_format(struct repository *repo, struct strbuf *buf)
      	return 0;
      }
    @@ builtin/repo.c: static int get_object_format(struct repository *repo, struct str
     +	if (!common_dir)
     +		return error(_("unable to get common directory"));
     +
    -+	format_path(buf, common_dir, startup_info->prefix, PATH_FORMAT_CANONICAL);
    ++	append_formatted_path(buf, common_dir, startup_info->prefix, PATH_FORMAT_CANONICAL);
     +	return 0;
     +}
     +
    @@ builtin/repo.c: static int get_object_format(struct repository *repo, struct str
     +	if (!common_dir)
     +		return error(_("unable to get common directory"));
     +
    -+	format_path(buf, common_dir, startup_info->prefix, PATH_FORMAT_RELATIVE);
    ++	append_formatted_path(buf, common_dir, startup_info->prefix, PATH_FORMAT_RELATIVE);
     +	return 0;
     +}
     +
    - static int get_path_gitdir_absolute(struct repository *repo, struct strbuf *buf)
    + static int get_references_format(struct repository *repo, struct strbuf *buf)
      {
    - 	const char *git_dir = repo_get_git_dir(repo);
    + 	strbuf_addstr(buf,
     @@ builtin/repo.c: 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 },
    - 	{ "path.gitdir.absolute", get_path_gitdir_absolute },
    - 	{ "path.gitdir.relative", get_path_gitdir_relative },
      	{ "references.format", get_references_format },
    + };
    + 
     
      ## t/t1900-repo-info.sh ##
    -@@ t/t1900-repo-info.sh: test_expect_success 'setup test repository layout for path fields' '
    - 	mkdir -p test-repo/sub
    +@@ t/t1900-repo-info.sh: test_expect_success 'git repo info -h shows only repo info usage' '
    + 	test_grep ! "git repo structure" actual
      '
      
    -+test_expect_success 'setup custom-common for commondir tests' '
    -+	git init --bare test-repo/custom-common
    -+'
    -+
    -+test_repo_info_path 'commondir' 'echo "$(cd .. && pwd)/.git"' '../.git'
    -+test_repo_info_path 'commondir' 'echo "$(cd .. && pwd)/custom-common"' '../custom-common' 'GIT_COMMON_DIR="$(cd .. && pwd)/custom-common" GIT_DIR=../.git'
    -+test_repo_info_path 'commondir' 'echo "$(cd .. && pwd)/.git"' '../.git' 'GIT_DIR=../.git'
    - test_repo_info_path 'gitdir' 'echo "$(cd .. && pwd)/.git"' '../.git'
    - 
    ++# 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
    ++
    ++	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"
    ++	'
    ++
    ++	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' \
    ++	'GIT_DIR="../.git" && export GIT_DIR'
    ++
      test_done
3:  ca95d51f6e ! 4:  ffd6a5bb16 repo: add path.gitdir with absolute and relative suffix formatting
    @@ Metadata
      ## Commit message ##
         repo: add path.gitdir with absolute and relative suffix formatting
     
    -    Scripts often need to locate the `.git` directory. While `git rev-parse`
    -    provides this, it relies on command-line flags to dictate path formatting.
    +    Scripts need a stable way to locate the git directory without
    +    parsing rev-parse output or relying on its flag-driven path format
    +    selection. There is no way to retrieve this path from git repo info
    +    today.
     
    -    Introduce `path.gitdir.absolute` and `path.gitdir.relative` keys to
    -    `git repo info`. Exposing separate format-specific keys instead of a base
    -    `path.gitdir` key avoids default fallbacks and requires callers to state
    -    their format requirements explicitly. Both keys use `format_path()` to
    -    resolve paths.
    -
    -    To test these keys, introduce the `test_repo_info_path` helper in
    -    `t/t1900-repo-info.sh`. The helper evaluates paths dynamically and accepts
    -    environment variable prefixes. This prepares the test suite for future path
    -    keys that depend on environment overrides, such as `commondir`.
    +    Introduce path.gitdir.absolute and path.gitdir.relative keys,
    +    consistent with the path.commondir keys added in the previous patch.
    +    Reuse the test_repo_info_path helper introduced there to validate
    +    both variants.
     
         Mentored-by: Justin Tobler <jltobler@gmail.com>
         Mentored-by: Lucas Seiki Oshiro <lucasseikioshiro@gmail.com>
    @@ Commit message
     
      ## Documentation/git-repo.adoc ##
     @@ Documentation/git-repo.adoc: values that they return:
    - `object.format`::
    - 	The object format (hash algorithm) used in the repository.
    + 	The path to the Git repository's common directory relative to
    + 	the current working directory.
      
     +`path.gitdir.absolute`::
     +	The canonical absolute path to the Git repository directory (the `.git` directory).
    @@ Documentation/git-repo.adoc: values that they return:
      +
     
      ## builtin/repo.c ##
    -@@
    - #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"
    -@@ builtin/repo.c: static int get_object_format(struct repository *repo, struct strbuf *buf)
    +@@ builtin/repo.c: static int get_path_commondir_relative(struct repository *repo, struct strbuf *b
      	return 0;
      }
      
    @@ builtin/repo.c: static int get_object_format(struct repository *repo, struct str
     +	if (!git_dir)
     +		return error(_("unable to get git directory"));
     +
    -+	format_path(buf, git_dir, startup_info->prefix, PATH_FORMAT_CANONICAL);
    ++	append_formatted_path(buf, git_dir, startup_info->prefix, PATH_FORMAT_CANONICAL);
     +	return 0;
     +}
     +
    @@ builtin/repo.c: static int get_object_format(struct repository *repo, struct str
     +	if (!git_dir)
     +		return error(_("unable to get git directory"));
     +
    -+	format_path(buf, git_dir, startup_info->prefix, PATH_FORMAT_RELATIVE);
    ++	append_formatted_path(buf, git_dir, startup_info->prefix, PATH_FORMAT_RELATIVE);
     +	return 0;
     +}
     +
    @@ builtin/repo.c: static int get_object_format(struct repository *repo, struct str
      {
      	strbuf_addstr(buf,
     @@ builtin/repo.c: 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 },
     +	{ "path.gitdir.absolute", get_path_gitdir_absolute },
     +	{ "path.gitdir.relative", get_path_gitdir_relative },
      	{ "references.format", get_references_format },
    @@ builtin/repo.c: static const struct repo_info_field repo_info_field[] = {
      
     
      ## t/t1900-repo-info.sh ##
    -@@ t/t1900-repo-info.sh: test_expect_success 'git repo info -h shows only repo info usage' '
    - 	test_grep ! "git repo structure" actual
    - '
    +@@ t/t1900-repo-info.sh: test_repo_info_path 'commondir with only GIT_DIR' 'commondir' \
    + 	'commondir-only-gitdir' '.git' '../.git' \
    + 	'GIT_DIR="../.git" && export GIT_DIR'
      
    -+test_repo_info_path () {
    -+	field_name=$1
    -+	expect_absolute_eval=$2
    -+	expect_relative=$3
    -+	env_prefix=$4
    -+
    -+	test_expect_success "query individual key: path.$field_name.absolute${env_prefix:+ ($env_prefix)}" '
    -+		(
    -+			cd test-repo/sub &&
    -+			expect_absolute=$(eval "$expect_absolute_eval") &&
    -+			echo "path.$field_name.absolute=$expect_absolute" >expect &&
    -+			eval "${env_prefix:+$env_prefix }git repo info \"path.$field_name.absolute\"" >actual &&
    -+			test_cmp expect actual
    -+		)
    -+	'
    -+
    -+	test_expect_success "query individual key: path.$field_name.relative${env_prefix:+ ($env_prefix)}" '
    -+		(
    -+			cd test-repo/sub &&
    -+			echo "path.$field_name.relative=$expect_relative" >expect &&
    -+			eval "${env_prefix:+$env_prefix }git repo info \"path.$field_name.relative\"" >actual &&
    -+			test_cmp expect actual
    -+		)
    -+	'
    -+}
    -+
    -+test_expect_success 'setup test repository layout for path fields' '
    -+	git init test-repo &&
    -+	mkdir -p test-repo/sub
    -+'
    ++test_repo_info_path 'gitdir standard' 'gitdir' 'gitdir-std' \
    ++	'.git' '../.git'
     +
    -+test_repo_info_path 'gitdir' 'echo "$(cd .. && pwd)/.git"' '../.git'
    ++test_repo_info_path 'gitdir with explicit GIT_DIR' 'gitdir' \
    ++	'gitdir-env' '.git' '../.git' \
    ++	'GIT_DIR="../.git" && export GIT_DIR'
     +
      test_done
-- 
2.54.0
K JayatheerthJun 12, 2026, 18:28 UTC in reply to K Jayatheerth on lore

[GSoC Patch v3 1/4] path: introduce append_formatted_path() for shared path formatting

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(+)
Show changes to 2 files +106 −0

path.c, path.h

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
K JayatheerthJun 12, 2026, 18:28 UTC in reply to K Jayatheerth on lore

[GSoC Patch v3 2/4] rev-parse: use append_formatted_path() for path formatting

Now that path formatting logic lives in a shared helper, keeping a duplicate implementation in rev-parse is unnecessary and risks the two diverging over time.

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 | 103 ++++++++++----------------------------------
 1 file changed, 23 insertions(+), 80 deletions(-)
Show changes to builtin/rev-parse.c +23 −80
diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c
index 218b5f34d6..2dd35361f3 100644
--- a/builtin/rev-parse.c
+++ b/builtin/rev-parse.c
@@ -632,73 +632,16 @@ 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;
+	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 +660,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 +740,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 +763,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 +928,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 +936,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 +971,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 +998,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 +1032,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
K JayatheerthJun 12, 2026, 18:28 UTC in reply to K Jayatheerth on lore

[GSoC Patch v3 3/4] repo: add path.commondir with absolute and relative suffix formatting

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.

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        | 61 +++++++++++++++++++++++++++++++++++++
 3 files changed, 96 insertions(+)
Show changes to 3 files +96 −0

Documentation/git-repo.adoc, builtin/repo.c, t/t1900-repo-info.sh

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..28fe76e25b 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
+
+	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"
+	'
+
+	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' \
+	'GIT_DIR="../.git" && export GIT_DIR'
+
 test_done
-- 
2.54.0
K JayatheerthJun 12, 2026, 18:28 UTC in reply to K Jayatheerth on lore

[GSoC Patch v3 4/4] repo: add path.gitdir with absolute and relative suffix formatting

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

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

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

Documentation/git-repo.adoc, builtin/repo.c, t/t1900-repo-info.sh

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

Re: [GSoC Patch v3 3/4] repo: add path.commondir with absolute and relative suffix formatting

> + test_expect_success "absolute: $label" '
> + (
> + cd "$absolute_root/sub" &&
> + ROOT="$(test-tool path-utils real_path "..")" && export ROOT &&

Be carful with the quotes here. Actually, there's no need to use quotes around `..`, and export can be used directly with the env var:

	export ROOT="$(test-tool path-utils real_path ..)" &&
Show 5 quoted lines
> 
> + test_expect_success "relative: $label" '
> + (
> + cd "$relative_root/sub" &&
> + ROOT="$(test-tool path-utils real_path "..")" && export ROOT &&
Same here.
> +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 &&
	export GIT_COMMON_DIR="$ROOT/custom-common" &&
	export GIT_DIR="../.git" &&
Lucas Seiki OshiroJun 15, 2026, 01:55 UTC in reply to K Jayatheerth on lore

Re: [GSoC Patch v3 4/4] repo: add path.gitdir with absolute and relative suffix formatting

> +test_repo_info_path 'gitdir with explicit GIT_DIR' 'gitdir' \
> + 'gitdir-env' '.git' '../.git' \
> + 'GIT_DIR="../.git" && export GIT_DIR'
	'export GIT_DIR="../.git"
Lucas Seiki OshiroJun 15, 2026, 01:59 UTC in reply to K Jayatheerth on lore

Re: [GSoC Patch v3 0/4] teach git repo info to handle path keys

Hi, Jayatheerth!

I've left some comments in your tests. From my side you only need to fix them.

K JayatheerthJun 15, 2026, 04:51 UTC in reply to K Jayatheerth on lore

[GSoC Patch v4 0/4] teach git repo info to handle path keys

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.

Since all the questions were answered I have removed them from this cover letter.

Changes since v3:
* Removed unnecessary double quotes around the `..` argument when calling
  `test-tool path-utils real_path` in the `test_repo_info_path` helper,
  as suggested by Lucas.
* Retained the POSIX-compliant `ROOT="..." && export ROOT` syntax in the
  test setup. Combining them into `export ROOT="..."` triggered Git's
  strict `test-lint-shell-syntax` portability checks, so the separate
  assignment and export remains.
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         | 103 ++++++++----------------------------
 path.c                      |  70 ++++++++++++++++++++++++
 path.h                      |  36 +++++++++++++
 t/t1900-repo-info.sh        |  68 ++++++++++++++++++++++++
 6 files changed, 262 insertions(+), 80 deletions(-)
Range-diff against v3:
1:  d276ac145e = 1:  a396b4f8e6 path: introduce append_formatted_path() for shared path formatting
2:  5dba41bcb3 = 2:  16198f96d1 rev-parse: use append_formatted_path() for path formatting
3:  b21c97f5d9 ! 3:  b45c6f0d12 repo: add path.commondir with absolute and relative suffix formatting
    @@ t/t1900-repo-info.sh: test_expect_success 'git repo info -h shows only repo info
     +	test_expect_success "absolute: $label" '
     +		(
     +			cd "$absolute_root/sub" &&
    -+			ROOT="$(test-tool path-utils real_path "..")" && export ROOT &&
    ++			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 &&
    @@ t/t1900-repo-info.sh: test_expect_success 'git repo info -h shows only repo info
     +	test_expect_success "relative: $label" '
     +		(
     +			cd "$relative_root/sub" &&
    -+			ROOT="$(test-tool path-utils real_path "..")" && export ROOT &&
    ++			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 &&
4:  fd7a899788 = 4:  b5234ffe3e repo: add path.gitdir with absolute and relative suffix formatting
-- 
2.54.0
K JayatheerthJun 15, 2026, 04:51 UTC in reply to K Jayatheerth on lore

[GSoC Patch v4 1/4] path: introduce append_formatted_path() for shared path formatting

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(+)
Show changes to 2 files +106 −0

path.c, path.h

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
K JayatheerthJun 15, 2026, 04:51 UTC in reply to K Jayatheerth on lore

[GSoC Patch v4 2/4] rev-parse: use append_formatted_path() for path formatting

Now that path formatting logic lives in a shared helper, keeping a duplicate implementation in rev-parse is unnecessary and risks the two diverging over time.

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 | 103 ++++++++++----------------------------------
 1 file changed, 23 insertions(+), 80 deletions(-)
Show changes to builtin/rev-parse.c +23 −80
diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c
index 218b5f34d6..2dd35361f3 100644
--- a/builtin/rev-parse.c
+++ b/builtin/rev-parse.c
@@ -632,73 +632,16 @@ 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;
+	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 +660,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 +740,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 +763,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 +928,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 +936,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 +971,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 +998,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 +1032,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
K JayatheerthJun 15, 2026, 04:51 UTC in reply to K Jayatheerth on lore

[GSoC Patch v4 3/4] repo: add path.commondir with absolute and relative suffix formatting

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.

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        | 61 +++++++++++++++++++++++++++++++++++++
 3 files changed, 96 insertions(+)
Show changes to 3 files +96 −0

Documentation/git-repo.adoc, builtin/repo.c, t/t1900-repo-info.sh

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..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
+
+	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"
+	'
+
+	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' \
+	'GIT_DIR="../.git" && export GIT_DIR'
+
 test_done
-- 
2.54.0
K JayatheerthJun 15, 2026, 04:51 UTC in reply to K Jayatheerth on lore

[GSoC Patch v4 4/4] repo: add path.gitdir with absolute and relative suffix formatting

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

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

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

Documentation/git-repo.adoc, builtin/repo.c, t/t1900-repo-info.sh

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

Re: [GSoC Patch v4 2/4] rev-parse: use append_formatted_path() for path formatting

On 26/06/15 10:21AM, K Jayatheerth wrote: [snip]

Show 51 quoted lines
> -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
Justin ToblerJun 15, 2026, 18:17 UTC in reply to K Jayatheerth on lore

Re: [GSoC Patch v4 3/4] repo: add path.commondir with absolute and relative suffix formatting

On 26/06/15 10:21AM, K Jayatheerth wrote:
Show 14 quoted lines
> 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]
Show 22 quoted lines
> 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?

Show 8 quoted lines
> +	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.

Show 35 quoted lines
> +	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
K JayatheerthJun 16, 2026, 04:19 UTC in reply to Justin Tobler on lore

Re: [GSoC Patch v4 2/4] rev-parse: use append_formatted_path() for path formatting

Hey Justin,
Show 6 quoted lines
>
> 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!

Show 15 quoted lines
> > 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!

Show 13 quoted lines
> > +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.

Show 20 quoted lines
> > +     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!

Show 5 quoted lines
> 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!

Show 18 quoted lines
> > +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.

Show 6 quoted lines
> > +     '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
K JayatheerthJun 16, 2026, 04:49 UTC in reply to K Jayatheerth on lore

[GSoC Patch v5 0/4] teach git repo info to handle path keys

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
K JayatheerthJun 16, 2026, 04:49 UTC in reply to K Jayatheerth on lore

[GSoC Patch v5 1/4] path: introduce append_formatted_path() for shared path formatting

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(+)
Show changes to 2 files +106 −0

path.c, path.h

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
K JayatheerthJun 16, 2026, 04:49 UTC in reply to K Jayatheerth on lore

[GSoC Patch v5 2/4] rev-parse: use append_formatted_path() for path formatting

Now that path formatting logic lives in a shared helper, keeping a duplicate implementation in rev-parse is unnecessary and risks the two diverging over time.

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(-)
Show changes to builtin/rev-parse.c +24 −80
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
K JayatheerthJun 16, 2026, 04:49 UTC in reply to K Jayatheerth on lore

[GSoC Patch v5 3/4] repo: add path.commondir with absolute and relative suffix formatting

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(+)
Show changes to 3 files +87 −0

Documentation/git-repo.adoc, builtin/repo.c, t/t1900-repo-info.sh

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
K JayatheerthJun 16, 2026, 04:49 UTC in reply to K Jayatheerth on lore

[GSoC Patch v5 4/4] repo: add path.gitdir with absolute and relative suffix formatting

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

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

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

Documentation/git-repo.adoc, builtin/repo.c, t/t1900-repo-info.sh

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

Re: [GSoC Patch v5 1/4] path: introduce append_formatted_path() for shared path formatting

On 16/06/2026 05:49, K Jayatheerth wrote:
Show 7 quoted lines
> 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.

Sorry I haven't had time to look at this series recently, it is looking much nicer now that we have a single enum. It would be helpful to explain why we need PATH_FORMAT_DEFAULT that acts exactly like PATH_FORMAT_UNMODIFIED. Looking at the next patch it seems this is still a wart in the api due to rev-parse wanting needing to distinguish the unmodified case from the default case.

Thanks
Phillip
Show 136 quoted lines
> 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"
Phillip WoodJun 16, 2026, 13:08 UTC in reply to K Jayatheerth on lore

Re: [GSoC Patch v5 2/4] rev-parse: use append_formatted_path() for path formatting

On 16/06/2026 05:49, K Jayatheerth wrote:
Show 66 quoted lines
> -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;

This is the source of the api wart I referred to in the previous patch. Could we keep the existing enums and convert them into the appropriate PATH_FORMAT_* flag in print_path() above? I think we already have the logic to do that in the existing code. That would mean that other users of append_formatted_path() don't have to worry about the extra flag.

Thanks
Phillip
Show 90 quoted lines
>   
>   	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;
>   			}
K JayatheerthJun 16, 2026, 17:04 UTC in reply to Phillip Wood on lore

Re: [GSoC Patch v5 2/4] rev-parse: use append_formatted_path() for path formatting

Hi Phillip, Thanks for taking a look!

Show 15 quoted lines
> On 16/06/2026 05:49, K Jayatheerth wrote:
> > 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.
>
> Sorry I haven't had time to look at this series recently, it is looking
> much nicer now that we have a single enum. It would be helpful to
> explain why we need PATH_FORMAT_DEFAULT that acts exactly like
> PATH_FORMAT_UNMODIFIED. Looking at the next patch it seems this is still
> a wart in the api due to rev-parse wanting needing to distinguish the
> unmodified case from the default case.
t);
Show 5 quoted lines
> > +
> >   # ifdef USE_THE_REPOSITORY_VARIABLE
> >   #  include "strbuf.h"
> >   #  include "repository.h"
>
Show 14 quoted lines
> >   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;
>
> This is the source of the api wart I referred to in the previous patch.
> Could we keep the existing enums and convert them into the appropriate
> PATH_FORMAT_* flag in print_path() above? I think we already have the
> logic to do that in the existing code. That would mean that other users
> of append_formatted_path() don't have to worry about the extra flag.
>
That is a much more elegant solution than the current one.

For v6, I will clean this up by keeping the fallback logic localized within builtin/rev-parse.c and removing PATH_FORMAT_DEFAULT entirely from enum path_format in path.h.

Instead, I'll re-introduce a small local enum (e.g., enum rev_parse_format) inside rev-parse.c to handle the command-line parsing state (tracking whether the user explicitly provided a flag or if we are still in a neutral/default state).

As you said, most of the logic is already present. In print_path(), we will check that local tracking enum. If it’s set to the local default, we can map it directly to the path-specific def_format before invoking append_formatted_path(). This ensures other users of the function don't have to worry about the extra flag.

I will send out the v6 series with these fixes shortly.
Regards,
- K Jayatheerth
Phillip WoodJun 16, 2026, 18:26 UTC in reply to K Jayatheerth on lore

Re: [GSoC Patch v5 2/4] rev-parse: use append_formatted_path() for path formatting

On 16/06/2026 18:04, K Jayatheerth wrote:
Show 52 quoted lines
> Hi Phillip,
> Thanks for taking a look!
> 
>> On 16/06/2026 05:49, K Jayatheerth wrote:
>>> 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.
>>
>> Sorry I haven't had time to look at this series recently, it is looking
>> much nicer now that we have a single enum. It would be helpful to
>> explain why we need PATH_FORMAT_DEFAULT that acts exactly like
>> PATH_FORMAT_UNMODIFIED. Looking at the next patch it seems this is still
>> a wart in the api due to rev-parse wanting needing to distinguish the
>> unmodified case from the default case.
> t);
>>> +
>>>    # ifdef USE_THE_REPOSITORY_VARIABLE
>>>    #  include "strbuf.h"
>>>    #  include "repository.h"
>>
> 
> 
>>>    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;
>>
>> This is the source of the api wart I referred to in the previous patch.
>> Could we keep the existing enums and convert them into the appropriate
>> PATH_FORMAT_* flag in print_path() above? I think we already have the
>> logic to do that in the existing code. That would mean that other users
>> of append_formatted_path() don't have to worry about the extra flag.
>>
> 
> That is a much more elegant solution than the current one.
> 
> For v6, I will clean this up by keeping the fallback logic
> localized within builtin/rev-parse.c and removing
> PATH_FORMAT_DEFAULT entirely from enum path_format in path.h.
> 
> Instead, I'll re-introduce a small local enum (e.g., enum
> rev_parse_format) inside rev-parse.c to handle the
> command-line parsing state (tracking whether the user
> explicitly provided a flag or if we are still in a
> neutral/default state).

I think it is probably simplest to keep the existing enums and modify print_path() to convert them to the appropriate PATH_FORMAT_*. That way we can keep the option parsing code as is.

Thanks
Phillip
Show 11 quoted lines
> As you said, most of the logic is already present. In
> print_path(), we will check that local tracking enum. If it’s
> set to the local default, we can map it directly to the
> path-specific def_format before invoking append_formatted_path().
> This ensures other users of the function don't have to worry
> about the extra flag.
> 
> I will send out the v6 series with these fixes shortly.
> 
> Regards,
> - K Jayatheerth
K JayatheerthJun 20, 2026, 03:16 UTC in reply to K Jayatheerth on lore

[GSoC Patch v6 0/4] teach git repo info to handle path keys

Hi!

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

The patches are structured as follows:
1. path: Extract the localized path-formatting logic
   out of `rev-parse` and expose it globally via
   `path.h` using clear append semantics.
2. rev-parse: Delegate the command's path-printing
   helper to the newly shared path engine, while
   leaving its existing option-parsing untouched.
3. repo: Introduce `path.commondir.absolute` and
   `path.commondir.relative` alongside a robust,
   isolated test helper.
4. repo: Introduce `path.gitdir.absolute` and
   `path.gitdir.relative` using the same standardized
   formatting rules.
Changes since v5:
* Dropped `PATH_FORMAT_DEFAULT` from the shared
  `path_format` enum in path.h. It only existed to let
  rev-parse track "no format was requested", which is a
  rev-parse-specific concern that other callers of
  `append_formatted_path()` shouldn't need to reason
  about (Phillip).
* Reverted `print_path()` in builtin/rev-parse.c to keep
  its original `format_type` and `default_type` local
  enums completely untouched (Phillip).
* As a result, patch 2 is now much smaller: it only
  touches the body of `print_path()`.

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

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

[GSoC Patch v6 1/4] path: introduce append_formatted_path() for shared path formatting

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

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

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

path.c, path.h

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

[GSoC Patch v6 2/4] rev-parse: use append_formatted_path() for path formatting

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

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

Mentored-by: Justin Tobler <jltobler@gmail.com>
Mentored-by: Lucas Seiki Oshiro <lucasseikioshiro@gmail.com>
Signed-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>
---
 builtin/rev-parse.c | 73 ++++++++++++++++++---------------------------
 1 file changed, 29 insertions(+), 44 deletions(-)
Show changes to builtin/rev-parse.c +29 −44
diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c
index bb882678fe..6de01466db 100644
--- a/builtin/rev-parse.c
+++ b/builtin/rev-parse.c
@@ -653,53 +653,38 @@ enum default_type {
 	DEFAULT_UNMODIFIED,
 };
 
-static void print_path(const char *path, const char *prefix, enum format_type format, enum default_type def)
+static void print_path(const char *path, const char *prefix,
+		       enum format_type format, enum default_type def)
 {
-	char *cwd = NULL;
-	/*
-	 * We don't ever produce a relative path if prefix is NULL, so set the
-	 * prefix to the current directory so that we can produce a relative
-	 * path whenever possible.  If we're using RELATIVE_IF_SHARED mode, then
-	 * we want an absolute path unless the two share a common prefix, so don't
-	 * set it in that case, since doing so causes a relative path to always
-	 * be produced if possible.
-	 */
-	if (!prefix && (format != FORMAT_DEFAULT || def != DEFAULT_RELATIVE_IF_SHARED))
-		prefix = cwd = xgetcwd();
-	if (format == FORMAT_DEFAULT && def == DEFAULT_UNMODIFIED) {
-		puts(path);
-	} else if (format == FORMAT_RELATIVE ||
-		  (format == FORMAT_DEFAULT && def == DEFAULT_RELATIVE)) {
-		/*
-		 * In order for relative_path to work as expected, we need to
-		 * make sure that both paths are absolute paths.  If we don't,
-		 * we can end up with an unexpected absolute path that the user
-		 * didn't want.
-		 */
-		struct strbuf buf = STRBUF_INIT, realbuf = STRBUF_INIT, prefixbuf = STRBUF_INIT;
-		if (!is_absolute_path(path)) {
-			strbuf_realpath_forgiving(&realbuf, path,  1);
-			path = realbuf.buf;
-		}
-		if (!is_absolute_path(prefix)) {
-			strbuf_realpath_forgiving(&prefixbuf, prefix, 1);
-			prefix = prefixbuf.buf;
+	struct strbuf sb = STRBUF_INIT;
+	enum path_format fmt;
+
+	if (format == FORMAT_RELATIVE) {
+		fmt = PATH_FORMAT_RELATIVE;
+	} else if (format == FORMAT_CANONICAL) {
+		fmt = PATH_FORMAT_CANONICAL;
+	} else /* FORMAT_DEFAULT */ {
+		switch (def) {
+		case DEFAULT_RELATIVE:
+			fmt = PATH_FORMAT_RELATIVE;
+			break;
+		case DEFAULT_RELATIVE_IF_SHARED:
+			fmt = PATH_FORMAT_RELATIVE_IF_SHARED;
+			break;
+		case DEFAULT_CANONICAL:
+			fmt = PATH_FORMAT_CANONICAL;
+			break;
+		case DEFAULT_UNMODIFIED:
+		default:
+			fmt = PATH_FORMAT_UNMODIFIED;
+			break;
 		}
-		puts(relative_path(path, prefix, &buf));
-		strbuf_release(&buf);
-		strbuf_release(&realbuf);
-		strbuf_release(&prefixbuf);
-	} else if (format == FORMAT_DEFAULT && def == DEFAULT_RELATIVE_IF_SHARED) {
-		struct strbuf buf = STRBUF_INIT;
-		puts(relative_path(path, prefix, &buf));
-		strbuf_release(&buf);
-	} else {
-		struct strbuf buf = STRBUF_INIT;
-		strbuf_realpath_forgiving(&buf, path, 1);
-		puts(buf.buf);
-		strbuf_release(&buf);
 	}
-	free(cwd);
+
+	append_formatted_path(&sb, path, prefix, fmt);
+	puts(sb.buf);
+
+	strbuf_release(&sb);
 }
 
 int cmd_rev_parse(int argc,
-- 
2.54.0
K JayatheerthJun 20, 2026, 03:16 UTC in reply to K Jayatheerth on lore

[GSoC Patch v6 3/4] repo: add path.commondir with absolute and relative suffix formatting

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(+)
Show changes to 3 files +87 −0

Documentation/git-repo.adoc, builtin/repo.c, t/t1900-repo-info.sh

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
K JayatheerthJun 20, 2026, 03:16 UTC in reply to K Jayatheerth on lore

[GSoC Patch v6 4/4] repo: add path.gitdir with absolute and relative suffix formatting

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

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

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

Documentation/git-repo.adoc, builtin/repo.c, t/t1900-repo-info.sh

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

Re: [GSoC Patch v6 1/4] path: introduce append_formatted_path() for shared path formatting

K Jayatheerth <jayatheerthkulkarni2005@gmail.com> writes:
Show 15 quoted lines
> The path-formatting logic in builtin/rev-parse.c is tightly coupled
> to that command and writes directly to stdout, making it impossible
> for other builtins to reuse.
>
> Extract the core algorithm into append_formatted_path() in path.c
> and expose a path_format enum in path.h so that any builtin can
> format paths consistently without duplicating logic.
>
> Mentored-by: Justin Tobler <jltobler@gmail.com>
> Mentored-by: Lucas Seiki Oshiro <lucasseikioshiro@gmail.com>
> Signed-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>
> ---
>  path.c | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
>  path.h | 30 +++++++++++++++++++++++++
>  2 files changed, 99 insertions(+)

It often, even though not always, is a sign of a bad topic structure to have an insertion-only patch without any removal of existing code, that adds totally unused code.

If the step is to "extract the core algorithm", shouldn't it be able to replace existing code already?

We may want to add new features to this helper function near the end of the topic, but wouldn't it make sense for the topic to first consolidate various path formatting logic already present in the existing code into a single helper for ease of extending it (which means replacing open-coded logic in existing code paths with a call to the new helper, which would have a code that may look very similar to the original code that was replaced with a single call to the helper function), and then expose the helper for use by new callers, and finally further add new features that existing code paths wouldn't have needed but the new callers would want?

How else can we make sure this new implementation added by the first step in the series is (1) capable enough to reproduce what we already have in different parts of the system, (2) does not bring in what the current codebase does not need, and (3) bug-to-bug compatible with the existing code paths?

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

Re: [GSoC Patch v6 1/4] path: introduce append_formatted_path() for shared path formatting

Hi Junio,
Show 6 quoted lines
> It often, even though not always, is a sign of a bad topic structure
> to have an insertion-only patch without any removal of existing
> code, that adds totally unused code.
>
> If the step is to "extract the core algorithm", shouldn't it be able
> to replace existing code already?

Giving the helper and converting its first caller (`rev-parse`) in the same step proves the implementation avoids leaving unused code lingering in the tree, even temporarily.

Show 10 quoted lines
> We may want to add new features to this helper function near the end
> of the topic, but wouldn't it make sense for the topic to first
> consolidate various path formatting logic already present in the
> existing code into a single helper for ease of extending it (which
> means replacing open-coded logic in existing code paths with a call
> to the new helper, which would have a code that may look very
> similar to the original code that was replaced with a single call to
> the helper function), and then expose the helper for use by new
> callers, and finally further add new features that existing code
> paths wouldn't have needed but the new callers would want?

Consolidating the existing logic first ensures we aren't introducing unnecessary complexity up front. I agree with restructuring the topic this way.

Show 5 quoted lines
> How else can we make sure this new implementation added by the first
> step in the series is (1) capable enough to reproduce what we
> already have in different parts of the system, (2) does not bring in
> what the current codebase does not need, and (3) bug-to-bug
> compatible with the existing code paths?

Introducing the helper and swapping out the `rev-parse` implementation in the same step is the best way to prove bug-to-bug compatibility and demonstrate its immediate utility.

For v7, I will squash patches 1 and 2 together so that the extraction and the replacement happen simultaneously, guaranteeing that the new `append_formatted_path()` perfectly mirrors the old behavior before we introduce the new `path.*` callers.

Thanks for taking the time to explain the rationale!
- K Jayatheerth
K JayatheerthJun 21, 2026, 05:55 UTC in reply to K Jayatheerth on lore

[GSoC Patch v7 0/3] teach git repo info to handle path keys

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. repo: Introduce `path.commondir.absolute` and
   `path.commondir.relative` alongside a robust,
   isolated test helper.
3. repo: Introduce `path.gitdir.absolute` and
   `path.gitdir.relative` using the same standardized
   formatting rules.
Changes since v6:
Squashed patches 1 and 2 to avoid dead code in the tree.

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

Thanks for helping improve this series!
K Jayatheerth (3):
  path: extract append_formatted_path() and use in rev-parse
  repo: add path.commondir with absolute and relative suffix formatting
  repo: add path.gitdir with absolute and relative suffix formatting
 Documentation/git-repo.adoc | 15 ++++++++
 builtin/repo.c              | 50 +++++++++++++++++++++++++
 builtin/rev-parse.c         | 73 +++++++++++++++----------------------
 path.c                      | 69 +++++++++++++++++++++++++++++++++++
 path.h                      | 30 +++++++++++++++
 t/t1900-repo-info.sh        | 58 +++++++++++++++++++++++++++++
 6 files changed, 251 insertions(+), 44 deletions(-)
-- 
2.55.0-rc1
K JayatheerthJun 21, 2026, 05:55 UTC in reply to K Jayatheerth on lore

[GSoC Patch v7 1/3] path: extract append_formatted_path() and use in rev-parse

Path formatting logic in builtin/rev-parse.c writes directly to stdout. Other builtins cannot reuse it.

Extract this logic into append_formatted_path() in path.c and expose a path_format enum in path.h.

Convert rev-parse to use the new helper in the same step to validate the API against existing tests and avoid introducing dead code.

Mentored-by: Justin Tobler <jltobler@gmail.com>
Mentored-by: Lucas Seiki Oshiro <lucasseikioshiro@gmail.com>
Signed-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>
---
 builtin/rev-parse.c | 73 ++++++++++++++++++---------------------------
 path.c              | 69 ++++++++++++++++++++++++++++++++++++++++++
 path.h              | 30 +++++++++++++++++++
 3 files changed, 128 insertions(+), 44 deletions(-)
Show changes to 3 files +128 −44

builtin/rev-parse.c, path.c, path.h

diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c
index bb882678fe..6de01466db 100644
--- a/builtin/rev-parse.c
+++ b/builtin/rev-parse.c
@@ -653,53 +653,38 @@ enum default_type {
 	DEFAULT_UNMODIFIED,
 };
 
-static void print_path(const char *path, const char *prefix, enum format_type format, enum default_type def)
+static void print_path(const char *path, const char *prefix,
+		       enum format_type format, enum default_type def)
 {
-	char *cwd = NULL;
-	/*
-	 * We don't ever produce a relative path if prefix is NULL, so set the
-	 * prefix to the current directory so that we can produce a relative
-	 * path whenever possible.  If we're using RELATIVE_IF_SHARED mode, then
-	 * we want an absolute path unless the two share a common prefix, so don't
-	 * set it in that case, since doing so causes a relative path to always
-	 * be produced if possible.
-	 */
-	if (!prefix && (format != FORMAT_DEFAULT || def != DEFAULT_RELATIVE_IF_SHARED))
-		prefix = cwd = xgetcwd();
-	if (format == FORMAT_DEFAULT && def == DEFAULT_UNMODIFIED) {
-		puts(path);
-	} else if (format == FORMAT_RELATIVE ||
-		  (format == FORMAT_DEFAULT && def == DEFAULT_RELATIVE)) {
-		/*
-		 * In order for relative_path to work as expected, we need to
-		 * make sure that both paths are absolute paths.  If we don't,
-		 * we can end up with an unexpected absolute path that the user
-		 * didn't want.
-		 */
-		struct strbuf buf = STRBUF_INIT, realbuf = STRBUF_INIT, prefixbuf = STRBUF_INIT;
-		if (!is_absolute_path(path)) {
-			strbuf_realpath_forgiving(&realbuf, path,  1);
-			path = realbuf.buf;
-		}
-		if (!is_absolute_path(prefix)) {
-			strbuf_realpath_forgiving(&prefixbuf, prefix, 1);
-			prefix = prefixbuf.buf;
+	struct strbuf sb = STRBUF_INIT;
+	enum path_format fmt;
+
+	if (format == FORMAT_RELATIVE) {
+		fmt = PATH_FORMAT_RELATIVE;
+	} else if (format == FORMAT_CANONICAL) {
+		fmt = PATH_FORMAT_CANONICAL;
+	} else /* FORMAT_DEFAULT */ {
+		switch (def) {
+		case DEFAULT_RELATIVE:
+			fmt = PATH_FORMAT_RELATIVE;
+			break;
+		case DEFAULT_RELATIVE_IF_SHARED:
+			fmt = PATH_FORMAT_RELATIVE_IF_SHARED;
+			break;
+		case DEFAULT_CANONICAL:
+			fmt = PATH_FORMAT_CANONICAL;
+			break;
+		case DEFAULT_UNMODIFIED:
+		default:
+			fmt = PATH_FORMAT_UNMODIFIED;
+			break;
 		}
-		puts(relative_path(path, prefix, &buf));
-		strbuf_release(&buf);
-		strbuf_release(&realbuf);
-		strbuf_release(&prefixbuf);
-	} else if (format == FORMAT_DEFAULT && def == DEFAULT_RELATIVE_IF_SHARED) {
-		struct strbuf buf = STRBUF_INIT;
-		puts(relative_path(path, prefix, &buf));
-		strbuf_release(&buf);
-	} else {
-		struct strbuf buf = STRBUF_INIT;
-		strbuf_realpath_forgiving(&buf, path, 1);
-		puts(buf.buf);
-		strbuf_release(&buf);
 	}
-	free(cwd);
+
+	append_formatted_path(&sb, path, prefix, fmt);
+	puts(sb.buf);
+
+	strbuf_release(&sb);
 }
 
 int cmd_rev_parse(int argc,
diff --git a/path.c b/path.c
index d7e17bf174..6d8e892ada 100644
--- a/path.c
+++ b/path.c
@@ -1579,6 +1579,75 @@ char *xdg_cache_home(const char *filename)
 	return NULL;
 }
 
+void append_formatted_path(struct strbuf *dest, const char *path,
+			   const char *prefix, enum path_format format)
+{
+	switch (format) {
+	case PATH_FORMAT_UNMODIFIED:
+		strbuf_addstr(dest, path);
+		break;
+
+	case PATH_FORMAT_RELATIVE: {
+		struct strbuf relative_buf = STRBUF_INIT;
+		struct strbuf real_path = STRBUF_INIT;
+		struct strbuf real_prefix = STRBUF_INIT;
+		char *cwd = NULL;
+
+		/*
+		 * We don't ever produce a relative path if prefix is NULL,
+		 * so set the prefix to the current directory so that we can
+		 * produce a relative path whenever possible.
+		 */
+		if (!prefix)
+			prefix = cwd = xgetcwd();
+
+		if (!is_absolute_path(path)) {
+			strbuf_realpath_forgiving(&real_path, path, 1);
+			path = real_path.buf;
+		}
+		if (!is_absolute_path(prefix)) {
+			strbuf_realpath_forgiving(&real_prefix, prefix, 1);
+			prefix = real_prefix.buf;
+		}
+
+		strbuf_addstr(dest, relative_path(path, prefix, &relative_buf));
+
+		strbuf_release(&relative_buf);
+		strbuf_release(&real_path);
+		strbuf_release(&real_prefix);
+		free(cwd);
+		break;
+	}
+
+	case PATH_FORMAT_RELATIVE_IF_SHARED: {
+		struct strbuf relative_buf = STRBUF_INIT;
+
+		/*
+		 * If we're using RELATIVE_IF_SHARED mode, then we want an
+		 * absolute path unless the two share a common prefix, so don't
+		 * default the prefix to the current working directory. Doing so
+		 * would cause a relative path to always be produced if possible.
+		 */
+		strbuf_addstr(dest, relative_path(path, prefix, &relative_buf));
+		strbuf_release(&relative_buf);
+		break;
+	}
+
+	case PATH_FORMAT_CANONICAL: {
+		struct strbuf canonical_buf = STRBUF_INIT;
+
+		strbuf_realpath_forgiving(&canonical_buf, path, 1);
+		strbuf_addbuf(dest, &canonical_buf);
+
+		strbuf_release(&canonical_buf);
+		break;
+	}
+
+	default:
+		BUG("unknown path_format value %d", format);
+	}
+}
+
 REPO_GIT_PATH_FUNC(squash_msg, "SQUASH_MSG")
 REPO_GIT_PATH_FUNC(merge_msg, "MERGE_MSG")
 REPO_GIT_PATH_FUNC(merge_rr, "MERGE_RR")
diff --git a/path.h b/path.h
index 4c2958a903..4d982a2c8e 100644
--- a/path.h
+++ b/path.h
@@ -262,6 +262,36 @@ enum scld_error safe_create_leading_directories_no_share(char *path);
 int safe_create_file_with_leading_directories(struct repository *repo,
 					      const char *path);
 
+/**
+ * The formatting strategy to apply when writing a path into a buffer.
+ */
+enum path_format {
+	/* Output the path exactly as-is without any modifications. */
+	PATH_FORMAT_UNMODIFIED,
+
+	/* Output a path relative to the provided directory prefix. */
+	PATH_FORMAT_RELATIVE,
+
+	/* Output a relative path only if the path shares a root with the prefix. */
+	PATH_FORMAT_RELATIVE_IF_SHARED,
+
+	/* Output a fully resolved, absolute canonical path. */
+	PATH_FORMAT_CANONICAL
+};
+
+/**
+ * Format a path according to the specified formatting strategy and append
+ * the result to the given strbuf.
+ *
+ * `dest`   : The string buffer to append the formatted path to.
+ * `path`   : The path string that needs to be formatted.
+ * `prefix` : The directory prefix to calculate relative offsets against.
+ * Pass NULL to default to the current working directory where applicable.
+ * `format` : The formatting behavior rule to execute.
+ */
+void append_formatted_path(struct strbuf *dest, const char *path,
+			   const char *prefix, enum path_format format);
+
 # ifdef USE_THE_REPOSITORY_VARIABLE
 #  include "strbuf.h"
 #  include "repository.h"
-- 
2.55.0-rc1
K JayatheerthJun 21, 2026, 05:55 UTC in reply to K Jayatheerth on lore

[GSoC Patch v7 2/3] repo: add path.commondir with absolute and relative suffix formatting

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(+)
Show changes to 3 files +87 −0

Documentation/git-repo.adoc, builtin/repo.c, t/t1900-repo-info.sh

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.55.0-rc1
K JayatheerthJun 21, 2026, 05:55 UTC in reply to K Jayatheerth on lore

[GSoC Patch v7 3/3] repo: add path.gitdir with absolute and relative suffix formatting

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

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

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

Documentation/git-repo.adoc, builtin/repo.c, t/t1900-repo-info.sh

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

Re: [GSoC Patch v7 1/3] path: extract append_formatted_path() and use in rev-parse

K Jayatheerth <jayatheerthkulkarni2005@gmail.com> writes:
So, for the existing user of this logic, the preimage ...
Show 48 quoted lines
> -static void print_path(const char *path, const char *prefix, enum format_type format, enum default_type def)
>  {
> -	char *cwd = NULL;
> -	/*
> -	 * We don't ever produce a relative path if prefix is NULL, so set the
> -	 * prefix to the current directory so that we can produce a relative
> -	 * path whenever possible.  If we're using RELATIVE_IF_SHARED mode, then
> -	 * we want an absolute path unless the two share a common prefix, so don't
> -	 * set it in that case, since doing so causes a relative path to always
> -	 * be produced if possible.
> -	 */
> -	if (!prefix && (format != FORMAT_DEFAULT || def != DEFAULT_RELATIVE_IF_SHARED))
> -		prefix = cwd = xgetcwd();
> -	if (format == FORMAT_DEFAULT && def == DEFAULT_UNMODIFIED) {
> -		puts(path);
> -	} else if (format == FORMAT_RELATIVE ||
> -		  (format == FORMAT_DEFAULT && def == DEFAULT_RELATIVE)) {
> -		/*
> -		 * In order for relative_path to work as expected, we need to
> -		 * make sure that both paths are absolute paths.  If we don't,
> -		 * we can end up with an unexpected absolute path that the user
> -		 * didn't want.
> -		 */
> -		struct strbuf buf = STRBUF_INIT, realbuf = STRBUF_INIT, prefixbuf = STRBUF_INIT;
> -		if (!is_absolute_path(path)) {
> -			strbuf_realpath_forgiving(&realbuf, path,  1);
> -			path = realbuf.buf;
> -		}
> -		if (!is_absolute_path(prefix)) {
> -			strbuf_realpath_forgiving(&prefixbuf, prefix, 1);
> -			prefix = prefixbuf.buf;
>  		}
> -		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);
>  }
... now becomes this postimage.
Show 33 quoted lines
> +static void print_path(const char *path, const char *prefix,
> +		       enum format_type format, enum default_type def)
>  {
> +	struct strbuf sb = STRBUF_INIT;
> +	enum path_format fmt;
> +
> +	if (format == FORMAT_RELATIVE) {
> +		fmt = PATH_FORMAT_RELATIVE;
> +	} else if (format == FORMAT_CANONICAL) {
> +		fmt = PATH_FORMAT_CANONICAL;
> +	} else /* FORMAT_DEFAULT */ {
> +		switch (def) {
> +		case DEFAULT_RELATIVE:
> +			fmt = PATH_FORMAT_RELATIVE;
> +			break;
> +		case DEFAULT_RELATIVE_IF_SHARED:
> +			fmt = PATH_FORMAT_RELATIVE_IF_SHARED;
> +			break;
> +		case DEFAULT_CANONICAL:
> +			fmt = PATH_FORMAT_CANONICAL;
> +			break;
> +		case DEFAULT_UNMODIFIED:
> +		default:
> +			fmt = PATH_FORMAT_UNMODIFIED;
> +			break;
>  		}
>  	}
> +
> +	append_formatted_path(&sb, path, prefix, fmt);
> +	puts(sb.buf);
> +
> +	strbuf_release(&sb);
>  }

Mostly, the code translates FORMAT_FOO constants into the new PATH_FORMAT_FOO constants, and lets append_formatted_path() do the heavy lifting.

It is a minor point, but wouldn't it make it simpler to handle format_default first? I.e.,

	if (format == FORMAT_DEFAULT)
		switch (def) {
		case DEFAULT_RELATIVE:
			format = DEFAULT_RELATIVE;
			break;
		...
		case DEFAULT_UNMODIFIED:
		default:
			format = DEFAULT_UNMODIFIED; 
			break;
	}
	switch (format) {
        case FORMAT_RELATIVE: fmt = PATH_FORMAT_RELATIVE; break;
	case FORMAT_CANONICAL: fmt = PATH_FORMAT_CANONICAL; break;
	...
	}
Perhaps yes, perhaps not.  I dunno.
Show 15 quoted lines
> diff --git a/path.c b/path.c
> index d7e17bf174..6d8e892ada 100644
> --- a/path.c
> +++ b/path.c
> @@ -1579,6 +1579,75 @@ char *xdg_cache_home(const char *filename)
>  	return NULL;
>  }
>  
> +void append_formatted_path(struct strbuf *dest, const char *path,
> +			   const char *prefix, enum path_format format)
> +{
> +	switch (format) {
> +	case PATH_FORMAT_UNMODIFIED:
> +		strbuf_addstr(dest, path);
> +		break;

In the orignal "print_path()", DEFAULT/UNMODIFIED did this "show unmodified". OK.

Show 13 quoted lines
> +	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();

This is what was done in the original "print_path()" upfront, with a similar comment to explay why this happens. Looking good. Also we no longer call xgetcwd() when we do not need to, which is goodd.

Show 8 quoted lines
> +		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;
> +		}

There used to be a comment explaining why we make realpath calls, which is now lost. Perhaps what the comment said was so obvious that we are better off without it? I offhand do not know.

What is done to make the paths real is the same as before, which is good.

Show 8 quoted lines
> +		strbuf_addstr(dest, relative_path(path, prefix, &relative_buf));
> +
> +		strbuf_release(&relative_buf);
> +		strbuf_release(&real_path);
> +		strbuf_release(&real_prefix);
> +		free(cwd);
> +		break;
> +	}
OK.
Show 13 quoted lines
> +	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;
> +	}
Identical to the original, which is good.
Show 15 quoted lines
> +
> +	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);
> +	}
> +}
OK.
Show 30 quoted lines
> +/**
> + * The formatting strategy to apply when writing a path into a buffer.
> + */
> +enum path_format {
> +	/* Output the path exactly as-is without any modifications. */
> +	PATH_FORMAT_UNMODIFIED,
> +
> +	/* Output a path relative to the provided directory prefix. */
> +	PATH_FORMAT_RELATIVE,
> +
> +	/* Output a relative path only if the path shares a root with the prefix. */
> +	PATH_FORMAT_RELATIVE_IF_SHARED,
> +
> +	/* Output a fully resolved, absolute canonical path. */
> +	PATH_FORMAT_CANONICAL
> +};
> +
> +/**
> + * Format a path according to the specified formatting strategy and append
> + * the result to the given strbuf.
> + *
> + * `dest`   : The string buffer to append the formatted path to.
> + * `path`   : The path string that needs to be formatted.
> + * `prefix` : The directory prefix to calculate relative offsets against.
> + * Pass NULL to default to the current working directory where applicable.
> + * `format` : The formatting behavior rule to execute.
> + */
> +void append_formatted_path(struct strbuf *dest, const char *path,
> +			   const char *prefix, enum path_format format);
> +

It is slightly unsatisfying that this function is defined to "append" to any existing value in the dest strbuf, rather than storing the result in the dest strbuf. The original caller print_path() passes an empty strbuf to this helper, so it can let strbuf_realpath_*() functions to strbuf_reset() it (e.g., abspath.c:get_root_part() called by strbuf_realpath_1(), wihch in turn is called by strbuf_realpath() and strbuf_realpath_forgiving()) it freely, which means that use of temporary strbuf like canonical_buf only to copy it out to dest is wasteful and unneeded. But other callers we will have for this helper later may want to append to what they already have, so perhaps it is OK (on the other hand, we could say that preserving and appending is what these callers can do themselves).

Otherwise, looking good as a no-op bug-to-bug compatible rewrite, with a slight optimization (to skip xgetcwd()).

Thanks.
Junio C HamanoJun 22, 2026, 16:03 UTC in reply to Junio C Hamano on lore

Re: [GSoC Patch v7 1/3] path: extract append_formatted_path() and use in rev-parse

Junio C Hamano <gitster@pobox.com> writes:
Show 23 quoted lines
> K Jayatheerth <jayatheerthkulkarni2005@gmail.com> writes:
> ...
> It is a minor point, but wouldn't it make it simpler to handle
> format_default first?  I.e.,
>
> 	if (format == FORMAT_DEFAULT)
> 		switch (def) {
> 		case DEFAULT_RELATIVE:
> 			format = DEFAULT_RELATIVE;
> 			break;
> 		...
> 		case DEFAULT_UNMODIFIED:
> 		default:
> 			format = DEFAULT_UNMODIFIED; 
> 			break;
> 	}
> 	switch (format) {
>         case FORMAT_RELATIVE: fmt = PATH_FORMAT_RELATIVE; break;
> 	case FORMAT_CANONICAL: fmt = PATH_FORMAT_CANONICAL; break;
> 	...
> 	}
>
> Perhaps yes, perhaps not.  I dunno.

I do not consider the above an blocker, but it might make a difference if we are going to acquire more modes and formats, so once somebody tries to rewrite the logic and finds the resulting code harder to follow (or not easier to follow), I would be happy to see the above discarded ;-)

Show 27 quoted lines
>> +/**
>> + * 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);
>> +
>
> It is slightly unsatisfying that this function is defined to
> "append" to any existing value in the dest strbuf, rather than
> storing the result in the dest strbuf.  The original caller
> print_path() passes an empty strbuf to this helper, so it can let
> strbuf_realpath_*() functions to strbuf_reset() it (e.g.,
> abspath.c:get_root_part() called by strbuf_realpath_1(), wihch in
> turn is called by strbuf_realpath() and strbuf_realpath_forgiving())
> it freely, which means that use of temporary strbuf like
> canonical_buf only to copy it out to dest is wasteful and unneeded.
> But other callers we will have for this helper later may want to
> append to what they already have, so perhaps it is OK (on the other
> hand, we could say that preserving and appending is what these
> callers can do themselves).

This one we may want to consider a bit more seriously, but it is entirely up to the future callers of the helper. If it would make the callers much easier to write for this helper to have "append" semantics, I'd be happy to accept the semantics of the above as-is, but otherwise, I suspect it would be simpler to use if the helper is defined to replase dest with the result, instead of appending the result to dest.

> Otherwise, looking good as a no-op bug-to-bug compatible rewrite,
> with a slight optimization (to skip xgetcwd()).

This part of the review does not change in any case. The refactoring looks good.

Thanks.
K JayatheerthJun 22, 2026, 17:41 UTC in reply to Junio C Hamano on lore

Re: [GSoC Patch v7 1/3] path: extract append_formatted_path() and use in rev-parse

Hey Junio,
On Mon, Jun 22, 2026 at 2:32 AM Junio C Hamano <gitster@pobox.com> wrote:
> So, for the existing user of this logic, the preimage ...
>
> > -static void print_path(const char *path, const char *prefix, enum format_type format, enum default_type def)
> >  {
...
Show 5 quoted lines
> > -     free(cwd);
> >  }
>
> ... now becomes this postimage.
>
Yes that's right!
Show 60 quoted lines
> > +static void print_path(const char *path, const char *prefix,
> > +                    enum format_type format, enum default_type def)
> >  {
> > +     struct strbuf sb = STRBUF_INIT;
> > +     enum path_format fmt;
> > +
> > +     if (format == FORMAT_RELATIVE) {
> > +             fmt = PATH_FORMAT_RELATIVE;
> > +     } else if (format == FORMAT_CANONICAL) {
> > +             fmt = PATH_FORMAT_CANONICAL;
> > +     } else /* FORMAT_DEFAULT */ {
> > +             switch (def) {
> > +             case DEFAULT_RELATIVE:
> > +                     fmt = PATH_FORMAT_RELATIVE;
> > +                     break;
> > +             case DEFAULT_RELATIVE_IF_SHARED:
> > +                     fmt = PATH_FORMAT_RELATIVE_IF_SHARED;
> > +                     break;
> > +             case DEFAULT_CANONICAL:
> > +                     fmt = PATH_FORMAT_CANONICAL;
> > +                     break;
> > +             case DEFAULT_UNMODIFIED:
> > +             default:
> > +                     fmt = PATH_FORMAT_UNMODIFIED;
> > +                     break;
> >               }
> >       }
> > +
> > +     append_formatted_path(&sb, path, prefix, fmt);
> > +     puts(sb.buf);
> > +
> > +     strbuf_release(&sb);
> >  }
>
> Mostly, the code translates FORMAT_FOO constants into the new
> PATH_FORMAT_FOO constants, and lets append_formatted_path() do the
> heavy lifting.
>
> It is a minor point, but wouldn't it make it simpler to handle
> format_default first?  I.e.,
>
>         if (format == FORMAT_DEFAULT)
>                 switch (def) {
>                 case DEFAULT_RELATIVE:
>                         format = DEFAULT_RELATIVE;
>                         break;
>                 ...
>                 case DEFAULT_UNMODIFIED:
>                 default:
>                         format = DEFAULT_UNMODIFIED;
>                         break;
>         }
>         switch (format) {
>         case FORMAT_RELATIVE: fmt = PATH_FORMAT_RELATIVE; break;
>         case FORMAT_CANONICAL: fmt = PATH_FORMAT_CANONICAL; break;
>         ...
>         }
>
> Perhaps yes, perhaps not.  I dunno.
>

I see you have continued this point further I am going to respond to this in detail there.

Show 50 quoted lines
> > diff --git a/path.c b/path.c
> > index d7e17bf174..6d8e892ada 100644
> > --- a/path.c
> > +++ b/path.c
> > @@ -1579,6 +1579,75 @@ char *xdg_cache_home(const char *filename)
> >       return NULL;
> >  }
> >
> > +void append_formatted_path(struct strbuf *dest, const char *path,
> > +                        const char *prefix, enum path_format format)
> > +{
> > +     switch (format) {
> > +     case PATH_FORMAT_UNMODIFIED:
> > +             strbuf_addstr(dest, path);
> > +             break;
>
> In the orignal "print_path()", DEFAULT/UNMODIFIED did this "show
> unmodified".  OK.
>
> > +     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();
>
> This is what was done in the original "print_path()" upfront, with
> a similar comment to explay why this happens.  Looking good.  Also
> we no longer call xgetcwd() when we do not need to, which is goodd.
>
> > +             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;
> > +             }
>
> There used to be a comment explaining why we make realpath calls,
> which is now lost.  Perhaps what the comment said was so obvious
> that we are better off without it?  I offhand do not know.
>

When the logic was a single block, the comment felt necessary to explain the flow. By splitting it into explicit switch cases, the logic became a bit more self-evident, so I removed it to reduce clutter. I kept the other comments where the reasoning is less obvious.

Show 23 quoted lines
> What is done to make the paths real is the same as before, which is
> good.
>
> > +             strbuf_addstr(dest, relative_path(path, prefix, &relative_buf));
> > +
> > +             strbuf_release(&relative_buf);
> > +             strbuf_release(&real_path);
> > +             strbuf_release(&real_prefix);
> > +             free(cwd);
> > +             break;
> > +     }
>
> OK.
>
> > +     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.
> > +              */
I thought this comment made sense keeping in for instance.
Show 47 quoted lines
> Identical to the original, which is good.
> > +
> > +     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);
> > +     }
> > +}
>
> OK.
>
> > +/**
> > + * 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);
> > +
>
> It is slightly unsatisfying that this function is defined to
> "append" to any existing value in the dest strbuf, rather than
> storing the result in the dest strbuf.  The original caller
> print_path() passes an empty strbuf to this helper, so it can let
> strbuf_realpath_*() functions to strbuf_reset() it (e.g.,
> abspath.c:get_root_part() called by strbuf_realpath_1(), wihch in
> turn is called by strbuf_realpath() and strbuf_realpath_forgiving())
> it freely, which means that use of temporary strbuf like
> canonical_buf only to copy it out to dest is wasteful and unneeded.
> But other callers we will have for this helper later may want to
> append to what they already have, so perhaps it is OK (on the other
> hand, we could say that preserving and appending is what these
> callers can do themselves).
>
Hmm, I thought about this for a while.

Then I looked at what ls-tree.c does(using an accumulator). They already routinely use temporary `strbuf`s to calculate relative/absolute paths before appending them to their main output string.

Because callers who need to accumulate can easily do the preserving and appending themselves with a temporary buffer, there is no reason to force that overhead into our helper.

I will change the semantics from "append" to "replace", rename the helper back to `format_path()`. I hope I am looking at ls-tree.c correctly here : )

Eliminate the wasteful `canonical_buf` allocations so we can pass the destination buffer directly to functions like `strbuf_realpath_forgiving()`. This is a good suggestion actually, thanks!

> Otherwise, looking good as a no-op bug-to-bug compatible rewrite,
> with a slight optimization (to skip xgetcwd()).
>
> Thanks.
On Mon, Jun 22, 2026 at 9:33 PM Junio C Hamano <gitster@pobox.com> wrote:
>
> Junio C Hamano <gitster@pobox.com> writes:
Show 29 quoted lines
> > ...
> > It is a minor point, but wouldn't it make it simpler to handle
> > format_default first?  I.e.,
> >
> >       if (format == FORMAT_DEFAULT)
> >               switch (def) {
> >               case DEFAULT_RELATIVE:
> >                       format = DEFAULT_RELATIVE;
> >                       break;
> >               ...
> >               case DEFAULT_UNMODIFIED:
> >               default:
> >                       format = DEFAULT_UNMODIFIED;
> >                       break;
> >       }
> >       switch (format) {
> >         case FORMAT_RELATIVE: fmt = PATH_FORMAT_RELATIVE; break;
> >       case FORMAT_CANONICAL: fmt = PATH_FORMAT_CANONICAL; break;
> >       ...
> >       }
> >
> > Perhaps yes, perhaps not.  I dunno.
>
> I do not consider the above an blocker, but it might make a
> difference if we are going to acquire more modes and formats, so
> once somebody tries to rewrite the logic and finds the resulting
> code harder to follow (or not easier to follow), I would be happy to
> see the above discarded ;-)
>

True, if new formats are introduced this would instantly become sloppy.

I will change it to future proof since I am looking to send v8 for append_formatted_path().

Although I would be surprised to see an example for a new format.
Show 36 quoted lines
> >> +/**
> >> + * 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);
> >> +
> >
> > It is slightly unsatisfying that this function is defined to
> > "append" to any existing value in the dest strbuf, rather than
> > storing the result in the dest strbuf.  The original caller
> > print_path() passes an empty strbuf to this helper, so it can let
> > strbuf_realpath_*() functions to strbuf_reset() it (e.g.,
> > abspath.c:get_root_part() called by strbuf_realpath_1(), wihch in
> > turn is called by strbuf_realpath() and strbuf_realpath_forgiving())
> > it freely, which means that use of temporary strbuf like
> > canonical_buf only to copy it out to dest is wasteful and unneeded.
> > But other callers we will have for this helper later may want to
> > append to what they already have, so perhaps it is OK (on the other
> > hand, we could say that preserving and appending is what these
> > callers can do themselves).
>
> This one we may want to consider a bit more seriously, but it is
> entirely up to the future callers of the helper.  If it would make
> the callers much easier to write for this helper to have "append"
> semantics, I'd be happy to accept the semantics of the above as-is,
> but otherwise, I suspect it would be simpler to use if the helper is
> defined to replase dest with the result, instead of appending the
> result to dest.
>

I am still unsure if I am following ls-tree.c correctly. If I am then I think it is a very good change to have for v8 as I specified above.

Show 5 quoted lines
> > Otherwise, looking good as a no-op bug-to-bug compatible rewrite,
> > with a slight optimization (to skip xgetcwd()).
>
> This part of the review does not change in any case.  The
> refactoring looks good.
Thank you ; )
Regards,
- K Jayatheerth
Phillip WoodJun 23, 2026, 15:57 UTC in reply to K Jayatheerth on lore

Re: [GSoC Patch v7 1/3] path: extract append_formatted_path() and use in rev-parse

On 21/06/2026 06:55, K Jayatheerth wrote:
Show 8 quoted lines
> Path formatting logic in builtin/rev-parse.c writes directly to
> stdout. Other builtins cannot reuse it.
> 
> Extract this logic into append_formatted_path() in path.c and expose
> a path_format enum in path.h.
> 
> Convert rev-parse to use the new helper in the same step to validate
> the API against existing tests and avoid introducing dead code.

The new API looks good now, and so does the conversion of the existing code. I'm very happy with this version and don't have anything to add to Junio's comments

Thanks
Phillip
Show 217 quoted lines
> Mentored-by: Justin Tobler <jltobler@gmail.com>
> Mentored-by: Lucas Seiki Oshiro <lucasseikioshiro@gmail.com>
> Signed-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>
> ---
>   builtin/rev-parse.c | 73 ++++++++++++++++++---------------------------
>   path.c              | 69 ++++++++++++++++++++++++++++++++++++++++++
>   path.h              | 30 +++++++++++++++++++
>   3 files changed, 128 insertions(+), 44 deletions(-)
> 
> diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c
> index bb882678fe..6de01466db 100644
> --- a/builtin/rev-parse.c
> +++ b/builtin/rev-parse.c
> @@ -653,53 +653,38 @@ enum default_type {
>   	DEFAULT_UNMODIFIED,
>   };
>   
> -static void print_path(const char *path, const char *prefix, enum format_type format, enum default_type def)
> +static void print_path(const char *path, const char *prefix,
> +		       enum format_type format, enum default_type def)
>   {
> -	char *cwd = NULL;
> -	/*
> -	 * We don't ever produce a relative path if prefix is NULL, so set the
> -	 * prefix to the current directory so that we can produce a relative
> -	 * path whenever possible.  If we're using RELATIVE_IF_SHARED mode, then
> -	 * we want an absolute path unless the two share a common prefix, so don't
> -	 * set it in that case, since doing so causes a relative path to always
> -	 * be produced if possible.
> -	 */
> -	if (!prefix && (format != FORMAT_DEFAULT || def != DEFAULT_RELATIVE_IF_SHARED))
> -		prefix = cwd = xgetcwd();
> -	if (format == FORMAT_DEFAULT && def == DEFAULT_UNMODIFIED) {
> -		puts(path);
> -	} else if (format == FORMAT_RELATIVE ||
> -		  (format == FORMAT_DEFAULT && def == DEFAULT_RELATIVE)) {
> -		/*
> -		 * In order for relative_path to work as expected, we need to
> -		 * make sure that both paths are absolute paths.  If we don't,
> -		 * we can end up with an unexpected absolute path that the user
> -		 * didn't want.
> -		 */
> -		struct strbuf buf = STRBUF_INIT, realbuf = STRBUF_INIT, prefixbuf = STRBUF_INIT;
> -		if (!is_absolute_path(path)) {
> -			strbuf_realpath_forgiving(&realbuf, path,  1);
> -			path = realbuf.buf;
> -		}
> -		if (!is_absolute_path(prefix)) {
> -			strbuf_realpath_forgiving(&prefixbuf, prefix, 1);
> -			prefix = prefixbuf.buf;
> +	struct strbuf sb = STRBUF_INIT;
> +	enum path_format fmt;
> +
> +	if (format == FORMAT_RELATIVE) {
> +		fmt = PATH_FORMAT_RELATIVE;
> +	} else if (format == FORMAT_CANONICAL) {
> +		fmt = PATH_FORMAT_CANONICAL;
> +	} else /* FORMAT_DEFAULT */ {
> +		switch (def) {
> +		case DEFAULT_RELATIVE:
> +			fmt = PATH_FORMAT_RELATIVE;
> +			break;
> +		case DEFAULT_RELATIVE_IF_SHARED:
> +			fmt = PATH_FORMAT_RELATIVE_IF_SHARED;
> +			break;
> +		case DEFAULT_CANONICAL:
> +			fmt = PATH_FORMAT_CANONICAL;
> +			break;
> +		case DEFAULT_UNMODIFIED:
> +		default:
> +			fmt = PATH_FORMAT_UNMODIFIED;
> +			break;
>   		}
> -		puts(relative_path(path, prefix, &buf));
> -		strbuf_release(&buf);
> -		strbuf_release(&realbuf);
> -		strbuf_release(&prefixbuf);
> -	} else if (format == FORMAT_DEFAULT && def == DEFAULT_RELATIVE_IF_SHARED) {
> -		struct strbuf buf = STRBUF_INIT;
> -		puts(relative_path(path, prefix, &buf));
> -		strbuf_release(&buf);
> -	} else {
> -		struct strbuf buf = STRBUF_INIT;
> -		strbuf_realpath_forgiving(&buf, path, 1);
> -		puts(buf.buf);
> -		strbuf_release(&buf);
>   	}
> -	free(cwd);
> +
> +	append_formatted_path(&sb, path, prefix, fmt);
> +	puts(sb.buf);
> +
> +	strbuf_release(&sb);
>   }
>   
>   int cmd_rev_parse(int argc,
> diff --git a/path.c b/path.c
> index d7e17bf174..6d8e892ada 100644
> --- a/path.c
> +++ b/path.c
> @@ -1579,6 +1579,75 @@ char *xdg_cache_home(const char *filename)
>   	return NULL;
>   }
>   
> +void append_formatted_path(struct strbuf *dest, const char *path,
> +			   const char *prefix, enum path_format format)
> +{
> +	switch (format) {
> +	case PATH_FORMAT_UNMODIFIED:
> +		strbuf_addstr(dest, path);
> +		break;
> +
> +	case PATH_FORMAT_RELATIVE: {
> +		struct strbuf relative_buf = STRBUF_INIT;
> +		struct strbuf real_path = STRBUF_INIT;
> +		struct strbuf real_prefix = STRBUF_INIT;
> +		char *cwd = NULL;
> +
> +		/*
> +		 * We don't ever produce a relative path if prefix is NULL,
> +		 * so set the prefix to the current directory so that we can
> +		 * produce a relative path whenever possible.
> +		 */
> +		if (!prefix)
> +			prefix = cwd = xgetcwd();
> +
> +		if (!is_absolute_path(path)) {
> +			strbuf_realpath_forgiving(&real_path, path, 1);
> +			path = real_path.buf;
> +		}
> +		if (!is_absolute_path(prefix)) {
> +			strbuf_realpath_forgiving(&real_prefix, prefix, 1);
> +			prefix = real_prefix.buf;
> +		}
> +
> +		strbuf_addstr(dest, relative_path(path, prefix, &relative_buf));
> +
> +		strbuf_release(&relative_buf);
> +		strbuf_release(&real_path);
> +		strbuf_release(&real_prefix);
> +		free(cwd);
> +		break;
> +	}
> +
> +	case PATH_FORMAT_RELATIVE_IF_SHARED: {
> +		struct strbuf relative_buf = STRBUF_INIT;
> +
> +		/*
> +		 * If we're using RELATIVE_IF_SHARED mode, then we want an
> +		 * absolute path unless the two share a common prefix, so don't
> +		 * default the prefix to the current working directory. Doing so
> +		 * would cause a relative path to always be produced if possible.
> +		 */
> +		strbuf_addstr(dest, relative_path(path, prefix, &relative_buf));
> +		strbuf_release(&relative_buf);
> +		break;
> +	}
> +
> +	case PATH_FORMAT_CANONICAL: {
> +		struct strbuf canonical_buf = STRBUF_INIT;
> +
> +		strbuf_realpath_forgiving(&canonical_buf, path, 1);
> +		strbuf_addbuf(dest, &canonical_buf);
> +
> +		strbuf_release(&canonical_buf);
> +		break;
> +	}
> +
> +	default:
> +		BUG("unknown path_format value %d", format);
> +	}
> +}
> +
>   REPO_GIT_PATH_FUNC(squash_msg, "SQUASH_MSG")
>   REPO_GIT_PATH_FUNC(merge_msg, "MERGE_MSG")
>   REPO_GIT_PATH_FUNC(merge_rr, "MERGE_RR")
> diff --git a/path.h b/path.h
> index 4c2958a903..4d982a2c8e 100644
> --- a/path.h
> +++ b/path.h
> @@ -262,6 +262,36 @@ enum scld_error safe_create_leading_directories_no_share(char *path);
>   int safe_create_file_with_leading_directories(struct repository *repo,
>   					      const char *path);
>   
> +/**
> + * The formatting strategy to apply when writing a path into a buffer.
> + */
> +enum path_format {
> +	/* Output the path exactly as-is without any modifications. */
> +	PATH_FORMAT_UNMODIFIED,
> +
> +	/* Output a path relative to the provided directory prefix. */
> +	PATH_FORMAT_RELATIVE,
> +
> +	/* Output a relative path only if the path shares a root with the prefix. */
> +	PATH_FORMAT_RELATIVE_IF_SHARED,
> +
> +	/* Output a fully resolved, absolute canonical path. */
> +	PATH_FORMAT_CANONICAL
> +};
> +
> +/**
> + * Format a path according to the specified formatting strategy and append
> + * the result to the given strbuf.
> + *
> + * `dest`   : The string buffer to append the formatted path to.
> + * `path`   : The path string that needs to be formatted.
> + * `prefix` : The directory prefix to calculate relative offsets against.
> + * Pass NULL to default to the current working directory where applicable.
> + * `format` : The formatting behavior rule to execute.
> + */
> +void append_formatted_path(struct strbuf *dest, const char *path,
> +			   const char *prefix, enum path_format format);
> +
>   # ifdef USE_THE_REPOSITORY_VARIABLE
>   #  include "strbuf.h"
>   #  include "repository.h"
K JayatheerthJun 24, 2026, 03:37 UTC in reply to K Jayatheerth on lore

[GSoC Patch v8 0/3] teach git repo info to handle path keys

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. repo: Introduce `path.commondir.absolute` and
   `path.commondir.relative` alongside a robust,
   isolated test helper.
3. repo: Introduce `path.gitdir.absolute` and
   `path.gitdir.relative` using the same standardized
   formatting rules.
   Changes since v7:
   * Renamed the helper to format_path() and changed semantics to replace/reset
     the destination buffer instead of appending (Junio).
   * Eliminated wasteful intermediate strbuf allocations (e.g., canonical_buf)
     by passing the destination buffer directly where safe (Junio).
   * Refactored the print_path() switch logic in rev-parse.c to evaluate
     FORMAT_DEFAULT first for better readability and future-proofing (Junio).
   Keeping the name as format_path() made sense to me. I understand we already
   had a discussion stating format_path() wasn't a good name back then because
   we were clearly appending to the buffer.
   I believe it is a good name now. Although, if there are any other name
   suggestions, I am happy to change it.
   P.S: I have thought of:
     replace_formatted_path
     strbuf_format_path
     populate_formatted_path
   In the end, I came to the conclusion that format_path() is simply better.

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

Thanks for helping improve this series!
K Jayatheerth (3):
  path: extract format_path() and use in rev-parse
  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         | 79 +++++++++++++++++--------------------
 path.c                      | 69 ++++++++++++++++++++++++++++++++
 path.h                      | 30 ++++++++++++++
 t/t1900-repo-info.sh        | 58 +++++++++++++++++++++++++++
 6 files changed, 258 insertions(+), 43 deletions(-)
Range-diff against v7:
1:  bb1d3fd06f ! 1:  287281935e path: extract append_formatted_path() and use in rev-parse
    @@ Metadata
     Author: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>
     
      ## Commit message ##
    -    path: extract append_formatted_path() and use in rev-parse
    +    path: extract format_path() and use in rev-parse
     
         Path formatting logic in builtin/rev-parse.c writes directly to
         stdout. Other builtins cannot reuse it.
     
    -    Extract this logic into append_formatted_path() in path.c and expose
    +    Extract this logic into format_path() in path.c and expose
         a path_format enum in path.h.
     
         Convert rev-parse to use the new helper in the same step to validate
    @@ builtin/rev-parse.c: enum default_type {
     +	struct strbuf sb = STRBUF_INIT;
     +	enum path_format fmt;
     +
    -+	if (format == FORMAT_RELATIVE) {
    -+		fmt = PATH_FORMAT_RELATIVE;
    -+	} else if (format == FORMAT_CANONICAL) {
    -+		fmt = PATH_FORMAT_CANONICAL;
    -+	} else /* FORMAT_DEFAULT */ {
    ++	if (format == FORMAT_DEFAULT) {
     +		switch (def) {
     +		case DEFAULT_RELATIVE:
     +			fmt = PATH_FORMAT_RELATIVE;
    @@ builtin/rev-parse.c: enum default_type {
     -		struct strbuf buf = STRBUF_INIT;
     -		puts(relative_path(path, prefix, &buf));
     -		strbuf_release(&buf);
    --	} else {
    + 	} else {
     -		struct strbuf buf = STRBUF_INIT;
     -		strbuf_realpath_forgiving(&buf, path, 1);
     -		puts(buf.buf);
     -		strbuf_release(&buf);
    ++		switch (format) {
    ++		case FORMAT_RELATIVE:
    ++			fmt = PATH_FORMAT_RELATIVE;
    ++			break;
    ++		case FORMAT_CANONICAL:
    ++			fmt = PATH_FORMAT_CANONICAL;
    ++			break;
    ++		default:
    ++			fmt = PATH_FORMAT_UNMODIFIED;
    ++			break;
    ++		}
      	}
     -	free(cwd);
     +
    -+	append_formatted_path(&sb, path, prefix, fmt);
    ++	format_path(&sb, path, prefix, fmt);
     +	puts(sb.buf);
     +
     +	strbuf_release(&sb);
    @@ path.c: 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)
    ++void format_path(struct strbuf *dest, const char *path,
    ++		 const char *prefix, enum path_format format)
     +{
    ++	strbuf_reset(dest);
    ++
     +	switch (format) {
     +	case PATH_FORMAT_UNMODIFIED:
     +		strbuf_addstr(dest, path);
    @@ path.c: char *xdg_cache_home(const char *filename)
     +		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);
    ++	case PATH_FORMAT_CANONICAL:
    ++		/*
    ++		 * strbuf_realpath_forgiving inherently resets the destination
    ++		 * buffer, safely aligning with our replace semantics.
    ++		 */
    ++		strbuf_realpath_forgiving(dest, path, 1);
     +		break;
    -+	}
     +
     +	default:
     +		BUG("unknown path_format value %d", format);
    @@ path.h: enum scld_error safe_create_leading_directories_no_share(char *path);
     +};
     +
     +/**
    -+ * Format a path according to the specified formatting strategy and append
    -+ * the result to the given strbuf.
    ++ * Format a path according to the specified formatting strategy and store
    ++ * the result in the given strbuf, replacing any existing contents.
     + *
    -+ * `dest`   : The string buffer to append the formatted path to.
    ++ * `dest`   : The string buffer to store the formatted path into.
     + * `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);
    ++void format_path(struct strbuf *dest, const char *path,
    ++		 const char *prefix, enum path_format format);
     +
      # ifdef USE_THE_REPOSITORY_VARIABLE
      #  include "strbuf.h"
2:  d2414bee58 ! 2:  69517f1a08 repo: add path.commondir with absolute and relative suffix formatting
    @@ builtin/repo.c: static int get_object_format(struct repository *repo, struct str
     +	if (!common_dir)
     +		return error(_("unable to get common directory"));
     +
    -+	append_formatted_path(buf, common_dir, startup_info->prefix, PATH_FORMAT_CANONICAL);
    ++	format_path(buf, common_dir, startup_info->prefix, PATH_FORMAT_CANONICAL);
     +	return 0;
     +}
     +
    @@ builtin/repo.c: static int get_object_format(struct repository *repo, struct str
     +	if (!common_dir)
     +		return error(_("unable to get common directory"));
     +
    -+	append_formatted_path(buf, common_dir, startup_info->prefix, PATH_FORMAT_RELATIVE);
    ++	format_path(buf, common_dir, startup_info->prefix, PATH_FORMAT_RELATIVE);
     +	return 0;
     +}
     +
3:  9962c7d530 ! 3:  ce43453975 repo: add path.gitdir with absolute and relative suffix formatting
    @@ builtin/repo.c: static int get_path_commondir_relative(struct repository *repo,
     +	if (!git_dir)
     +		return error(_("unable to get git directory"));
     +
    -+	append_formatted_path(buf, git_dir, startup_info->prefix, PATH_FORMAT_CANONICAL);
    ++	format_path(buf, git_dir, startup_info->prefix, PATH_FORMAT_CANONICAL);
     +	return 0;
     +}
     +
    @@ builtin/repo.c: static int get_path_commondir_relative(struct repository *repo,
     +	if (!git_dir)
     +		return error(_("unable to get git directory"));
     +
    -+	append_formatted_path(buf, git_dir, startup_info->prefix, PATH_FORMAT_RELATIVE);
    ++	format_path(buf, git_dir, startup_info->prefix, PATH_FORMAT_RELATIVE);
     +	return 0;
     +}
     +
-- 
2.55.0-rc1
K JayatheerthJun 24, 2026, 03:37 UTC in reply to K Jayatheerth on lore

[GSoC Patch v8 1/3] path: extract format_path() and use in rev-parse

Path formatting logic in builtin/rev-parse.c writes directly to stdout. Other builtins cannot reuse it.

Extract this logic into format_path() in path.c and expose a path_format enum in path.h.

Convert rev-parse to use the new helper in the same step to validate the API against existing tests and avoid introducing dead code.

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 | 79 +++++++++++++++++++++------------------------
 path.c              | 69 +++++++++++++++++++++++++++++++++++++++
 path.h              | 30 +++++++++++++++++
 3 files changed, 135 insertions(+), 43 deletions(-)
Show changes to 3 files +135 −43

builtin/rev-parse.c, path.c, path.h

diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c
index bb882678fe..7d6ac92038 100644
--- a/builtin/rev-parse.c
+++ b/builtin/rev-parse.c
@@ -653,53 +653,46 @@ enum default_type {
 	DEFAULT_UNMODIFIED,
 };
 
-static void print_path(const char *path, const char *prefix, enum format_type format, enum default_type def)
+static void print_path(const char *path, const char *prefix,
+		       enum format_type format, enum default_type def)
 {
-	char *cwd = NULL;
-	/*
-	 * We don't ever produce a relative path if prefix is NULL, so set the
-	 * prefix to the current directory so that we can produce a relative
-	 * path whenever possible.  If we're using RELATIVE_IF_SHARED mode, then
-	 * we want an absolute path unless the two share a common prefix, so don't
-	 * set it in that case, since doing so causes a relative path to always
-	 * be produced if possible.
-	 */
-	if (!prefix && (format != FORMAT_DEFAULT || def != DEFAULT_RELATIVE_IF_SHARED))
-		prefix = cwd = xgetcwd();
-	if (format == FORMAT_DEFAULT && def == DEFAULT_UNMODIFIED) {
-		puts(path);
-	} else if (format == FORMAT_RELATIVE ||
-		  (format == FORMAT_DEFAULT && def == DEFAULT_RELATIVE)) {
-		/*
-		 * In order for relative_path to work as expected, we need to
-		 * make sure that both paths are absolute paths.  If we don't,
-		 * we can end up with an unexpected absolute path that the user
-		 * didn't want.
-		 */
-		struct strbuf buf = STRBUF_INIT, realbuf = STRBUF_INIT, prefixbuf = STRBUF_INIT;
-		if (!is_absolute_path(path)) {
-			strbuf_realpath_forgiving(&realbuf, path,  1);
-			path = realbuf.buf;
-		}
-		if (!is_absolute_path(prefix)) {
-			strbuf_realpath_forgiving(&prefixbuf, prefix, 1);
-			prefix = prefixbuf.buf;
+	struct strbuf sb = STRBUF_INIT;
+	enum path_format fmt;
+
+	if (format == FORMAT_DEFAULT) {
+		switch (def) {
+		case DEFAULT_RELATIVE:
+			fmt = PATH_FORMAT_RELATIVE;
+			break;
+		case DEFAULT_RELATIVE_IF_SHARED:
+			fmt = PATH_FORMAT_RELATIVE_IF_SHARED;
+			break;
+		case DEFAULT_CANONICAL:
+			fmt = PATH_FORMAT_CANONICAL;
+			break;
+		case DEFAULT_UNMODIFIED:
+		default:
+			fmt = PATH_FORMAT_UNMODIFIED;
+			break;
 		}
-		puts(relative_path(path, prefix, &buf));
-		strbuf_release(&buf);
-		strbuf_release(&realbuf);
-		strbuf_release(&prefixbuf);
-	} else if (format == FORMAT_DEFAULT && def == DEFAULT_RELATIVE_IF_SHARED) {
-		struct strbuf buf = STRBUF_INIT;
-		puts(relative_path(path, prefix, &buf));
-		strbuf_release(&buf);
 	} else {
-		struct strbuf buf = STRBUF_INIT;
-		strbuf_realpath_forgiving(&buf, path, 1);
-		puts(buf.buf);
-		strbuf_release(&buf);
+		switch (format) {
+		case FORMAT_RELATIVE:
+			fmt = PATH_FORMAT_RELATIVE;
+			break;
+		case FORMAT_CANONICAL:
+			fmt = PATH_FORMAT_CANONICAL;
+			break;
+		default:
+			fmt = PATH_FORMAT_UNMODIFIED;
+			break;
+		}
 	}
-	free(cwd);
+
+	format_path(&sb, path, prefix, fmt);
+	puts(sb.buf);
+
+	strbuf_release(&sb);
 }
 
 int cmd_rev_parse(int argc,
diff --git a/path.c b/path.c
index d7e17bf174..c3a709a928 100644
--- a/path.c
+++ b/path.c
@@ -1579,6 +1579,75 @@ char *xdg_cache_home(const char *filename)
 	return NULL;
 }
 
+void format_path(struct strbuf *dest, const char *path,
+		 const char *prefix, enum path_format format)
+{
+	strbuf_reset(dest);
+
+	switch (format) {
+	case PATH_FORMAT_UNMODIFIED:
+		strbuf_addstr(dest, path);
+		break;
+
+	case PATH_FORMAT_RELATIVE: {
+		struct strbuf relative_buf = STRBUF_INIT;
+		struct strbuf real_path = STRBUF_INIT;
+		struct strbuf real_prefix = STRBUF_INIT;
+		char *cwd = NULL;
+
+		/*
+		 * We don't ever produce a relative path if prefix is NULL,
+		 * so set the prefix to the current directory so that we can
+		 * produce a relative path whenever possible.
+		 */
+		if (!prefix)
+			prefix = cwd = xgetcwd();
+
+		if (!is_absolute_path(path)) {
+			strbuf_realpath_forgiving(&real_path, path, 1);
+			path = real_path.buf;
+		}
+		if (!is_absolute_path(prefix)) {
+			strbuf_realpath_forgiving(&real_prefix, prefix, 1);
+			prefix = real_prefix.buf;
+		}
+
+		strbuf_addstr(dest, relative_path(path, prefix, &relative_buf));
+
+		strbuf_release(&relative_buf);
+		strbuf_release(&real_path);
+		strbuf_release(&real_prefix);
+		free(cwd);
+		break;
+	}
+
+	case PATH_FORMAT_RELATIVE_IF_SHARED: {
+		struct strbuf relative_buf = STRBUF_INIT;
+
+		/*
+		 * If we're using RELATIVE_IF_SHARED mode, then we want an
+		 * absolute path unless the two share a common prefix, so don't
+		 * default the prefix to the current working directory. Doing so
+		 * would cause a relative path to always be produced if possible.
+		 */
+		strbuf_addstr(dest, relative_path(path, prefix, &relative_buf));
+		strbuf_release(&relative_buf);
+		break;
+	}
+
+	case PATH_FORMAT_CANONICAL:
+		/*
+		 * strbuf_realpath_forgiving inherently resets the destination
+		 * buffer, safely aligning with our replace semantics.
+		 */
+		strbuf_realpath_forgiving(dest, path, 1);
+		break;
+
+	default:
+		BUG("unknown path_format value %d", format);
+	}
+}
+
 REPO_GIT_PATH_FUNC(squash_msg, "SQUASH_MSG")
 REPO_GIT_PATH_FUNC(merge_msg, "MERGE_MSG")
 REPO_GIT_PATH_FUNC(merge_rr, "MERGE_RR")
diff --git a/path.h b/path.h
index 4c2958a903..7e7408dd05 100644
--- a/path.h
+++ b/path.h
@@ -262,6 +262,36 @@ enum scld_error safe_create_leading_directories_no_share(char *path);
 int safe_create_file_with_leading_directories(struct repository *repo,
 					      const char *path);
 
+/**
+ * The formatting strategy to apply when writing a path into a buffer.
+ */
+enum path_format {
+	/* Output the path exactly as-is without any modifications. */
+	PATH_FORMAT_UNMODIFIED,
+
+	/* Output a path relative to the provided directory prefix. */
+	PATH_FORMAT_RELATIVE,
+
+	/* Output a relative path only if the path shares a root with the prefix. */
+	PATH_FORMAT_RELATIVE_IF_SHARED,
+
+	/* Output a fully resolved, absolute canonical path. */
+	PATH_FORMAT_CANONICAL
+};
+
+/**
+ * Format a path according to the specified formatting strategy and store
+ * the result in the given strbuf, replacing any existing contents.
+ *
+ * `dest`   : The string buffer to store the formatted path into.
+ * `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 format_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.55.0-rc1
K JayatheerthJun 24, 2026, 03:37 UTC in reply to K Jayatheerth on lore

[GSoC Patch v8 2/3] repo: add path.commondir with absolute and relative suffix formatting

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(+)
Show changes to 3 files +87 −0

Documentation/git-repo.adoc, builtin/repo.c, t/t1900-repo-info.sh

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..4c3fbc26b9 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"));
+
+	format_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"));
+
+	format_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.55.0-rc1
K JayatheerthJun 24, 2026, 03:37 UTC in reply to K Jayatheerth on lore

[GSoC Patch v8 3/3] repo: add path.gitdir with absolute and relative suffix formatting

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

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

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

Documentation/git-repo.adoc, builtin/repo.c, t/t1900-repo-info.sh

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

Re: [GSoC Patch v7 1/3] path: extract append_formatted_path() and use in rev-parse

Hey Phillip,
On Tue, Jun 23, 2026 at 9:27 PM Phillip Wood <phillip.wood123@gmail.com> wrote:
Show 19 quoted lines
>
> On 21/06/2026 06:55, K Jayatheerth wrote:
> > Path formatting logic in builtin/rev-parse.c writes directly to
> > stdout. Other builtins cannot reuse it.
> >
> > Extract this logic into append_formatted_path() in path.c and expose
> > a path_format enum in path.h.
> >
> > Convert rev-parse to use the new helper in the same step to validate
> > the API against existing tests and avoid introducing dead code.
>
> The new API looks good now, and so does the conversion of the existing
> code. I'm very happy with this version and don't have anything to add to
> Junio's comments
>
> Thanks
>
> Phillip
>

I have sent a v8 with Junio's feedback addressed. I wouldn't have a problem with either of the versions getting merged.

Both of them are good in their own ways.
Thank you,
- K Jayatheerth
Junio C HamanoJun 24, 2026, 18:15 UTC in reply to K Jayatheerth on lore

Re: [GSoC Patch v8 1/3] path: extract format_path() and use in rev-parse

K Jayatheerth <jayatheerthkulkarni2005@gmail.com> writes:
Show 40 quoted lines
> +void format_path(struct strbuf *dest, const char *path,
> +		 const char *prefix, enum path_format format)
> +{
> +	strbuf_reset(dest);
> +
> +	switch (format) {
> +	case PATH_FORMAT_UNMODIFIED:
> +		strbuf_addstr(dest, path);
> +		break;
> +
> +	case PATH_FORMAT_RELATIVE: {
> ...
> +		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: {
> ...
> +		strbuf_addstr(dest, relative_path(path, prefix, &relative_buf));
> +		strbuf_release(&relative_buf);
> +		break;
> +	}
> +
> +	case PATH_FORMAT_CANONICAL:
> +		/*
> +		 * strbuf_realpath_forgiving inherently resets the destination
> +		 * buffer, safely aligning with our replace semantics.
> +		 */
> +		strbuf_realpath_forgiving(dest, path, 1);
> +		break;
> +
> +	default:
> +		BUG("unknown path_format value %d", format);
> +	}
> +}
Hmph.

I was hoping that we could lose even more strbuf, but since relative_path() does not always leave its result in the strbuf that is passed to it as its third parameter, we do need addstr() into dest, which is a bit unsatisfying but not a fault of this patch at all. At least, we lost extra copy in the canonical codepath ;-)

Looking good.  Thanks.

Back to recent threads