{"thread":{"id":"65725","subject":"[GSoC][PATCH 0/4] teach git repo info to handle path keys","startedAt":"2026-06-01T15:20:46Z","lastAt":"2026-06-24T18:15:58Z","messageCount":82,"participants":["K Jayatheerth","Lucas Seiki Oshiro","Junio C Hamano","Phillip Wood","Kristoffer Haugsbakk","Justin Tobler"],"isPatch":true,"patchVersion":1,"patchTotal":4},"messages":[{"id":"544395","messageId":"20260601151950.30686-1-jayatheerthkulkarni2005@gmail.com","threadId":"65725","inReplyTo":null,"subject":"[GSoC][PATCH 0/4] teach git repo info to handle path keys","fromName":"K Jayatheerth","fromEmail":"jayatheerthkulkarni2005@gmail.com","sentAt":"2026-06-01T15:19:46Z","receivedAt":"2026-06-01T15:20:46Z","isPatch":true,"body":"Hi!\n\nThe first and second patches are self-explanatory, so I will\nfocus more on the third and fourth patches, which introduce the\npath-related fields to `git repo info`.\n\nIn the last discussion [1] we had on the mailing list about paths\nin repo info, we didn't reach a definitive conclusion, but\nadding both options made the most sense based on the feedback.\n\nSo in patches 3 and 4, we add both `path.<field>.absolute` and\n`path.<field>.relative` for `gitdir` and `commondir`. Initially,\nit was proposed by Ayush to use `path.absolute.<field>`, but\nthis would break the lexicographical order of the internal field\narray. I tweaked it to place the variant at the end as a suffix instead.\n\nThere are still a few open questions that should be addressed\nby the community. I am tagging members who were involved in the\nprevious discussions:\n\nJustin Tobler, Lucas Seiki Oshiro, Junio, Phillip Wood,\nbrian m. carlson, and Ayush Jha.\n\nApologies if I missed anyone; I included everyone who reviewed\nor participated in the discussions of Eslam's and Lucas's\npatches.\n\nQuestions:\n\n1. Should there still be a --path-format flag?\n2. Should we consider a default option?\n   Currently we have path.gitdir.absolute; should we consider\n   an option where a plain path.gitdir returns some default?\n   If yes:\n     2.1 Should we keep the default the same as rev-parse? Or\n         should either relative or absolute be the default?\n     2.2 When printing using --all, should the default be\n         printed, or should we print both absolute and\n         relative?\n3. Is printing both absolute and relative in a single call\n   using --all acceptable?\n   If no:\n     3.1 What's a better approach?\n\nI have discussed these changes with both Justin and Lucas\ninternally. This series is presented to gather opinions from the\nwider community before moving forward.\n\nK Jayatheerth (4):\n  path: add strbuf_add_path for formatting paths\n  rev-parse: use strbuf_add_path for path formatting\n  repo: add path.gitdir with absolute and relative suffix formatting\n  repo: add path.commondir with absolute and relative suffix formatting\n\n Documentation/git-repo.adoc |  15 ++++++\n builtin/repo.c              |  50 ++++++++++++++++++\n builtin/rev-parse.c         | 100 ++++++++----------------------------\n path.c                      |  58 +++++++++++++++++++++\n path.h                      |  16 ++++++\n t/t1900-repo-info.sh        |  32 ++++++++++++\n 6 files changed, 192 insertions(+), 79 deletions(-)\n\n-- \n2.54.0\n"},{"id":"544396","messageId":"20260601151950.30686-2-jayatheerthkulkarni2005@gmail.com","threadId":"65725","inReplyTo":"20260601151950.30686-1-jayatheerthkulkarni2005@gmail.com","subject":"[GSoC][PATCH 1/4] path: add strbuf_add_path for formatting paths","fromName":"K Jayatheerth","fromEmail":"jayatheerthkulkarni2005@gmail.com","sentAt":"2026-06-01T15:19:47Z","receivedAt":"2026-06-01T15:21:19Z","isPatch":true,"body":"The `print_path()` function in `builtin/rev-parse.c` contains\nlogic for formatting paths as either absolute or relative based on user\npreferences and default behaviors. However, this logic is currently\nlocked inside `rev-parse` and writes directly to stdout using `puts()`.\n\nTo allow other builtins (such as the new `git repo` command) to utilize\nthis same path-formatting logic, extract the core algorithm into a new\nstring-builder function, `strbuf_add_path()`, in `path.c`.\n\nAdditionally, extract the associated enums (`format_type` and\n`default_type`), and prefix them with `path_` (e.g., `path_format_type`)\nto safely expose them in `path.h` without polluting the global namespace.\n\nSigned-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>\nMentored-by: Justin Tobler <jltobler@gmail.com>\nMentored-by: Lucas Seiki Oshiro <lucasseikioshiro@gmail.com>\n---\n path.c | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n path.h | 16 ++++++++++++++++\n 2 files changed, 74 insertions(+)\n\ndiff --git a/path.c b/path.c\nindex d7e17bf174..914812320f 100644\n--- a/path.c\n+++ b/path.c\n@@ -1579,6 +1579,64 @@ char *xdg_cache_home(const char *filename)\n \treturn NULL;\n }\n \n+void strbuf_add_path(struct strbuf *sb, const char *path, const char *prefix,\n+\t\t     enum path_format_type format, enum path_default_type def)\n+{\n+\tchar *cwd = NULL;\n+\n+\t/*\n+\t * We don't ever produce a relative path if prefix is NULL, so set the\n+\t * prefix to the current directory so that we can produce a relative\n+\t * path whenever possible. If we're using RELATIVE_IF_SHARED mode, then\n+\t * we want an absolute path unless the two share a common prefix, so don't\n+\t * set it in that case, since doing so causes a relative path to always\n+\t * be produced if possible.\n+\t */\n+\tif (!prefix && (format != PATH_FORMAT_DEFAULT || def != PATH_DEFAULT_RELATIVE_IF_SHARED))\n+\t\tprefix = cwd = xgetcwd();\n+\n+\tif (format == PATH_FORMAT_DEFAULT && def == PATH_DEFAULT_UNMODIFIED) {\n+\t\t/* Case 1: Return the path exactly as-is without modifications */\n+\t\tstrbuf_addstr(sb, path);\n+\t} else if (format == PATH_FORMAT_RELATIVE ||\n+\t\t   (format == PATH_FORMAT_DEFAULT && def == PATH_DEFAULT_RELATIVE)) {\n+\t\t/*\n+\t\t * Case 2: Explicitly or implicitly relative.\n+\t\t * inside relative_path(), both targets must be absolute paths\n+\t\t * to compute a reliable relative tracking offset.\n+\t\t */\n+\t\tstruct strbuf buf = STRBUF_INIT, realbuf = STRBUF_INIT, prefixbuf = STRBUF_INIT;\n+\n+\t\tif (!is_absolute_path(path)) {\n+\t\t\tstrbuf_realpath_forgiving(&realbuf, path, 1);\n+\t\t\tpath = realbuf.buf;\n+\t\t}\n+\t\tif (!is_absolute_path(prefix)) {\n+\t\t\tstrbuf_realpath_forgiving(&prefixbuf, prefix, 1);\n+\t\t\tprefix = prefixbuf.buf;\n+\t\t}\n+\n+\t\tstrbuf_addstr(sb, relative_path(path, prefix, &buf));\n+\n+\t\tstrbuf_release(&buf);\n+\t\tstrbuf_release(&realbuf);\n+\t\tstrbuf_release(&prefixbuf);\n+\t} else if (format == PATH_FORMAT_DEFAULT && def == PATH_DEFAULT_RELATIVE_IF_SHARED) {\n+\t\t/* Case 3: Relative format if they share a common root pathway */\n+\t\tstruct strbuf buf = STRBUF_INIT;\n+\t\tstrbuf_addstr(sb, relative_path(path, prefix, &buf));\n+\t\tstrbuf_release(&buf);\n+\t} else {\n+\t\t/* Case 4: Forced absolute / canonical format optimization */\n+\t\tstruct strbuf buf = STRBUF_INIT;\n+\t\tstrbuf_realpath_forgiving(&buf, path, 1);\n+\t\tstrbuf_addbuf(sb, &buf);\n+\t\tstrbuf_release(&buf);\n+\t}\n+\n+\tfree(cwd);\n+}\n+\n REPO_GIT_PATH_FUNC(squash_msg, \"SQUASH_MSG\")\n REPO_GIT_PATH_FUNC(merge_msg, \"MERGE_MSG\")\n REPO_GIT_PATH_FUNC(merge_rr, \"MERGE_RR\")\ndiff --git a/path.h b/path.h\nindex 0434ba5e07..b9b626ce4a 100644\n--- a/path.h\n+++ b/path.h\n@@ -262,6 +262,22 @@ enum scld_error safe_create_leading_directories_no_share(char *path);\n int safe_create_file_with_leading_directories(struct repository *repo,\n \t\t\t\t\t      const char *path);\n \n+enum path_format_type {\n+\tPATH_FORMAT_DEFAULT,\n+\tPATH_FORMAT_RELATIVE,\n+\tPATH_FORMAT_CANONICAL\n+};\n+\n+enum path_default_type {\n+\tPATH_DEFAULT_RELATIVE,\n+\tPATH_DEFAULT_RELATIVE_IF_SHARED,\n+\tPATH_DEFAULT_CANONICAL,\n+\tPATH_DEFAULT_UNMODIFIED\n+};\n+\n+void strbuf_add_path(struct strbuf *buf, const char *path, const char *prefix,\n+\t\t     enum path_format_type format, enum path_default_type def);\n+\n # ifdef USE_THE_REPOSITORY_VARIABLE\n #  include \"strbuf.h\"\n #  include \"repository.h\"\n-- \n2.54.0\n\n"},{"id":"544397","messageId":"20260601151950.30686-3-jayatheerthkulkarni2005@gmail.com","threadId":"65725","inReplyTo":"20260601151950.30686-1-jayatheerthkulkarni2005@gmail.com","subject":"[GSoC][PATCH 2/4] rev-parse: use strbuf_add_path for path formatting","fromName":"K Jayatheerth","fromEmail":"jayatheerthkulkarni2005@gmail.com","sentAt":"2026-06-01T15:19:48Z","receivedAt":"2026-06-01T15:21:47Z","isPatch":true,"body":"Now that the core path-formatting logic has been abstracted into\nstrbuf_add_path() inside path.c, remove the duplicate localized\nimplementation from builtin/rev-parse.c.\n\nDrop the local format_type and default_type enums from the builtin, and\nupdate print_path() to act as a light wrapper around the new shared\nstrbuf engine. Update cmd_rev_parse() to use the new path_ format and\ndefault enum types exposed via path.h.\n\nSigned-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>\nMentored-by: Justin Tobler <jltobler@gmail.com>\nMentored-by: Lucas Seiki Oshiro <lucasseikioshiro@gmail.com>\n---\n builtin/rev-parse.c | 100 ++++++++++----------------------------------\n 1 file changed, 21 insertions(+), 79 deletions(-)\n\ndiff --git a/builtin/rev-parse.c b/builtin/rev-parse.c\nindex 218b5f34d6..812cfd55ad 100644\n--- a/builtin/rev-parse.c\n+++ b/builtin/rev-parse.c\n@@ -632,73 +632,15 @@ static void handle_ref_opt(const char *pattern, const char *prefix)\n \tclear_ref_exclusions(&ref_excludes);\n }\n \n-enum format_type {\n-\t/* We would like a relative path. */\n-\tFORMAT_RELATIVE,\n-\t/* We would like a canonical absolute path. */\n-\tFORMAT_CANONICAL,\n-\t/* We would like the default behavior. */\n-\tFORMAT_DEFAULT,\n-};\n-\n-enum default_type {\n-\t/* Our default is a relative path. */\n-\tDEFAULT_RELATIVE,\n-\t/* Our default is a relative path if there's a shared root. */\n-\tDEFAULT_RELATIVE_IF_SHARED,\n-\t/* Our default is a canonical absolute path. */\n-\tDEFAULT_CANONICAL,\n-\t/* Our default is not to modify the item. */\n-\tDEFAULT_UNMODIFIED,\n-};\n-\n-static void print_path(const char *path, const char *prefix, enum format_type format, enum default_type def)\n+static void print_path(const char *path, const char *prefix,\n+\t\t       enum path_format_type format, enum path_default_type def)\n {\n-\tchar *cwd = NULL;\n-\t/*\n-\t * We don't ever produce a relative path if prefix is NULL, so set the\n-\t * prefix to the current directory so that we can produce a relative\n-\t * path whenever possible.  If we're using RELATIVE_IF_SHARED mode, then\n-\t * we want an absolute path unless the two share a common prefix, so don't\n-\t * set it in that case, since doing so causes a relative path to always\n-\t * be produced if possible.\n-\t */\n-\tif (!prefix && (format != FORMAT_DEFAULT || def != DEFAULT_RELATIVE_IF_SHARED))\n-\t\tprefix = cwd = xgetcwd();\n-\tif (format == FORMAT_DEFAULT && def == DEFAULT_UNMODIFIED) {\n-\t\tputs(path);\n-\t} else if (format == FORMAT_RELATIVE ||\n-\t\t  (format == FORMAT_DEFAULT && def == DEFAULT_RELATIVE)) {\n-\t\t/*\n-\t\t * In order for relative_path to work as expected, we need to\n-\t\t * make sure that both paths are absolute paths.  If we don't,\n-\t\t * we can end up with an unexpected absolute path that the user\n-\t\t * didn't want.\n-\t\t */\n-\t\tstruct strbuf buf = STRBUF_INIT, realbuf = STRBUF_INIT, prefixbuf = STRBUF_INIT;\n-\t\tif (!is_absolute_path(path)) {\n-\t\t\tstrbuf_realpath_forgiving(&realbuf, path,  1);\n-\t\t\tpath = realbuf.buf;\n-\t\t}\n-\t\tif (!is_absolute_path(prefix)) {\n-\t\t\tstrbuf_realpath_forgiving(&prefixbuf, prefix, 1);\n-\t\t\tprefix = prefixbuf.buf;\n-\t\t}\n-\t\tputs(relative_path(path, prefix, &buf));\n-\t\tstrbuf_release(&buf);\n-\t\tstrbuf_release(&realbuf);\n-\t\tstrbuf_release(&prefixbuf);\n-\t} else if (format == FORMAT_DEFAULT && def == DEFAULT_RELATIVE_IF_SHARED) {\n-\t\tstruct strbuf buf = STRBUF_INIT;\n-\t\tputs(relative_path(path, prefix, &buf));\n-\t\tstrbuf_release(&buf);\n-\t} else {\n-\t\tstruct strbuf buf = STRBUF_INIT;\n-\t\tstrbuf_realpath_forgiving(&buf, path, 1);\n-\t\tputs(buf.buf);\n-\t\tstrbuf_release(&buf);\n-\t}\n-\tfree(cwd);\n+\tstruct strbuf sb = STRBUF_INIT;\n+\n+\tstrbuf_add_path(&sb, path, prefix, format, def);\n+\tputs(sb.buf);\n+\n+\tstrbuf_release(&sb);\n }\n \n int cmd_rev_parse(int argc,\n@@ -717,7 +659,7 @@ int cmd_rev_parse(int argc,\n \tconst char *name = NULL;\n \tstruct strbuf buf = STRBUF_INIT;\n \tint seen_end_of_options = 0;\n-\tenum format_type format = FORMAT_DEFAULT;\n+\tenum path_format_type format = PATH_FORMAT_DEFAULT;\n \n \tshow_usage_if_asked(argc, argv, builtin_rev_parse_usage);\n \n@@ -798,7 +740,7 @@ int cmd_rev_parse(int argc,\n \t\t\t\tprint_path(repo_git_path_replace(the_repository, &buf,\n \t\t\t\t\t\t\t\t \"%s\", argv[i + 1]), prefix,\n \t\t\t\t\t\tformat,\n-\t\t\t\t\t\tDEFAULT_RELATIVE_IF_SHARED);\n+\t\t\t\t\t\tPATH_DEFAULT_RELATIVE_IF_SHARED);\n \t\t\t\ti++;\n \t\t\t\tcontinue;\n \t\t\t}\n@@ -820,9 +762,9 @@ int cmd_rev_parse(int argc,\n \t\t\t\tif (!arg)\n \t\t\t\t\tdie(_(\"--path-format requires an argument\"));\n \t\t\t\tif (!strcmp(arg, \"absolute\")) {\n-\t\t\t\t\tformat = FORMAT_CANONICAL;\n+\t\t\t\t\tformat = PATH_FORMAT_CANONICAL;\n \t\t\t\t} else if (!strcmp(arg, \"relative\")) {\n-\t\t\t\t\tformat = FORMAT_RELATIVE;\n+\t\t\t\t\tformat = PATH_FORMAT_RELATIVE;\n \t\t\t\t} else {\n \t\t\t\t\tdie(_(\"unknown argument to --path-format: %s\"), arg);\n \t\t\t\t}\n@@ -985,7 +927,7 @@ int cmd_rev_parse(int argc,\n \t\t\tif (!strcmp(arg, \"--show-toplevel\")) {\n \t\t\t\tconst char *work_tree = repo_get_work_tree(the_repository);\n \t\t\t\tif (work_tree)\n-\t\t\t\t\tprint_path(work_tree, prefix, format, DEFAULT_UNMODIFIED);\n+\t\t\t\t\tprint_path(work_tree, prefix, format, PATH_DEFAULT_UNMODIFIED);\n \t\t\t\telse\n \t\t\t\t\tdie(_(\"this operation must be run in a work tree\"));\n \t\t\t\tcontinue;\n@@ -993,7 +935,7 @@ int cmd_rev_parse(int argc,\n \t\t\tif (!strcmp(arg, \"--show-superproject-working-tree\")) {\n \t\t\t\tstruct strbuf superproject = STRBUF_INIT;\n \t\t\t\tif (get_superproject_working_tree(&superproject))\n-\t\t\t\t\tprint_path(superproject.buf, prefix, format, DEFAULT_UNMODIFIED);\n+\t\t\t\t\tprint_path(superproject.buf, prefix, format, PATH_DEFAULT_UNMODIFIED);\n \t\t\t\tstrbuf_release(&superproject);\n \t\t\t\tcontinue;\n \t\t\t}\n@@ -1028,18 +970,18 @@ int cmd_rev_parse(int argc,\n \t\t\t\tconst char *gitdir = getenv(GIT_DIR_ENVIRONMENT);\n \t\t\t\tchar *cwd;\n \t\t\t\tint len;\n-\t\t\t\tenum format_type wanted = format;\n+\t\t\t\tenum path_format_type wanted = format;\n \t\t\t\tif (arg[2] == 'g') {\t/* --git-dir */\n \t\t\t\t\tif (gitdir) {\n-\t\t\t\t\t\tprint_path(gitdir, prefix, format, DEFAULT_UNMODIFIED);\n+\t\t\t\t\t\tprint_path(gitdir, prefix, format, PATH_DEFAULT_UNMODIFIED);\n \t\t\t\t\t\tcontinue;\n \t\t\t\t\t}\n \t\t\t\t\tif (!prefix) {\n-\t\t\t\t\t\tprint_path(\".git\", prefix, format, DEFAULT_UNMODIFIED);\n+\t\t\t\t\t\tprint_path(\".git\", prefix, format, PATH_DEFAULT_UNMODIFIED);\n \t\t\t\t\t\tcontinue;\n \t\t\t\t\t}\n \t\t\t\t} else {\t\t/* --absolute-git-dir */\n-\t\t\t\t\twanted = FORMAT_CANONICAL;\n+\t\t\t\t\twanted = PATH_FORMAT_CANONICAL;\n \t\t\t\t\tif (!gitdir && !prefix)\n \t\t\t\t\t\tgitdir = \".git\";\n \t\t\t\t\tif (gitdir) {\n@@ -1055,11 +997,11 @@ int cmd_rev_parse(int argc,\n \t\t\t\tstrbuf_reset(&buf);\n \t\t\t\tstrbuf_addf(&buf, \"%s%s.git\", cwd, len && cwd[len-1] != '/' ? \"/\" : \"\");\n \t\t\t\tfree(cwd);\n-\t\t\t\tprint_path(buf.buf, prefix, wanted, DEFAULT_CANONICAL);\n+\t\t\t\tprint_path(buf.buf, prefix, wanted, PATH_DEFAULT_CANONICAL);\n \t\t\t\tcontinue;\n \t\t\t}\n \t\t\tif (!strcmp(arg, \"--git-common-dir\")) {\n-\t\t\t\tprint_path(repo_get_common_dir(the_repository), prefix, format, DEFAULT_RELATIVE_IF_SHARED);\n+\t\t\t\tprint_path(repo_get_common_dir(the_repository), prefix, format, PATH_DEFAULT_RELATIVE_IF_SHARED);\n \t\t\t\tcontinue;\n \t\t\t}\n \t\t\tif (!strcmp(arg, \"--is-inside-git-dir\")) {\n@@ -1089,7 +1031,7 @@ int cmd_rev_parse(int argc,\n \t\t\t\tif (the_repository->index->split_index) {\n \t\t\t\t\tconst struct object_id *oid = &the_repository->index->split_index->base_oid;\n \t\t\t\t\tconst char *path = repo_git_path_replace(the_repository, &buf, \"sharedindex.%s\", oid_to_hex(oid));\n-\t\t\t\t\tprint_path(path, prefix, format, DEFAULT_RELATIVE);\n+\t\t\t\t\tprint_path(path, prefix, format, PATH_DEFAULT_RELATIVE);\n \t\t\t\t}\n \t\t\t\tcontinue;\n \t\t\t}\n-- \n2.54.0\n\n"},{"id":"544398","messageId":"20260601151950.30686-4-jayatheerthkulkarni2005@gmail.com","threadId":"65725","inReplyTo":"20260601151950.30686-1-jayatheerthkulkarni2005@gmail.com","subject":"[GSoC][PATCH 3/4] repo: add path.gitdir with absolute and relative suffix formatting","fromName":"K Jayatheerth","fromEmail":"jayatheerthkulkarni2005@gmail.com","sentAt":"2026-06-01T15:19:49Z","receivedAt":"2026-06-01T15:22:26Z","isPatch":true,"body":"Introduce path-related metadata fields to `git repo info` by adding\nexplicit `path.gitdir.absolute` and `path.gitdir.relative` keys. This\nreplaces dynamic prefix parsing machinery with individual, predictable\nlexicographically-sorted keys that map directly to dedicated formatting\ncallbacks.\n\nTo calculate paths relative to the current working directory, update\n`builtin/repo.c` to include `setup.h` and supply `startup_info->prefix`\nto the path-formatting engine. Both explicit variants automatically\npopulate bulk dumps via `--all` and output predictably under `--keys`.\n\nUpdate `t/t1900-repo-info.sh` to use a modernized, function-based loop\nhelper (`test_repo_info_path`) and `test_grep` to cleanly assert separate\npath variation lookups.\n\nSigned-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>\nMentored-by: Justin Tobler <jltobler@gmail.com>\nMentored-by: Lucas Seiki Oshiro <lucasseikioshiro@gmail.com>\n---\n Documentation/git-repo.adoc |  6 ++++++\n builtin/repo.c              | 26 ++++++++++++++++++++++++++\n t/t1900-repo-info.sh        | 31 +++++++++++++++++++++++++++++++\n 3 files changed, 63 insertions(+)\n\ndiff --git a/Documentation/git-repo.adoc b/Documentation/git-repo.adoc\nindex 42262c1983..a0dca7ce88 100644\n--- a/Documentation/git-repo.adoc\n+++ b/Documentation/git-repo.adoc\n@@ -104,6 +104,12 @@ values that they return:\n `object.format`::\n \tThe object format (hash algorithm) used in the repository.\n \n+`path.gitdir.absolute`::\n+\tThe canonical absolute path to the Git repository directory (the `.git` directory).\n+\n+`path.gitdir.relative`::\n+\tThe path to the Git repository directory relative to the current working directory.\n+\n `references.format`::\n \tThe reference storage format. The valid values are:\n +\ndiff --git a/builtin/repo.c b/builtin/repo.c\nindex 71a5c1c29c..c141ef892a 100644\n--- a/builtin/repo.c\n+++ b/builtin/repo.c\n@@ -7,12 +7,14 @@\n #include \"hex.h\"\n #include \"odb.h\"\n #include \"parse-options.h\"\n+#include \"path.h\"\n #include \"path-walk.h\"\n #include \"progress.h\"\n #include \"quote.h\"\n #include \"ref-filter.h\"\n #include \"refs.h\"\n #include \"revision.h\"\n+#include \"setup.h\"\n #include \"strbuf.h\"\n #include \"string-list.h\"\n #include \"shallow.h\"\n@@ -75,6 +77,28 @@ static int get_object_format(struct repository *repo, struct strbuf *buf)\n \treturn 0;\n }\n \n+static int get_path_gitdir_absolute(struct repository *repo, struct strbuf *buf)\n+{\n+\tconst char *git_dir = repo_get_git_dir(repo);\n+\n+\tif (!git_dir)\n+\t\treturn error(_(\"unable to get git directory\"));\n+\n+\tstrbuf_add_path(buf, git_dir, startup_info->prefix, PATH_FORMAT_CANONICAL, PATH_DEFAULT_UNMODIFIED);\n+\treturn 0;\n+}\n+\n+static int get_path_gitdir_relative(struct repository *repo, struct strbuf *buf)\n+{\n+\tconst char *git_dir = repo_get_git_dir(repo);\n+\n+\tif (!git_dir)\n+\t\treturn error(_(\"unable to get git directory\"));\n+\n+\tstrbuf_add_path(buf, git_dir, startup_info->prefix, PATH_FORMAT_RELATIVE, PATH_DEFAULT_UNMODIFIED);\n+\treturn 0;\n+}\n+\n static int get_references_format(struct repository *repo, struct strbuf *buf)\n {\n \tstrbuf_addstr(buf,\n@@ -87,6 +111,8 @@ static const struct repo_info_field repo_info_field[] = {\n \t{ \"layout.bare\", get_layout_bare },\n \t{ \"layout.shallow\", get_layout_shallow },\n \t{ \"object.format\", get_object_format },\n+\t{ \"path.gitdir.absolute\", get_path_gitdir_absolute },\n+\t{ \"path.gitdir.relative\", get_path_gitdir_relative },\n \t{ \"references.format\", get_references_format },\n };\n \ndiff --git a/t/t1900-repo-info.sh b/t/t1900-repo-info.sh\nindex 39bb77dda0..7c7dfbb052 100755\n--- a/t/t1900-repo-info.sh\n+++ b/t/t1900-repo-info.sh\n@@ -155,4 +155,35 @@ test_expect_success 'git repo info -h shows only repo info usage' '\n \ttest_grep ! \"git repo structure\" actual\n '\n \n+test_repo_info_path () {\n+\tfield_name=$1\n+\texpect_relative=$2\n+\n+\ttest_expect_success \"query individual key: path.$field_name.absolute\" '\n+\t\t(\n+\t\t\tcd test-repo/sub &&\n+\t\t\texpect_absolute=$(cd .. && pwd)/.git &&\n+\t\t\techo \"path.$field_name.absolute=$expect_absolute\" >expect &&\n+\t\t\tgit repo info path.$field_name.absolute >actual &&\n+\t\t\ttest_cmp expect actual\n+\t\t)\n+\t'\n+\n+\ttest_expect_success \"query individual key: path.$field_name.relative\" '\n+\t\t(\n+\t\t\tcd test-repo/sub &&\n+\t\t\techo \"path.$field_name.relative=$expect_relative\" >expect &&\n+\t\t\tgit repo info path.$field_name.relative >actual &&\n+\t\t\ttest_cmp expect actual\n+\t\t)\n+\t'\n+}\n+\n+test_expect_success 'setup test repository layout for path fields' '\n+\tgit init test-repo &&\n+\tmkdir -p test-repo/sub\n+'\n+\n+test_repo_info_path 'gitdir' '../.git'\n+\n test_done\n-- \n2.54.0\n\n"},{"id":"544399","messageId":"20260601151950.30686-5-jayatheerthkulkarni2005@gmail.com","threadId":"65725","inReplyTo":"20260601151950.30686-1-jayatheerthkulkarni2005@gmail.com","subject":"[GSoC][PATCH 4/4] repo: add path.commondir with absolute and relative suffix formatting","fromName":"K Jayatheerth","fromEmail":"jayatheerthkulkarni2005@gmail.com","sentAt":"2026-06-01T15:19:50Z","receivedAt":"2026-06-01T15:23:01Z","isPatch":true,"body":"Introduce `path.commondir.absolute` and `path.commondir.relative` keys\nto `git repo info`. These track the repository's common directory path,\nextending the path metadata engine alongside the existing `gitdir` fields.\n\nUpdate `repo_info_field` to store the new keys in proper lexicographical\norder to protect binary search operations, and expand the test matrix in\n`t/t1900-repo-info.sh` to validate separate queries, bulk dumps, and\nkey listings.\n\nSigned-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>\nMentored-by: Justin Tobler <jltobler@gmail.com>\nMentored-by: Lucas Seiki Oshiro <lucasseikioshiro@gmail.com>\n---\n Documentation/git-repo.adoc |  9 +++++++++\n builtin/repo.c              | 24 ++++++++++++++++++++++++\n t/t1900-repo-info.sh        |  1 +\n 3 files changed, 34 insertions(+)\n\ndiff --git a/Documentation/git-repo.adoc b/Documentation/git-repo.adoc\nindex a0dca7ce88..ed7d80c690 100644\n--- a/Documentation/git-repo.adoc\n+++ b/Documentation/git-repo.adoc\n@@ -104,6 +104,15 @@ values that they return:\n `object.format`::\n \tThe object format (hash algorithm) used in the repository.\n \n+`path.commondir.absolute`::\n+\tThe canonical absolute path to the Git repository's common\n+\tdirectory (the shared `.git` directory containing objects,\n+\trefs, and global configuration).\n+\n+`path.commondir.relative`::\n+\tThe path to the Git repository's common directory relative to\n+\tthe current working directory.\n+\n `path.gitdir.absolute`::\n \tThe canonical absolute path to the Git repository directory (the `.git` directory).\n \ndiff --git a/builtin/repo.c b/builtin/repo.c\nindex c141ef892a..be24a5a8e8 100644\n--- a/builtin/repo.c\n+++ b/builtin/repo.c\n@@ -77,6 +77,28 @@ static int get_object_format(struct repository *repo, struct strbuf *buf)\n \treturn 0;\n }\n \n+static int get_path_commondir_absolute(struct repository *repo, struct strbuf *buf)\n+{\n+\tconst char *common_dir = repo_get_common_dir(repo);\n+\n+\tif (!common_dir)\n+\t\treturn error(_(\"unable to get common directory\"));\n+\n+\tstrbuf_add_path(buf, common_dir, startup_info->prefix, PATH_FORMAT_CANONICAL, PATH_DEFAULT_UNMODIFIED);\n+\treturn 0;\n+}\n+\n+static int get_path_commondir_relative(struct repository *repo, struct strbuf *buf)\n+{\n+\tconst char *common_dir = repo_get_common_dir(repo);\n+\n+\tif (!common_dir)\n+\t\treturn error(_(\"unable to get common directory\"));\n+\n+\tstrbuf_add_path(buf, common_dir, startup_info->prefix, PATH_FORMAT_RELATIVE, PATH_DEFAULT_UNMODIFIED);\n+\treturn 0;\n+}\n+\n static int get_path_gitdir_absolute(struct repository *repo, struct strbuf *buf)\n {\n \tconst char *git_dir = repo_get_git_dir(repo);\n@@ -111,6 +133,8 @@ static const struct repo_info_field repo_info_field[] = {\n \t{ \"layout.bare\", get_layout_bare },\n \t{ \"layout.shallow\", get_layout_shallow },\n \t{ \"object.format\", get_object_format },\n+\t{ \"path.commondir.absolute\", get_path_commondir_absolute },\n+\t{ \"path.commondir.relative\", get_path_commondir_relative },\n \t{ \"path.gitdir.absolute\", get_path_gitdir_absolute },\n \t{ \"path.gitdir.relative\", get_path_gitdir_relative },\n \t{ \"references.format\", get_references_format },\ndiff --git a/t/t1900-repo-info.sh b/t/t1900-repo-info.sh\nindex 7c7dfbb052..dd2706e1f7 100755\n--- a/t/t1900-repo-info.sh\n+++ b/t/t1900-repo-info.sh\n@@ -184,6 +184,7 @@ test_expect_success 'setup test repository layout for path fields' '\n \tmkdir -p test-repo/sub\n '\n \n+test_repo_info_path 'commondir' '../.git'\n test_repo_info_path 'gitdir' '../.git'\n \n test_done\n-- \n2.54.0\n"},{"id":"544414","messageId":"289C2CFD-EE70-43E4-87D9-DA3393A572C6@gmail.com","threadId":"65725","inReplyTo":"20260601151950.30686-1-jayatheerthkulkarni2005@gmail.com","subject":"Re: [GSoC][PATCH 0/4] teach git repo info to handle path keys","fromName":"Lucas Seiki Oshiro","fromEmail":"lucasseikioshiro@gmail.com","sentAt":"2026-06-01T16:25:16Z","receivedAt":"2026-06-01T16:26:32Z","isPatch":true,"body":"\n> 1. Should there still be a --path-format flag?\n\nIf you specify \"absolute\" and \"relative\" in the keys, it won't\nmake sense to use it.\n\n> 2. Should we consider a default option?\n\nSome pros and cons:\n\n- Pro: some values make more sense to be in absolute or relative\n  format\n- Pro: it's boring to always add `.(relative|absolute)` to the\n  paths\n- Con: it will be perpetuating what git-rev-parse does, and we\n  don't git-repo-info to be git-rev-parse with a different\n  interface. It's our chance to learn with [1] for example.\n- Con: the user will need if the value is relative or absolute\n\n> 3. Is printing both absolute and relative in a single call\n>   using --all acceptable?\n\nIf you're providing both keys, I think it's not only acceptable\nbut mandatory. `--all` should mean \"all\", not \"all, but ...\".\n\n> I have discussed these changes with both Justin and Lucas\n> internally. This series is presented to gather opinions from the\n> wider community before moving forward.\n\nI probably sent the same comments internally, but I'm sending\nhere to share my opinions with the rest of the community ;-)\n\n[1] fac60b8925 (rev-parse: add option for absolute or relative path formatting, 2020-12-13)\n\n\n"},{"id":"544415","messageId":"FE164E62-C086-4902-B21F-157CD2C4ED0E@gmail.com","threadId":"65725","inReplyTo":"20260601151950.30686-4-jayatheerthkulkarni2005@gmail.com","subject":"Re: [GSoC][PATCH 3/4] repo: add path.gitdir with absolute and relative suffix formatting","fromName":"Lucas Seiki Oshiro","fromEmail":"lucasseikioshiro@gmail.com","sentAt":"2026-06-01T16:28:57Z","receivedAt":"2026-06-01T16:29:12Z","isPatch":true,"body":"\n> +test_repo_info_path () {\n> + field_name=$1\n> + expect_relative=$2\n> +\n> + test_expect_success \"query individual key: path.$field_name.absolute\" '\n> + (\n> + cd test-repo/sub &&\n> + expect_absolute=$(cd .. && pwd)/.git &&\n\nNote that this semi-hardcoded path won't work for other values (e.g.\ntop level dir, superproject working tree). This needs to be a parameter\njust like `expect_relative`\n"},{"id":"544421","messageId":"11C531FB-2397-42AE-8F18-BF78BA779088@gmail.com","threadId":"65725","inReplyTo":"20260601151950.30686-5-jayatheerthkulkarni2005@gmail.com","subject":"Re: [GSoC][PATCH 4/4] repo: add path.commondir with absolute and relative suffix formatting","fromName":"Lucas Seiki Oshiro","fromEmail":"lucasseikioshiro@gmail.com","sentAt":"2026-06-01T16:34:29Z","receivedAt":"2026-06-01T21:31:02Z","isPatch":true,"body":"\n> diff --git a/t/t1900-repo-info.sh b/t/t1900-repo-info.sh\n> index 7c7dfbb052..dd2706e1f7 100755\n> --- a/t/t1900-repo-info.sh\n> +++ b/t/t1900-repo-info.sh\n> @@ -184,6 +184,7 @@ test_expect_success 'setup test repository layout for path fields' '\n> mkdir -p test-repo/sub\n> '\n> \n> +test_repo_info_path 'commondir' '../.git'\n> test_repo_info_path 'gitdir' '../.git'\n\nI was thinking here, maybe you need to take a look at\ngit-rev-parse's tests and check what are the corner cases.\n\nFor example, `git rev-parse --git-common-dir` documentation\nsays:\n\n    --git-common-dir:\n        Show $GIT_COMMON_DIR if defined, else $GIT_DIR\n\nThis way, you should take a look on how git-rev-parse tests\ntest those two branches (GIT_COMMON_DIR and GIT_DIR).\n\n"},{"id":"544430","messageId":"81475C6C-3E0F-492D-BC20-45518BC00FE8@gmail.com","threadId":"65725","inReplyTo":"20260601151950.30686-5-jayatheerthkulkarni2005@gmail.com","subject":"Re: [GSoC][PATCH 4/4] repo: add path.commondir with absolute and relative suffix formatting","fromName":"Lucas Seiki Oshiro","fromEmail":"lucasseikioshiro@gmail.com","sentAt":"2026-06-01T21:58:35Z","receivedAt":"2026-06-01T21:58:51Z","isPatch":true,"body":"\n> diff --git a/t/t1900-repo-info.sh b/t/t1900-repo-info.sh\n> index 7c7dfbb052..dd2706e1f7 100755\n> --- a/t/t1900-repo-info.sh\n> +++ b/t/t1900-repo-info.sh\n> @@ -184,6 +184,7 @@ test_expect_success 'setup test repository layout for path fields' '\n> mkdir -p test-repo/sub\n> '\n> \n> +test_repo_info_path 'commondir' '../.git'\n> test_repo_info_path 'gitdir' '../.git'\n\nI was thinking here, maybe you need to take a look at\ngit-rev-parse's tests and check what are the corner cases.\n\nFor example, `git rev-parse --git-common-dir` documentation\nsays:\n\n    --git-common-dir:\n        Show $GIT_COMMON_DIR if defined, else $GIT_DIR\n\nThis way, you should take a look on how git-rev-parse tests\ntest those two cases (GIT_COMMON_DIR and GIT_DIR) and do\nsomething similar here.\n\n"},{"id":"544431","messageId":"921ED129-FFFD-4F40-9232-EDD3621EBEF9@gmail.com","threadId":"65725","inReplyTo":"20260601151950.30686-1-jayatheerthkulkarni2005@gmail.com","subject":"Re: [GSoC][PATCH 0/4] teach git repo info to handle path keys","fromName":"Lucas Seiki Oshiro","fromEmail":"lucasseikioshiro@gmail.com","sentAt":"2026-06-01T22:04:16Z","receivedAt":"2026-06-01T22:04:32Z","isPatch":true,"body":"Nitpick: use [GSoC PATCH] instead of [GSoC][PATCH] as prefix.\nUse --subject-prefix='GSoC PATCH' in git-send-email or\ngit-format-patch or set the configuration variable\n`format.subjectPrefix` to that until you finish your GSoC:\n\n$ git config --local format.subjectPrefix 'GSoC PATCH'\n"},{"id":"544441","messageId":"xmqqse75275u.fsf@gitster.g","threadId":"65725","inReplyTo":"20260601151950.30686-1-jayatheerthkulkarni2005@gmail.com","subject":"Re: [GSoC][PATCH 0/4] teach git repo info to handle path keys","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-06-01T23:05:33Z","receivedAt":"2026-06-01T23:05:36Z","isPatch":true,"body":"K Jayatheerth <jayatheerthkulkarni2005@gmail.com> writes:\n\n> Hi!\n>\n> The first and second patches are self-explanatory, so I will\n> focus more on the third and fourth patches, which introduce the\n> path-related fields to `git repo info`.\n\nI sense that a paragraph or two is missing before that.  The purpose\nof your cover letter is to _sell_ your changes, explain what it is\nabout, and get your target audience interested enough to read the\npatches.  The above goes totally backwards---your readers do not yet\nknow what the series is about, they haven't decided if it is worth\ntheir time to read it, and you are telling \"go read first two\nyourself, I am not going to tell you what they are about\"?\n\n> In the last discussion [1] we had on the mailing list about paths\n> in repo info, we didn't reach a definitive conclusion, but\n> adding both options made the most sense based on the feedback.\n\nAnd again, you are excluding those who were not in, or do not\nremember, what was discussed in \"the last discussion\".\n\n"},{"id":"544442","messageId":"xmqqo6ht26zv.fsf@gitster.g","threadId":"65725","inReplyTo":"FE164E62-C086-4902-B21F-157CD2C4ED0E@gmail.com","subject":"Re: [GSoC][PATCH 3/4] repo: add path.gitdir with absolute and relative suffix formatting","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-06-01T23:09:08Z","receivedAt":"2026-06-01T23:09:11Z","isPatch":true,"body":"Lucas Seiki Oshiro <lucasseikioshiro@gmail.com> writes:\n\n>> +test_repo_info_path () {\n>> + field_name=$1\n>> + expect_relative=$2\n>> +\n>> + test_expect_success \"query individual key: path.$field_name.absolute\" '\n>> + (\n>> + cd test-repo/sub &&\n>> + expect_absolute=$(cd .. && pwd)/.git &&\n>\n> Note that this semi-hardcoded path won't work for other values (e.g.\n> top level dir, superproject working tree). This needs to be a parameter\n> just like `expect_relative`\n\nGood thinking.  Thanks for carefully reading.\n"},{"id":"544501","messageId":"73c1f6ee-9461-4cf3-8d51-33de05f6d070@gmail.com","threadId":"65725","inReplyTo":"20260601151950.30686-2-jayatheerthkulkarni2005@gmail.com","subject":"Re: [GSoC][PATCH 1/4] path: add strbuf_add_path for formatting paths","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2026-06-02T13:00:37Z","receivedAt":"2026-06-02T13:00:44Z","isPatch":true,"body":"On 01/06/2026 16:19, K Jayatheerth wrote:\n> \n> diff --git a/path.h b/path.h\n> index 0434ba5e07..b9b626ce4a 100644\n> --- a/path.h\n> +++ b/path.h\n> @@ -262,6 +262,22 @@ enum scld_error safe_create_leading_directories_no_share(char *path);\n>   int safe_create_file_with_leading_directories(struct repository *repo,\n>   \t\t\t\t\t      const char *path);\n>   \n> +enum path_format_type {\n> +\tPATH_FORMAT_DEFAULT,\n> +\tPATH_FORMAT_RELATIVE,\n> +\tPATH_FORMAT_CANONICAL\n> +};\n> +\n> +enum path_default_type {\n> +\tPATH_DEFAULT_RELATIVE,\n> +\tPATH_DEFAULT_RELATIVE_IF_SHARED,\n> +\tPATH_DEFAULT_CANONICAL,\n> +\tPATH_DEFAULT_UNMODIFIED\n> +};\n> +\n> +void strbuf_add_path(struct strbuf *buf, const char *path, const char *prefix,\n> +\t\t     enum path_format_type format, enum path_default_type def);\n\nThis API is very specific to rev-parse and to me at least it is hard to \nunderstand. I think it would be clearer if we had a single enum \ndescribing the desired format and let the rev-parse code worry about \npassing the appropriate value based on the options the user passed.\n\nenum path_format {\n\tPATH_FORMAT_ABSOLUTE,\n\tPATH_FORMAT_CANONICAL,\n\tPATH_FORMAT_RELATIVE,\n\tPATH_FORMAT_RELATIVE_IF_SHARED\tPATH_FORMAT_UNMODIFIED,\n};\n\nvoid format_path(struct strbuf *buf, const char *path,\n\t\t const char *prefix, enum path_format format);\n\nWe tend to avoid adding \"strbuf_\" to the beginning of functions these \ndays when they're adding things to a strbuf. This function also needs \nsome documentation explaining what the arguments are.\n\nThanks\n\nPhillip\n\n"},{"id":"544502","messageId":"8ebc3d98-40a5-4e99-a205-34254cf5172b@gmail.com","threadId":"65725","inReplyTo":"20260601151950.30686-1-jayatheerthkulkarni2005@gmail.com","subject":"Re: [GSoC][PATCH 0/4] teach git repo info to handle path keys","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2026-06-02T13:03:17Z","receivedAt":"2026-06-02T13:03:23Z","isPatch":true,"body":"On 01/06/2026 16:19, K Jayatheerth wrote:\n> \n> So in patches 3 and 4, we add both `path.<field>.absolute` and\n> `path.<field>.relative` for `gitdir` and `commondir`. Initially,\n> it was proposed by Ayush to use `path.absolute.<field>`, but\n> this would break the lexicographical order of the internal field\n> array. I tweaked it to place the variant at the end as a suffix instead.\n\nI don't understand the comment about breaking the lexicographical order, \nsurely it only breaks if the new items are added out of order? Why can't \nwe have\n\n\tpath.absolute.commondir\n\tpath.absolute.gitdir\n\tpath.relative.commondir\n\tpath.relative.gitdir\n\n?\n\nThanks\n\nPhillip\n\n> There are still a few open questions that should be addressed\n> by the community. I am tagging members who were involved in the\n> previous discussions:\n> \n> Justin Tobler, Lucas Seiki Oshiro, Junio, Phillip Wood,\n> brian m. carlson, and Ayush Jha.\n> \n> Apologies if I missed anyone; I included everyone who reviewed\n> or participated in the discussions of Eslam's and Lucas's\n> patches.\n> \n> Questions:\n> \n> 1. Should there still be a --path-format flag?\n> 2. Should we consider a default option?\n>     Currently we have path.gitdir.absolute; should we consider\n>     an option where a plain path.gitdir returns some default?\n>     If yes:\n>       2.1 Should we keep the default the same as rev-parse? Or\n>           should either relative or absolute be the default?\n>       2.2 When printing using --all, should the default be\n>           printed, or should we print both absolute and\n>           relative?\n> 3. Is printing both absolute and relative in a single call\n>     using --all acceptable?\n>     If no:\n>       3.1 What's a better approach?\n> \n> I have discussed these changes with both Justin and Lucas\n> internally. This series is presented to gather opinions from the\n> wider community before moving forward.\n> \n> K Jayatheerth (4):\n>    path: add strbuf_add_path for formatting paths\n>    rev-parse: use strbuf_add_path for path formatting\n>    repo: add path.gitdir with absolute and relative suffix formatting\n>    repo: add path.commondir with absolute and relative suffix formatting\n> \n>   Documentation/git-repo.adoc |  15 ++++++\n>   builtin/repo.c              |  50 ++++++++++++++++++\n>   builtin/rev-parse.c         | 100 ++++++++----------------------------\n>   path.c                      |  58 +++++++++++++++++++++\n>   path.h                      |  16 ++++++\n>   t/t1900-repo-info.sh        |  32 ++++++++++++\n>   6 files changed, 192 insertions(+), 79 deletions(-)\n> \n\n"},{"id":"544799","messageId":"20260605163012.181089-1-jayatheerthkulkarni2005@gmail.com","threadId":"65725","inReplyTo":"20260601151950.30686-1-jayatheerthkulkarni2005@gmail.com","subject":"[GSoC PATCH v2 0/4] teach git repo info to handle path keys","fromName":"K Jayatheerth","fromEmail":"jayatheerthkulkarni2005@gmail.com","sentAt":"2026-06-05T16:30:08Z","receivedAt":"2026-06-05T16:30:33Z","isPatch":true,"body":"Hi everyone,\n\nThis series teaches `git repo info` to handle `path.*` keys, so\nscripts can easily discover repository paths.\n\nThe commits are divided into 4 parts:\n\n  1. path: extract the path-formatting logic from rev-parse and\n     expose it via path.h with a better naming convention.\n  2. rev-parse: refactor the command to use the exported function\n     and enum.\n  3. repo: introduce path.gitdir with standardized tests and docs.\n  4. repo: introduce path.commondir.\n\nAbout patches 3 and 4:\nIn our last discussion [1], we didn't reach a definitive conclusion\nabout paths in repo info, but based on the feedback, explicitly\noffering both relative and absolute options made the most sense. So,\npatches 3 and 4 add both `path.<field>.absolute` and\n`path.<field>.relative` for `gitdir` and `commondir`.\n\nThere are still a few open questions. Tagging Justin, Lucas, Junio,\nPhillip, brian, and Ayush.\n\nQuestions:\n1. Should there still be a --path-format flag?\n2. Should we consider a default option?\n   Currently we have path.gitdir.absolute. Should we consider an\n   option where a plain `path.gitdir` returns some default?\n   If yes:\n     2.1 Should we keep the default the same as rev-parse? Or should\n         either relative or absolute be the default?\n     2.2 When printing using --all, should the default be printed,\n         or should we print both absolute and relative?\n3. Is printing both absolute and relative in a single call using\n   --all acceptable? If no, what's a better approach?\n\nI have discussed these changes with both Justin and Lucas internally\nand wanted to gather opinions from the wider community before moving\nforward.\n\nChanges since v1:\n\n* Lucas's feedback: Added corner cases covering GIT_COMMON_DIR and\n  GIT_DIR. Parameterized the test helper fields instead of hardcoding\n  them. Also fixed the subject prefix to [GSoC PATCH v2].\n\n* Junio's feedback: Added a clearer description of what the series\n  does up front. I also realized the commit messages for patches 3\n  and 4 explained the \"what\" and not the \"why\", so I have (hopefully)\n  improved them :)\n\n* Phillip's feedback: Changed the helper function name and combined\n  the two enums into one, which made a lot of sense.\n\n  I have also added comments within the path.h files to document\n  the API.\n\n* About lexicographical order: \"Breaking\" wasn't the right term\n  before, but I do believe keeping .absolute and .relative as\n  suffixes is a better choice. I prefer having the two choices\n  side-by-side grouped by entity, rather than a cluster of absolute\n  keys followed by relative ones. Open to hearing if the latter is\n  preferred!\n\nThanks for this round of feedback guys, this has been fruitful!\n\nP.S - I realized that I didn't add the link to Lucas's patch thread\nlast time :) sorry bout that!\n\n[1] https://public-inbox.org/git/041DCF2E-75FB-4B0A-9128-FDBB1A6DAC3C@gmail.com/T/#t\n\nK Jayatheerth (4):\n  path: introduce format_path() for centralized path formatting\n  rev-parse: use format_path for path formatting\n  repo: add path.gitdir with absolute and relative suffix formatting\n  repo: add path.commondir with absolute and relative suffix formatting\n\n Documentation/git-repo.adoc |  15 ++++++\n builtin/repo.c              |  50 +++++++++++++++++\n builtin/rev-parse.c         | 103 ++++++++----------------------------\n path.c                      |  58 ++++++++++++++++++++\n path.h                      |  30 +++++++++++\n t/t1900-repo-info.sh        |  40 ++++++++++++++\n 6 files changed, 216 insertions(+), 80 deletions(-)\n\n-- \n2.54.0\n"},{"id":"544800","messageId":"20260605163012.181089-2-jayatheerthkulkarni2005@gmail.com","threadId":"65725","inReplyTo":"20260605163012.181089-1-jayatheerthkulkarni2005@gmail.com","subject":"[GSoC PATCH v2 1/4] path: introduce format_path() for centralized path formatting","fromName":"K Jayatheerth","fromEmail":"jayatheerthkulkarni2005@gmail.com","sentAt":"2026-06-05T16:30:09Z","receivedAt":"2026-06-05T16:30:58Z","isPatch":true,"body":"The path-formatting logic inside `builtin/rev-parse.c` handles absolute,\ncanonical, and relative formatting rules based on user-supplied options.\nHowever, this logic is tightly coupled to `rev-parse` and writes directly\nto stdout.\n\nTo allow other builtins (such as the upcoming `git repo` path keys) to\nre-use this logic, extract the core path-formatting algorithm into a centralized\nhelper function, `format_path()`, in `path.c`.\n\nExpose a single, streamlined `path_format` enum in `path.h` to let callers\nexplicitly declare their formatting strategy (UNMODIFIED, RELATIVE,\nRELATIVE_IF_SHARED, or CANONICAL). This decouples the core algorithm from\nthe localized fallback mechanics specific to `rev-parse`.\n\nSigned-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>\nMentored-by: Justin Tobler <jltobler@gmail.com>\nMentored-by: Lucas Seiki Oshiro <lucasseikioshiro@gmail.com>\n---\n path.c | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n path.h | 30 ++++++++++++++++++++++++++++++\n 2 files changed, 88 insertions(+)\n\ndiff --git a/path.c b/path.c\nindex d7e17bf174..2fcd24c5eb 100644\n--- a/path.c\n+++ b/path.c\n@@ -1579,6 +1579,64 @@ char *xdg_cache_home(const char *filename)\n \treturn NULL;\n }\n \n+void format_path(struct strbuf *buf, const char *path,\n+\t\t const char *prefix, enum path_format format)\n+{\n+\tif (format == PATH_FORMAT_UNMODIFIED) {\n+\t\tstrbuf_addstr(buf, path);\n+\t\treturn;\n+\t}\n+\n+\tif (format == PATH_FORMAT_RELATIVE) {\n+\t\tstruct strbuf relative_buf = STRBUF_INIT;\n+\t\tstruct strbuf real_path = STRBUF_INIT;\n+\t\tstruct strbuf real_prefix = STRBUF_INIT;\n+\t\tchar *cwd = NULL;\n+\n+\t\t/*\n+\t\t * We don't ever produce a relative path if prefix is NULL,\n+\t\t * so set the prefix to the current directory so that we can\n+\t\t * produce a relative path whenever possible.\n+\t\t */\n+\t\tif (!prefix)\n+\t\t\tprefix = cwd = xgetcwd();\n+\n+\t\tif (!is_absolute_path(path)) {\n+\t\t\tstrbuf_realpath_forgiving(&real_path, path, 1);\n+\t\t\tpath = real_path.buf;\n+\t\t}\n+\t\tif (!is_absolute_path(prefix)) {\n+\t\t\tstrbuf_realpath_forgiving(&real_prefix, prefix, 1);\n+\t\t\tprefix = real_prefix.buf;\n+\t\t}\n+\n+\t\tstrbuf_addstr(buf, relative_path(path, prefix, &relative_buf));\n+\n+\t\tstrbuf_release(&relative_buf);\n+\t\tstrbuf_release(&real_path);\n+\t\tstrbuf_release(&real_prefix);\n+\t\tfree(cwd);\n+\t} else if (format == PATH_FORMAT_RELATIVE_IF_SHARED) {\n+\t\tstruct strbuf relative_buf = STRBUF_INIT;\n+\n+\t\t/*\n+\t\t * If we're using RELATIVE_IF_SHARED mode, then we want an\n+\t\t * absolute path unless the two share a common prefix, so don't\n+\t\t * default the prefix to the current working directory. Doing so\n+\t\t * would cause a relative path to always be produced if possible.\n+\t\t */\n+\t\tstrbuf_addstr(buf, relative_path(path, prefix, &relative_buf));\n+\t\tstrbuf_release(&relative_buf);\n+\t} else if (format == PATH_FORMAT_CANONICAL) {\n+\t\tstruct strbuf canonical_buf = STRBUF_INIT;\n+\n+\t\tstrbuf_realpath_forgiving(&canonical_buf, path, 1);\n+\t\tstrbuf_addbuf(buf, &canonical_buf);\n+\n+\t\tstrbuf_release(&canonical_buf);\n+\t}\n+}\n+\n REPO_GIT_PATH_FUNC(squash_msg, \"SQUASH_MSG\")\n REPO_GIT_PATH_FUNC(merge_msg, \"MERGE_MSG\")\n REPO_GIT_PATH_FUNC(merge_rr, \"MERGE_RR\")\ndiff --git a/path.h b/path.h\nindex 0434ba5e07..a78e0fc141 100644\n--- a/path.h\n+++ b/path.h\n@@ -262,6 +262,36 @@ enum scld_error safe_create_leading_directories_no_share(char *path);\n int safe_create_file_with_leading_directories(struct repository *repo,\n \t\t\t\t\t      const char *path);\n \n+/**\n+ * The formatting strategy to apply when writing a path into a buffer.\n+ */\n+enum path_format {\n+\t/* Output the path exactly as-is without any modifications. */\n+\tPATH_FORMAT_UNMODIFIED,\n+\n+\t/* Output a path relative to the provided directory prefix. */\n+\tPATH_FORMAT_RELATIVE,\n+\n+\t/* Output a relative path only if the path shares a root with the prefix. */\n+\tPATH_FORMAT_RELATIVE_IF_SHARED,\n+\n+\t/* Output a fully resolved, absolute canonical path. */\n+\tPATH_FORMAT_CANONICAL\n+};\n+\n+/**\n+ * Format a path according to the specified formatting strategy and append\n+ * the result to the given strbuf.\n+ *\n+ * `buf`    : The string buffer to append the formatted path to.\n+ * `path`   : The path string that needs to be formatted.\n+ * `prefix` : The directory prefix to calculate relative offsets against.\n+ * Pass NULL to default to the current working directory where applicable.\n+ * `format` : The formatting behavior rule to execute.\n+ */\n+void format_path(struct strbuf *buf, const char *path,\n+\t\t const char *prefix, enum path_format format);\n+\n # ifdef USE_THE_REPOSITORY_VARIABLE\n #  include \"strbuf.h\"\n #  include \"repository.h\"\n-- \n2.54.0\n\n"},{"id":"544801","messageId":"20260605163012.181089-3-jayatheerthkulkarni2005@gmail.com","threadId":"65725","inReplyTo":"20260605163012.181089-1-jayatheerthkulkarni2005@gmail.com","subject":"[GSoC PATCH v2 2/4] rev-parse: use format_path for path formatting","fromName":"K Jayatheerth","fromEmail":"jayatheerthkulkarni2005@gmail.com","sentAt":"2026-06-05T16:30:10Z","receivedAt":"2026-06-05T16:31:23Z","isPatch":true,"body":"Now that the core path-formatting logic has been abstracted into\nformat_path() inside path.c, remove the localized duplicate formatting\nmechanics from builtin/rev-parse.c.\n\nDrop the usage of the old local format_type and default_type enums,\nand update print_path() to act as a light wrapper around the new shared\nengine. Resolve user-provided formatting flags directly within rev-parse\nto pass the final determined path_format to format_path().\n\nSigned-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>\nMentored-by: Justin Tobler <jltobler@gmail.com>\nMentored-by: Lucas Seiki Oshiro <lucasseikioshiro@gmail.com>\n---\n builtin/rev-parse.c | 103 ++++++++++----------------------------------\n 1 file changed, 23 insertions(+), 80 deletions(-)\n\ndiff --git a/builtin/rev-parse.c b/builtin/rev-parse.c\nindex 218b5f34d6..c78bdc04c1 100644\n--- a/builtin/rev-parse.c\n+++ b/builtin/rev-parse.c\n@@ -632,73 +632,16 @@ static void handle_ref_opt(const char *pattern, const char *prefix)\n \tclear_ref_exclusions(&ref_excludes);\n }\n \n-enum format_type {\n-\t/* We would like a relative path. */\n-\tFORMAT_RELATIVE,\n-\t/* We would like a canonical absolute path. */\n-\tFORMAT_CANONICAL,\n-\t/* We would like the default behavior. */\n-\tFORMAT_DEFAULT,\n-};\n-\n-enum default_type {\n-\t/* Our default is a relative path. */\n-\tDEFAULT_RELATIVE,\n-\t/* Our default is a relative path if there's a shared root. */\n-\tDEFAULT_RELATIVE_IF_SHARED,\n-\t/* Our default is a canonical absolute path. */\n-\tDEFAULT_CANONICAL,\n-\t/* Our default is not to modify the item. */\n-\tDEFAULT_UNMODIFIED,\n-};\n-\n-static void print_path(const char *path, const char *prefix, enum format_type format, enum default_type def)\n+static void print_path(const char *path, const char *prefix,\n+\t\t       int arg_path_format, enum path_format def_format)\n {\n-\tchar *cwd = NULL;\n-\t/*\n-\t * We don't ever produce a relative path if prefix is NULL, so set the\n-\t * prefix to the current directory so that we can produce a relative\n-\t * path whenever possible.  If we're using RELATIVE_IF_SHARED mode, then\n-\t * we want an absolute path unless the two share a common prefix, so don't\n-\t * set it in that case, since doing so causes a relative path to always\n-\t * be produced if possible.\n-\t */\n-\tif (!prefix && (format != FORMAT_DEFAULT || def != DEFAULT_RELATIVE_IF_SHARED))\n-\t\tprefix = cwd = xgetcwd();\n-\tif (format == FORMAT_DEFAULT && def == DEFAULT_UNMODIFIED) {\n-\t\tputs(path);\n-\t} else if (format == FORMAT_RELATIVE ||\n-\t\t  (format == FORMAT_DEFAULT && def == DEFAULT_RELATIVE)) {\n-\t\t/*\n-\t\t * In order for relative_path to work as expected, we need to\n-\t\t * make sure that both paths are absolute paths.  If we don't,\n-\t\t * we can end up with an unexpected absolute path that the user\n-\t\t * didn't want.\n-\t\t */\n-\t\tstruct strbuf buf = STRBUF_INIT, realbuf = STRBUF_INIT, prefixbuf = STRBUF_INIT;\n-\t\tif (!is_absolute_path(path)) {\n-\t\t\tstrbuf_realpath_forgiving(&realbuf, path,  1);\n-\t\t\tpath = realbuf.buf;\n-\t\t}\n-\t\tif (!is_absolute_path(prefix)) {\n-\t\t\tstrbuf_realpath_forgiving(&prefixbuf, prefix, 1);\n-\t\t\tprefix = prefixbuf.buf;\n-\t\t}\n-\t\tputs(relative_path(path, prefix, &buf));\n-\t\tstrbuf_release(&buf);\n-\t\tstrbuf_release(&realbuf);\n-\t\tstrbuf_release(&prefixbuf);\n-\t} else if (format == FORMAT_DEFAULT && def == DEFAULT_RELATIVE_IF_SHARED) {\n-\t\tstruct strbuf buf = STRBUF_INIT;\n-\t\tputs(relative_path(path, prefix, &buf));\n-\t\tstrbuf_release(&buf);\n-\t} else {\n-\t\tstruct strbuf buf = STRBUF_INIT;\n-\t\tstrbuf_realpath_forgiving(&buf, path, 1);\n-\t\tputs(buf.buf);\n-\t\tstrbuf_release(&buf);\n-\t}\n-\tfree(cwd);\n+\tstruct strbuf sb = STRBUF_INIT;\n+\tenum path_format fmt = (arg_path_format != -1) ? arg_path_format : def_format;\n+\n+\tformat_path(&sb, path, prefix, fmt);\n+\tputs(sb.buf);\n+\n+\tstrbuf_release(&sb);\n }\n \n int cmd_rev_parse(int argc,\n@@ -717,7 +660,7 @@ int cmd_rev_parse(int argc,\n \tconst char *name = NULL;\n \tstruct strbuf buf = STRBUF_INIT;\n \tint seen_end_of_options = 0;\n-\tenum format_type format = FORMAT_DEFAULT;\n+\tint arg_path_format = -1;\n \n \tshow_usage_if_asked(argc, argv, builtin_rev_parse_usage);\n \n@@ -797,8 +740,8 @@ int cmd_rev_parse(int argc,\n \t\t\t\t\tdie(_(\"--git-path requires an argument\"));\n \t\t\t\tprint_path(repo_git_path_replace(the_repository, &buf,\n \t\t\t\t\t\t\t\t \"%s\", argv[i + 1]), prefix,\n-\t\t\t\t\t\tformat,\n-\t\t\t\t\t\tDEFAULT_RELATIVE_IF_SHARED);\n+\t\t\t\t\t\targ_path_format,\n+\t\t\t\t\t\tPATH_FORMAT_RELATIVE_IF_SHARED);\n \t\t\t\ti++;\n \t\t\t\tcontinue;\n \t\t\t}\n@@ -820,9 +763,9 @@ int cmd_rev_parse(int argc,\n \t\t\t\tif (!arg)\n \t\t\t\t\tdie(_(\"--path-format requires an argument\"));\n \t\t\t\tif (!strcmp(arg, \"absolute\")) {\n-\t\t\t\t\tformat = FORMAT_CANONICAL;\n+\t\t\t\t\targ_path_format = PATH_FORMAT_CANONICAL;\n \t\t\t\t} else if (!strcmp(arg, \"relative\")) {\n-\t\t\t\t\tformat = FORMAT_RELATIVE;\n+\t\t\t\t\targ_path_format = PATH_FORMAT_RELATIVE;\n \t\t\t\t} else {\n \t\t\t\t\tdie(_(\"unknown argument to --path-format: %s\"), arg);\n \t\t\t\t}\n@@ -985,7 +928,7 @@ int cmd_rev_parse(int argc,\n \t\t\tif (!strcmp(arg, \"--show-toplevel\")) {\n \t\t\t\tconst char *work_tree = repo_get_work_tree(the_repository);\n \t\t\t\tif (work_tree)\n-\t\t\t\t\tprint_path(work_tree, prefix, format, DEFAULT_UNMODIFIED);\n+\t\t\t\t\tprint_path(work_tree, prefix, arg_path_format, PATH_FORMAT_UNMODIFIED);\n \t\t\t\telse\n \t\t\t\t\tdie(_(\"this operation must be run in a work tree\"));\n \t\t\t\tcontinue;\n@@ -993,7 +936,7 @@ int cmd_rev_parse(int argc,\n \t\t\tif (!strcmp(arg, \"--show-superproject-working-tree\")) {\n \t\t\t\tstruct strbuf superproject = STRBUF_INIT;\n \t\t\t\tif (get_superproject_working_tree(&superproject))\n-\t\t\t\t\tprint_path(superproject.buf, prefix, format, DEFAULT_UNMODIFIED);\n+\t\t\t\t\tprint_path(superproject.buf, prefix, arg_path_format, PATH_FORMAT_UNMODIFIED);\n \t\t\t\tstrbuf_release(&superproject);\n \t\t\t\tcontinue;\n \t\t\t}\n@@ -1028,18 +971,18 @@ int cmd_rev_parse(int argc,\n \t\t\t\tconst char *gitdir = getenv(GIT_DIR_ENVIRONMENT);\n \t\t\t\tchar *cwd;\n \t\t\t\tint len;\n-\t\t\t\tenum format_type wanted = format;\n+\t\t\t\tint wanted = arg_path_format;\n \t\t\t\tif (arg[2] == 'g') {\t/* --git-dir */\n \t\t\t\t\tif (gitdir) {\n-\t\t\t\t\t\tprint_path(gitdir, prefix, format, DEFAULT_UNMODIFIED);\n+\t\t\t\t\t\tprint_path(gitdir, prefix, arg_path_format, PATH_FORMAT_UNMODIFIED);\n \t\t\t\t\t\tcontinue;\n \t\t\t\t\t}\n \t\t\t\t\tif (!prefix) {\n-\t\t\t\t\t\tprint_path(\".git\", prefix, format, DEFAULT_UNMODIFIED);\n+\t\t\t\t\t\tprint_path(\".git\", prefix, arg_path_format, PATH_FORMAT_UNMODIFIED);\n \t\t\t\t\t\tcontinue;\n \t\t\t\t\t}\n \t\t\t\t} else {\t\t/* --absolute-git-dir */\n-\t\t\t\t\twanted = FORMAT_CANONICAL;\n+\t\t\t\t\twanted = PATH_FORMAT_CANONICAL;\n \t\t\t\t\tif (!gitdir && !prefix)\n \t\t\t\t\t\tgitdir = \".git\";\n \t\t\t\t\tif (gitdir) {\n@@ -1055,11 +998,11 @@ int cmd_rev_parse(int argc,\n \t\t\t\tstrbuf_reset(&buf);\n \t\t\t\tstrbuf_addf(&buf, \"%s%s.git\", cwd, len && cwd[len-1] != '/' ? \"/\" : \"\");\n \t\t\t\tfree(cwd);\n-\t\t\t\tprint_path(buf.buf, prefix, wanted, DEFAULT_CANONICAL);\n+\t\t\t\tprint_path(buf.buf, prefix, wanted, PATH_FORMAT_CANONICAL);\n \t\t\t\tcontinue;\n \t\t\t}\n \t\t\tif (!strcmp(arg, \"--git-common-dir\")) {\n-\t\t\t\tprint_path(repo_get_common_dir(the_repository), prefix, format, DEFAULT_RELATIVE_IF_SHARED);\n+\t\t\t\tprint_path(repo_get_common_dir(the_repository), prefix, arg_path_format, PATH_FORMAT_RELATIVE_IF_SHARED);\n \t\t\t\tcontinue;\n \t\t\t}\n \t\t\tif (!strcmp(arg, \"--is-inside-git-dir\")) {\n@@ -1089,7 +1032,7 @@ int cmd_rev_parse(int argc,\n \t\t\t\tif (the_repository->index->split_index) {\n \t\t\t\t\tconst struct object_id *oid = &the_repository->index->split_index->base_oid;\n \t\t\t\t\tconst char *path = repo_git_path_replace(the_repository, &buf, \"sharedindex.%s\", oid_to_hex(oid));\n-\t\t\t\t\tprint_path(path, prefix, format, DEFAULT_RELATIVE);\n+\t\t\t\t\tprint_path(path, prefix, arg_path_format, PATH_FORMAT_RELATIVE);\n \t\t\t\t}\n \t\t\t\tcontinue;\n \t\t\t}\n-- \n2.54.0\n\n"},{"id":"544803","messageId":"20260605163012.181089-4-jayatheerthkulkarni2005@gmail.com","threadId":"65725","inReplyTo":"20260605163012.181089-1-jayatheerthkulkarni2005@gmail.com","subject":"[GSoC PATCH v2 3/4] repo: add path.gitdir with absolute and relative suffix formatting","fromName":"K Jayatheerth","fromEmail":"jayatheerthkulkarni2005@gmail.com","sentAt":"2026-06-05T16:30:11Z","receivedAt":"2026-06-05T16:31:39Z","isPatch":true,"body":"Scripts often need to locate the `.git` directory. While `git rev-parse`\nprovides this, it relies on command-line flags to dictate path formatting.\n\nIntroduce `path.gitdir.absolute` and `path.gitdir.relative` keys to\n`git repo info`. Exposing separate format-specific keys instead of a base\n`path.gitdir` key avoids default fallbacks and requires callers to state\ntheir format requirements explicitly. Both keys use `format_path()` to\nresolve paths.\n\nTo test these keys, introduce the `test_repo_info_path` helper in\n`t/t1900-repo-info.sh`. The helper evaluates paths dynamically and accepts\nenvironment variable prefixes. This prepares the test suite for future path\nkeys that depend on environment overrides, such as `commondir`.\n\nSigned-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>\nMentored-by: Justin Tobler <jltobler@gmail.com>\nMentored-by: Lucas Seiki Oshiro <lucasseikioshiro@gmail.com>\n---\n Documentation/git-repo.adoc |  6 ++++++\n builtin/repo.c              | 26 ++++++++++++++++++++++++++\n t/t1900-repo-info.sh        | 33 +++++++++++++++++++++++++++++++++\n 3 files changed, 65 insertions(+)\n\ndiff --git a/Documentation/git-repo.adoc b/Documentation/git-repo.adoc\nindex 42262c1983..a0dca7ce88 100644\n--- a/Documentation/git-repo.adoc\n+++ b/Documentation/git-repo.adoc\n@@ -104,6 +104,12 @@ values that they return:\n `object.format`::\n \tThe object format (hash algorithm) used in the repository.\n \n+`path.gitdir.absolute`::\n+\tThe canonical absolute path to the Git repository directory (the `.git` directory).\n+\n+`path.gitdir.relative`::\n+\tThe path to the Git repository directory relative to the current working directory.\n+\n `references.format`::\n \tThe reference storage format. The valid values are:\n +\ndiff --git a/builtin/repo.c b/builtin/repo.c\nindex 71a5c1c29c..6e97f6a0e4 100644\n--- a/builtin/repo.c\n+++ b/builtin/repo.c\n@@ -7,12 +7,14 @@\n #include \"hex.h\"\n #include \"odb.h\"\n #include \"parse-options.h\"\n+#include \"path.h\"\n #include \"path-walk.h\"\n #include \"progress.h\"\n #include \"quote.h\"\n #include \"ref-filter.h\"\n #include \"refs.h\"\n #include \"revision.h\"\n+#include \"setup.h\"\n #include \"strbuf.h\"\n #include \"string-list.h\"\n #include \"shallow.h\"\n@@ -75,6 +77,28 @@ static int get_object_format(struct repository *repo, struct strbuf *buf)\n \treturn 0;\n }\n \n+static int get_path_gitdir_absolute(struct repository *repo, struct strbuf *buf)\n+{\n+\tconst char *git_dir = repo_get_git_dir(repo);\n+\n+\tif (!git_dir)\n+\t\treturn error(_(\"unable to get git directory\"));\n+\n+\tformat_path(buf, git_dir, startup_info->prefix, PATH_FORMAT_CANONICAL);\n+\treturn 0;\n+}\n+\n+static int get_path_gitdir_relative(struct repository *repo, struct strbuf *buf)\n+{\n+\tconst char *git_dir = repo_get_git_dir(repo);\n+\n+\tif (!git_dir)\n+\t\treturn error(_(\"unable to get git directory\"));\n+\n+\tformat_path(buf, git_dir, startup_info->prefix, PATH_FORMAT_RELATIVE);\n+\treturn 0;\n+}\n+\n static int get_references_format(struct repository *repo, struct strbuf *buf)\n {\n \tstrbuf_addstr(buf,\n@@ -87,6 +111,8 @@ static const struct repo_info_field repo_info_field[] = {\n \t{ \"layout.bare\", get_layout_bare },\n \t{ \"layout.shallow\", get_layout_shallow },\n \t{ \"object.format\", get_object_format },\n+\t{ \"path.gitdir.absolute\", get_path_gitdir_absolute },\n+\t{ \"path.gitdir.relative\", get_path_gitdir_relative },\n \t{ \"references.format\", get_references_format },\n };\n \ndiff --git a/t/t1900-repo-info.sh b/t/t1900-repo-info.sh\nindex 39bb77dda0..0660b00bbc 100755\n--- a/t/t1900-repo-info.sh\n+++ b/t/t1900-repo-info.sh\n@@ -155,4 +155,37 @@ test_expect_success 'git repo info -h shows only repo info usage' '\n \ttest_grep ! \"git repo structure\" actual\n '\n \n+test_repo_info_path () {\n+\tfield_name=$1\n+\texpect_absolute_eval=$2\n+\texpect_relative=$3\n+\tenv_prefix=$4\n+\n+\ttest_expect_success \"query individual key: path.$field_name.absolute${env_prefix:+ ($env_prefix)}\" '\n+\t\t(\n+\t\t\tcd test-repo/sub &&\n+\t\t\texpect_absolute=$(eval \"$expect_absolute_eval\") &&\n+\t\t\techo \"path.$field_name.absolute=$expect_absolute\" >expect &&\n+\t\t\teval \"${env_prefix:+$env_prefix }git repo info \\\"path.$field_name.absolute\\\"\" >actual &&\n+\t\t\ttest_cmp expect actual\n+\t\t)\n+\t'\n+\n+\ttest_expect_success \"query individual key: path.$field_name.relative${env_prefix:+ ($env_prefix)}\" '\n+\t\t(\n+\t\t\tcd test-repo/sub &&\n+\t\t\techo \"path.$field_name.relative=$expect_relative\" >expect &&\n+\t\t\teval \"${env_prefix:+$env_prefix }git repo info \\\"path.$field_name.relative\\\"\" >actual &&\n+\t\t\ttest_cmp expect actual\n+\t\t)\n+\t'\n+}\n+\n+test_expect_success 'setup test repository layout for path fields' '\n+\tgit init test-repo &&\n+\tmkdir -p test-repo/sub\n+'\n+\n+test_repo_info_path 'gitdir' 'echo \"$(cd .. && pwd)/.git\"' '../.git'\n+\n test_done\n-- \n2.54.0\n\n"},{"id":"544804","messageId":"20260605163012.181089-5-jayatheerthkulkarni2005@gmail.com","threadId":"65725","inReplyTo":"20260605163012.181089-1-jayatheerthkulkarni2005@gmail.com","subject":"[GSoC PATCH v2 4/4] repo: add path.commondir with absolute and relative suffix formatting","fromName":"K Jayatheerth","fromEmail":"jayatheerthkulkarni2005@gmail.com","sentAt":"2026-06-05T16:30:12Z","receivedAt":"2026-06-05T16:31:58Z","isPatch":true,"body":"In standard Git repositories, the Git directory and the common directory\nare identical. However, in environments utilizing multiple worktrees, the\nlocal working state ($GIT_DIR) is separated from the shared central data\n($GIT_COMMON_DIR). Scripts require a reliable way to discover this shared\npath.\n\nIntroduce `path.commondir.absolute` and `path.commondir.relative` keys\nto `git repo info`. Similar to the `path.gitdir` keys, exposing explicit\nformat variants removes the ambiguity of default fallbacks. Both keys are\nevaluated via the `format_path()` engine.\n\nInsert the new keys into the `repo_info_field` array in lexicographical\norder to maintain the integrity of binary search lookups.\n\nUtilize the parameterized `test_repo_info_path` helper to validate the\nworktree edge cases. This ensures that path resolution correctly respects\n$GIT_COMMON_DIR when defined and safely falls back to $GIT_DIR otherwise.\n\nSigned-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>\nMentored-by: Justin Tobler <jltobler@gmail.com>\nMentored-by: Lucas Seiki Oshiro <lucasseikioshiro@gmail.com>\n---\n Documentation/git-repo.adoc |  9 +++++++++\n builtin/repo.c              | 24 ++++++++++++++++++++++++\n t/t1900-repo-info.sh        |  7 +++++++\n 3 files changed, 40 insertions(+)\n\ndiff --git a/Documentation/git-repo.adoc b/Documentation/git-repo.adoc\nindex a0dca7ce88..ed7d80c690 100644\n--- a/Documentation/git-repo.adoc\n+++ b/Documentation/git-repo.adoc\n@@ -104,6 +104,15 @@ values that they return:\n `object.format`::\n \tThe object format (hash algorithm) used in the repository.\n \n+`path.commondir.absolute`::\n+\tThe canonical absolute path to the Git repository's common\n+\tdirectory (the shared `.git` directory containing objects,\n+\trefs, and global configuration).\n+\n+`path.commondir.relative`::\n+\tThe path to the Git repository's common directory relative to\n+\tthe current working directory.\n+\n `path.gitdir.absolute`::\n \tThe canonical absolute path to the Git repository directory (the `.git` directory).\n \ndiff --git a/builtin/repo.c b/builtin/repo.c\nindex 6e97f6a0e4..27c8caff38 100644\n--- a/builtin/repo.c\n+++ b/builtin/repo.c\n@@ -77,6 +77,28 @@ static int get_object_format(struct repository *repo, struct strbuf *buf)\n \treturn 0;\n }\n \n+static int get_path_commondir_absolute(struct repository *repo, struct strbuf *buf)\n+{\n+\tconst char *common_dir = repo_get_common_dir(repo);\n+\n+\tif (!common_dir)\n+\t\treturn error(_(\"unable to get common directory\"));\n+\n+\tformat_path(buf, common_dir, startup_info->prefix, PATH_FORMAT_CANONICAL);\n+\treturn 0;\n+}\n+\n+static int get_path_commondir_relative(struct repository *repo, struct strbuf *buf)\n+{\n+\tconst char *common_dir = repo_get_common_dir(repo);\n+\n+\tif (!common_dir)\n+\t\treturn error(_(\"unable to get common directory\"));\n+\n+\tformat_path(buf, common_dir, startup_info->prefix, PATH_FORMAT_RELATIVE);\n+\treturn 0;\n+}\n+\n static int get_path_gitdir_absolute(struct repository *repo, struct strbuf *buf)\n {\n \tconst char *git_dir = repo_get_git_dir(repo);\n@@ -111,6 +133,8 @@ static const struct repo_info_field repo_info_field[] = {\n \t{ \"layout.bare\", get_layout_bare },\n \t{ \"layout.shallow\", get_layout_shallow },\n \t{ \"object.format\", get_object_format },\n+\t{ \"path.commondir.absolute\", get_path_commondir_absolute },\n+\t{ \"path.commondir.relative\", get_path_commondir_relative },\n \t{ \"path.gitdir.absolute\", get_path_gitdir_absolute },\n \t{ \"path.gitdir.relative\", get_path_gitdir_relative },\n \t{ \"references.format\", get_references_format },\ndiff --git a/t/t1900-repo-info.sh b/t/t1900-repo-info.sh\nindex 0660b00bbc..21755d9d14 100755\n--- a/t/t1900-repo-info.sh\n+++ b/t/t1900-repo-info.sh\n@@ -186,6 +186,13 @@ test_expect_success 'setup test repository layout for path fields' '\n \tmkdir -p test-repo/sub\n '\n \n+test_expect_success 'setup custom-common for commondir tests' '\n+\tgit init --bare test-repo/custom-common\n+'\n+\n+test_repo_info_path 'commondir' 'echo \"$(cd .. && pwd)/.git\"' '../.git'\n+test_repo_info_path 'commondir' 'echo \"$(cd .. && pwd)/custom-common\"' '../custom-common' 'GIT_COMMON_DIR=\"$(cd .. && pwd)/custom-common\" GIT_DIR=../.git'\n+test_repo_info_path 'commondir' 'echo \"$(cd .. && pwd)/.git\"' '../.git' 'GIT_DIR=../.git'\n test_repo_info_path 'gitdir' 'echo \"$(cd .. && pwd)/.git\"' '../.git'\n \n test_done\n-- \n2.54.0\n\n"},{"id":"544807","messageId":"bd9bc9aa-60b6-4e5d-9ce1-bf38b6032309@app.fastmail.com","threadId":"65725","inReplyTo":"20260605163012.181089-2-jayatheerthkulkarni2005@gmail.com","subject":"Re: [GSoC PATCH v2 1/4] path: introduce format_path() for centralized path formatting","fromName":"Kristoffer Haugsbakk","fromEmail":"kristofferhaugsbakk@fastmail.com","sentAt":"2026-06-05T16:55:56Z","receivedAt":"2026-06-05T16:56:17Z","isPatch":true,"body":"On Fri, Jun 5, 2026, at 18:30, K Jayatheerth wrote:\n> The path-formatting logic inside `builtin/rev-parse.c` handles absolute,\n> canonical, and relative formatting rules based on user-supplied options.\n> However, this logic is tightly coupled to `rev-parse` and writes directly\n> to stdout.\n>\n> To allow other builtins (such as the upcoming `git repo` path keys) to\n> re-use this logic, extract the core path-formatting algorithm into a centralized\n> helper function, `format_path()`, in `path.c`.\n>\n> Expose a single, streamlined `path_format` enum in `path.h` to let callers\n> explicitly declare their formatting strategy (UNMODIFIED, RELATIVE,\n> RELATIVE_IF_SHARED, or CANONICAL). This decouples the core algorithm from\n> the localized fallback mechanics specific to `rev-parse`.\n\nThis looks very well explained to my naive eyes.\n\n>\n> Signed-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>\n> Mentored-by: Justin Tobler <jltobler@gmail.com>\n> Mentored-by: Lucas Seiki Oshiro <lucasseikioshiro@gmail.com>\n\nNitpick. You are supposed to add your `Signed-off-by` at the end. You\nare saying with that line that you are signing off on the changes and\nthe commit message, including the trailers (mentors) you’ve decided to\nadd. Imagine if the maintainer applies this patch and fixes a typo and\nthe commit becomes:\n\n    Mentored-by: Justin Tobler <jltobler@gmail.com>\n    Mentored-by: Lucas Seiki Oshiro <lucasseikioshiro@gmail.com>\n    Signed-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>\n    [jc: typo fix]\n    Signed-off-by: Junio ...\n\nThe chain of custody is then very clear.\n\n> ---\n>[snip]\n"},{"id":"544810","messageId":"A67C8C8B-2600-41D2-9E61-0923BFDDD06B@gmail.com","threadId":"65725","inReplyTo":"20260605163012.181089-1-jayatheerthkulkarni2005@gmail.com","subject":"Re: [GSoC PATCH v2 0/4] teach git repo info to handle path keys","fromName":"Lucas Seiki Oshiro","fromEmail":"lucasseikioshiro@gmail.com","sentAt":"2026-06-05T17:35:37Z","receivedAt":"2026-06-05T17:35:54Z","isPatch":true,"body":"\n> * About lexicographical order: \"Breaking\" wasn't the right term\n>  before, but I do believe keeping .absolute and .relative as\n>  suffixes is a better choice. I prefer having the two choices\n>  side-by-side grouped by entity, rather than a cluster of absolute\n>  keys followed by relative ones. Open to hearing if the latter is\n>  preferred!\n\nI prefer `.(absolute|relative)` at the end. `path.gitdir.relative`\nmeans that we have a collection of paths, in those collections we\nhave gitdir that can be relative or absolute, and we want the\nrelative. `path.relative.gitdir` means that we have a collection\nof relative paths and from those we're picking gitdir. The first\nfeels more natural.\n\n> Thanks for this round of feedback guys, this has been fruitful!\n\nThanks again for your interest in improving `git repo info`!\nI'll review your patchset with more attention later.\n\nPS: this is a nitpick, but it would be really helpful if you provide\na range-diff in the cover letter. Check the usage of `--range-diff`\nin git-format-patch documentation (this flag also works for\ngit-send-email). Or, if you prefer, you can generate it by running\n`git range-diff` and copying the output.\n"},{"id":"544935","messageId":"22E79E77-BCC3-4622-BD39-F4ED7DDA9511@gmail.com","threadId":"65725","inReplyTo":"20260605163012.181089-2-jayatheerthkulkarni2005@gmail.com","subject":"Re: [GSoC PATCH v2 1/4] path: introduce format_path() for centralized path formatting","fromName":"Lucas Seiki Oshiro","fromEmail":"lucasseikioshiro@gmail.com","sentAt":"2026-06-08T15:05:40Z","receivedAt":"2026-06-08T15:05:57Z","isPatch":true,"body":"\n> +++ b/path.h\n> @@ -262,6 +262,36 @@ enum scld_error safe_create_leading_directories_no_share(char *path);\n> int safe_create_file_with_leading_directories(struct repository *repo,\n>      const char *path);\n> \n> +/**\n> + * The formatting strategy to apply when writing a path into a buffer.\n> + */\n> +enum path_format {\n> + /* Output the path exactly as-is without any modifications. */\n> + PATH_FORMAT_UNMODIFIED,\n> +\n> + /* Output a path relative to the provided directory prefix. */\n> + PATH_FORMAT_RELATIVE,\n> +\n> + /* Output a relative path only if the path shares a root with the prefix. */\n> + PATH_FORMAT_RELATIVE_IF_SHARED,\n> +\n> + /* Output a fully resolved, absolute canonical path. */\n> + PATH_FORMAT_CANONICAL\n> +};\n> +\n> +/**\n> + * Format a path according to the specified formatting strategy and append\n> + * the result to the given strbuf.\n> + *\n> + * `buf`    : The string buffer to append the formatted path to.\n> + * `path`   : The path string that needs to be formatted.\n> + * `prefix` : The directory prefix to calculate relative offsets against.\n> + * Pass NULL to default to the current working directory where applicable.\n> + * `format` : The formatting behavior rule to execute.\n> + */\n> +void format_path(struct strbuf *buf, const char *path,\n> + const char *prefix, enum path_format format);\n\nNitpick: the documentation is clear to me, but maybe the function name\n\"format\" and the parameter name \"buf\" can mislead the user to think\nthat it only formats the path without appending to the existing string\nin `buf`. My suggestion is to rename them to something like \n`append_formatted_path` and `dest`, respectively.\n\n"},{"id":"544952","messageId":"aib2DuP7uS3YF5VD@denethor","threadId":"65725","inReplyTo":"20260605163012.181089-2-jayatheerthkulkarni2005@gmail.com","subject":"Re: [GSoC PATCH v2 1/4] path: introduce format_path() for centralized path formatting","fromName":"Justin Tobler","fromEmail":"jltobler@gmail.com","sentAt":"2026-06-08T17:28:49Z","receivedAt":"2026-06-08T17:28:53Z","isPatch":true,"body":"On 26/06/05 10:00PM, K Jayatheerth wrote:\n> The path-formatting logic inside `builtin/rev-parse.c` handles absolute,\n> canonical, and relative formatting rules based on user-supplied options.\n> However, this logic is tightly coupled to `rev-parse` and writes directly\n> to stdout.\n> \n> To allow other builtins (such as the upcoming `git repo` path keys) to\n> re-use this logic, extract the core path-formatting algorithm into a centralized\n> helper function, `format_path()`, in `path.c`.\n\nMakes sense.\n\n> Expose a single, streamlined `path_format` enum in `path.h` to let callers\n> explicitly declare their formatting strategy (UNMODIFIED, RELATIVE,\n> RELATIVE_IF_SHARED, or CANONICAL). This decouples the core algorithm from\n> the localized fallback mechanics specific to `rev-parse`.\n\nOk, so rev-parse has its own logic to select the formatting strategy\nused when printing paths that either relies on what the user provides or\na designated fallback format that is specific to the type of path. Since\nthat is specific to rev-parse, it makes to factor it out of the generic\nhelper function here.\n\n> Signed-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>\n> Mentored-by: Justin Tobler <jltobler@gmail.com>\n> Mentored-by: Lucas Seiki Oshiro <lucasseikioshiro@gmail.com>\n> ---\n>  path.c | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n>  path.h | 30 ++++++++++++++++++++++++++++++\n>  2 files changed, 88 insertions(+)\n> \n> diff --git a/path.c b/path.c\n> index d7e17bf174..2fcd24c5eb 100644\n> --- a/path.c\n> +++ b/path.c\n> @@ -1579,6 +1579,64 @@ char *xdg_cache_home(const char *filename)\n>  \treturn NULL;\n>  }\n>  \n> +void format_path(struct strbuf *buf, const char *path,\n> +\t\t const char *prefix, enum path_format format)\n> +{\n> +\tif (format == PATH_FORMAT_UNMODIFIED) {\n> +\t\tstrbuf_addstr(buf, path);\n> +\t\treturn;\n> +\t}\n> +\n> +\tif (format == PATH_FORMAT_RELATIVE) {\n\nnit: we could just continue the \"else if\" chain here instead of\nrestarting it.\n\n> +\t\tstruct strbuf relative_buf = STRBUF_INIT;\n> +\t\tstruct strbuf real_path = STRBUF_INIT;\n> +\t\tstruct strbuf real_prefix = STRBUF_INIT;\n> +\t\tchar *cwd = NULL;\n> +\n> +\t\t/*\n> +\t\t * We don't ever produce a relative path if prefix is NULL,\n> +\t\t * so set the prefix to the current directory so that we can\n> +\t\t * produce a relative path whenever possible.\n> +\t\t */\n> +\t\tif (!prefix)\n> +\t\t\tprefix = cwd = xgetcwd();\n> +\n> +\t\tif (!is_absolute_path(path)) {\n> +\t\t\tstrbuf_realpath_forgiving(&real_path, path, 1);\n> +\t\t\tpath = real_path.buf;\n> +\t\t}\n> +\t\tif (!is_absolute_path(prefix)) {\n> +\t\t\tstrbuf_realpath_forgiving(&real_prefix, prefix, 1);\n> +\t\t\tprefix = real_prefix.buf;\n> +\t\t}\n> +\n> +\t\tstrbuf_addstr(buf, relative_path(path, prefix, &relative_buf));\n> +\n> +\t\tstrbuf_release(&relative_buf);\n> +\t\tstrbuf_release(&real_path);\n> +\t\tstrbuf_release(&real_prefix);\n> +\t\tfree(cwd);\n> +\t} else if (format == PATH_FORMAT_RELATIVE_IF_SHARED) {\n> +\t\tstruct strbuf relative_buf = STRBUF_INIT;\n> +\n> +\t\t/*\n> +\t\t * If we're using RELATIVE_IF_SHARED mode, then we want an\n> +\t\t * absolute path unless the two share a common prefix, so don't\n> +\t\t * default the prefix to the current working directory. Doing so\n> +\t\t * would cause a relative path to always be produced if possible.\n> +\t\t */\n> +\t\tstrbuf_addstr(buf, relative_path(path, prefix, &relative_buf));\n> +\t\tstrbuf_release(&relative_buf);\n> +\t} else if (format == PATH_FORMAT_CANONICAL) {\n> +\t\tstruct strbuf canonical_buf = STRBUF_INIT;\n> +\n> +\t\tstrbuf_realpath_forgiving(&canonical_buf, path, 1);\n> +\t\tstrbuf_addbuf(buf, &canonical_buf);\n\nDo we need `canonical_buf` here? Can we just add the path to `buf`\ndirectly?\n\n> +\n> +\t\tstrbuf_release(&canonical_buf);\n> +\t}\n> +}\n> +\n>  REPO_GIT_PATH_FUNC(squash_msg, \"SQUASH_MSG\")\n>  REPO_GIT_PATH_FUNC(merge_msg, \"MERGE_MSG\")\n>  REPO_GIT_PATH_FUNC(merge_rr, \"MERGE_RR\")\n> diff --git a/path.h b/path.h\n> index 0434ba5e07..a78e0fc141 100644\n> --- a/path.h\n> +++ b/path.h\n> @@ -262,6 +262,36 @@ enum scld_error safe_create_leading_directories_no_share(char *path);\n>  int safe_create_file_with_leading_directories(struct repository *repo,\n>  \t\t\t\t\t      const char *path);\n>  \n> +/**\n> + * The formatting strategy to apply when writing a path into a buffer.\n> + */\n> +enum path_format {\n> +\t/* Output the path exactly as-is without any modifications. */\n> +\tPATH_FORMAT_UNMODIFIED,\n> +\n> +\t/* Output a path relative to the provided directory prefix. */\n> +\tPATH_FORMAT_RELATIVE,\n> +\n> +\t/* Output a relative path only if the path shares a root with the prefix. */\n> +\tPATH_FORMAT_RELATIVE_IF_SHARED,\n> +\n> +\t/* Output a fully resolved, absolute canonical path. */\n> +\tPATH_FORMAT_CANONICAL\n> +};\n> +\n> +/**\n> + * Format a path according to the specified formatting strategy and append\n> + * the result to the given strbuf.\n> + *\n> + * `buf`    : The string buffer to append the formatted path to.\n> + * `path`   : The path string that needs to be formatted.\n> + * `prefix` : The directory prefix to calculate relative offsets against.\n> + * Pass NULL to default to the current working directory where applicable.\n> + * `format` : The formatting behavior rule to execute.\n> + */\n> +void format_path(struct strbuf *buf, const char *path,\n> +\t\t const char *prefix, enum path_format format);\n> +\n\nOk so in this patch we are just adding the new path formatting\ninterface and will integrate it in the next one. Overall the direction\nof this patch looks good to me.\n\n-Justin\n"},{"id":"544953","messageId":"aib73oQtXYOOQqmW@denethor","threadId":"65725","inReplyTo":"20260605163012.181089-3-jayatheerthkulkarni2005@gmail.com","subject":"Re: [GSoC PATCH v2 2/4] rev-parse: use format_path for path formatting","fromName":"Justin Tobler","fromEmail":"jltobler@gmail.com","sentAt":"2026-06-08T17:54:17Z","receivedAt":"2026-06-08T17:54:22Z","isPatch":true,"body":"On 26/06/05 10:00PM, K Jayatheerth wrote:\n> Now that the core path-formatting logic has been abstracted into\n> format_path() inside path.c, remove the localized duplicate formatting\n> mechanics from builtin/rev-parse.c.\n> \n> Drop the usage of the old local format_type and default_type enums,\n> and update print_path() to act as a light wrapper around the new shared\n> engine. Resolve user-provided formatting flags directly within rev-parse\n> to pass the final determined path_format to format_path().\n\nSo if the format isn't explicitly set by the user via the\n`--path-format` option, the default formatting strategy used depends on\nthe path being printed. IOW, there is no consistent default path format\nhere.\n\n> Signed-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>\n> Mentored-by: Justin Tobler <jltobler@gmail.com>\n> Mentored-by: Lucas Seiki Oshiro <lucasseikioshiro@gmail.com>\n> ---\n>  builtin/rev-parse.c | 103 ++++++++++----------------------------------\n>  1 file changed, 23 insertions(+), 80 deletions(-)\n> \n> diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c\n> index 218b5f34d6..c78bdc04c1 100644\n> --- a/builtin/rev-parse.c\n> +++ b/builtin/rev-parse.c\n> @@ -632,73 +632,16 @@ static void handle_ref_opt(const char *pattern, const char *prefix)\n>  \tclear_ref_exclusions(&ref_excludes);\n>  }\n>  \n> -enum format_type {\n> -\t/* We would like a relative path. */\n> -\tFORMAT_RELATIVE,\n> -\t/* We would like a canonical absolute path. */\n> -\tFORMAT_CANONICAL,\n> -\t/* We would like the default behavior. */\n> -\tFORMAT_DEFAULT,\n> -};\n> -\n> -enum default_type {\n> -\t/* Our default is a relative path. */\n> -\tDEFAULT_RELATIVE,\n> -\t/* Our default is a relative path if there's a shared root. */\n> -\tDEFAULT_RELATIVE_IF_SHARED,\n> -\t/* Our default is a canonical absolute path. */\n> -\tDEFAULT_CANONICAL,\n> -\t/* Our default is not to modify the item. */\n> -\tDEFAULT_UNMODIFIED,\n> -};\n> -\n> -static void print_path(const char *path, const char *prefix, enum format_type format, enum default_type def)\n> +static void print_path(const char *path, const char *prefix,\n> +\t\t       int arg_path_format, enum path_format def_format)\n>  {\n[snip]\n> +\tstruct strbuf sb = STRBUF_INIT;\n> +\tenum path_format fmt = (arg_path_format != -1) ? arg_path_format : def_format;\n\nhmmm, so `arg_path_format` specifies what the user-provided format and\nacts as a sentinel to signal there is no value provided and the fallback\nformat needs to be used. This feels a tad bit awkward to me.\n\nI wonder if we should introduce a PATH_FORMAT_DEFAULT to the\n`path_format` enum that maps to one of the existing enum values in\n`path.c:format_path()`. Here in `print_path()`, we could then intercept\na PATH_FORMAT_DEFAULT value and override it to the specified\n`def_format`. I'm not sure if this is ultimately that much better\nthough.\n\n-Justin\n"},{"id":"544959","messageId":"aicDOlJdUrgMi3sA@denethor","threadId":"65725","inReplyTo":"20260605163012.181089-4-jayatheerthkulkarni2005@gmail.com","subject":"Re: [GSoC PATCH v2 3/4] repo: add path.gitdir with absolute and relative suffix formatting","fromName":"Justin Tobler","fromEmail":"jltobler@gmail.com","sentAt":"2026-06-08T18:50:36Z","receivedAt":"2026-06-08T18:50:40Z","isPatch":true,"body":"On 26/06/05 10:00PM, K Jayatheerth wrote:\n> Scripts often need to locate the `.git` directory. While `git rev-parse`\n> provides this, it relies on command-line flags to dictate path formatting.\n> \n> Introduce `path.gitdir.absolute` and `path.gitdir.relative` keys to\n> `git repo info`. Exposing separate format-specific keys instead of a base\n> `path.gitdir` key avoids default fallbacks and requires callers to state\n> their format requirements explicitly. Both keys use `format_path()` to\n> resolve paths.\n\nMakes sense.\n\n> To test these keys, introduce the `test_repo_info_path` helper in\n> `t/t1900-repo-info.sh`. The helper evaluates paths dynamically and accepts\n> environment variable prefixes. This prepares the test suite for future path\n> keys that depend on environment overrides, such as `commondir`.\n> \n> Signed-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>\n> Mentored-by: Justin Tobler <jltobler@gmail.com>\n> Mentored-by: Lucas Seiki Oshiro <lucasseikioshiro@gmail.com>\n> ---\n>  Documentation/git-repo.adoc |  6 ++++++\n>  builtin/repo.c              | 26 ++++++++++++++++++++++++++\n>  t/t1900-repo-info.sh        | 33 +++++++++++++++++++++++++++++++++\n>  3 files changed, 65 insertions(+)\n> \n> diff --git a/Documentation/git-repo.adoc b/Documentation/git-repo.adoc\n> index 42262c1983..a0dca7ce88 100644\n> --- a/Documentation/git-repo.adoc\n> +++ b/Documentation/git-repo.adoc\n> @@ -104,6 +104,12 @@ values that they return:\n>  `object.format`::\n>  \tThe object format (hash algorithm) used in the repository.\n>  \n> +`path.gitdir.absolute`::\n> +\tThe canonical absolute path to the Git repository directory (the `.git` directory).\n> +\n> +`path.gitdir.relative`::\n> +\tThe path to the Git repository directory relative to the current working directory.\n> +\n>  `references.format`::\n>  \tThe reference storage format. The valid values are:\n>  +\n> diff --git a/builtin/repo.c b/builtin/repo.c\n> index 71a5c1c29c..6e97f6a0e4 100644\n> --- a/builtin/repo.c\n> +++ b/builtin/repo.c\n> @@ -7,12 +7,14 @@\n>  #include \"hex.h\"\n>  #include \"odb.h\"\n>  #include \"parse-options.h\"\n> +#include \"path.h\"\n>  #include \"path-walk.h\"\n>  #include \"progress.h\"\n>  #include \"quote.h\"\n>  #include \"ref-filter.h\"\n>  #include \"refs.h\"\n>  #include \"revision.h\"\n> +#include \"setup.h\"\n>  #include \"strbuf.h\"\n>  #include \"string-list.h\"\n>  #include \"shallow.h\"\n> @@ -75,6 +77,28 @@ static int get_object_format(struct repository *repo, struct strbuf *buf)\n>  \treturn 0;\n>  }\n>  \n> +static int get_path_gitdir_absolute(struct repository *repo, struct strbuf *buf)\n> +{\n> +\tconst char *git_dir = repo_get_git_dir(repo);\n> +\n> +\tif (!git_dir)\n> +\t\treturn error(_(\"unable to get git directory\"));\n> +\n> +\tformat_path(buf, git_dir, startup_info->prefix, PATH_FORMAT_CANONICAL);\n\nFor absolute paths, I don't think we actually need the prefix, but\nproviding it doesn't probably matter too much either way.\n\n> +\treturn 0;\n> +}\n> +\n> +static int get_path_gitdir_relative(struct repository *repo, struct strbuf *buf)\n> +{\n> +\tconst char *git_dir = repo_get_git_dir(repo);\n> +\n> +\tif (!git_dir)\n> +\t\treturn error(_(\"unable to get git directory\"));\n> +\n> +\tformat_path(buf, git_dir, startup_info->prefix, PATH_FORMAT_RELATIVE);\n> +\treturn 0;\n> +}\n\nLooks good.\n\n> +\n>  static int get_references_format(struct repository *repo, struct strbuf *buf)\n>  {\n>  \tstrbuf_addstr(buf,\n> @@ -87,6 +111,8 @@ static const struct repo_info_field repo_info_field[] = {\n>  \t{ \"layout.bare\", get_layout_bare },\n>  \t{ \"layout.shallow\", get_layout_shallow },\n>  \t{ \"object.format\", get_object_format },\n> +\t{ \"path.gitdir.absolute\", get_path_gitdir_absolute },\n> +\t{ \"path.gitdir.relative\", get_path_gitdir_relative },\n>  \t{ \"references.format\", get_references_format },\n>  };\n>  \n> diff --git a/t/t1900-repo-info.sh b/t/t1900-repo-info.sh\n> index 39bb77dda0..0660b00bbc 100755\n> --- a/t/t1900-repo-info.sh\n> +++ b/t/t1900-repo-info.sh\n> @@ -155,4 +155,37 @@ test_expect_success 'git repo info -h shows only repo info usage' '\n>  \ttest_grep ! \"git repo structure\" actual\n>  '\n>  \n> +test_repo_info_path () {\n> +\tfield_name=$1\n> +\texpect_absolute_eval=$2\n> +\texpect_relative=$3\n> +\tenv_prefix=$4\n\nnit: I was a bit uncertain regarding the purpose of env_prefix here.\nSince the env_prefix is not used by any tests yet, I wonder if it we\nshould delay adding it until the next patch. If we want to reduce churn\nthough, I think we could also swap the order of patch 3 and 4.\n\n> +\n> +\ttest_expect_success \"query individual key: path.$field_name.absolute${env_prefix:+ ($env_prefix)}\" '\n> +\t\t(\n> +\t\t\tcd test-repo/sub &&\n> +\t\t\texpect_absolute=$(eval \"$expect_absolute_eval\") &&\n\nCan we just compute `expect_absolute` prior to passing it instead of\nusing eval here?\n\n> +\t\t\techo \"path.$field_name.absolute=$expect_absolute\" >expect &&\n> +\t\t\teval \"${env_prefix:+$env_prefix }git repo info \\\"path.$field_name.absolute\\\"\" >actual &&\n> +\t\t\ttest_cmp expect actual\n> +\t\t)\n> +\t'\n> +\n> +\ttest_expect_success \"query individual key: path.$field_name.relative${env_prefix:+ ($env_prefix)}\" '\n> +\t\t(\n> +\t\t\tcd test-repo/sub &&\n> +\t\t\techo \"path.$field_name.relative=$expect_relative\" >expect &&\n> +\t\t\teval \"${env_prefix:+$env_prefix }git repo info \\\"path.$field_name.relative\\\"\" >actual &&\n> +\t\t\ttest_cmp expect actual\n> +\t\t)\n> +\t'\n> +}\n> +\n> +test_expect_success 'setup test repository layout for path fields' '\n> +\tgit init test-repo &&\n> +\tmkdir -p test-repo/sub\n> +'\n> +\n> +test_repo_info_path 'gitdir' 'echo \"$(cd .. && pwd)/.git\"' '../.git'\n\nhmmm, do we expect the path suffix to be the same between relative and\nabsolute paths for all test cases? If so, we could just have a single\n`expect_path_suffix` argument and let the helper compute the appropriate\nabsolute and relative paths internally.\n\n-Justin\n"},{"id":"544970","messageId":"D14CA105-3690-4BB1-932D-7081B1A1DBFA@gmail.com","threadId":"65725","inReplyTo":"20260605163012.181089-4-jayatheerthkulkarni2005@gmail.com","subject":"Re: [GSoC PATCH v2 3/4] repo: add path.gitdir with absolute and relative suffix formatting","fromName":"Lucas Seiki Oshiro","fromEmail":"lucasseikioshiro@gmail.com","sentAt":"2026-06-08T22:17:30Z","receivedAt":"2026-06-08T22:17:46Z","isPatch":true,"body":"\n> +test_repo_info_path () {\n> + field_name=$1\n> + expect_absolute_eval=$2\n> + expect_relative=$3\n> + env_prefix=$4\n\nThis helper function needs a documentation.\n\n> + test_expect_success \"query individual key: path.$field_name.absolute${env_prefix:+ ($env_prefix)}\" '\n\nThis makes the output polluted. What about changing it by something like:\n\n\ttest_expect_success \"absolute: $label' '...'\n\ttest_expect_success \"relative: $label' '...'\n\nwith a custom label?\n\n> +\n> +test_expect_success 'setup test repository layout for path fields' '\n> + git init test-repo &&\n> + mkdir -p test-repo/sub\n> +'\n\nThe helper function `test_repo_info_path` is relying too much on the\nexistence of the `test-repo`. I think it would be better to add a new\nparameter `repo_name` (or similar) because:\n\n1. You could move this creation to the helper function and\n   you won't need to place the test after that creation\n\n2. You could use different for each (test_repo_info_path call, path format)\n   pair. Currently, if more than one test fails, its result is overwritten\n   and the `expect` and `actual` files from the trash directory will be\n   the last of the broken tests.\n\n3. You won't need to use the hacky 'echo \"$(cd .. && pwd)' \n\nThis applies my suggestions (feel free to use, adapt or discard it):\n\ntest_repo_info_path () {\n        label=$1\n\tfield_name=$2\n\trepo_name=$3\n\texpect_absolute=$4\n\texpect_relative=$5\n\tinit_command=$6\n\n\tabsolute_root=\"$repo_name\"-absolute\n\trelative_root=\"$repo_name\"-relative\n\n\texpect_absolute=\"$PWD\"/\"$absolute_root\"/\"$expect_absolute\"\n\n\ttest_expect_success 'setup test repository layout for path fields' '\n\t\tgit init \"$absolute_root\" &&\n\t\tgit init \"$relative_root\" &&\n\t       \tmkdir -p \"$absolute_root\"/sub \"$relative_root\"/sub\n\t'\n\n\ttest_expect_success \"absolute: $label\" '\n\t\t(\n\t\t\texport ROOT=\"$PWD\"/\"$absolute_root\" &&\n\t\t\tcd \"$absolute_root\"/sub &&\n\t\t\teval \"$init_command\" &&\n\t\t\techo \"path.$field_name.absolute=$expect_absolute\" >expect &&\n\t\t\tgit repo info path.$field_name.absolute >actual &&\n\t\t\ttest_cmp expect actual\n\t\t)\n\t'\n\n\ttest_expect_success \"relative: $label\" '\n\t\t(\n\t\t\texport ROOT=\"$PWD\"/\"$relative_root\" &&\n\t\t\tcd \"$relative_root\"/sub &&\n\t\t\teval \"$init_command\" &&\n\t\t\techo \"path.$field_name.relative=$expect_relative\" >expect &&\n\t\t\tgit repo info path.$field_name.relative >actual &&\n\t\t\ttest_cmp expect actual\n\t\t)\n\t'\n}\n\ntest_repo_info_path 'gitdir' 'gitdir' 'gitdir' '.git' '../.git'\n\n\n"},{"id":"544973","messageId":"xmqqcxy0vevi.fsf@gitster.g","threadId":"65725","inReplyTo":"20260605163012.181089-1-jayatheerthkulkarni2005@gmail.com","subject":"Re: [GSoC PATCH v2 0/4] teach git repo info to handle path keys","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-06-08T22:36:33Z","receivedAt":"2026-06-08T22:36:36Z","isPatch":true,"body":"K Jayatheerth <jayatheerthkulkarni2005@gmail.com> writes:\n\n> 2. Should we consider a default option?\n>    Currently we have path.gitdir.absolute. Should we consider an\n>    option where a plain `path.gitdir` returns some default?\n\nProbably not.  It will invite folks wanting to tweak the default\nbetween absolute and relative, rendering this feature useless for\nrobust scripting.  You do not necessarily want to save typing in\nplumbing interface.  You want to reduce ambiguity by reducing more\nthan one ways to do a thing down to just one way, and as long as\nthat one way is not overly verbose, you are fine.\n"},{"id":"544975","messageId":"D0CF1CC0-DF1A-482A-AD1D-D41680A1672D@gmail.com","threadId":"65725","inReplyTo":"20260605163012.181089-5-jayatheerthkulkarni2005@gmail.com","subject":"Re: [GSoC PATCH v2 4/4] repo: add path.commondir with absolute and relative suffix formatting","fromName":"Lucas Seiki Oshiro","fromEmail":"lucasseikioshiro@gmail.com","sentAt":"2026-06-08T22:40:25Z","receivedAt":"2026-06-08T22:40:41Z","isPatch":true,"body":"This patch looks really straightforward after the previous one.\nI hope the rest of the path.* series will be just like that.\n\n> +test_repo_info_path 'commondir' 'echo \"$(cd .. && pwd)/.git\"' '../.git'\n> +test_repo_info_path 'commondir' 'echo \"$(cd .. && pwd)/custom-common\"' '../custom-common' 'GIT_COMMON_DIR=\"$(cd .. && pwd)/custom-common\" GIT_DIR=../.git'\n> +test_repo_info_path 'commondir' 'echo \"$(cd .. && pwd)/.git\"' '../.git' 'GIT_DIR=../.git'\n\nIf you use the test_repo_info_path that I suggested in the\nother answer, this would be:\n\ntest_repo_info_path 'commondir without env vars' 'commondir' 'common-no-env' \\\n                    '.git' '../.git'\n\ntest_repo_info_path 'commondir with GIT_COMMON_DIR and GIT_DIR' 'commondir' \\\n                    'commondir-envs' 'custom-common' '../custom-common'\\\n\t\t    'export GIT_COMMON_DIR=\"$ROOT/custom-common\" &&\n\t\t     export GIT_DIR=\"../.git\" &&\n\t\t     git init --bare \"$ROOT/custom-common\"\n\t\t    '\n\ntest_repo_info_path 'commondir with only GIT_DIR' 'commondir' \\\n                    'commondir-only-gitdir' '.git' '../.git' 'GIT_DIR=../.git'\n\n"},{"id":"544994","messageId":"CA+rGoLckSpPq5iHUsgUQpXGopS4-9LCh4=pxRt0rkRpdNNoemQ@mail.gmail.com","threadId":"65725","inReplyTo":"bd9bc9aa-60b6-4e5d-9ce1-bf38b6032309@app.fastmail.com","subject":"Re: [GSoC PATCH v2 1/4] path: introduce format_path() for centralized path formatting","fromName":"K Jayatheerth","fromEmail":"jayatheerthkulkarni2005@gmail.com","sentAt":"2026-06-09T02:27:10Z","receivedAt":"2026-06-09T02:27:24Z","isPatch":true,"body":"> > the localized fallback mechanics specific to `rev-parse`.\n>\n> This looks very well explained to my naive eyes.\n\nThank you!\n\n\n> Nitpick. You are supposed to add your `Signed-off-by` at the end. You\n> are saying with that line that you are signing off on the changes and\n> the commit message, including the trailers (mentors) you’ve decided to\n> add. Imagine if the maintainer applies this patch and fixes a typo and\n> the commit becomes:\n>\n>     Mentored-by: Justin Tobler <jltobler@gmail.com>\n>     Mentored-by: Lucas Seiki Oshiro <lucasseikioshiro@gmail.com>\n>     Signed-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>\n>     [jc: typo fix]\n>     Signed-off-by: Junio ...\n>\n> The chain of custody is then very clear.\n>\n> > ---\n> >[snip]\n\nOk, I understand\nthanks for taking time to explain that\n\nWill change it in the next patch series.\n"},{"id":"544995","messageId":"CA+rGoLeRfJXmYRhOTkE=R5JUJBp3LmbACwdt2sezQHqo=1LQtg@mail.gmail.com","threadId":"65725","inReplyTo":"A67C8C8B-2600-41D2-9E61-0923BFDDD06B@gmail.com","subject":"Re: [GSoC PATCH v2 0/4] teach git repo info to handle path keys","fromName":"K Jayatheerth","fromEmail":"jayatheerthkulkarni2005@gmail.com","sentAt":"2026-06-09T02:30:42Z","receivedAt":"2026-06-09T02:30:56Z","isPatch":true,"body":">\n> I prefer `.(absolute|relative)` at the end. `path.gitdir.relative`\n> means that we have a collection of paths, in those collections we\n> have gitdir that can be relative or absolute, and we want the\n> relative. `path.relative.gitdir` means that we have a collection\n> of relative paths and from those we're picking gitdir. The first\n> feels more natural.\n>\n\nYes, I believe the same.\n\n\n> PS: this is a nitpick, but it would be really helpful if you provide\n> a range-diff in the cover letter. Check the usage of `--range-diff`\n> in git-format-patch documentation (this flag also works for\n> git-send-email). Or, if you prefer, you can generate it by running\n> `git range-diff` and copying the output.\n\nAlright, I will add that as well in the next series.\n\nThank you!\n"},{"id":"545002","messageId":"CA+rGoLdJY7d=331c_DyG9WXJbFxmaabag3yJpbts22uFWTjYPg@mail.gmail.com","threadId":"65725","inReplyTo":"22E79E77-BCC3-4622-BD39-F4ED7DDA9511@gmail.com","subject":"Re: [GSoC PATCH v2 1/4] path: introduce format_path() for centralized path formatting","fromName":"K Jayatheerth","fromEmail":"jayatheerthkulkarni2005@gmail.com","sentAt":"2026-06-09T02:47:11Z","receivedAt":"2026-06-09T02:47:24Z","isPatch":true,"body":">\n> Nitpick: the documentation is clear to me, but maybe the function name\n> \"format\" and the parameter name \"buf\" can mislead the user to think\n> that it only formats the path without appending to the existing string\n> in `buf`. My suggestion is to rename them to something like\n> `append_formatted_path` and `dest`, respectively.\n>\n\nOk, that's a good point!\nI will add this in the next series!\n\n\n>\n> > +test_repo_info_path () {\n> > + field_name=$1\n> > + expect_absolute_eval=$2\n> > + expect_relative=$3\n> > + env_prefix=$4\n>\n> This helper function needs a documentation.\n>\n\nAlright, I will add that.\n\n> > + test_expect_success \"query individual key: path.$field_name.absolute${env_prefix:+ ($env_prefix)}\" '\n>\n> This makes the output polluted. What about changing it by something like:\n>\n>         test_expect_success \"absolute: $label' '...'\n>         test_expect_success \"relative: $label' '...'\n>\n> with a custom label?\n>\n\nAhh, interesting.\nI agree, I will look into this!\n\n> > +\n> > +test_expect_success 'setup test repository layout for path fields' '\n> > + git init test-repo &&\n> > + mkdir -p test-repo/sub\n> > +'\n>\n> The helper function `test_repo_info_path` is relying too much on the\n> existence of the `test-repo`. I think it would be better to add a new\n> parameter `repo_name` (or similar) because\n>\n> 1. You could move this creation to the helper function and\n>    you won't need to place the test after that creation\n>\n> 2. You could use different for each (test_repo_info_path call, path format)\n>    pair. Currently, if more than one test fails, its result is overwritten\n>    and the `expect` and `actual` files from the trash directory will be\n>    the last of the broken tests.\n>\n> 3. You won't need to use the hacky 'echo \"$(cd .. && pwd)'\n>\n> This applies my suggestions (feel free to use, adapt or discard it):\n>\n\nThanks!\nThat is helpful.\n\nRegards,\n- K Jayatheerth\n"},{"id":"545008","messageId":"CA+rGoLdpkuigWXqNSk3bS7-uhtzCizkPx2GGtNaTyy5J1SF7Rg@mail.gmail.com","threadId":"65725","inReplyTo":"aicDOlJdUrgMi3sA@denethor","subject":"Re: [GSoC PATCH v2 3/4] repo: add path.gitdir with absolute and relative suffix formatting","fromName":"K Jayatheerth","fromEmail":"jayatheerthkulkarni2005@gmail.com","sentAt":"2026-06-09T04:41:38Z","receivedAt":"2026-06-09T04:41:51Z","isPatch":true,"body":"> > +     if (format == PATH_FORMAT_UNMODIFIED) {\n> > +             strbuf_addstr(buf, path);\n> > +             return;\n> > +     }\n> > +\n> > +     if (format == PATH_FORMAT_RELATIVE) {\n>\n> nit: we could just continue the \"else if\" chain here instead of\n> restarting it.\n\nAhh, good catch!\nTrue we can.\n\n\n> > +             strbuf_realpath_forgiving(&canonical_buf, path, 1);\n> > +             strbuf_addbuf(buf, &canonical_buf);\n>\n> Do we need `canonical_buf` here? Can we just add the path to `buf`\n> directly?\n>\n\ncanonical_buf is necessary if I my understanding is correct.\nWe can't pass buf directly to strbuf_realpath_forgiving() because it\nresets its destination buffer before writing.\nSince format_path() has append semantics, doing so would clobber any\nexisting content in buf.\nThe intermediate canonical_buf is needed to keep that safe.\n\n\n> > +void format_path(struct strbuf *buf, const char *path,\n> > +              const char *prefix, enum path_format format);\n> > +\n>\n> Ok so in this patch we are just adding the new path formatting\n> interface and will integrate it in the next one. Overall the direction\n> of this patch looks good to me.\n\nYup, that's correct!\n\n\n> > and update print_path() to act as a light wrapper around the new shared\n> > engine. Resolve user-provided formatting flags directly within rev-parse\n> > to pass the final determined path_format to format_path().\n>\n> So if the format isn't explicitly set by the user via the\n> `--path-format` option, the default formatting strategy used depends on\n> the path being printed. IOW, there is no consistent default path format\n> here.\n>\n\nYes, that's correct.\n\n\n\n> > +     struct strbuf sb = STRBUF_INIT;\n> > +     enum path_format fmt = (arg_path_format != -1) ? arg_path_format : def_format;\n>\n> hmmm, so `arg_path_format` specifies what the user-provided format and\n> acts as a sentinel to signal there is no value provided and the fallback\n> format needs to be used. This feels a tad bit awkward to me.\n>\n> I wonder if we should introduce a PATH_FORMAT_DEFAULT to the\n> `path_format` enum that maps to one of the existing enum values in\n> `path.c:format_path()`. Here in `print_path()`, we could then intercept\n> a PATH_FORMAT_DEFAULT value and override it to the specified\n> `def_format`. I'm not sure if this is ultimately that much better\n> though.\n>\n> -Justin\n\nYou're right that the -1 is awkward\nit forces arg_path_format to be an int rather than the enum type\nitself, which loses type safety.\n\nPATH_FORMAT_DEFAULT is cleaner in that regard, but it pushes the \"what\ndoes default mean?\" question into format_path()\nwhich currently has no notion of a fallback.\nSince the fallback is call-site specific (each path type in rev-parse\nhas its own default),\nI'd rather keep that logic in print_path() where the context lives.\n\nA middle ground would be adding PATH_FORMAT_DEFAULT to the enum but\nnot handling it in format_path().\n\n---\nenum path_format_type format = PATH_FORMAT_DEFAULT;\n\n/* ... */\n\nstatic void print_path(const char *path, const char *prefix,\n                       enum path_format_type format,\n                       enum path_format_type def_format)\n{\n        struct strbuf sb = STRBUF_INIT;\n        enum path_format_type fmt =\n            (format == PATH_FORMAT_DEFAULT) ? def_format : format;\n\n        format_path(&sb, path, prefix, fmt);\n        puts(sb.buf);\n        strbuf_release(&sb);\n}\n---\n\n\n\n> > +     format_path(buf, git_dir, startup_info->prefix, PATH_FORMAT_CANONICAL);\n>\n> For absolute paths, I don't think we actually need the prefix, but\n> providing it doesn't probably matter too much either way.\n>\n\nYeah, true. Since the relative had the prefix\nI just added the prefix here too.\nIt is consistent.\n\n\n\n> >\n> > +test_repo_info_path () {\n> > +     field_name=$1\n> > +     expect_absolute_eval=$2\n> > +     expect_relative=$3\n> > +     env_prefix=$4\n>\n> nit: I was a bit uncertain regarding the purpose of env_prefix here.\n> Since the env_prefix is not used by any tests yet, I wonder if it we\n> should delay adding it until the next patch. If we want to reduce churn\n> though, I think we could also swap the order of patch 3 and 4.\n>\n\nGood point\nI will actually swap 3 and 4\nIt is just better tbh.\n\n       (\n> > +                     cd test-repo/sub &&\n> > +                     expect_absolute=$(eval \"$expect_absolute_eval\") &&\n>\n> Can we just compute `expect_absolute` prior to passing it instead of\n> using eval here?\n>\n\nYes, I plan to follow Lucas's suggestion from his review.\npassing a repo_name parameter and capturing $PWD before the cd to\nconstruct the absolute path at helper-call time.\nThat avoids eval entirely and also addresses his other concerns about\ntest isolation. Will fix in v3.\n\n> > +test_expect_success 'setup test repository layout for path fields' '\n> > +     git init test-repo &&\n> > +     mkdir -p test-repo/sub\n> > +'\n> > +\n> > +test_repo_info_path 'gitdir' 'echo \"$(cd .. && pwd)/.git\"' '../.git'\n>\n> hmmm, do we expect the path suffix to be the same between relative and\n> absolute paths for all test cases? If so, we could just have a single\n> `expect_path_suffix` argument and let the helper compute the appropriate\n> absolute and relative paths internally.\n>\n\nYes it is consistent between absolute and relative.\nThis is a good suggestion.\nAlso aligns with what Lucas said.\n\n\nThank you,\nThis will help building v3 much smoother.\n\nRegards,\n- K Jayatheerth\n"},{"id":"545009","messageId":"CA+rGoLf39iQH9X-xKW7HeTS3sMv-N-QzGiqm0Y=RYGOAqDcaoA@mail.gmail.com","threadId":"65725","inReplyTo":"xmqqcxy0vevi.fsf@gitster.g","subject":"Re: [GSoC PATCH v2 0/4] teach git repo info to handle path keys","fromName":"K Jayatheerth","fromEmail":"jayatheerthkulkarni2005@gmail.com","sentAt":"2026-06-09T05:00:19Z","receivedAt":"2026-06-09T05:00:33Z","isPatch":true,"body":"On Tue, Jun 9, 2026 at 4:06 AM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> K Jayatheerth <jayatheerthkulkarni2005@gmail.com> writes:\n>\n> > 2. Should we consider a default option?\n> >    Currently we have path.gitdir.absolute. Should we consider an\n> >    option where a plain `path.gitdir` returns some default?\n>\n> Probably not.  It will invite folks wanting to tweak the default\n> between absolute and relative, rendering this feature useless for\n> robust scripting.  You do not necessarily want to save typing in\n> plumbing interface.  You want to reduce ambiguity by reducing more\n> than one ways to do a thing down to just one way, and as long as\n> that one way is not overly verbose, you are fine.\n\nMakes sense.\nExplicit keys like path.gitdir.absolute and path.gitdir.relative are\nunambiguous for scripting,\nand saving a few keystrokes isn't worth introducing a configurable\ndefault that would make the output unpredictable.\nI'll actually drop question 2 from the open questions in v3's cover letter.\n\nThanks!\n\nRegards,\n- K Jayatheerth\n"},{"id":"545077","messageId":"aighAZXRtLaz6sg8@denethor","threadId":"65725","inReplyTo":"CA+rGoLdpkuigWXqNSk3bS7-uhtzCizkPx2GGtNaTyy5J1SF7Rg@mail.gmail.com","subject":"Re: [GSoC PATCH v2 3/4] repo: add path.gitdir with absolute and relative suffix formatting","fromName":"Justin Tobler","fromEmail":"jltobler@gmail.com","sentAt":"2026-06-09T14:31:39Z","receivedAt":"2026-06-09T14:31:44Z","isPatch":true,"body":"On 26/06/09 10:11AM, K Jayatheerth wrote:\n> > > +     struct strbuf sb = STRBUF_INIT;\n> > > +     enum path_format fmt = (arg_path_format != -1) ? arg_path_format : def_format;\n> >\n> > hmmm, so `arg_path_format` specifies what the user-provided format and\n> > acts as a sentinel to signal there is no value provided and the fallback\n> > format needs to be used. This feels a tad bit awkward to me.\n> >\n> > I wonder if we should introduce a PATH_FORMAT_DEFAULT to the\n> > `path_format` enum that maps to one of the existing enum values in\n> > `path.c:format_path()`. Here in `print_path()`, we could then intercept\n> > a PATH_FORMAT_DEFAULT value and override it to the specified\n> > `def_format`. I'm not sure if this is ultimately that much better\n> > though.\n> \n> You're right that the -1 is awkward\n> it forces arg_path_format to be an int rather than the enum type\n> itself, which loses type safety.\n> \n> PATH_FORMAT_DEFAULT is cleaner in that regard, but it pushes the \"what\n> does default mean?\" question into format_path()\n> which currently has no notion of a fallback.\n> Since the fallback is call-site specific (each path type in rev-parse\n> has its own default),\n> I'd rather keep that logic in print_path() where the context lives.\n> \n> A middle ground would be adding PATH_FORMAT_DEFAULT to the enum but\n> not handling it in format_path().\n> \n> ---\n> enum path_format_type format = PATH_FORMAT_DEFAULT;\n> \n> /* ... */\n> \n> static void print_path(const char *path, const char *prefix,\n>                        enum path_format_type format,\n>                        enum path_format_type def_format)\n> {\n>         struct strbuf sb = STRBUF_INIT;\n>         enum path_format_type fmt =\n>             (format == PATH_FORMAT_DEFAULT) ? def_format : format;\n> \n>         format_path(&sb, path, prefix, fmt);\n>         puts(sb.buf);\n>         strbuf_release(&sb);\n> }\n> ---\n\nIntercepting PATH_FORMAT_DEFAULT in print_path() and overriding it to\nthe appropriate default needed for the specific path printed by\ngit-rev-parse(1), as shown above, seems reasonable to me.\n\nBut I do think that PATH_FORMAT_DEFAULT should have an actual default in\nformat_path(). Otherwise we would have an enum value that requires\ncallers to explicitly handle prior to invoking format_path() which would\nalso be rather awkward. IMO, it probably wouldn't be a big deal to just\nsay PATH_FORMAT_DEFAULT is treated as PATH_FORMAT_UNMODIFIED when passed\nto format_path() and document it. In practice, our rev-parse use-case\nwould always replace PATH_FORMAT_DEFAULT with the appropriate value\nprior to invoking format_path().\n\n-Justin\n"},{"id":"545132","messageId":"CA+rGoLcz_U70XTQn1_D6U7Qjfmdz7rbpKZxds2zBhPr2hYxw6g@mail.gmail.com","threadId":"65725","inReplyTo":"aighAZXRtLaz6sg8@denethor","subject":"Re: [GSoC PATCH v2 3/4] repo: add path.gitdir with absolute and relative suffix formatting","fromName":"K Jayatheerth","fromEmail":"jayatheerthkulkarni2005@gmail.com","sentAt":"2026-06-10T12:11:35Z","receivedAt":"2026-06-10T12:11:50Z","isPatch":true,"body":">\n> Intercepting PATH_FORMAT_DEFAULT in print_path() and overriding it to\n> the appropriate default needed for the specific path printed by\n> git-rev-parse(1), as shown above, seems reasonable to me.\n>\n> But I do think that PATH_FORMAT_DEFAULT should have an actual default in\n> format_path(). Otherwise we would have an enum value that requires\n> callers to explicitly handle prior to invoking format_path() which would\n> also be rather awkward. IMO, it probably wouldn't be a big deal to just\n> say PATH_FORMAT_DEFAULT is treated as PATH_FORMAT_UNMODIFIED when passed\n> to format_path() and document it. In practice, our rev-parse use-case\n> would always replace PATH_FORMAT_DEFAULT with the appropriate value\n> prior to invoking format_path().\n>\n\nMakes sense. I'll have format_path() treat PATH_FORMAT_DEFAULT as\nPATH_FORMAT_UNMODIFIED\ninternally and document it, so there's no unhandled enum value.\nprint_path() in rev-parse will still intercept PATH_FORMAT_DEFAULT\nand replace it with the path-specific default before calling format_path(),\nso in practice it won't fall through to the unmodified behavior.\nAlmost done with v3 this was the only one left.\n\nThanks for taking time!\n"},{"id":"545136","messageId":"F7C0E6A1-577B-4516-9B07-DD6EFB145E65@gmail.com","threadId":"65725","inReplyTo":"CA+rGoLf39iQH9X-xKW7HeTS3sMv-N-QzGiqm0Y=RYGOAqDcaoA@mail.gmail.com","subject":"Re: [GSoC PATCH v2 0/4] teach git repo info to handle path keys","fromName":"Lucas Seiki Oshiro","fromEmail":"lucasseikioshiro@gmail.com","sentAt":"2026-06-10T12:42:27Z","receivedAt":"2026-06-10T12:42:43Z","isPatch":true,"body":"Junio has a good point here.\n\nThis is a plumbing command and we should design it\nfor machines instead of humans.\n"},{"id":"545407","messageId":"20260612182847.562816-1-jayatheerthkulkarni2005@gmail.com","threadId":"65725","inReplyTo":"20260601151950.30686-1-jayatheerthkulkarni2005@gmail.com","subject":"[GSoC Patch v3 0/4] teach git repo info to handle path keys","fromName":"K Jayatheerth","fromEmail":"jayatheerthkulkarni2005@gmail.com","sentAt":"2026-06-12T18:28:43Z","receivedAt":"2026-06-12T18:29:57Z","isPatch":true,"body":"Hi!\n\nThis series teaches `git repo info` to handle `path.*`\nkeys, allowing scripts to reliably discover core\nrepository paths without resorting to `git rev-parse`.\n\nThe patches are structured as follows:\n\n1. path: Extract the localized path-formatting logic\n   out of `rev-parse` and expose it globally via\n   `path.h` using clear append semantics.\n\n2. rev-parse: Refactor the command to leverage the\n   newly shared path engine.\n\n3. repo: Introduce `path.commondir.absolute` and\n   `path.commondir.relative` alongside a robust,\n   isolated test helper.\n\n4. repo: Introduce `path.gitdir.absolute` and\n   `path.gitdir.relative` using the same standardized\n   formatting rules.\n\nSince all the questions were answered\nI have removed them from this cover letter.\n\nChanges since v2:\n\n* Renamed the shared helper from `format_path()` to\n  `append_formatted_path()`, and renamed the `buf`\n  parameter to `dest` to better reflect its\n  append-style behavior (Lucas, Phillip).\n\n* Introduced a dedicated `PATH_FORMAT_DEFAULT`\n  enumerator. This removes the awkward `-1`\n  sentinel in `print_path()` while preserving enum\n  type safety (Phillip, Justin).\n\n* Handled `PATH_FORMAT_DEFAULT` as\n  `PATH_FORMAT_UNMODIFIED` inside\n  `append_formatted_path()`, while intercepting it\n  in `print_path()` for rev-parse-specific fallback\n  behavior (Justin).\n\n* Replaced the `else if` chain in `append_formatted_path()` with a \n  clean `switch` statement setup (Justin, Lucas).\n\n* Reordered the `commondir` and `gitdir` patches so\n  the parameterized test helper\n  (`test_repo_info_path`) is introduced first,\n  establishing the isolated test infrastructure up\n  front (Justin).\n\n* Reworked the test helper to accept a label,\n  `repo_name`, and `path_suffix`; moved repository\n  creation into the helper for isolation; and\n  replaced `eval` by capturing `$PWD` before\n  changing directories (Justin, Lucas).\n\n* Corrected trailer ordering so `Signed-off-by`\n  appears after `Mentored-by` (Kristoffer).\n\n* Cleaned up minor trailing whitespace issues across\n  the patch array declarations.\n\nTagging Justin Tobler, Lucas Seiki Oshiro, Junio,\nPhillip Wood, brian m. carlson, and Ayush Jha.\n\nThanks for taking another look!\n\n\nK Jayatheerth (4):\n  path: introduce append_formatted_path() for shared path formatting\n  rev-parse: use append_formatted_path() for path formatting\n  repo: add path.commondir with absolute and relative suffix formatting\n  repo: add path.gitdir with absolute and relative suffix formatting\n\n Documentation/git-repo.adoc |  15 ++++++\n builtin/repo.c              |  50 +++++++++++++++++\n builtin/rev-parse.c         | 103 ++++++++----------------------------\n path.c                      |  70 ++++++++++++++++++++++++\n path.h                      |  36 +++++++++++++\n t/t1900-repo-info.sh        |  68 ++++++++++++++++++++++++\n 6 files changed, 262 insertions(+), 80 deletions(-)\n\nRange-diff against v2:\n1:  c1f1e87fe9 ! 1:  a396b4f8e6 path: introduce format_path() for centralized path formatting\n    @@ Metadata\n     Author: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>\n     \n      ## Commit message ##\n    -    path: introduce format_path() for centralized path formatting\n    +    path: introduce append_formatted_path() for shared path formatting\n     \n    -    The path-formatting logic inside `builtin/rev-parse.c` handles absolute,\n    -    canonical, and relative formatting rules based on user-supplied options.\n    -    However, this logic is tightly coupled to `rev-parse` and writes directly\n    -    to stdout.\n    +    The path-formatting logic in builtin/rev-parse.c is tightly coupled\n    +    to that command and writes directly to stdout, making it impossible\n    +    for other builtins to reuse.\n     \n    -    To allow other builtins (such as the upcoming `git repo` path keys) to\n    -    re-use this logic, extract the core path-formatting algorithm into a centralized\n    -    helper function, `format_path()`, in `path.c`.\n    -\n    -    Expose a single, streamlined `path_format` enum in `path.h` to let callers\n    -    explicitly declare their formatting strategy (UNMODIFIED, RELATIVE,\n    -    RELATIVE_IF_SHARED, or CANONICAL). This decouples the core algorithm from\n    -    the localized fallback mechanics specific to `rev-parse`.\n    +    Extract the core algorithm into append_formatted_path() in path.c\n    +    and expose a path_format enum in path.h so that any builtin can\n    +    format paths consistently without duplicating logic.\n     \n         Mentored-by: Justin Tobler <jltobler@gmail.com>\n         Mentored-by: Lucas Seiki Oshiro <lucasseikioshiro@gmail.com>\n    @@ path.c: char *xdg_cache_home(const char *filename)\n      \treturn NULL;\n      }\n      \n    -+void format_path(struct strbuf *buf, const char *path,\n    -+\t\t const char *prefix, enum path_format format)\n    ++void append_formatted_path(struct strbuf *dest, const char *path,\n    ++\t\t\t   const char *prefix, enum path_format format)\n     +{\n    -+\tif (format == PATH_FORMAT_UNMODIFIED) {\n    -+\t\tstrbuf_addstr(buf, path);\n    -+\t\treturn;\n    -+\t}\n    ++\tswitch (format) {\n    ++\tcase PATH_FORMAT_DEFAULT:\n    ++\tcase PATH_FORMAT_UNMODIFIED:\n    ++\t\tstrbuf_addstr(dest, path);\n    ++\t\tbreak;\n     +\n    -+\tif (format == PATH_FORMAT_RELATIVE) {\n    ++\tcase PATH_FORMAT_RELATIVE: {\n     +\t\tstruct strbuf relative_buf = STRBUF_INIT;\n     +\t\tstruct strbuf real_path = STRBUF_INIT;\n     +\t\tstruct strbuf real_prefix = STRBUF_INIT;\n    @@ path.c: char *xdg_cache_home(const char *filename)\n     +\t\t\tprefix = real_prefix.buf;\n     +\t\t}\n     +\n    -+\t\tstrbuf_addstr(buf, relative_path(path, prefix, &relative_buf));\n    ++\t\tstrbuf_addstr(dest, relative_path(path, prefix, &relative_buf));\n     +\n     +\t\tstrbuf_release(&relative_buf);\n     +\t\tstrbuf_release(&real_path);\n     +\t\tstrbuf_release(&real_prefix);\n     +\t\tfree(cwd);\n    -+\t} else if (format == PATH_FORMAT_RELATIVE_IF_SHARED) {\n    ++\t\tbreak;\n    ++\t}\n    ++\n    ++\tcase PATH_FORMAT_RELATIVE_IF_SHARED: {\n     +\t\tstruct strbuf relative_buf = STRBUF_INIT;\n     +\n     +\t\t/*\n    @@ path.c: char *xdg_cache_home(const char *filename)\n     +\t\t * default the prefix to the current working directory. Doing so\n     +\t\t * would cause a relative path to always be produced if possible.\n     +\t\t */\n    -+\t\tstrbuf_addstr(buf, relative_path(path, prefix, &relative_buf));\n    ++\t\tstrbuf_addstr(dest, relative_path(path, prefix, &relative_buf));\n     +\t\tstrbuf_release(&relative_buf);\n    -+\t} else if (format == PATH_FORMAT_CANONICAL) {\n    ++\t\tbreak;\n    ++\t}\n    ++\n    ++\tcase PATH_FORMAT_CANONICAL: {\n     +\t\tstruct strbuf canonical_buf = STRBUF_INIT;\n     +\n     +\t\tstrbuf_realpath_forgiving(&canonical_buf, path, 1);\n    -+\t\tstrbuf_addbuf(buf, &canonical_buf);\n    ++\t\tstrbuf_addbuf(dest, &canonical_buf);\n     +\n     +\t\tstrbuf_release(&canonical_buf);\n    ++\t\tbreak;\n    ++\t}\n    ++\n    ++\tdefault:\n    ++\t\tBUG(\"unknown path_format value %d\", format);\n     +\t}\n     +}\n     +\n    @@ path.h: enum scld_error safe_create_leading_directories_no_share(char *path);\n     + * The formatting strategy to apply when writing a path into a buffer.\n     + */\n     +enum path_format {\n    ++\t/*\n    ++\t * Represents the default formatting behavior. Treated as\n    ++\t * PATH_FORMAT_UNMODIFIED by append_formatted_path().\n    ++\t */\n    ++\tPATH_FORMAT_DEFAULT,\n    ++\n     +\t/* Output the path exactly as-is without any modifications. */\n     +\tPATH_FORMAT_UNMODIFIED,\n     +\n    @@ path.h: enum scld_error safe_create_leading_directories_no_share(char *path);\n     + * Format a path according to the specified formatting strategy and append\n     + * the result to the given strbuf.\n     + *\n    -+ * `buf`    : The string buffer to append the formatted path to.\n    ++ * `dest`   : The string buffer to append the formatted path to.\n     + * `path`   : The path string that needs to be formatted.\n     + * `prefix` : The directory prefix to calculate relative offsets against.\n     + * Pass NULL to default to the current working directory where applicable.\n     + * `format` : The formatting behavior rule to execute.\n     + */\n    -+void format_path(struct strbuf *buf, const char *path,\n    -+\t\t const char *prefix, enum path_format format);\n    ++void append_formatted_path(struct strbuf *dest, const char *path,\n    ++\t\t\t   const char *prefix, enum path_format format);\n     +\n      # ifdef USE_THE_REPOSITORY_VARIABLE\n      #  include \"strbuf.h\"\n2:  2cc3e671af ! 2:  16198f96d1 rev-parse: use format_path for path formatting\n    @@ Metadata\n     Author: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>\n     \n      ## Commit message ##\n    -    rev-parse: use format_path for path formatting\n    +    rev-parse: use append_formatted_path() for path formatting\n     \n    -    Now that the core path-formatting logic has been abstracted into\n    -    format_path() inside path.c, remove the localized duplicate formatting\n    -    mechanics from builtin/rev-parse.c.\n    +    Now that path formatting logic lives in a shared helper, keeping a\n    +    duplicate implementation in rev-parse is unnecessary and risks the\n    +    two diverging over time.\n     \n    -    Drop the usage of the old local format_type and default_type enums,\n    -    and update print_path() to act as a light wrapper around the new shared\n    -    engine. Resolve user-provided formatting flags directly within rev-parse\n    -    to pass the final determined path_format to format_path().\n    +    Replace the local format_type and default_type enums and the\n    +    hand-rolled formatting logic with a call to append_formatted_path().\n    +    Introduce PATH_FORMAT_DEFAULT as the initial value of arg_path_format\n    +    so that per-path fallback behavior is resolved in print_path() rather\n    +    than leaked into the shared helper.\n     \n         Mentored-by: Justin Tobler <jltobler@gmail.com>\n         Mentored-by: Lucas Seiki Oshiro <lucasseikioshiro@gmail.com>\n    @@ builtin/rev-parse.c: static void handle_ref_opt(const char *pattern, const char\n     -\n     -static void print_path(const char *path, const char *prefix, enum format_type format, enum default_type def)\n     +static void print_path(const char *path, const char *prefix,\n    -+\t\t       int arg_path_format, enum path_format def_format)\n    ++\t\t       enum path_format arg_path_format, enum path_format def_format)\n      {\n     -\tchar *cwd = NULL;\n     -\t/*\n    @@ builtin/rev-parse.c: static void handle_ref_opt(const char *pattern, const char\n     -\t}\n     -\tfree(cwd);\n     +\tstruct strbuf sb = STRBUF_INIT;\n    -+\tenum path_format fmt = (arg_path_format != -1) ? arg_path_format : def_format;\n    ++\tenum path_format fmt = (arg_path_format != PATH_FORMAT_DEFAULT) ? arg_path_format : def_format;\n     +\n    -+\tformat_path(&sb, path, prefix, fmt);\n    ++\tappend_formatted_path(&sb, path, prefix, fmt);\n     +\tputs(sb.buf);\n     +\n     +\tstrbuf_release(&sb);\n    @@ builtin/rev-parse.c: int cmd_rev_parse(int argc,\n      \tstruct strbuf buf = STRBUF_INIT;\n      \tint seen_end_of_options = 0;\n     -\tenum format_type format = FORMAT_DEFAULT;\n    -+\tint arg_path_format = -1;\n    ++\tenum path_format arg_path_format = PATH_FORMAT_DEFAULT;\n      \n      \tshow_usage_if_asked(argc, argv, builtin_rev_parse_usage);\n      \n    @@ builtin/rev-parse.c: int cmd_rev_parse(int argc,\n      \t\t\t\tchar *cwd;\n      \t\t\t\tint len;\n     -\t\t\t\tenum format_type wanted = format;\n    -+\t\t\t\tint wanted = arg_path_format;\n    ++\t\t\t\tenum path_format wanted = arg_path_format;\n      \t\t\t\tif (arg[2] == 'g') {\t/* --git-dir */\n      \t\t\t\t\tif (gitdir) {\n     -\t\t\t\t\t\tprint_path(gitdir, prefix, format, DEFAULT_UNMODIFIED);\n4:  61b5d69306 ! 3:  7de41faa04 repo: add path.commondir with absolute and relative suffix formatting\n    @@ Metadata\n      ## Commit message ##\n         repo: add path.commondir with absolute and relative suffix formatting\n     \n    -    In standard Git repositories, the Git directory and the common directory\n    -    are identical. However, in environments utilizing multiple worktrees, the\n    -    local working state ($GIT_DIR) is separated from the shared central data\n    -    ($GIT_COMMON_DIR). Scripts require a reliable way to discover this shared\n    -    path.\n    +    Scripts working with worktree setups need a reliable way to discover\n    +    the common directory, which diverges from the git directory when\n    +    multiple worktrees are in use. There is no way to retrieve this path\n    +    from git repo info today.\n     \n    -    Introduce `path.commondir.absolute` and `path.commondir.relative` keys\n    -    to `git repo info`. Similar to the `path.gitdir` keys, exposing explicit\n    -    format variants removes the ambiguity of default fallbacks. Both keys are\n    -    evaluated via the `format_path()` engine.\n    +    Introduce path.commondir.absolute and path.commondir.relative keys.\n    +    Exposing explicit format variants rather than a single key with a\n    +    default avoids ambiguity for scripts that require predictable output.\n     \n    -    Insert the new keys into the `repo_info_field` array in lexicographical\n    -    order to maintain the integrity of binary search lookups.\n    -\n    -    Utilize the parameterized `test_repo_info_path` helper to validate the\n    -    worktree edge cases. This ensures that path resolution correctly respects\n    -    $GIT_COMMON_DIR when defined and safely falls back to $GIT_DIR otherwise.\n    +    Add a test helper test_repo_info_path that creates isolated\n    +    repositories per test case to prevent state leaks, captures the repo\n    +    root before changing directories to avoid eval, and accepts an optional\n    +    init_command to cover environment variable overrides such as\n    +    GIT_COMMON_DIR and GIT_DIR.\n     \n         Mentored-by: Justin Tobler <jltobler@gmail.com>\n         Mentored-by: Lucas Seiki Oshiro <lucasseikioshiro@gmail.com>\n    @@ Documentation/git-repo.adoc: values that they return:\n     +\tThe path to the Git repository's common directory relative to\n     +\tthe current working directory.\n     +\n    - `path.gitdir.absolute`::\n    - \tThe canonical absolute path to the Git repository directory (the `.git` directory).\n    - \n    + `references.format`::\n    + \tThe reference storage format. The valid values are:\n    + +\n     \n      ## builtin/repo.c ##\n    +@@\n    + #include \"hex.h\"\n    + #include \"odb.h\"\n    + #include \"parse-options.h\"\n    ++#include \"path.h\"\n    + #include \"path-walk.h\"\n    + #include \"progress.h\"\n    + #include \"quote.h\"\n    + #include \"ref-filter.h\"\n    + #include \"refs.h\"\n    + #include \"revision.h\"\n    ++#include \"setup.h\"\n    + #include \"strbuf.h\"\n    + #include \"string-list.h\"\n    + #include \"shallow.h\"\n     @@ builtin/repo.c: static int get_object_format(struct repository *repo, struct strbuf *buf)\n      \treturn 0;\n      }\n    @@ builtin/repo.c: static int get_object_format(struct repository *repo, struct str\n     +\tif (!common_dir)\n     +\t\treturn error(_(\"unable to get common directory\"));\n     +\n    -+\tformat_path(buf, common_dir, startup_info->prefix, PATH_FORMAT_CANONICAL);\n    ++\tappend_formatted_path(buf, common_dir, startup_info->prefix, PATH_FORMAT_CANONICAL);\n     +\treturn 0;\n     +}\n     +\n    @@ builtin/repo.c: static int get_object_format(struct repository *repo, struct str\n     +\tif (!common_dir)\n     +\t\treturn error(_(\"unable to get common directory\"));\n     +\n    -+\tformat_path(buf, common_dir, startup_info->prefix, PATH_FORMAT_RELATIVE);\n    ++\tappend_formatted_path(buf, common_dir, startup_info->prefix, PATH_FORMAT_RELATIVE);\n     +\treturn 0;\n     +}\n     +\n    - static int get_path_gitdir_absolute(struct repository *repo, struct strbuf *buf)\n    + static int get_references_format(struct repository *repo, struct strbuf *buf)\n      {\n    - \tconst char *git_dir = repo_get_git_dir(repo);\n    + \tstrbuf_addstr(buf,\n     @@ builtin/repo.c: static const struct repo_info_field repo_info_field[] = {\n      \t{ \"layout.bare\", get_layout_bare },\n      \t{ \"layout.shallow\", get_layout_shallow },\n      \t{ \"object.format\", get_object_format },\n     +\t{ \"path.commondir.absolute\", get_path_commondir_absolute },\n     +\t{ \"path.commondir.relative\", get_path_commondir_relative },\n    - \t{ \"path.gitdir.absolute\", get_path_gitdir_absolute },\n    - \t{ \"path.gitdir.relative\", get_path_gitdir_relative },\n      \t{ \"references.format\", get_references_format },\n    + };\n    + \n     \n      ## t/t1900-repo-info.sh ##\n    -@@ t/t1900-repo-info.sh: test_expect_success 'setup test repository layout for path fields' '\n    - \tmkdir -p test-repo/sub\n    +@@ t/t1900-repo-info.sh: test_expect_success 'git repo info -h shows only repo info usage' '\n    + \ttest_grep ! \"git repo structure\" actual\n      '\n      \n    -+test_expect_success 'setup custom-common for commondir tests' '\n    -+\tgit init --bare test-repo/custom-common\n    -+'\n    -+\n    -+test_repo_info_path 'commondir' 'echo \"$(cd .. && pwd)/.git\"' '../.git'\n    -+test_repo_info_path 'commondir' 'echo \"$(cd .. && pwd)/custom-common\"' '../custom-common' 'GIT_COMMON_DIR=\"$(cd .. && pwd)/custom-common\" GIT_DIR=../.git'\n    -+test_repo_info_path 'commondir' 'echo \"$(cd .. && pwd)/.git\"' '../.git' 'GIT_DIR=../.git'\n    - test_repo_info_path 'gitdir' 'echo \"$(cd .. && pwd)/.git\"' '../.git'\n    - \n    ++# Helper function to test path keys in both absolute and relative formats.\n    ++# $1: label for the test\n    ++# $2: field_name (e.g., commondir)\n    ++# $3: unique repo name for isolation\n    ++# $4: expect_absolute (suffix appended to repo root)\n    ++# $5: expect_relative (the relative path string expected)\n    ++# $6: init_command (extra setup like exporting env vars)\n    ++test_repo_info_path () {\n    ++\tlabel=$1\n    ++\tfield_name=$2\n    ++\trepo_name=$3\n    ++\texpect_absolute_suffix=$4\n    ++\texpect_relative=$5\n    ++\tinit_command=$6\n    ++\n    ++\tabsolute_root=\"$repo_name-absolute\"\n    ++\trelative_root=\"$repo_name-relative\"\n    ++\n    ++\ttest_expect_success \"setup: $label\" '\n    ++\t\tgit init \"$absolute_root\" &&\n    ++\t\tgit init \"$relative_root\" &&\n    ++\t\tmkdir -p \"$absolute_root/sub\" \"$relative_root/sub\"\n    ++\t'\n    ++\n    ++\ttest_expect_success \"absolute: $label\" '\n    ++\t\t(\n    ++\t\t\tcd \"$absolute_root/sub\" &&\n    ++\t\t\tROOT=\"$(test-tool path-utils real_path \"..\")\" && export ROOT &&\n    ++\t\t\teval \"$init_command\" &&\n    ++\t\t\texpect_path=\"$ROOT${expect_absolute_suffix:+/$expect_absolute_suffix}\" &&\n    ++\t\t\techo \"path.$field_name.absolute=$expect_path\" >expect &&\n    ++\t\t\tgit repo info \"path.$field_name.absolute\" >actual &&\n    ++\t\t\ttest_cmp expect actual\n    ++\t\t)\n    ++\t'\n    ++\n    ++\ttest_expect_success \"relative: $label\" '\n    ++\t\t(\n    ++\t\t\tcd \"$relative_root/sub\" &&\n    ++\t\t\tROOT=\"$(test-tool path-utils real_path \"..\")\" && export ROOT &&\n    ++\t\t\teval \"$init_command\" &&\n    ++\t\t\techo \"path.$field_name.relative=$expect_relative\" >expect &&\n    ++\t\t\tgit repo info \"path.$field_name.relative\" >actual &&\n    ++\t\t\ttest_cmp expect actual\n    ++\t\t)\n    ++\t'\n    ++}\n    ++\n    ++test_repo_info_path 'commondir standard' 'commondir' 'commondir-std' \\\n    ++\t'.git' '../.git'\n    ++\n    ++test_repo_info_path 'commondir with GIT_COMMON_DIR and GIT_DIR' 'commondir' \\\n    ++\t'commondir-envs' 'custom-common' '../custom-common' \\\n    ++\t'GIT_COMMON_DIR=\"$ROOT/custom-common\" && export GIT_COMMON_DIR &&\n    ++\t GIT_DIR=\"../.git\" && export GIT_DIR &&\n    ++\t git init --bare \"$ROOT/custom-common\"'\n    ++\n    ++test_repo_info_path 'commondir with only GIT_DIR' 'commondir' \\\n    ++\t'commondir-only-gitdir' '.git' '../.git' \\\n    ++\t'GIT_DIR=\"../.git\" && export GIT_DIR'\n    ++\n      test_done\n3:  ca95d51f6e ! 4:  ffd6a5bb16 repo: add path.gitdir with absolute and relative suffix formatting\n    @@ Metadata\n      ## Commit message ##\n         repo: add path.gitdir with absolute and relative suffix formatting\n     \n    -    Scripts often need to locate the `.git` directory. While `git rev-parse`\n    -    provides this, it relies on command-line flags to dictate path formatting.\n    +    Scripts need a stable way to locate the git directory without\n    +    parsing rev-parse output or relying on its flag-driven path format\n    +    selection. There is no way to retrieve this path from git repo info\n    +    today.\n     \n    -    Introduce `path.gitdir.absolute` and `path.gitdir.relative` keys to\n    -    `git repo info`. Exposing separate format-specific keys instead of a base\n    -    `path.gitdir` key avoids default fallbacks and requires callers to state\n    -    their format requirements explicitly. Both keys use `format_path()` to\n    -    resolve paths.\n    -\n    -    To test these keys, introduce the `test_repo_info_path` helper in\n    -    `t/t1900-repo-info.sh`. The helper evaluates paths dynamically and accepts\n    -    environment variable prefixes. This prepares the test suite for future path\n    -    keys that depend on environment overrides, such as `commondir`.\n    +    Introduce path.gitdir.absolute and path.gitdir.relative keys,\n    +    consistent with the path.commondir keys added in the previous patch.\n    +    Reuse the test_repo_info_path helper introduced there to validate\n    +    both variants.\n     \n         Mentored-by: Justin Tobler <jltobler@gmail.com>\n         Mentored-by: Lucas Seiki Oshiro <lucasseikioshiro@gmail.com>\n    @@ Commit message\n     \n      ## Documentation/git-repo.adoc ##\n     @@ Documentation/git-repo.adoc: values that they return:\n    - `object.format`::\n    - \tThe object format (hash algorithm) used in the repository.\n    + \tThe path to the Git repository's common directory relative to\n    + \tthe current working directory.\n      \n     +`path.gitdir.absolute`::\n     +\tThe canonical absolute path to the Git repository directory (the `.git` directory).\n    @@ Documentation/git-repo.adoc: values that they return:\n      +\n     \n      ## builtin/repo.c ##\n    -@@\n    - #include \"hex.h\"\n    - #include \"odb.h\"\n    - #include \"parse-options.h\"\n    -+#include \"path.h\"\n    - #include \"path-walk.h\"\n    - #include \"progress.h\"\n    - #include \"quote.h\"\n    - #include \"ref-filter.h\"\n    - #include \"refs.h\"\n    - #include \"revision.h\"\n    -+#include \"setup.h\"\n    - #include \"strbuf.h\"\n    - #include \"string-list.h\"\n    - #include \"shallow.h\"\n    -@@ builtin/repo.c: static int get_object_format(struct repository *repo, struct strbuf *buf)\n    +@@ builtin/repo.c: static int get_path_commondir_relative(struct repository *repo, struct strbuf *b\n      \treturn 0;\n      }\n      \n    @@ builtin/repo.c: static int get_object_format(struct repository *repo, struct str\n     +\tif (!git_dir)\n     +\t\treturn error(_(\"unable to get git directory\"));\n     +\n    -+\tformat_path(buf, git_dir, startup_info->prefix, PATH_FORMAT_CANONICAL);\n    ++\tappend_formatted_path(buf, git_dir, startup_info->prefix, PATH_FORMAT_CANONICAL);\n     +\treturn 0;\n     +}\n     +\n    @@ builtin/repo.c: static int get_object_format(struct repository *repo, struct str\n     +\tif (!git_dir)\n     +\t\treturn error(_(\"unable to get git directory\"));\n     +\n    -+\tformat_path(buf, git_dir, startup_info->prefix, PATH_FORMAT_RELATIVE);\n    ++\tappend_formatted_path(buf, git_dir, startup_info->prefix, PATH_FORMAT_RELATIVE);\n     +\treturn 0;\n     +}\n     +\n    @@ builtin/repo.c: static int get_object_format(struct repository *repo, struct str\n      {\n      \tstrbuf_addstr(buf,\n     @@ builtin/repo.c: static const struct repo_info_field repo_info_field[] = {\n    - \t{ \"layout.bare\", get_layout_bare },\n    - \t{ \"layout.shallow\", get_layout_shallow },\n      \t{ \"object.format\", get_object_format },\n    + \t{ \"path.commondir.absolute\", get_path_commondir_absolute },\n    + \t{ \"path.commondir.relative\", get_path_commondir_relative },\n     +\t{ \"path.gitdir.absolute\", get_path_gitdir_absolute },\n     +\t{ \"path.gitdir.relative\", get_path_gitdir_relative },\n      \t{ \"references.format\", get_references_format },\n    @@ builtin/repo.c: static const struct repo_info_field repo_info_field[] = {\n      \n     \n      ## t/t1900-repo-info.sh ##\n    -@@ t/t1900-repo-info.sh: test_expect_success 'git repo info -h shows only repo info usage' '\n    - \ttest_grep ! \"git repo structure\" actual\n    - '\n    +@@ t/t1900-repo-info.sh: test_repo_info_path 'commondir with only GIT_DIR' 'commondir' \\\n    + \t'commondir-only-gitdir' '.git' '../.git' \\\n    + \t'GIT_DIR=\"../.git\" && export GIT_DIR'\n      \n    -+test_repo_info_path () {\n    -+\tfield_name=$1\n    -+\texpect_absolute_eval=$2\n    -+\texpect_relative=$3\n    -+\tenv_prefix=$4\n    -+\n    -+\ttest_expect_success \"query individual key: path.$field_name.absolute${env_prefix:+ ($env_prefix)}\" '\n    -+\t\t(\n    -+\t\t\tcd test-repo/sub &&\n    -+\t\t\texpect_absolute=$(eval \"$expect_absolute_eval\") &&\n    -+\t\t\techo \"path.$field_name.absolute=$expect_absolute\" >expect &&\n    -+\t\t\teval \"${env_prefix:+$env_prefix }git repo info \\\"path.$field_name.absolute\\\"\" >actual &&\n    -+\t\t\ttest_cmp expect actual\n    -+\t\t)\n    -+\t'\n    -+\n    -+\ttest_expect_success \"query individual key: path.$field_name.relative${env_prefix:+ ($env_prefix)}\" '\n    -+\t\t(\n    -+\t\t\tcd test-repo/sub &&\n    -+\t\t\techo \"path.$field_name.relative=$expect_relative\" >expect &&\n    -+\t\t\teval \"${env_prefix:+$env_prefix }git repo info \\\"path.$field_name.relative\\\"\" >actual &&\n    -+\t\t\ttest_cmp expect actual\n    -+\t\t)\n    -+\t'\n    -+}\n    -+\n    -+test_expect_success 'setup test repository layout for path fields' '\n    -+\tgit init test-repo &&\n    -+\tmkdir -p test-repo/sub\n    -+'\n    ++test_repo_info_path 'gitdir standard' 'gitdir' 'gitdir-std' \\\n    ++\t'.git' '../.git'\n     +\n    -+test_repo_info_path 'gitdir' 'echo \"$(cd .. && pwd)/.git\"' '../.git'\n    ++test_repo_info_path 'gitdir with explicit GIT_DIR' 'gitdir' \\\n    ++\t'gitdir-env' '.git' '../.git' \\\n    ++\t'GIT_DIR=\"../.git\" && export GIT_DIR'\n     +\n      test_done\n-- \n2.54.0\n"},{"id":"545408","messageId":"20260612182847.562816-2-jayatheerthkulkarni2005@gmail.com","threadId":"65725","inReplyTo":"20260612182847.562816-1-jayatheerthkulkarni2005@gmail.com","subject":"[GSoC Patch v3 1/4] path: introduce append_formatted_path() for shared path formatting","fromName":"K Jayatheerth","fromEmail":"jayatheerthkulkarni2005@gmail.com","sentAt":"2026-06-12T18:28:44Z","receivedAt":"2026-06-12T18:30:15Z","isPatch":true,"body":"The path-formatting logic in builtin/rev-parse.c is tightly coupled\nto that command and writes directly to stdout, making it impossible\nfor other builtins to reuse.\n\nExtract the core algorithm into append_formatted_path() in path.c\nand expose a path_format enum in path.h so that any builtin can\nformat paths consistently without duplicating logic.\n\nMentored-by: Justin Tobler <jltobler@gmail.com>\nMentored-by: Lucas Seiki Oshiro <lucasseikioshiro@gmail.com>\nSigned-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>\n---\n path.c | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n path.h | 36 ++++++++++++++++++++++++++++++\n 2 files changed, 106 insertions(+)\n\ndiff --git a/path.c b/path.c\nindex d7e17bf174..5e83e3e4f6 100644\n--- a/path.c\n+++ b/path.c\n@@ -1579,6 +1579,76 @@ char *xdg_cache_home(const char *filename)\n \treturn NULL;\n }\n \n+void append_formatted_path(struct strbuf *dest, const char *path,\n+\t\t\t   const char *prefix, enum path_format format)\n+{\n+\tswitch (format) {\n+\tcase PATH_FORMAT_DEFAULT:\n+\tcase PATH_FORMAT_UNMODIFIED:\n+\t\tstrbuf_addstr(dest, path);\n+\t\tbreak;\n+\n+\tcase PATH_FORMAT_RELATIVE: {\n+\t\tstruct strbuf relative_buf = STRBUF_INIT;\n+\t\tstruct strbuf real_path = STRBUF_INIT;\n+\t\tstruct strbuf real_prefix = STRBUF_INIT;\n+\t\tchar *cwd = NULL;\n+\n+\t\t/*\n+\t\t * We don't ever produce a relative path if prefix is NULL,\n+\t\t * so set the prefix to the current directory so that we can\n+\t\t * produce a relative path whenever possible.\n+\t\t */\n+\t\tif (!prefix)\n+\t\t\tprefix = cwd = xgetcwd();\n+\n+\t\tif (!is_absolute_path(path)) {\n+\t\t\tstrbuf_realpath_forgiving(&real_path, path, 1);\n+\t\t\tpath = real_path.buf;\n+\t\t}\n+\t\tif (!is_absolute_path(prefix)) {\n+\t\t\tstrbuf_realpath_forgiving(&real_prefix, prefix, 1);\n+\t\t\tprefix = real_prefix.buf;\n+\t\t}\n+\n+\t\tstrbuf_addstr(dest, relative_path(path, prefix, &relative_buf));\n+\n+\t\tstrbuf_release(&relative_buf);\n+\t\tstrbuf_release(&real_path);\n+\t\tstrbuf_release(&real_prefix);\n+\t\tfree(cwd);\n+\t\tbreak;\n+\t}\n+\n+\tcase PATH_FORMAT_RELATIVE_IF_SHARED: {\n+\t\tstruct strbuf relative_buf = STRBUF_INIT;\n+\n+\t\t/*\n+\t\t * If we're using RELATIVE_IF_SHARED mode, then we want an\n+\t\t * absolute path unless the two share a common prefix, so don't\n+\t\t * default the prefix to the current working directory. Doing so\n+\t\t * would cause a relative path to always be produced if possible.\n+\t\t */\n+\t\tstrbuf_addstr(dest, relative_path(path, prefix, &relative_buf));\n+\t\tstrbuf_release(&relative_buf);\n+\t\tbreak;\n+\t}\n+\n+\tcase PATH_FORMAT_CANONICAL: {\n+\t\tstruct strbuf canonical_buf = STRBUF_INIT;\n+\n+\t\tstrbuf_realpath_forgiving(&canonical_buf, path, 1);\n+\t\tstrbuf_addbuf(dest, &canonical_buf);\n+\n+\t\tstrbuf_release(&canonical_buf);\n+\t\tbreak;\n+\t}\n+\n+\tdefault:\n+\t\tBUG(\"unknown path_format value %d\", format);\n+\t}\n+}\n+\n REPO_GIT_PATH_FUNC(squash_msg, \"SQUASH_MSG\")\n REPO_GIT_PATH_FUNC(merge_msg, \"MERGE_MSG\")\n REPO_GIT_PATH_FUNC(merge_rr, \"MERGE_RR\")\ndiff --git a/path.h b/path.h\nindex 0434ba5e07..6aca53b100 100644\n--- a/path.h\n+++ b/path.h\n@@ -262,6 +262,42 @@ enum scld_error safe_create_leading_directories_no_share(char *path);\n int safe_create_file_with_leading_directories(struct repository *repo,\n \t\t\t\t\t      const char *path);\n \n+/**\n+ * The formatting strategy to apply when writing a path into a buffer.\n+ */\n+enum path_format {\n+\t/*\n+\t * Represents the default formatting behavior. Treated as\n+\t * PATH_FORMAT_UNMODIFIED by append_formatted_path().\n+\t */\n+\tPATH_FORMAT_DEFAULT,\n+\n+\t/* Output the path exactly as-is without any modifications. */\n+\tPATH_FORMAT_UNMODIFIED,\n+\n+\t/* Output a path relative to the provided directory prefix. */\n+\tPATH_FORMAT_RELATIVE,\n+\n+\t/* Output a relative path only if the path shares a root with the prefix. */\n+\tPATH_FORMAT_RELATIVE_IF_SHARED,\n+\n+\t/* Output a fully resolved, absolute canonical path. */\n+\tPATH_FORMAT_CANONICAL\n+};\n+\n+/**\n+ * Format a path according to the specified formatting strategy and append\n+ * the result to the given strbuf.\n+ *\n+ * `dest`   : The string buffer to append the formatted path to.\n+ * `path`   : The path string that needs to be formatted.\n+ * `prefix` : The directory prefix to calculate relative offsets against.\n+ * Pass NULL to default to the current working directory where applicable.\n+ * `format` : The formatting behavior rule to execute.\n+ */\n+void append_formatted_path(struct strbuf *dest, const char *path,\n+\t\t\t   const char *prefix, enum path_format format);\n+\n # ifdef USE_THE_REPOSITORY_VARIABLE\n #  include \"strbuf.h\"\n #  include \"repository.h\"\n-- \n2.54.0\n\n"},{"id":"545409","messageId":"20260612182847.562816-3-jayatheerthkulkarni2005@gmail.com","threadId":"65725","inReplyTo":"20260612182847.562816-1-jayatheerthkulkarni2005@gmail.com","subject":"[GSoC Patch v3 2/4] rev-parse: use append_formatted_path() for path formatting","fromName":"K Jayatheerth","fromEmail":"jayatheerthkulkarni2005@gmail.com","sentAt":"2026-06-12T18:28:45Z","receivedAt":"2026-06-12T18:30:27Z","isPatch":true,"body":"Now that path formatting logic lives in a shared helper, keeping a\nduplicate implementation in rev-parse is unnecessary and risks the\ntwo diverging over time.\n\nReplace the local format_type and default_type enums and the\nhand-rolled formatting logic with a call to append_formatted_path().\nIntroduce PATH_FORMAT_DEFAULT as the initial value of arg_path_format\nso that per-path fallback behavior is resolved in print_path() rather\nthan leaked into the shared helper.\n\nMentored-by: Justin Tobler <jltobler@gmail.com>\nMentored-by: Lucas Seiki Oshiro <lucasseikioshiro@gmail.com>\nSigned-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>\n---\n builtin/rev-parse.c | 103 ++++++++++----------------------------------\n 1 file changed, 23 insertions(+), 80 deletions(-)\n\ndiff --git a/builtin/rev-parse.c b/builtin/rev-parse.c\nindex 218b5f34d6..2dd35361f3 100644\n--- a/builtin/rev-parse.c\n+++ b/builtin/rev-parse.c\n@@ -632,73 +632,16 @@ static void handle_ref_opt(const char *pattern, const char *prefix)\n \tclear_ref_exclusions(&ref_excludes);\n }\n \n-enum format_type {\n-\t/* We would like a relative path. */\n-\tFORMAT_RELATIVE,\n-\t/* We would like a canonical absolute path. */\n-\tFORMAT_CANONICAL,\n-\t/* We would like the default behavior. */\n-\tFORMAT_DEFAULT,\n-};\n-\n-enum default_type {\n-\t/* Our default is a relative path. */\n-\tDEFAULT_RELATIVE,\n-\t/* Our default is a relative path if there's a shared root. */\n-\tDEFAULT_RELATIVE_IF_SHARED,\n-\t/* Our default is a canonical absolute path. */\n-\tDEFAULT_CANONICAL,\n-\t/* Our default is not to modify the item. */\n-\tDEFAULT_UNMODIFIED,\n-};\n-\n-static void print_path(const char *path, const char *prefix, enum format_type format, enum default_type def)\n+static void print_path(const char *path, const char *prefix,\n+\t\t       enum path_format arg_path_format, enum path_format def_format)\n {\n-\tchar *cwd = NULL;\n-\t/*\n-\t * We don't ever produce a relative path if prefix is NULL, so set the\n-\t * prefix to the current directory so that we can produce a relative\n-\t * path whenever possible.  If we're using RELATIVE_IF_SHARED mode, then\n-\t * we want an absolute path unless the two share a common prefix, so don't\n-\t * set it in that case, since doing so causes a relative path to always\n-\t * be produced if possible.\n-\t */\n-\tif (!prefix && (format != FORMAT_DEFAULT || def != DEFAULT_RELATIVE_IF_SHARED))\n-\t\tprefix = cwd = xgetcwd();\n-\tif (format == FORMAT_DEFAULT && def == DEFAULT_UNMODIFIED) {\n-\t\tputs(path);\n-\t} else if (format == FORMAT_RELATIVE ||\n-\t\t  (format == FORMAT_DEFAULT && def == DEFAULT_RELATIVE)) {\n-\t\t/*\n-\t\t * In order for relative_path to work as expected, we need to\n-\t\t * make sure that both paths are absolute paths.  If we don't,\n-\t\t * we can end up with an unexpected absolute path that the user\n-\t\t * didn't want.\n-\t\t */\n-\t\tstruct strbuf buf = STRBUF_INIT, realbuf = STRBUF_INIT, prefixbuf = STRBUF_INIT;\n-\t\tif (!is_absolute_path(path)) {\n-\t\t\tstrbuf_realpath_forgiving(&realbuf, path,  1);\n-\t\t\tpath = realbuf.buf;\n-\t\t}\n-\t\tif (!is_absolute_path(prefix)) {\n-\t\t\tstrbuf_realpath_forgiving(&prefixbuf, prefix, 1);\n-\t\t\tprefix = prefixbuf.buf;\n-\t\t}\n-\t\tputs(relative_path(path, prefix, &buf));\n-\t\tstrbuf_release(&buf);\n-\t\tstrbuf_release(&realbuf);\n-\t\tstrbuf_release(&prefixbuf);\n-\t} else if (format == FORMAT_DEFAULT && def == DEFAULT_RELATIVE_IF_SHARED) {\n-\t\tstruct strbuf buf = STRBUF_INIT;\n-\t\tputs(relative_path(path, prefix, &buf));\n-\t\tstrbuf_release(&buf);\n-\t} else {\n-\t\tstruct strbuf buf = STRBUF_INIT;\n-\t\tstrbuf_realpath_forgiving(&buf, path, 1);\n-\t\tputs(buf.buf);\n-\t\tstrbuf_release(&buf);\n-\t}\n-\tfree(cwd);\n+\tstruct strbuf sb = STRBUF_INIT;\n+\tenum path_format fmt = (arg_path_format != PATH_FORMAT_DEFAULT) ? arg_path_format : def_format;\n+\n+\tappend_formatted_path(&sb, path, prefix, fmt);\n+\tputs(sb.buf);\n+\n+\tstrbuf_release(&sb);\n }\n \n int cmd_rev_parse(int argc,\n@@ -717,7 +660,7 @@ int cmd_rev_parse(int argc,\n \tconst char *name = NULL;\n \tstruct strbuf buf = STRBUF_INIT;\n \tint seen_end_of_options = 0;\n-\tenum format_type format = FORMAT_DEFAULT;\n+\tenum path_format arg_path_format = PATH_FORMAT_DEFAULT;\n \n \tshow_usage_if_asked(argc, argv, builtin_rev_parse_usage);\n \n@@ -797,8 +740,8 @@ int cmd_rev_parse(int argc,\n \t\t\t\t\tdie(_(\"--git-path requires an argument\"));\n \t\t\t\tprint_path(repo_git_path_replace(the_repository, &buf,\n \t\t\t\t\t\t\t\t \"%s\", argv[i + 1]), prefix,\n-\t\t\t\t\t\tformat,\n-\t\t\t\t\t\tDEFAULT_RELATIVE_IF_SHARED);\n+\t\t\t\t\t\targ_path_format,\n+\t\t\t\t\t\tPATH_FORMAT_RELATIVE_IF_SHARED);\n \t\t\t\ti++;\n \t\t\t\tcontinue;\n \t\t\t}\n@@ -820,9 +763,9 @@ int cmd_rev_parse(int argc,\n \t\t\t\tif (!arg)\n \t\t\t\t\tdie(_(\"--path-format requires an argument\"));\n \t\t\t\tif (!strcmp(arg, \"absolute\")) {\n-\t\t\t\t\tformat = FORMAT_CANONICAL;\n+\t\t\t\t\targ_path_format = PATH_FORMAT_CANONICAL;\n \t\t\t\t} else if (!strcmp(arg, \"relative\")) {\n-\t\t\t\t\tformat = FORMAT_RELATIVE;\n+\t\t\t\t\targ_path_format = PATH_FORMAT_RELATIVE;\n \t\t\t\t} else {\n \t\t\t\t\tdie(_(\"unknown argument to --path-format: %s\"), arg);\n \t\t\t\t}\n@@ -985,7 +928,7 @@ int cmd_rev_parse(int argc,\n \t\t\tif (!strcmp(arg, \"--show-toplevel\")) {\n \t\t\t\tconst char *work_tree = repo_get_work_tree(the_repository);\n \t\t\t\tif (work_tree)\n-\t\t\t\t\tprint_path(work_tree, prefix, format, DEFAULT_UNMODIFIED);\n+\t\t\t\t\tprint_path(work_tree, prefix, arg_path_format, PATH_FORMAT_UNMODIFIED);\n \t\t\t\telse\n \t\t\t\t\tdie(_(\"this operation must be run in a work tree\"));\n \t\t\t\tcontinue;\n@@ -993,7 +936,7 @@ int cmd_rev_parse(int argc,\n \t\t\tif (!strcmp(arg, \"--show-superproject-working-tree\")) {\n \t\t\t\tstruct strbuf superproject = STRBUF_INIT;\n \t\t\t\tif (get_superproject_working_tree(&superproject))\n-\t\t\t\t\tprint_path(superproject.buf, prefix, format, DEFAULT_UNMODIFIED);\n+\t\t\t\t\tprint_path(superproject.buf, prefix, arg_path_format, PATH_FORMAT_UNMODIFIED);\n \t\t\t\tstrbuf_release(&superproject);\n \t\t\t\tcontinue;\n \t\t\t}\n@@ -1028,18 +971,18 @@ int cmd_rev_parse(int argc,\n \t\t\t\tconst char *gitdir = getenv(GIT_DIR_ENVIRONMENT);\n \t\t\t\tchar *cwd;\n \t\t\t\tint len;\n-\t\t\t\tenum format_type wanted = format;\n+\t\t\t\tenum path_format wanted = arg_path_format;\n \t\t\t\tif (arg[2] == 'g') {\t/* --git-dir */\n \t\t\t\t\tif (gitdir) {\n-\t\t\t\t\t\tprint_path(gitdir, prefix, format, DEFAULT_UNMODIFIED);\n+\t\t\t\t\t\tprint_path(gitdir, prefix, arg_path_format, PATH_FORMAT_UNMODIFIED);\n \t\t\t\t\t\tcontinue;\n \t\t\t\t\t}\n \t\t\t\t\tif (!prefix) {\n-\t\t\t\t\t\tprint_path(\".git\", prefix, format, DEFAULT_UNMODIFIED);\n+\t\t\t\t\t\tprint_path(\".git\", prefix, arg_path_format, PATH_FORMAT_UNMODIFIED);\n \t\t\t\t\t\tcontinue;\n \t\t\t\t\t}\n \t\t\t\t} else {\t\t/* --absolute-git-dir */\n-\t\t\t\t\twanted = FORMAT_CANONICAL;\n+\t\t\t\t\twanted = PATH_FORMAT_CANONICAL;\n \t\t\t\t\tif (!gitdir && !prefix)\n \t\t\t\t\t\tgitdir = \".git\";\n \t\t\t\t\tif (gitdir) {\n@@ -1055,11 +998,11 @@ int cmd_rev_parse(int argc,\n \t\t\t\tstrbuf_reset(&buf);\n \t\t\t\tstrbuf_addf(&buf, \"%s%s.git\", cwd, len && cwd[len-1] != '/' ? \"/\" : \"\");\n \t\t\t\tfree(cwd);\n-\t\t\t\tprint_path(buf.buf, prefix, wanted, DEFAULT_CANONICAL);\n+\t\t\t\tprint_path(buf.buf, prefix, wanted, PATH_FORMAT_CANONICAL);\n \t\t\t\tcontinue;\n \t\t\t}\n \t\t\tif (!strcmp(arg, \"--git-common-dir\")) {\n-\t\t\t\tprint_path(repo_get_common_dir(the_repository), prefix, format, DEFAULT_RELATIVE_IF_SHARED);\n+\t\t\t\tprint_path(repo_get_common_dir(the_repository), prefix, arg_path_format, PATH_FORMAT_RELATIVE_IF_SHARED);\n \t\t\t\tcontinue;\n \t\t\t}\n \t\t\tif (!strcmp(arg, \"--is-inside-git-dir\")) {\n@@ -1089,7 +1032,7 @@ int cmd_rev_parse(int argc,\n \t\t\t\tif (the_repository->index->split_index) {\n \t\t\t\t\tconst struct object_id *oid = &the_repository->index->split_index->base_oid;\n \t\t\t\t\tconst char *path = repo_git_path_replace(the_repository, &buf, \"sharedindex.%s\", oid_to_hex(oid));\n-\t\t\t\t\tprint_path(path, prefix, format, DEFAULT_RELATIVE);\n+\t\t\t\t\tprint_path(path, prefix, arg_path_format, PATH_FORMAT_RELATIVE);\n \t\t\t\t}\n \t\t\t\tcontinue;\n \t\t\t}\n-- \n2.54.0\n\n"},{"id":"545410","messageId":"20260612182847.562816-4-jayatheerthkulkarni2005@gmail.com","threadId":"65725","inReplyTo":"20260612182847.562816-1-jayatheerthkulkarni2005@gmail.com","subject":"[GSoC Patch v3 3/4] repo: add path.commondir with absolute and relative suffix formatting","fromName":"K Jayatheerth","fromEmail":"jayatheerthkulkarni2005@gmail.com","sentAt":"2026-06-12T18:28:46Z","receivedAt":"2026-06-12T18:30:58Z","isPatch":true,"body":"Scripts working with worktree setups need a reliable way to discover\nthe common directory, which diverges from the git directory when\nmultiple worktrees are in use. There is no way to retrieve this path\nfrom git repo info today.\n\nIntroduce path.commondir.absolute and path.commondir.relative keys.\nExposing explicit format variants rather than a single key with a\ndefault avoids ambiguity for scripts that require predictable output.\n\nAdd a test helper test_repo_info_path that creates isolated\nrepositories per test case to prevent state leaks, captures the repo\nroot before changing directories to avoid eval, and accepts an optional\ninit_command to cover environment variable overrides such as\nGIT_COMMON_DIR and GIT_DIR.\n\nMentored-by: Justin Tobler <jltobler@gmail.com>\nMentored-by: Lucas Seiki Oshiro <lucasseikioshiro@gmail.com>\nSigned-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>\n---\n Documentation/git-repo.adoc |  9 ++++++\n builtin/repo.c              | 26 ++++++++++++++++\n t/t1900-repo-info.sh        | 61 +++++++++++++++++++++++++++++++++++++\n 3 files changed, 96 insertions(+)\n\ndiff --git a/Documentation/git-repo.adoc b/Documentation/git-repo.adoc\nindex 42262c1983..890c34051d 100644\n--- a/Documentation/git-repo.adoc\n+++ b/Documentation/git-repo.adoc\n@@ -104,6 +104,15 @@ values that they return:\n `object.format`::\n \tThe object format (hash algorithm) used in the repository.\n \n+`path.commondir.absolute`::\n+\tThe canonical absolute path to the Git repository's common\n+\tdirectory (the shared `.git` directory containing objects,\n+\trefs, and global configuration).\n+\n+`path.commondir.relative`::\n+\tThe path to the Git repository's common directory relative to\n+\tthe current working directory.\n+\n `references.format`::\n \tThe reference storage format. The valid values are:\n +\ndiff --git a/builtin/repo.c b/builtin/repo.c\nindex 71a5c1c29c..c4cc3bf3fc 100644\n--- a/builtin/repo.c\n+++ b/builtin/repo.c\n@@ -7,12 +7,14 @@\n #include \"hex.h\"\n #include \"odb.h\"\n #include \"parse-options.h\"\n+#include \"path.h\"\n #include \"path-walk.h\"\n #include \"progress.h\"\n #include \"quote.h\"\n #include \"ref-filter.h\"\n #include \"refs.h\"\n #include \"revision.h\"\n+#include \"setup.h\"\n #include \"strbuf.h\"\n #include \"string-list.h\"\n #include \"shallow.h\"\n@@ -75,6 +77,28 @@ static int get_object_format(struct repository *repo, struct strbuf *buf)\n \treturn 0;\n }\n \n+static int get_path_commondir_absolute(struct repository *repo, struct strbuf *buf)\n+{\n+\tconst char *common_dir = repo_get_common_dir(repo);\n+\n+\tif (!common_dir)\n+\t\treturn error(_(\"unable to get common directory\"));\n+\n+\tappend_formatted_path(buf, common_dir, startup_info->prefix, PATH_FORMAT_CANONICAL);\n+\treturn 0;\n+}\n+\n+static int get_path_commondir_relative(struct repository *repo, struct strbuf *buf)\n+{\n+\tconst char *common_dir = repo_get_common_dir(repo);\n+\n+\tif (!common_dir)\n+\t\treturn error(_(\"unable to get common directory\"));\n+\n+\tappend_formatted_path(buf, common_dir, startup_info->prefix, PATH_FORMAT_RELATIVE);\n+\treturn 0;\n+}\n+\n static int get_references_format(struct repository *repo, struct strbuf *buf)\n {\n \tstrbuf_addstr(buf,\n@@ -87,6 +111,8 @@ static const struct repo_info_field repo_info_field[] = {\n \t{ \"layout.bare\", get_layout_bare },\n \t{ \"layout.shallow\", get_layout_shallow },\n \t{ \"object.format\", get_object_format },\n+\t{ \"path.commondir.absolute\", get_path_commondir_absolute },\n+\t{ \"path.commondir.relative\", get_path_commondir_relative },\n \t{ \"references.format\", get_references_format },\n };\n \ndiff --git a/t/t1900-repo-info.sh b/t/t1900-repo-info.sh\nindex 39bb77dda0..28fe76e25b 100755\n--- a/t/t1900-repo-info.sh\n+++ b/t/t1900-repo-info.sh\n@@ -155,4 +155,65 @@ test_expect_success 'git repo info -h shows only repo info usage' '\n \ttest_grep ! \"git repo structure\" actual\n '\n \n+# Helper function to test path keys in both absolute and relative formats.\n+# $1: label for the test\n+# $2: field_name (e.g., commondir)\n+# $3: unique repo name for isolation\n+# $4: expect_absolute (suffix appended to repo root)\n+# $5: expect_relative (the relative path string expected)\n+# $6: init_command (extra setup like exporting env vars)\n+test_repo_info_path () {\n+\tlabel=$1\n+\tfield_name=$2\n+\trepo_name=$3\n+\texpect_absolute_suffix=$4\n+\texpect_relative=$5\n+\tinit_command=$6\n+\n+\tabsolute_root=\"$repo_name-absolute\"\n+\trelative_root=\"$repo_name-relative\"\n+\n+\ttest_expect_success \"setup: $label\" '\n+\t\tgit init \"$absolute_root\" &&\n+\t\tgit init \"$relative_root\" &&\n+\t\tmkdir -p \"$absolute_root/sub\" \"$relative_root/sub\"\n+\t'\n+\n+\ttest_expect_success \"absolute: $label\" '\n+\t\t(\n+\t\t\tcd \"$absolute_root/sub\" &&\n+\t\t\tROOT=\"$(test-tool path-utils real_path \"..\")\" && export ROOT &&\n+\t\t\teval \"$init_command\" &&\n+\t\t\texpect_path=\"$ROOT${expect_absolute_suffix:+/$expect_absolute_suffix}\" &&\n+\t\t\techo \"path.$field_name.absolute=$expect_path\" >expect &&\n+\t\t\tgit repo info \"path.$field_name.absolute\" >actual &&\n+\t\t\ttest_cmp expect actual\n+\t\t)\n+\t'\n+\n+\ttest_expect_success \"relative: $label\" '\n+\t\t(\n+\t\t\tcd \"$relative_root/sub\" &&\n+\t\t\tROOT=\"$(test-tool path-utils real_path \"..\")\" && export ROOT &&\n+\t\t\teval \"$init_command\" &&\n+\t\t\techo \"path.$field_name.relative=$expect_relative\" >expect &&\n+\t\t\tgit repo info \"path.$field_name.relative\" >actual &&\n+\t\t\ttest_cmp expect actual\n+\t\t)\n+\t'\n+}\n+\n+test_repo_info_path 'commondir standard' 'commondir' 'commondir-std' \\\n+\t'.git' '../.git'\n+\n+test_repo_info_path 'commondir with GIT_COMMON_DIR and GIT_DIR' 'commondir' \\\n+\t'commondir-envs' 'custom-common' '../custom-common' \\\n+\t'GIT_COMMON_DIR=\"$ROOT/custom-common\" && export GIT_COMMON_DIR &&\n+\t GIT_DIR=\"../.git\" && export GIT_DIR &&\n+\t git init --bare \"$ROOT/custom-common\"'\n+\n+test_repo_info_path 'commondir with only GIT_DIR' 'commondir' \\\n+\t'commondir-only-gitdir' '.git' '../.git' \\\n+\t'GIT_DIR=\"../.git\" && export GIT_DIR'\n+\n test_done\n-- \n2.54.0\n\n"},{"id":"545411","messageId":"20260612182847.562816-5-jayatheerthkulkarni2005@gmail.com","threadId":"65725","inReplyTo":"20260612182847.562816-1-jayatheerthkulkarni2005@gmail.com","subject":"[GSoC Patch v3 4/4] repo: add path.gitdir with absolute and relative suffix formatting","fromName":"K Jayatheerth","fromEmail":"jayatheerthkulkarni2005@gmail.com","sentAt":"2026-06-12T18:28:47Z","receivedAt":"2026-06-12T18:31:14Z","isPatch":true,"body":"Scripts need a stable way to locate the git directory without\nparsing rev-parse output or relying on its flag-driven path format\nselection. There is no way to retrieve this path from git repo info\ntoday.\n\nIntroduce path.gitdir.absolute and path.gitdir.relative keys,\nconsistent with the path.commondir keys added in the previous patch.\nReuse the test_repo_info_path helper introduced there to validate\nboth variants.\n\nMentored-by: Justin Tobler <jltobler@gmail.com>\nMentored-by: Lucas Seiki Oshiro <lucasseikioshiro@gmail.com>\nSigned-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>\n---\n Documentation/git-repo.adoc |  6 ++++++\n builtin/repo.c              | 24 ++++++++++++++++++++++++\n t/t1900-repo-info.sh        |  7 +++++++\n 3 files changed, 37 insertions(+)\n\ndiff --git a/Documentation/git-repo.adoc b/Documentation/git-repo.adoc\nindex 890c34051d..ed7d80c690 100644\n--- a/Documentation/git-repo.adoc\n+++ b/Documentation/git-repo.adoc\n@@ -113,6 +113,12 @@ values that they return:\n \tThe path to the Git repository's common directory relative to\n \tthe current working directory.\n \n+`path.gitdir.absolute`::\n+\tThe canonical absolute path to the Git repository directory (the `.git` directory).\n+\n+`path.gitdir.relative`::\n+\tThe path to the Git repository directory relative to the current working directory.\n+\n `references.format`::\n \tThe reference storage format. The valid values are:\n +\ndiff --git a/builtin/repo.c b/builtin/repo.c\nindex c4cc3bf3fc..9a312d127a 100644\n--- a/builtin/repo.c\n+++ b/builtin/repo.c\n@@ -99,6 +99,28 @@ static int get_path_commondir_relative(struct repository *repo, struct strbuf *b\n \treturn 0;\n }\n \n+static int get_path_gitdir_absolute(struct repository *repo, struct strbuf *buf)\n+{\n+\tconst char *git_dir = repo_get_git_dir(repo);\n+\n+\tif (!git_dir)\n+\t\treturn error(_(\"unable to get git directory\"));\n+\n+\tappend_formatted_path(buf, git_dir, startup_info->prefix, PATH_FORMAT_CANONICAL);\n+\treturn 0;\n+}\n+\n+static int get_path_gitdir_relative(struct repository *repo, struct strbuf *buf)\n+{\n+\tconst char *git_dir = repo_get_git_dir(repo);\n+\n+\tif (!git_dir)\n+\t\treturn error(_(\"unable to get git directory\"));\n+\n+\tappend_formatted_path(buf, git_dir, startup_info->prefix, PATH_FORMAT_RELATIVE);\n+\treturn 0;\n+}\n+\n static int get_references_format(struct repository *repo, struct strbuf *buf)\n {\n \tstrbuf_addstr(buf,\n@@ -113,6 +135,8 @@ static const struct repo_info_field repo_info_field[] = {\n \t{ \"object.format\", get_object_format },\n \t{ \"path.commondir.absolute\", get_path_commondir_absolute },\n \t{ \"path.commondir.relative\", get_path_commondir_relative },\n+\t{ \"path.gitdir.absolute\", get_path_gitdir_absolute },\n+\t{ \"path.gitdir.relative\", get_path_gitdir_relative },\n \t{ \"references.format\", get_references_format },\n };\n \ndiff --git a/t/t1900-repo-info.sh b/t/t1900-repo-info.sh\nindex 28fe76e25b..26acb5fe82 100755\n--- a/t/t1900-repo-info.sh\n+++ b/t/t1900-repo-info.sh\n@@ -216,4 +216,11 @@ test_repo_info_path 'commondir with only GIT_DIR' 'commondir' \\\n \t'commondir-only-gitdir' '.git' '../.git' \\\n \t'GIT_DIR=\"../.git\" && export GIT_DIR'\n \n+test_repo_info_path 'gitdir standard' 'gitdir' 'gitdir-std' \\\n+\t'.git' '../.git'\n+\n+test_repo_info_path 'gitdir with explicit GIT_DIR' 'gitdir' \\\n+\t'gitdir-env' '.git' '../.git' \\\n+\t'GIT_DIR=\"../.git\" && export GIT_DIR'\n+\n test_done\n-- \n2.54.0\n\n"},{"id":"545506","messageId":"B0AAB2FE-5E7B-4C67-8A71-84C1A3ACBA44@gmail.com","threadId":"65725","inReplyTo":"20260612182847.562816-4-jayatheerthkulkarni2005@gmail.com","subject":"Re: [GSoC Patch v3 3/4] repo: add path.commondir with absolute and relative suffix formatting","fromName":"Lucas Seiki Oshiro","fromEmail":"lucasseikioshiro@gmail.com","sentAt":"2026-06-15T01:54:17Z","receivedAt":"2026-06-15T01:54:36Z","isPatch":true,"body":"\n> + test_expect_success \"absolute: $label\" '\n> + (\n> + cd \"$absolute_root/sub\" &&\n> + ROOT=\"$(test-tool path-utils real_path \"..\")\" && export ROOT &&\n\nBe carful with the quotes here. Actually, there's no need to use\nquotes around `..`, and export can be used directly with the env\nvar:\n\n\texport ROOT=\"$(test-tool path-utils real_path ..)\" &&\n\n> \n> + test_expect_success \"relative: $label\" '\n> + (\n> + cd \"$relative_root/sub\" &&\n> + ROOT=\"$(test-tool path-utils real_path \"..\")\" && export ROOT &&\n\nSame here.\n\n> +test_repo_info_path 'commondir with GIT_COMMON_DIR and GIT_DIR' 'commondir' \\\n> + 'commondir-envs' 'custom-common' '../custom-common' \\\n> + 'GIT_COMMON_DIR=\"$ROOT/custom-common\" && export GIT_COMMON_DIR &&\n> + GIT_DIR=\"../.git\" && export GIT_DIR &&\n\n\texport GIT_COMMON_DIR=\"$ROOT/custom-common\" &&\n\texport GIT_DIR=\"../.git\" &&"},{"id":"545507","messageId":"4F32FD89-2E90-4E85-BE8C-2E2BB4D9E890@gmail.com","threadId":"65725","inReplyTo":"20260612182847.562816-5-jayatheerthkulkarni2005@gmail.com","subject":"Re: [GSoC Patch v3 4/4] repo: add path.gitdir with absolute and relative suffix formatting","fromName":"Lucas Seiki Oshiro","fromEmail":"lucasseikioshiro@gmail.com","sentAt":"2026-06-15T01:55:28Z","receivedAt":"2026-06-15T01:55:42Z","isPatch":true,"body":"\n> +test_repo_info_path 'gitdir with explicit GIT_DIR' 'gitdir' \\\n> + 'gitdir-env' '.git' '../.git' \\\n> + 'GIT_DIR=\"../.git\" && export GIT_DIR'\n\n\t'export GIT_DIR=\"../.git\"\n\n"},{"id":"545508","messageId":"83C69466-3EC6-4C57-88B3-3342D63B97CD@gmail.com","threadId":"65725","inReplyTo":"20260612182847.562816-1-jayatheerthkulkarni2005@gmail.com","subject":"Re: [GSoC Patch v3 0/4] teach git repo info to handle path keys","fromName":"Lucas Seiki Oshiro","fromEmail":"lucasseikioshiro@gmail.com","sentAt":"2026-06-15T01:59:22Z","receivedAt":"2026-06-15T01:59:41Z","isPatch":true,"body":"Hi, Jayatheerth!\n\nI've left some comments in your tests. From my side\nyou only need to fix them.\n"},{"id":"545511","messageId":"20260615045112.50686-1-jayatheerthkulkarni2005@gmail.com","threadId":"65725","inReplyTo":"20260601151950.30686-1-jayatheerthkulkarni2005@gmail.com","subject":"[GSoC Patch v4 0/4] teach git repo info to handle path keys","fromName":"K Jayatheerth","fromEmail":"jayatheerthkulkarni2005@gmail.com","sentAt":"2026-06-15T04:51:08Z","receivedAt":"2026-06-15T04:51:47Z","isPatch":true,"body":"Hi!\n\nThis series teaches `git repo info` to handle `path.*`\nkeys, allowing scripts to reliably discover core\nrepository paths without resorting to `git rev-parse`.\n\nThe patches are structured as follows:\n\n1. path: Extract the localized path-formatting logic\n   out of `rev-parse` and expose it globally via\n   `path.h` using clear append semantics.\n\n2. rev-parse: Refactor the command to leverage the\n   newly shared path engine.\n\n3. repo: Introduce `path.commondir.absolute` and\n   `path.commondir.relative` alongside a robust,\n   isolated test helper.\n\n4. repo: Introduce `path.gitdir.absolute` and\n   `path.gitdir.relative` using the same standardized\n   formatting rules.\n\nSince all the questions were answered\nI have removed them from this cover letter.\n\nChanges since v3:\n* Removed unnecessary double quotes around the `..` argument when calling\n  `test-tool path-utils real_path` in the `test_repo_info_path` helper,\n  as suggested by Lucas.\n* Retained the POSIX-compliant `ROOT=\"...\" && export ROOT` syntax in the\n  test setup. Combining them into `export ROOT=\"...\"` triggered Git's\n  strict `test-lint-shell-syntax` portability checks, so the separate\n  assignment and export remains.\n\nK Jayatheerth (4):\n  path: introduce append_formatted_path() for shared path formatting\n  rev-parse: use append_formatted_path() for path formatting\n  repo: add path.commondir with absolute and relative suffix formatting\n  repo: add path.gitdir with absolute and relative suffix formatting\n\n Documentation/git-repo.adoc |  15 ++++++\n builtin/repo.c              |  50 +++++++++++++++++\n builtin/rev-parse.c         | 103 ++++++++----------------------------\n path.c                      |  70 ++++++++++++++++++++++++\n path.h                      |  36 +++++++++++++\n t/t1900-repo-info.sh        |  68 ++++++++++++++++++++++++\n 6 files changed, 262 insertions(+), 80 deletions(-)\n\nRange-diff against v3:\n1:  d276ac145e = 1:  a396b4f8e6 path: introduce append_formatted_path() for shared path formatting\n2:  5dba41bcb3 = 2:  16198f96d1 rev-parse: use append_formatted_path() for path formatting\n3:  b21c97f5d9 ! 3:  b45c6f0d12 repo: add path.commondir with absolute and relative suffix formatting\n    @@ t/t1900-repo-info.sh: test_expect_success 'git repo info -h shows only repo info\n     +\ttest_expect_success \"absolute: $label\" '\n     +\t\t(\n     +\t\t\tcd \"$absolute_root/sub\" &&\n    -+\t\t\tROOT=\"$(test-tool path-utils real_path \"..\")\" && export ROOT &&\n    ++\t\t\tROOT=\"$(test-tool path-utils real_path ..)\" && export ROOT &&\n     +\t\t\teval \"$init_command\" &&\n     +\t\t\texpect_path=\"$ROOT${expect_absolute_suffix:+/$expect_absolute_suffix}\" &&\n     +\t\t\techo \"path.$field_name.absolute=$expect_path\" >expect &&\n    @@ t/t1900-repo-info.sh: test_expect_success 'git repo info -h shows only repo info\n     +\ttest_expect_success \"relative: $label\" '\n     +\t\t(\n     +\t\t\tcd \"$relative_root/sub\" &&\n    -+\t\t\tROOT=\"$(test-tool path-utils real_path \"..\")\" && export ROOT &&\n    ++\t\t\tROOT=\"$(test-tool path-utils real_path ..)\" && export ROOT &&\n     +\t\t\teval \"$init_command\" &&\n     +\t\t\techo \"path.$field_name.relative=$expect_relative\" >expect &&\n     +\t\t\tgit repo info \"path.$field_name.relative\" >actual &&\n4:  fd7a899788 = 4:  b5234ffe3e repo: add path.gitdir with absolute and relative suffix formatting\n-- \n2.54.0\n"},{"id":"545512","messageId":"20260615045112.50686-2-jayatheerthkulkarni2005@gmail.com","threadId":"65725","inReplyTo":"20260615045112.50686-1-jayatheerthkulkarni2005@gmail.com","subject":"[GSoC Patch v4 1/4] path: introduce append_formatted_path() for shared path formatting","fromName":"K Jayatheerth","fromEmail":"jayatheerthkulkarni2005@gmail.com","sentAt":"2026-06-15T04:51:09Z","receivedAt":"2026-06-15T04:52:17Z","isPatch":true,"body":"The path-formatting logic in builtin/rev-parse.c is tightly coupled\nto that command and writes directly to stdout, making it impossible\nfor other builtins to reuse.\n\nExtract the core algorithm into append_formatted_path() in path.c\nand expose a path_format enum in path.h so that any builtin can\nformat paths consistently without duplicating logic.\n\nMentored-by: Justin Tobler <jltobler@gmail.com>\nMentored-by: Lucas Seiki Oshiro <lucasseikioshiro@gmail.com>\nSigned-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>\n---\n path.c | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n path.h | 36 ++++++++++++++++++++++++++++++\n 2 files changed, 106 insertions(+)\n\ndiff --git a/path.c b/path.c\nindex d7e17bf174..5e83e3e4f6 100644\n--- a/path.c\n+++ b/path.c\n@@ -1579,6 +1579,76 @@ char *xdg_cache_home(const char *filename)\n \treturn NULL;\n }\n \n+void append_formatted_path(struct strbuf *dest, const char *path,\n+\t\t\t   const char *prefix, enum path_format format)\n+{\n+\tswitch (format) {\n+\tcase PATH_FORMAT_DEFAULT:\n+\tcase PATH_FORMAT_UNMODIFIED:\n+\t\tstrbuf_addstr(dest, path);\n+\t\tbreak;\n+\n+\tcase PATH_FORMAT_RELATIVE: {\n+\t\tstruct strbuf relative_buf = STRBUF_INIT;\n+\t\tstruct strbuf real_path = STRBUF_INIT;\n+\t\tstruct strbuf real_prefix = STRBUF_INIT;\n+\t\tchar *cwd = NULL;\n+\n+\t\t/*\n+\t\t * We don't ever produce a relative path if prefix is NULL,\n+\t\t * so set the prefix to the current directory so that we can\n+\t\t * produce a relative path whenever possible.\n+\t\t */\n+\t\tif (!prefix)\n+\t\t\tprefix = cwd = xgetcwd();\n+\n+\t\tif (!is_absolute_path(path)) {\n+\t\t\tstrbuf_realpath_forgiving(&real_path, path, 1);\n+\t\t\tpath = real_path.buf;\n+\t\t}\n+\t\tif (!is_absolute_path(prefix)) {\n+\t\t\tstrbuf_realpath_forgiving(&real_prefix, prefix, 1);\n+\t\t\tprefix = real_prefix.buf;\n+\t\t}\n+\n+\t\tstrbuf_addstr(dest, relative_path(path, prefix, &relative_buf));\n+\n+\t\tstrbuf_release(&relative_buf);\n+\t\tstrbuf_release(&real_path);\n+\t\tstrbuf_release(&real_prefix);\n+\t\tfree(cwd);\n+\t\tbreak;\n+\t}\n+\n+\tcase PATH_FORMAT_RELATIVE_IF_SHARED: {\n+\t\tstruct strbuf relative_buf = STRBUF_INIT;\n+\n+\t\t/*\n+\t\t * If we're using RELATIVE_IF_SHARED mode, then we want an\n+\t\t * absolute path unless the two share a common prefix, so don't\n+\t\t * default the prefix to the current working directory. Doing so\n+\t\t * would cause a relative path to always be produced if possible.\n+\t\t */\n+\t\tstrbuf_addstr(dest, relative_path(path, prefix, &relative_buf));\n+\t\tstrbuf_release(&relative_buf);\n+\t\tbreak;\n+\t}\n+\n+\tcase PATH_FORMAT_CANONICAL: {\n+\t\tstruct strbuf canonical_buf = STRBUF_INIT;\n+\n+\t\tstrbuf_realpath_forgiving(&canonical_buf, path, 1);\n+\t\tstrbuf_addbuf(dest, &canonical_buf);\n+\n+\t\tstrbuf_release(&canonical_buf);\n+\t\tbreak;\n+\t}\n+\n+\tdefault:\n+\t\tBUG(\"unknown path_format value %d\", format);\n+\t}\n+}\n+\n REPO_GIT_PATH_FUNC(squash_msg, \"SQUASH_MSG\")\n REPO_GIT_PATH_FUNC(merge_msg, \"MERGE_MSG\")\n REPO_GIT_PATH_FUNC(merge_rr, \"MERGE_RR\")\ndiff --git a/path.h b/path.h\nindex 0434ba5e07..6aca53b100 100644\n--- a/path.h\n+++ b/path.h\n@@ -262,6 +262,42 @@ enum scld_error safe_create_leading_directories_no_share(char *path);\n int safe_create_file_with_leading_directories(struct repository *repo,\n \t\t\t\t\t      const char *path);\n \n+/**\n+ * The formatting strategy to apply when writing a path into a buffer.\n+ */\n+enum path_format {\n+\t/*\n+\t * Represents the default formatting behavior. Treated as\n+\t * PATH_FORMAT_UNMODIFIED by append_formatted_path().\n+\t */\n+\tPATH_FORMAT_DEFAULT,\n+\n+\t/* Output the path exactly as-is without any modifications. */\n+\tPATH_FORMAT_UNMODIFIED,\n+\n+\t/* Output a path relative to the provided directory prefix. */\n+\tPATH_FORMAT_RELATIVE,\n+\n+\t/* Output a relative path only if the path shares a root with the prefix. */\n+\tPATH_FORMAT_RELATIVE_IF_SHARED,\n+\n+\t/* Output a fully resolved, absolute canonical path. */\n+\tPATH_FORMAT_CANONICAL\n+};\n+\n+/**\n+ * Format a path according to the specified formatting strategy and append\n+ * the result to the given strbuf.\n+ *\n+ * `dest`   : The string buffer to append the formatted path to.\n+ * `path`   : The path string that needs to be formatted.\n+ * `prefix` : The directory prefix to calculate relative offsets against.\n+ * Pass NULL to default to the current working directory where applicable.\n+ * `format` : The formatting behavior rule to execute.\n+ */\n+void append_formatted_path(struct strbuf *dest, const char *path,\n+\t\t\t   const char *prefix, enum path_format format);\n+\n # ifdef USE_THE_REPOSITORY_VARIABLE\n #  include \"strbuf.h\"\n #  include \"repository.h\"\n-- \n2.54.0\n\n"},{"id":"545513","messageId":"20260615045112.50686-3-jayatheerthkulkarni2005@gmail.com","threadId":"65725","inReplyTo":"20260615045112.50686-1-jayatheerthkulkarni2005@gmail.com","subject":"[GSoC Patch v4 2/4] rev-parse: use append_formatted_path() for path formatting","fromName":"K Jayatheerth","fromEmail":"jayatheerthkulkarni2005@gmail.com","sentAt":"2026-06-15T04:51:10Z","receivedAt":"2026-06-15T04:52:39Z","isPatch":true,"body":"Now that path formatting logic lives in a shared helper, keeping a\nduplicate implementation in rev-parse is unnecessary and risks the\ntwo diverging over time.\n\nReplace the local format_type and default_type enums and the\nhand-rolled formatting logic with a call to append_formatted_path().\nIntroduce PATH_FORMAT_DEFAULT as the initial value of arg_path_format\nso that per-path fallback behavior is resolved in print_path() rather\nthan leaked into the shared helper.\n\nMentored-by: Justin Tobler <jltobler@gmail.com>\nMentored-by: Lucas Seiki Oshiro <lucasseikioshiro@gmail.com>\nSigned-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>\n---\n builtin/rev-parse.c | 103 ++++++++++----------------------------------\n 1 file changed, 23 insertions(+), 80 deletions(-)\n\ndiff --git a/builtin/rev-parse.c b/builtin/rev-parse.c\nindex 218b5f34d6..2dd35361f3 100644\n--- a/builtin/rev-parse.c\n+++ b/builtin/rev-parse.c\n@@ -632,73 +632,16 @@ static void handle_ref_opt(const char *pattern, const char *prefix)\n \tclear_ref_exclusions(&ref_excludes);\n }\n \n-enum format_type {\n-\t/* We would like a relative path. */\n-\tFORMAT_RELATIVE,\n-\t/* We would like a canonical absolute path. */\n-\tFORMAT_CANONICAL,\n-\t/* We would like the default behavior. */\n-\tFORMAT_DEFAULT,\n-};\n-\n-enum default_type {\n-\t/* Our default is a relative path. */\n-\tDEFAULT_RELATIVE,\n-\t/* Our default is a relative path if there's a shared root. */\n-\tDEFAULT_RELATIVE_IF_SHARED,\n-\t/* Our default is a canonical absolute path. */\n-\tDEFAULT_CANONICAL,\n-\t/* Our default is not to modify the item. */\n-\tDEFAULT_UNMODIFIED,\n-};\n-\n-static void print_path(const char *path, const char *prefix, enum format_type format, enum default_type def)\n+static void print_path(const char *path, const char *prefix,\n+\t\t       enum path_format arg_path_format, enum path_format def_format)\n {\n-\tchar *cwd = NULL;\n-\t/*\n-\t * We don't ever produce a relative path if prefix is NULL, so set the\n-\t * prefix to the current directory so that we can produce a relative\n-\t * path whenever possible.  If we're using RELATIVE_IF_SHARED mode, then\n-\t * we want an absolute path unless the two share a common prefix, so don't\n-\t * set it in that case, since doing so causes a relative path to always\n-\t * be produced if possible.\n-\t */\n-\tif (!prefix && (format != FORMAT_DEFAULT || def != DEFAULT_RELATIVE_IF_SHARED))\n-\t\tprefix = cwd = xgetcwd();\n-\tif (format == FORMAT_DEFAULT && def == DEFAULT_UNMODIFIED) {\n-\t\tputs(path);\n-\t} else if (format == FORMAT_RELATIVE ||\n-\t\t  (format == FORMAT_DEFAULT && def == DEFAULT_RELATIVE)) {\n-\t\t/*\n-\t\t * In order for relative_path to work as expected, we need to\n-\t\t * make sure that both paths are absolute paths.  If we don't,\n-\t\t * we can end up with an unexpected absolute path that the user\n-\t\t * didn't want.\n-\t\t */\n-\t\tstruct strbuf buf = STRBUF_INIT, realbuf = STRBUF_INIT, prefixbuf = STRBUF_INIT;\n-\t\tif (!is_absolute_path(path)) {\n-\t\t\tstrbuf_realpath_forgiving(&realbuf, path,  1);\n-\t\t\tpath = realbuf.buf;\n-\t\t}\n-\t\tif (!is_absolute_path(prefix)) {\n-\t\t\tstrbuf_realpath_forgiving(&prefixbuf, prefix, 1);\n-\t\t\tprefix = prefixbuf.buf;\n-\t\t}\n-\t\tputs(relative_path(path, prefix, &buf));\n-\t\tstrbuf_release(&buf);\n-\t\tstrbuf_release(&realbuf);\n-\t\tstrbuf_release(&prefixbuf);\n-\t} else if (format == FORMAT_DEFAULT && def == DEFAULT_RELATIVE_IF_SHARED) {\n-\t\tstruct strbuf buf = STRBUF_INIT;\n-\t\tputs(relative_path(path, prefix, &buf));\n-\t\tstrbuf_release(&buf);\n-\t} else {\n-\t\tstruct strbuf buf = STRBUF_INIT;\n-\t\tstrbuf_realpath_forgiving(&buf, path, 1);\n-\t\tputs(buf.buf);\n-\t\tstrbuf_release(&buf);\n-\t}\n-\tfree(cwd);\n+\tstruct strbuf sb = STRBUF_INIT;\n+\tenum path_format fmt = (arg_path_format != PATH_FORMAT_DEFAULT) ? arg_path_format : def_format;\n+\n+\tappend_formatted_path(&sb, path, prefix, fmt);\n+\tputs(sb.buf);\n+\n+\tstrbuf_release(&sb);\n }\n \n int cmd_rev_parse(int argc,\n@@ -717,7 +660,7 @@ int cmd_rev_parse(int argc,\n \tconst char *name = NULL;\n \tstruct strbuf buf = STRBUF_INIT;\n \tint seen_end_of_options = 0;\n-\tenum format_type format = FORMAT_DEFAULT;\n+\tenum path_format arg_path_format = PATH_FORMAT_DEFAULT;\n \n \tshow_usage_if_asked(argc, argv, builtin_rev_parse_usage);\n \n@@ -797,8 +740,8 @@ int cmd_rev_parse(int argc,\n \t\t\t\t\tdie(_(\"--git-path requires an argument\"));\n \t\t\t\tprint_path(repo_git_path_replace(the_repository, &buf,\n \t\t\t\t\t\t\t\t \"%s\", argv[i + 1]), prefix,\n-\t\t\t\t\t\tformat,\n-\t\t\t\t\t\tDEFAULT_RELATIVE_IF_SHARED);\n+\t\t\t\t\t\targ_path_format,\n+\t\t\t\t\t\tPATH_FORMAT_RELATIVE_IF_SHARED);\n \t\t\t\ti++;\n \t\t\t\tcontinue;\n \t\t\t}\n@@ -820,9 +763,9 @@ int cmd_rev_parse(int argc,\n \t\t\t\tif (!arg)\n \t\t\t\t\tdie(_(\"--path-format requires an argument\"));\n \t\t\t\tif (!strcmp(arg, \"absolute\")) {\n-\t\t\t\t\tformat = FORMAT_CANONICAL;\n+\t\t\t\t\targ_path_format = PATH_FORMAT_CANONICAL;\n \t\t\t\t} else if (!strcmp(arg, \"relative\")) {\n-\t\t\t\t\tformat = FORMAT_RELATIVE;\n+\t\t\t\t\targ_path_format = PATH_FORMAT_RELATIVE;\n \t\t\t\t} else {\n \t\t\t\t\tdie(_(\"unknown argument to --path-format: %s\"), arg);\n \t\t\t\t}\n@@ -985,7 +928,7 @@ int cmd_rev_parse(int argc,\n \t\t\tif (!strcmp(arg, \"--show-toplevel\")) {\n \t\t\t\tconst char *work_tree = repo_get_work_tree(the_repository);\n \t\t\t\tif (work_tree)\n-\t\t\t\t\tprint_path(work_tree, prefix, format, DEFAULT_UNMODIFIED);\n+\t\t\t\t\tprint_path(work_tree, prefix, arg_path_format, PATH_FORMAT_UNMODIFIED);\n \t\t\t\telse\n \t\t\t\t\tdie(_(\"this operation must be run in a work tree\"));\n \t\t\t\tcontinue;\n@@ -993,7 +936,7 @@ int cmd_rev_parse(int argc,\n \t\t\tif (!strcmp(arg, \"--show-superproject-working-tree\")) {\n \t\t\t\tstruct strbuf superproject = STRBUF_INIT;\n \t\t\t\tif (get_superproject_working_tree(&superproject))\n-\t\t\t\t\tprint_path(superproject.buf, prefix, format, DEFAULT_UNMODIFIED);\n+\t\t\t\t\tprint_path(superproject.buf, prefix, arg_path_format, PATH_FORMAT_UNMODIFIED);\n \t\t\t\tstrbuf_release(&superproject);\n \t\t\t\tcontinue;\n \t\t\t}\n@@ -1028,18 +971,18 @@ int cmd_rev_parse(int argc,\n \t\t\t\tconst char *gitdir = getenv(GIT_DIR_ENVIRONMENT);\n \t\t\t\tchar *cwd;\n \t\t\t\tint len;\n-\t\t\t\tenum format_type wanted = format;\n+\t\t\t\tenum path_format wanted = arg_path_format;\n \t\t\t\tif (arg[2] == 'g') {\t/* --git-dir */\n \t\t\t\t\tif (gitdir) {\n-\t\t\t\t\t\tprint_path(gitdir, prefix, format, DEFAULT_UNMODIFIED);\n+\t\t\t\t\t\tprint_path(gitdir, prefix, arg_path_format, PATH_FORMAT_UNMODIFIED);\n \t\t\t\t\t\tcontinue;\n \t\t\t\t\t}\n \t\t\t\t\tif (!prefix) {\n-\t\t\t\t\t\tprint_path(\".git\", prefix, format, DEFAULT_UNMODIFIED);\n+\t\t\t\t\t\tprint_path(\".git\", prefix, arg_path_format, PATH_FORMAT_UNMODIFIED);\n \t\t\t\t\t\tcontinue;\n \t\t\t\t\t}\n \t\t\t\t} else {\t\t/* --absolute-git-dir */\n-\t\t\t\t\twanted = FORMAT_CANONICAL;\n+\t\t\t\t\twanted = PATH_FORMAT_CANONICAL;\n \t\t\t\t\tif (!gitdir && !prefix)\n \t\t\t\t\t\tgitdir = \".git\";\n \t\t\t\t\tif (gitdir) {\n@@ -1055,11 +998,11 @@ int cmd_rev_parse(int argc,\n \t\t\t\tstrbuf_reset(&buf);\n \t\t\t\tstrbuf_addf(&buf, \"%s%s.git\", cwd, len && cwd[len-1] != '/' ? \"/\" : \"\");\n \t\t\t\tfree(cwd);\n-\t\t\t\tprint_path(buf.buf, prefix, wanted, DEFAULT_CANONICAL);\n+\t\t\t\tprint_path(buf.buf, prefix, wanted, PATH_FORMAT_CANONICAL);\n \t\t\t\tcontinue;\n \t\t\t}\n \t\t\tif (!strcmp(arg, \"--git-common-dir\")) {\n-\t\t\t\tprint_path(repo_get_common_dir(the_repository), prefix, format, DEFAULT_RELATIVE_IF_SHARED);\n+\t\t\t\tprint_path(repo_get_common_dir(the_repository), prefix, arg_path_format, PATH_FORMAT_RELATIVE_IF_SHARED);\n \t\t\t\tcontinue;\n \t\t\t}\n \t\t\tif (!strcmp(arg, \"--is-inside-git-dir\")) {\n@@ -1089,7 +1032,7 @@ int cmd_rev_parse(int argc,\n \t\t\t\tif (the_repository->index->split_index) {\n \t\t\t\t\tconst struct object_id *oid = &the_repository->index->split_index->base_oid;\n \t\t\t\t\tconst char *path = repo_git_path_replace(the_repository, &buf, \"sharedindex.%s\", oid_to_hex(oid));\n-\t\t\t\t\tprint_path(path, prefix, format, DEFAULT_RELATIVE);\n+\t\t\t\t\tprint_path(path, prefix, arg_path_format, PATH_FORMAT_RELATIVE);\n \t\t\t\t}\n \t\t\t\tcontinue;\n \t\t\t}\n-- \n2.54.0\n\n"},{"id":"545514","messageId":"20260615045112.50686-4-jayatheerthkulkarni2005@gmail.com","threadId":"65725","inReplyTo":"20260615045112.50686-1-jayatheerthkulkarni2005@gmail.com","subject":"[GSoC Patch v4 3/4] repo: add path.commondir with absolute and relative suffix formatting","fromName":"K Jayatheerth","fromEmail":"jayatheerthkulkarni2005@gmail.com","sentAt":"2026-06-15T04:51:11Z","receivedAt":"2026-06-15T04:52:50Z","isPatch":true,"body":"Scripts working with worktree setups need a reliable way to discover\nthe common directory, which diverges from the git directory when\nmultiple worktrees are in use. There is no way to retrieve this path\nfrom git repo info today.\n\nIntroduce path.commondir.absolute and path.commondir.relative keys.\nExposing explicit format variants rather than a single key with a\ndefault avoids ambiguity for scripts that require predictable output.\n\nAdd a test helper test_repo_info_path that creates isolated\nrepositories per test case to prevent state leaks, captures the repo\nroot before changing directories to avoid eval, and accepts an optional\ninit_command to cover environment variable overrides such as\nGIT_COMMON_DIR and GIT_DIR.\n\nMentored-by: Justin Tobler <jltobler@gmail.com>\nMentored-by: Lucas Seiki Oshiro <lucasseikioshiro@gmail.com>\nSigned-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>\n---\n Documentation/git-repo.adoc |  9 ++++++\n builtin/repo.c              | 26 ++++++++++++++++\n t/t1900-repo-info.sh        | 61 +++++++++++++++++++++++++++++++++++++\n 3 files changed, 96 insertions(+)\n\ndiff --git a/Documentation/git-repo.adoc b/Documentation/git-repo.adoc\nindex 42262c1983..890c34051d 100644\n--- a/Documentation/git-repo.adoc\n+++ b/Documentation/git-repo.adoc\n@@ -104,6 +104,15 @@ values that they return:\n `object.format`::\n \tThe object format (hash algorithm) used in the repository.\n \n+`path.commondir.absolute`::\n+\tThe canonical absolute path to the Git repository's common\n+\tdirectory (the shared `.git` directory containing objects,\n+\trefs, and global configuration).\n+\n+`path.commondir.relative`::\n+\tThe path to the Git repository's common directory relative to\n+\tthe current working directory.\n+\n `references.format`::\n \tThe reference storage format. The valid values are:\n +\ndiff --git a/builtin/repo.c b/builtin/repo.c\nindex 71a5c1c29c..c4cc3bf3fc 100644\n--- a/builtin/repo.c\n+++ b/builtin/repo.c\n@@ -7,12 +7,14 @@\n #include \"hex.h\"\n #include \"odb.h\"\n #include \"parse-options.h\"\n+#include \"path.h\"\n #include \"path-walk.h\"\n #include \"progress.h\"\n #include \"quote.h\"\n #include \"ref-filter.h\"\n #include \"refs.h\"\n #include \"revision.h\"\n+#include \"setup.h\"\n #include \"strbuf.h\"\n #include \"string-list.h\"\n #include \"shallow.h\"\n@@ -75,6 +77,28 @@ static int get_object_format(struct repository *repo, struct strbuf *buf)\n \treturn 0;\n }\n \n+static int get_path_commondir_absolute(struct repository *repo, struct strbuf *buf)\n+{\n+\tconst char *common_dir = repo_get_common_dir(repo);\n+\n+\tif (!common_dir)\n+\t\treturn error(_(\"unable to get common directory\"));\n+\n+\tappend_formatted_path(buf, common_dir, startup_info->prefix, PATH_FORMAT_CANONICAL);\n+\treturn 0;\n+}\n+\n+static int get_path_commondir_relative(struct repository *repo, struct strbuf *buf)\n+{\n+\tconst char *common_dir = repo_get_common_dir(repo);\n+\n+\tif (!common_dir)\n+\t\treturn error(_(\"unable to get common directory\"));\n+\n+\tappend_formatted_path(buf, common_dir, startup_info->prefix, PATH_FORMAT_RELATIVE);\n+\treturn 0;\n+}\n+\n static int get_references_format(struct repository *repo, struct strbuf *buf)\n {\n \tstrbuf_addstr(buf,\n@@ -87,6 +111,8 @@ static const struct repo_info_field repo_info_field[] = {\n \t{ \"layout.bare\", get_layout_bare },\n \t{ \"layout.shallow\", get_layout_shallow },\n \t{ \"object.format\", get_object_format },\n+\t{ \"path.commondir.absolute\", get_path_commondir_absolute },\n+\t{ \"path.commondir.relative\", get_path_commondir_relative },\n \t{ \"references.format\", get_references_format },\n };\n \ndiff --git a/t/t1900-repo-info.sh b/t/t1900-repo-info.sh\nindex 39bb77dda0..0c0228687f 100755\n--- a/t/t1900-repo-info.sh\n+++ b/t/t1900-repo-info.sh\n@@ -155,4 +155,65 @@ test_expect_success 'git repo info -h shows only repo info usage' '\n \ttest_grep ! \"git repo structure\" actual\n '\n \n+# Helper function to test path keys in both absolute and relative formats.\n+# $1: label for the test\n+# $2: field_name (e.g., commondir)\n+# $3: unique repo name for isolation\n+# $4: expect_absolute (suffix appended to repo root)\n+# $5: expect_relative (the relative path string expected)\n+# $6: init_command (extra setup like exporting env vars)\n+test_repo_info_path () {\n+\tlabel=$1\n+\tfield_name=$2\n+\trepo_name=$3\n+\texpect_absolute_suffix=$4\n+\texpect_relative=$5\n+\tinit_command=$6\n+\n+\tabsolute_root=\"$repo_name-absolute\"\n+\trelative_root=\"$repo_name-relative\"\n+\n+\ttest_expect_success \"setup: $label\" '\n+\t\tgit init \"$absolute_root\" &&\n+\t\tgit init \"$relative_root\" &&\n+\t\tmkdir -p \"$absolute_root/sub\" \"$relative_root/sub\"\n+\t'\n+\n+\ttest_expect_success \"absolute: $label\" '\n+\t\t(\n+\t\t\tcd \"$absolute_root/sub\" &&\n+\t\t\tROOT=\"$(test-tool path-utils real_path ..)\" && export ROOT &&\n+\t\t\teval \"$init_command\" &&\n+\t\t\texpect_path=\"$ROOT${expect_absolute_suffix:+/$expect_absolute_suffix}\" &&\n+\t\t\techo \"path.$field_name.absolute=$expect_path\" >expect &&\n+\t\t\tgit repo info \"path.$field_name.absolute\" >actual &&\n+\t\t\ttest_cmp expect actual\n+\t\t)\n+\t'\n+\n+\ttest_expect_success \"relative: $label\" '\n+\t\t(\n+\t\t\tcd \"$relative_root/sub\" &&\n+\t\t\tROOT=\"$(test-tool path-utils real_path ..)\" && export ROOT &&\n+\t\t\teval \"$init_command\" &&\n+\t\t\techo \"path.$field_name.relative=$expect_relative\" >expect &&\n+\t\t\tgit repo info \"path.$field_name.relative\" >actual &&\n+\t\t\ttest_cmp expect actual\n+\t\t)\n+\t'\n+}\n+\n+test_repo_info_path 'commondir standard' 'commondir' 'commondir-std' \\\n+\t'.git' '../.git'\n+\n+test_repo_info_path 'commondir with GIT_COMMON_DIR and GIT_DIR' 'commondir' \\\n+\t'commondir-envs' 'custom-common' '../custom-common' \\\n+\t'GIT_COMMON_DIR=\"$ROOT/custom-common\" && export GIT_COMMON_DIR &&\n+\t GIT_DIR=\"../.git\" && export GIT_DIR &&\n+\t git init --bare \"$ROOT/custom-common\"'\n+\n+test_repo_info_path 'commondir with only GIT_DIR' 'commondir' \\\n+\t'commondir-only-gitdir' '.git' '../.git' \\\n+\t'GIT_DIR=\"../.git\" && export GIT_DIR'\n+\n test_done\n-- \n2.54.0\n\n"},{"id":"545515","messageId":"20260615045112.50686-5-jayatheerthkulkarni2005@gmail.com","threadId":"65725","inReplyTo":"20260615045112.50686-1-jayatheerthkulkarni2005@gmail.com","subject":"[GSoC Patch v4 4/4] repo: add path.gitdir with absolute and relative suffix formatting","fromName":"K Jayatheerth","fromEmail":"jayatheerthkulkarni2005@gmail.com","sentAt":"2026-06-15T04:51:12Z","receivedAt":"2026-06-15T04:53:01Z","isPatch":true,"body":"Scripts need a stable way to locate the git directory without\nparsing rev-parse output or relying on its flag-driven path format\nselection. There is no way to retrieve this path from git repo info\ntoday.\n\nIntroduce path.gitdir.absolute and path.gitdir.relative keys,\nconsistent with the path.commondir keys added in the previous patch.\nReuse the test_repo_info_path helper introduced there to validate\nboth variants.\n\nMentored-by: Justin Tobler <jltobler@gmail.com>\nMentored-by: Lucas Seiki Oshiro <lucasseikioshiro@gmail.com>\nSigned-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>\n---\n Documentation/git-repo.adoc |  6 ++++++\n builtin/repo.c              | 24 ++++++++++++++++++++++++\n t/t1900-repo-info.sh        |  7 +++++++\n 3 files changed, 37 insertions(+)\n\ndiff --git a/Documentation/git-repo.adoc b/Documentation/git-repo.adoc\nindex 890c34051d..ed7d80c690 100644\n--- a/Documentation/git-repo.adoc\n+++ b/Documentation/git-repo.adoc\n@@ -113,6 +113,12 @@ values that they return:\n \tThe path to the Git repository's common directory relative to\n \tthe current working directory.\n \n+`path.gitdir.absolute`::\n+\tThe canonical absolute path to the Git repository directory (the `.git` directory).\n+\n+`path.gitdir.relative`::\n+\tThe path to the Git repository directory relative to the current working directory.\n+\n `references.format`::\n \tThe reference storage format. The valid values are:\n +\ndiff --git a/builtin/repo.c b/builtin/repo.c\nindex c4cc3bf3fc..9a312d127a 100644\n--- a/builtin/repo.c\n+++ b/builtin/repo.c\n@@ -99,6 +99,28 @@ static int get_path_commondir_relative(struct repository *repo, struct strbuf *b\n \treturn 0;\n }\n \n+static int get_path_gitdir_absolute(struct repository *repo, struct strbuf *buf)\n+{\n+\tconst char *git_dir = repo_get_git_dir(repo);\n+\n+\tif (!git_dir)\n+\t\treturn error(_(\"unable to get git directory\"));\n+\n+\tappend_formatted_path(buf, git_dir, startup_info->prefix, PATH_FORMAT_CANONICAL);\n+\treturn 0;\n+}\n+\n+static int get_path_gitdir_relative(struct repository *repo, struct strbuf *buf)\n+{\n+\tconst char *git_dir = repo_get_git_dir(repo);\n+\n+\tif (!git_dir)\n+\t\treturn error(_(\"unable to get git directory\"));\n+\n+\tappend_formatted_path(buf, git_dir, startup_info->prefix, PATH_FORMAT_RELATIVE);\n+\treturn 0;\n+}\n+\n static int get_references_format(struct repository *repo, struct strbuf *buf)\n {\n \tstrbuf_addstr(buf,\n@@ -113,6 +135,8 @@ static const struct repo_info_field repo_info_field[] = {\n \t{ \"object.format\", get_object_format },\n \t{ \"path.commondir.absolute\", get_path_commondir_absolute },\n \t{ \"path.commondir.relative\", get_path_commondir_relative },\n+\t{ \"path.gitdir.absolute\", get_path_gitdir_absolute },\n+\t{ \"path.gitdir.relative\", get_path_gitdir_relative },\n \t{ \"references.format\", get_references_format },\n };\n \ndiff --git a/t/t1900-repo-info.sh b/t/t1900-repo-info.sh\nindex 0c0228687f..45741fc9f1 100755\n--- a/t/t1900-repo-info.sh\n+++ b/t/t1900-repo-info.sh\n@@ -216,4 +216,11 @@ test_repo_info_path 'commondir with only GIT_DIR' 'commondir' \\\n \t'commondir-only-gitdir' '.git' '../.git' \\\n \t'GIT_DIR=\"../.git\" && export GIT_DIR'\n \n+test_repo_info_path 'gitdir standard' 'gitdir' 'gitdir-std' \\\n+\t'.git' '../.git'\n+\n+test_repo_info_path 'gitdir with explicit GIT_DIR' 'gitdir' \\\n+\t'gitdir-env' '.git' '../.git' \\\n+\t'GIT_DIR=\"../.git\" && export GIT_DIR'\n+\n test_done\n-- \n2.54.0\n\n"},{"id":"545604","messageId":"ajAy1it6CGDQzVes@denethor","threadId":"65725","inReplyTo":"20260615045112.50686-3-jayatheerthkulkarni2005@gmail.com","subject":"Re: [GSoC Patch v4 2/4] rev-parse: use append_formatted_path() for path formatting","fromName":"Justin Tobler","fromEmail":"jltobler@gmail.com","sentAt":"2026-06-15T17:18:18Z","receivedAt":"2026-06-15T17:18:20Z","isPatch":true,"body":"On 26/06/15 10:21AM, K Jayatheerth wrote:\n[snip]\n> -static void print_path(const char *path, const char *prefix, enum format_type format, enum default_type def)\n> +static void print_path(const char *path, const char *prefix,\n> +\t\t       enum path_format arg_path_format, enum path_format def_format)\n>  {\n> -\tchar *cwd = NULL;\n> -\t/*\n> -\t * We don't ever produce a relative path if prefix is NULL, so set the\n> -\t * prefix to the current directory so that we can produce a relative\n> -\t * path whenever possible.  If we're using RELATIVE_IF_SHARED mode, then\n> -\t * we want an absolute path unless the two share a common prefix, so don't\n> -\t * set it in that case, since doing so causes a relative path to always\n> -\t * be produced if possible.\n> -\t */\n> -\tif (!prefix && (format != FORMAT_DEFAULT || def != DEFAULT_RELATIVE_IF_SHARED))\n> -\t\tprefix = cwd = xgetcwd();\n> -\tif (format == FORMAT_DEFAULT && def == DEFAULT_UNMODIFIED) {\n> -\t\tputs(path);\n> -\t} else if (format == FORMAT_RELATIVE ||\n> -\t\t  (format == FORMAT_DEFAULT && def == DEFAULT_RELATIVE)) {\n> -\t\t/*\n> -\t\t * In order for relative_path to work as expected, we need to\n> -\t\t * make sure that both paths are absolute paths.  If we don't,\n> -\t\t * we can end up with an unexpected absolute path that the user\n> -\t\t * didn't want.\n> -\t\t */\n> -\t\tstruct strbuf buf = STRBUF_INIT, realbuf = STRBUF_INIT, prefixbuf = STRBUF_INIT;\n> -\t\tif (!is_absolute_path(path)) {\n> -\t\t\tstrbuf_realpath_forgiving(&realbuf, path,  1);\n> -\t\t\tpath = realbuf.buf;\n> -\t\t}\n> -\t\tif (!is_absolute_path(prefix)) {\n> -\t\t\tstrbuf_realpath_forgiving(&prefixbuf, prefix, 1);\n> -\t\t\tprefix = prefixbuf.buf;\n> -\t\t}\n> -\t\tputs(relative_path(path, prefix, &buf));\n> -\t\tstrbuf_release(&buf);\n> -\t\tstrbuf_release(&realbuf);\n> -\t\tstrbuf_release(&prefixbuf);\n> -\t} else if (format == FORMAT_DEFAULT && def == DEFAULT_RELATIVE_IF_SHARED) {\n> -\t\tstruct strbuf buf = STRBUF_INIT;\n> -\t\tputs(relative_path(path, prefix, &buf));\n> -\t\tstrbuf_release(&buf);\n> -\t} else {\n> -\t\tstruct strbuf buf = STRBUF_INIT;\n> -\t\tstrbuf_realpath_forgiving(&buf, path, 1);\n> -\t\tputs(buf.buf);\n> -\t\tstrbuf_release(&buf);\n> -\t}\n> -\tfree(cwd);\n> +\tstruct strbuf sb = STRBUF_INIT;\n> +\tenum path_format fmt = (arg_path_format != PATH_FORMAT_DEFAULT) ? arg_path_format : def_format;\n\nWithout context, it might be a bit confusing to readers as to why we\noverride PATH_FORMAT_DEFAULT without our own provided default. It may be\nworth leaving a comment to provide some breadcrumbs.\n\nThe rest of this patch looks good to me.\n\n-Justin\n"},{"id":"545606","messageId":"ajAz5izOU1FSVrKW@denethor","threadId":"65725","inReplyTo":"20260615045112.50686-4-jayatheerthkulkarni2005@gmail.com","subject":"Re: [GSoC Patch v4 3/4] repo: add path.commondir with absolute and relative suffix formatting","fromName":"Justin Tobler","fromEmail":"jltobler@gmail.com","sentAt":"2026-06-15T18:17:33Z","receivedAt":"2026-06-15T18:17:38Z","isPatch":true,"body":"On 26/06/15 10:21AM, K Jayatheerth wrote:\n> Scripts working with worktree setups need a reliable way to discover\n> the common directory, which diverges from the git directory when\n> multiple worktrees are in use. There is no way to retrieve this path\n> from git repo info today.\n> \n> Introduce path.commondir.absolute and path.commondir.relative keys.\n> Exposing explicit format variants rather than a single key with a\n> default avoids ambiguity for scripts that require predictable output.\n> \n> Add a test helper test_repo_info_path that creates isolated\n> repositories per test case to prevent state leaks, captures the repo\n> root before changing directories to avoid eval, and accepts an optional\n> init_command to cover environment variable overrides such as\n> GIT_COMMON_DIR and GIT_DIR.\n\nI'm not sure this last paragraph in the log message provides much value.\nTo me it's a bit verbose and focuses mostly on what the test helper is\ndoing. Maybe we can just omit this section? If we want to have a note\nthough maybe we could say something like:\n\n  Each path key is expected to have an absolute and relative form. To\n  reduce duplication, a test_repo_info_path helper function is\n  introduced to configure and exercise both cases.\n\n> Mentored-by: Justin Tobler <jltobler@gmail.com>\n> Mentored-by: Lucas Seiki Oshiro <lucasseikioshiro@gmail.com>\n> Signed-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>\n> ---\n[snip]\n> diff --git a/t/t1900-repo-info.sh b/t/t1900-repo-info.sh\n> index 39bb77dda0..0c0228687f 100755\n> --- a/t/t1900-repo-info.sh\n> +++ b/t/t1900-repo-info.sh\n> @@ -155,4 +155,65 @@ test_expect_success 'git repo info -h shows only repo info usage' '\n>  \ttest_grep ! \"git repo structure\" actual\n>  '\n>  \n> +# Helper function to test path keys in both absolute and relative formats.\n> +# $1: label for the test\n> +# $2: field_name (e.g., commondir)\n> +# $3: unique repo name for isolation\n> +# $4: expect_absolute (suffix appended to repo root)\n> +# $5: expect_relative (the relative path string expected)\n> +# $6: init_command (extra setup like exporting env vars)\n> +test_repo_info_path () {\n> +\tlabel=$1\n> +\tfield_name=$2\n> +\trepo_name=$3\n> +\texpect_absolute_suffix=$4\n> +\texpect_relative=$5\n> +\tinit_command=$6\n\nI may be overthinking it, but I can't help but feel this test helper is\noverly complicated. I wonder if we can simlify and reduce the number of\narguments. For example, could we programatically construct the label\nfrom the field name and init_command instead of explicitly passing it?\n\n> +\tabsolute_root=\"$repo_name-absolute\"\n> +\trelative_root=\"$repo_name-relative\"\n> +\n> +\ttest_expect_success \"setup: $label\" '\n> +\t\tgit init \"$absolute_root\" &&\n> +\t\tgit init \"$relative_root\" &&\n> +\t\tmkdir -p \"$absolute_root/sub\" \"$relative_root/sub\"\n> +\t'\n\nDo really need this setup test case? Could we instead embed the setup in\nboth test cases below? Something like:\n\n\ttest_when_finished rm -rf repo &&\n\tgit init repo &&\n\t(\n\t  mkdir repo/sub &&\n\t  cd repo/sub &&\n\t  ...\n\t)\n\nWith something like this, each test case is responsible to creating its\nown repo and cleaning it up when finished. Then we could avoid have to\nprovide a separate repo name for each set of test cases and remove the\nrepo_name argument.\n\n> +\ttest_expect_success \"absolute: $label\" '\n> +\t\t(\n> +\t\t\tcd \"$absolute_root/sub\" &&\n> +\t\t\tROOT=\"$(test-tool path-utils real_path ..)\" && export ROOT &&\n> +\t\t\teval \"$init_command\" &&\n> +\t\t\texpect_path=\"$ROOT${expect_absolute_suffix:+/$expect_absolute_suffix}\" &&\n> +\t\t\techo \"path.$field_name.absolute=$expect_path\" >expect &&\n> +\t\t\tgit repo info \"path.$field_name.absolute\" >actual &&\n> +\t\t\ttest_cmp expect actual\n> +\t\t)\n> +\t'\n> +\n> +\ttest_expect_success \"relative: $label\" '\n> +\t\t(\n> +\t\t\tcd \"$relative_root/sub\" &&\n> +\t\t\tROOT=\"$(test-tool path-utils real_path ..)\" && export ROOT &&\n> +\t\t\teval \"$init_command\" &&\n> +\t\t\techo \"path.$field_name.relative=$expect_relative\" >expect &&\n> +\t\t\tgit repo info \"path.$field_name.relative\" >actual &&\n> +\t\t\ttest_cmp expect actual\n> +\t\t)\n> +\t'\n> +}\n> +\n> +test_repo_info_path 'commondir standard' 'commondir' 'commondir-std' \\\n> +\t'.git' '../.git'\n> +\n> +test_repo_info_path 'commondir with GIT_COMMON_DIR and GIT_DIR' 'commondir' \\\n> +\t'commondir-envs' 'custom-common' '../custom-common' \\\n> +\t'GIT_COMMON_DIR=\"$ROOT/custom-common\" && export GIT_COMMON_DIR &&\n> +\t GIT_DIR=\"../.git\" && export GIT_DIR &&\n> +\t git init --bare \"$ROOT/custom-common\"'\n> +\n> +test_repo_info_path 'commondir with only GIT_DIR' 'commondir' \\\n> +\t'commondir-only-gitdir' '.git' '../.git' \\\n\nFor each of these test cases, the `expect_absolute_suffix` and\n`expect_relative` and exactly the same. This also appears to be the case\nfor the test cases in the next patch. Do these really need to be\nconfigurable at all? Can we just embed them directly in each test case\nassertion? Or maybe future keys will need this to be configurable?\n\n> +\t'GIT_DIR=\"../.git\" && export GIT_DIR'\n> +\n>  test_done\n\nOverall the rest of the patch looks good to me.\n\n-Justin\n"},{"id":"545619","messageId":"CA+rGoLf6Tj-j0r3cCReBaKK5bGFUALJ638-yPi2GSoRML0kbgA@mail.gmail.com","threadId":"65725","inReplyTo":"ajAy1it6CGDQzVes@denethor","subject":"Re: [GSoC Patch v4 2/4] rev-parse: use append_formatted_path() for path formatting","fromName":"K Jayatheerth","fromEmail":"jayatheerthkulkarni2005@gmail.com","sentAt":"2026-06-16T04:19:11Z","receivedAt":"2026-06-16T04:19:26Z","isPatch":true,"body":"Hey Justin,\n\n>\n> Without context, it might be a bit confusing to readers as to why we\n> override PATH_FORMAT_DEFAULT without our own provided default. It may be\n> worth leaving a comment to provide some breadcrumbs.\n>\n> The rest of this patch looks good to me.\n\nThat makes sense.\nThat's a minor change.\nI will add proper comments!\n\n\n> > Add a test helper test_repo_info_path that creates isolated\n> > repositories per test case to prevent state leaks, captures the repo\n> > root before changing directories to avoid eval, and accepts an optional\n> > init_command to cover environment variable overrides such as\n> > GIT_COMMON_DIR and GIT_DIR.\n>\n> I'm not sure this last paragraph in the log message provides much value.\n> To me it's a bit verbose and focuses mostly on what the test helper is\n> doing. Maybe we can just omit this section? If we want to have a note\n> though maybe we could say something like:\n>\n>   Each path key is expected to have an absolute and relative form. To\n>   reduce duplication, a test_repo_info_path helper function is\n>   introduced to configure and exercise both cases.\n>\n\nNow that I think about it\nMaybe removing it is a better option.\n\nI mean the patch itself contains the test and it has comments explaining\nthe test itself.\n\nI am gonna remove the last para in the next series.\nThanks for pointing that out!\n\n\n> > +test_repo_info_path () {\n> > +     label=$1\n> > +     field_name=$2\n> > +     repo_name=$3\n> > +     expect_absolute_suffix=$4\n> > +     expect_relative=$5\n> > +     init_command=$6\n>\n> I may be overthinking it, but I can't help but feel this test helper is\n> overly complicated. I wonder if we can simlify and reduce the number of\n> arguments. For example, could we programatically construct the label\n> from the field name and init_command instead of explicitly passing it?\n>\n\nThat’s a fair question\nBut I personally don't think the helper is overly complicated.\nI think a lot of the current helper can be mapped with test_repo_info's\nstructure itself.\n\nThe existing helper uses a very similar 5-argument signature (label,\ninit_command, repo_name, key, expected_value)\nand separates the setup step from the assertion steps.\n\nRegarding the labels, I'd prefer to keep them explicitly passed in.\nProgrammatically constructing the label from the init_command could\nresult in messy\nor hard-to-read test descriptions in the console output,\nand having explicit strings makes it much easier to debug when a\nspecific test fails.\n\n\n> > +     absolute_root=\"$repo_name-absolute\"\n> > +     relative_root=\"$repo_name-relative\"\n> > +\n> > +     test_expect_success \"setup: $label\" '\n> > +             git init \"$absolute_root\" &&\n> > +             git init \"$relative_root\" &&\n> > +             mkdir -p \"$absolute_root/sub\" \"$relative_root/sub\"\n> > +     '\n>\n> Do really need this setup test case? Could we instead embed the setup in\n> both test cases below? Something like:\n>\n>         test_when_finished rm -rf repo &&\n>         git init repo &&\n>         (\n>           mkdir repo/sub &&\n>           cd repo/sub &&\n>           ...\n>         )\n>\n\nThat's a much more elegant way to handle it.\nI will incorporate this in v5!\n\n> With something like this, each test case is responsible to creating its\n> own repo and cleaning it up when finished. Then we could avoid have to\n> provide a separate repo name for each set of test cases and remove the\n> repo_name argument.\n>\n\nTrue,\nThanks!\n\n\n> > +test_repo_info_path 'commondir standard' 'commondir' 'commondir-std' \\\n> > +     '.git' '../.git'\n> > +\n> > +test_repo_info_path 'commondir with GIT_COMMON_DIR and GIT_DIR' 'commondir' \\\n> > +     'commondir-envs' 'custom-common' '../custom-common' \\\n> > +     'GIT_COMMON_DIR=\"$ROOT/custom-common\" && export GIT_COMMON_DIR &&\n> > +      GIT_DIR=\"../.git\" && export GIT_DIR &&\n> > +      git init --bare \"$ROOT/custom-common\"'\n> > +\n> > +test_repo_info_path 'commondir with only GIT_DIR' 'commondir' \\\n> > +     'commondir-only-gitdir' '.git' '../.git' \\\n>\n> For each of these test cases, the `expect_absolute_suffix` and\n> `expect_relative` and exactly the same. This also appears to be the case\n> for the test cases in the next patch. Do these really need to be\n> configurable at all? Can we just embed them directly in each test case\n> assertion? Or maybe future keys will need this to be configurable?\n>\n\nYou're right that passing both is completely redundant!\nHowever, the path does still need to be configurable because the directory\nname changes between test cases (e.g., `.git` in the standard case vs.\n`custom-common` when GIT_COMMON_DIR is exported).\n\nSince the relative path is always just `../` appended to the directory name,\nI will condense these two arguments into a single `expected_dir` argument in v5.\nThe helper will then just construct `$ROOT/$expected_dir` and\n`../$expected_dir` internally.\n\n> > +     'GIT_DIR=\"../.git\" && export GIT_DIR'\n> > +\n> >  test_done\n>\n> Overall the rest of the patch looks good to me.\n>\n\nThanks again!\nThese are helpful.\n\nRegards,\n- K Jayatheerth\n"},{"id":"545620","messageId":"20260616044953.184806-1-jayatheerthkulkarni2005@gmail.com","threadId":"65725","inReplyTo":"20260601151950.30686-1-jayatheerthkulkarni2005@gmail.com","subject":"[GSoC Patch v5 0/4] teach git repo info to handle path keys","fromName":"K Jayatheerth","fromEmail":"jayatheerthkulkarni2005@gmail.com","sentAt":"2026-06-16T04:49:49Z","receivedAt":"2026-06-16T04:50:53Z","isPatch":true,"body":"Hi!\n\nThis series teaches `git repo info` to handle `path.*`\nkeys, allowing scripts to reliably discover core\nrepository paths without resorting to `git rev-parse`.\n\nThe patches are structured as follows:\n\n1. path: Extract the localized path-formatting logic\n   out of `rev-parse` and expose it globally via\n   `path.h` using clear append semantics.\n\n2. rev-parse: Refactor the command to leverage the\n   newly shared path engine.\n\n3. repo: Introduce `path.commondir.absolute` and\n   `path.commondir.relative` alongside a robust,\n   isolated test helper.\n\n4. repo: Introduce `path.gitdir.absolute` and\n   `path.gitdir.relative` using the same standardized\n   formatting rules.\n\nchanges since v4:\n* Simplified the `test_repo_info_path` helper by dropping the `repo_name` \n  argument and utilizing `test_when_finished \"rm -rf repo\"` to handle \n  repository setup/teardown inline. This ensures perfect test isolation.\n* Condensed the redundant `expect_absolute_suffix` and `expect_relative` \n  test helper arguments into a single `expected_dir` argument, reducing \n  the helper signature to 4 arguments (Justin).\n* Added a contextual comment in `builtin/rev-parse.c`'s `print_path()` \n  explaining why `PATH_FORMAT_DEFAULT` is intercepted and overridden with \n  a path-specific fallback (Justin).\n* Trimmed the verbose test helper explanations from the commit messages \n  in patches 3 and 4.\n\nK Jayatheerth (4):\n  path: introduce append_formatted_path() for shared path formatting\n  rev-parse: use append_formatted_path() for path formatting\n  repo: add path.commondir with absolute and relative suffix formatting\n  repo: add path.gitdir with absolute and relative suffix formatting\n\n Documentation/git-repo.adoc |  15 ++++++\n builtin/repo.c              |  50 +++++++++++++++++\n builtin/rev-parse.c         | 104 +++++++++---------------------------\n path.c                      |  70 ++++++++++++++++++++++++\n path.h                      |  36 +++++++++++++\n t/t1900-repo-info.sh        |  58 ++++++++++++++++++++\n 6 files changed, 253 insertions(+), 80 deletions(-)\n\nRange-diff against v4:\n1:  a396b4f8e6 = 1:  a396b4f8e6 path: introduce append_formatted_path() for shared path formatting\n2:  16198f96d1 ! 2:  16b42a51d2 rev-parse: use append_formatted_path() for path formatting\n    @@ builtin/rev-parse.c: static void handle_ref_opt(const char *pattern, const char\n     -\t}\n     -\tfree(cwd);\n     +\tstruct strbuf sb = STRBUF_INIT;\n    ++\t/* If the user didn't explicitly specify a format, fallback to the path-specific default. */\n     +\tenum path_format fmt = (arg_path_format != PATH_FORMAT_DEFAULT) ? arg_path_format : def_format;\n     +\n     +\tappend_formatted_path(&sb, path, prefix, fmt);\n3:  b45c6f0d12 ! 3:  38b733ea64 repo: add path.commondir with absolute and relative suffix formatting\n    @@ Commit message\n         Exposing explicit format variants rather than a single key with a\n         default avoids ambiguity for scripts that require predictable output.\n     \n    -    Add a test helper test_repo_info_path that creates isolated\n    -    repositories per test case to prevent state leaks, captures the repo\n    -    root before changing directories to avoid eval, and accepts an optional\n    -    init_command to cover environment variable overrides such as\n    -    GIT_COMMON_DIR and GIT_DIR.\n    -\n         Mentored-by: Justin Tobler <jltobler@gmail.com>\n         Mentored-by: Lucas Seiki Oshiro <lucasseikioshiro@gmail.com>\n         Signed-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>\n    @@ t/t1900-repo-info.sh: test_expect_success 'git repo info -h shows only repo info\n     +# Helper function to test path keys in both absolute and relative formats.\n     +# $1: label for the test\n     +# $2: field_name (e.g., commondir)\n    -+# $3: unique repo name for isolation\n    -+# $4: expect_absolute (suffix appended to repo root)\n    -+# $5: expect_relative (the relative path string expected)\n    -+# $6: init_command (extra setup like exporting env vars)\n    ++# $3: expected_dir (the directory name, e.g., .git or custom-common)\n    ++# $4: init_command (extra setup like exporting env vars)\n     +test_repo_info_path () {\n     +\tlabel=$1\n     +\tfield_name=$2\n    -+\trepo_name=$3\n    -+\texpect_absolute_suffix=$4\n    -+\texpect_relative=$5\n    -+\tinit_command=$6\n    -+\n    -+\tabsolute_root=\"$repo_name-absolute\"\n    -+\trelative_root=\"$repo_name-relative\"\n    -+\n    -+\ttest_expect_success \"setup: $label\" '\n    -+\t\tgit init \"$absolute_root\" &&\n    -+\t\tgit init \"$relative_root\" &&\n    -+\t\tmkdir -p \"$absolute_root/sub\" \"$relative_root/sub\"\n    -+\t'\n    ++\texpected_dir=$3\n    ++\tinit_command=$4\n     +\n     +\ttest_expect_success \"absolute: $label\" '\n    ++\t\ttest_when_finished \"rm -rf repo\" &&\n    ++\t\tgit init repo &&\n     +\t\t(\n    -+\t\t\tcd \"$absolute_root/sub\" &&\n    ++\t\t\tmkdir -p repo/sub &&\n    ++\t\t\tcd repo/sub &&\n     +\t\t\tROOT=\"$(test-tool path-utils real_path ..)\" && export ROOT &&\n     +\t\t\teval \"$init_command\" &&\n    -+\t\t\texpect_path=\"$ROOT${expect_absolute_suffix:+/$expect_absolute_suffix}\" &&\n    -+\t\t\techo \"path.$field_name.absolute=$expect_path\" >expect &&\n    ++\t\t\techo \"path.$field_name.absolute=$ROOT/$expected_dir\" >expect &&\n     +\t\t\tgit repo info \"path.$field_name.absolute\" >actual &&\n     +\t\t\ttest_cmp expect actual\n     +\t\t)\n     +\t'\n     +\n     +\ttest_expect_success \"relative: $label\" '\n    ++\t\ttest_when_finished \"rm -rf repo\" &&\n    ++\t\tgit init repo &&\n     +\t\t(\n    -+\t\t\tcd \"$relative_root/sub\" &&\n    ++\t\t\tmkdir -p repo/sub &&\n    ++\t\t\tcd repo/sub &&\n     +\t\t\tROOT=\"$(test-tool path-utils real_path ..)\" && export ROOT &&\n     +\t\t\teval \"$init_command\" &&\n    -+\t\t\techo \"path.$field_name.relative=$expect_relative\" >expect &&\n    ++\t\t\techo \"path.$field_name.relative=../$expected_dir\" >expect &&\n     +\t\t\tgit repo info \"path.$field_name.relative\" >actual &&\n     +\t\t\ttest_cmp expect actual\n     +\t\t)\n     +\t'\n     +}\n     +\n    -+test_repo_info_path 'commondir standard' 'commondir' 'commondir-std' \\\n    -+\t'.git' '../.git'\n    ++test_repo_info_path 'commondir standard' 'commondir' '.git'\n     +\n     +test_repo_info_path 'commondir with GIT_COMMON_DIR and GIT_DIR' 'commondir' \\\n    -+\t'commondir-envs' 'custom-common' '../custom-common' \\\n    ++\t'custom-common' \\\n     +\t'GIT_COMMON_DIR=\"$ROOT/custom-common\" && export GIT_COMMON_DIR &&\n     +\t GIT_DIR=\"../.git\" && export GIT_DIR &&\n     +\t git init --bare \"$ROOT/custom-common\"'\n     +\n     +test_repo_info_path 'commondir with only GIT_DIR' 'commondir' \\\n    -+\t'commondir-only-gitdir' '.git' '../.git' \\\n    ++\t'.git' \\\n     +\t'GIT_DIR=\"../.git\" && export GIT_DIR'\n     +\n      test_done\n4:  b5234ffe3e ! 4:  ead1117332 repo: add path.gitdir with absolute and relative suffix formatting\n    @@ builtin/repo.c: static const struct repo_info_field repo_info_field[] = {\n     \n      ## t/t1900-repo-info.sh ##\n     @@ t/t1900-repo-info.sh: test_repo_info_path 'commondir with only GIT_DIR' 'commondir' \\\n    - \t'commondir-only-gitdir' '.git' '../.git' \\\n    + \t'.git' \\\n      \t'GIT_DIR=\"../.git\" && export GIT_DIR'\n      \n    -+test_repo_info_path 'gitdir standard' 'gitdir' 'gitdir-std' \\\n    -+\t'.git' '../.git'\n    ++test_repo_info_path 'gitdir standard' 'gitdir' '.git'\n     +\n     +test_repo_info_path 'gitdir with explicit GIT_DIR' 'gitdir' \\\n    -+\t'gitdir-env' '.git' '../.git' \\\n    ++\t'.git' \\\n     +\t'GIT_DIR=\"../.git\" && export GIT_DIR'\n     +\n      test_done\n-- \n2.54.0\n"},{"id":"545621","messageId":"20260616044953.184806-2-jayatheerthkulkarni2005@gmail.com","threadId":"65725","inReplyTo":"20260616044953.184806-1-jayatheerthkulkarni2005@gmail.com","subject":"[GSoC Patch v5 1/4] path: introduce append_formatted_path() for shared path formatting","fromName":"K Jayatheerth","fromEmail":"jayatheerthkulkarni2005@gmail.com","sentAt":"2026-06-16T04:49:50Z","receivedAt":"2026-06-16T04:51:25Z","isPatch":true,"body":"The path-formatting logic in builtin/rev-parse.c is tightly coupled\nto that command and writes directly to stdout, making it impossible\nfor other builtins to reuse.\n\nExtract the core algorithm into append_formatted_path() in path.c\nand expose a path_format enum in path.h so that any builtin can\nformat paths consistently without duplicating logic.\n\nMentored-by: Justin Tobler <jltobler@gmail.com>\nMentored-by: Lucas Seiki Oshiro <lucasseikioshiro@gmail.com>\nSigned-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>\n---\n path.c | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n path.h | 36 ++++++++++++++++++++++++++++++\n 2 files changed, 106 insertions(+)\n\ndiff --git a/path.c b/path.c\nindex d7e17bf174..5e83e3e4f6 100644\n--- a/path.c\n+++ b/path.c\n@@ -1579,6 +1579,76 @@ char *xdg_cache_home(const char *filename)\n \treturn NULL;\n }\n \n+void append_formatted_path(struct strbuf *dest, const char *path,\n+\t\t\t   const char *prefix, enum path_format format)\n+{\n+\tswitch (format) {\n+\tcase PATH_FORMAT_DEFAULT:\n+\tcase PATH_FORMAT_UNMODIFIED:\n+\t\tstrbuf_addstr(dest, path);\n+\t\tbreak;\n+\n+\tcase PATH_FORMAT_RELATIVE: {\n+\t\tstruct strbuf relative_buf = STRBUF_INIT;\n+\t\tstruct strbuf real_path = STRBUF_INIT;\n+\t\tstruct strbuf real_prefix = STRBUF_INIT;\n+\t\tchar *cwd = NULL;\n+\n+\t\t/*\n+\t\t * We don't ever produce a relative path if prefix is NULL,\n+\t\t * so set the prefix to the current directory so that we can\n+\t\t * produce a relative path whenever possible.\n+\t\t */\n+\t\tif (!prefix)\n+\t\t\tprefix = cwd = xgetcwd();\n+\n+\t\tif (!is_absolute_path(path)) {\n+\t\t\tstrbuf_realpath_forgiving(&real_path, path, 1);\n+\t\t\tpath = real_path.buf;\n+\t\t}\n+\t\tif (!is_absolute_path(prefix)) {\n+\t\t\tstrbuf_realpath_forgiving(&real_prefix, prefix, 1);\n+\t\t\tprefix = real_prefix.buf;\n+\t\t}\n+\n+\t\tstrbuf_addstr(dest, relative_path(path, prefix, &relative_buf));\n+\n+\t\tstrbuf_release(&relative_buf);\n+\t\tstrbuf_release(&real_path);\n+\t\tstrbuf_release(&real_prefix);\n+\t\tfree(cwd);\n+\t\tbreak;\n+\t}\n+\n+\tcase PATH_FORMAT_RELATIVE_IF_SHARED: {\n+\t\tstruct strbuf relative_buf = STRBUF_INIT;\n+\n+\t\t/*\n+\t\t * If we're using RELATIVE_IF_SHARED mode, then we want an\n+\t\t * absolute path unless the two share a common prefix, so don't\n+\t\t * default the prefix to the current working directory. Doing so\n+\t\t * would cause a relative path to always be produced if possible.\n+\t\t */\n+\t\tstrbuf_addstr(dest, relative_path(path, prefix, &relative_buf));\n+\t\tstrbuf_release(&relative_buf);\n+\t\tbreak;\n+\t}\n+\n+\tcase PATH_FORMAT_CANONICAL: {\n+\t\tstruct strbuf canonical_buf = STRBUF_INIT;\n+\n+\t\tstrbuf_realpath_forgiving(&canonical_buf, path, 1);\n+\t\tstrbuf_addbuf(dest, &canonical_buf);\n+\n+\t\tstrbuf_release(&canonical_buf);\n+\t\tbreak;\n+\t}\n+\n+\tdefault:\n+\t\tBUG(\"unknown path_format value %d\", format);\n+\t}\n+}\n+\n REPO_GIT_PATH_FUNC(squash_msg, \"SQUASH_MSG\")\n REPO_GIT_PATH_FUNC(merge_msg, \"MERGE_MSG\")\n REPO_GIT_PATH_FUNC(merge_rr, \"MERGE_RR\")\ndiff --git a/path.h b/path.h\nindex 0434ba5e07..6aca53b100 100644\n--- a/path.h\n+++ b/path.h\n@@ -262,6 +262,42 @@ enum scld_error safe_create_leading_directories_no_share(char *path);\n int safe_create_file_with_leading_directories(struct repository *repo,\n \t\t\t\t\t      const char *path);\n \n+/**\n+ * The formatting strategy to apply when writing a path into a buffer.\n+ */\n+enum path_format {\n+\t/*\n+\t * Represents the default formatting behavior. Treated as\n+\t * PATH_FORMAT_UNMODIFIED by append_formatted_path().\n+\t */\n+\tPATH_FORMAT_DEFAULT,\n+\n+\t/* Output the path exactly as-is without any modifications. */\n+\tPATH_FORMAT_UNMODIFIED,\n+\n+\t/* Output a path relative to the provided directory prefix. */\n+\tPATH_FORMAT_RELATIVE,\n+\n+\t/* Output a relative path only if the path shares a root with the prefix. */\n+\tPATH_FORMAT_RELATIVE_IF_SHARED,\n+\n+\t/* Output a fully resolved, absolute canonical path. */\n+\tPATH_FORMAT_CANONICAL\n+};\n+\n+/**\n+ * Format a path according to the specified formatting strategy and append\n+ * the result to the given strbuf.\n+ *\n+ * `dest`   : The string buffer to append the formatted path to.\n+ * `path`   : The path string that needs to be formatted.\n+ * `prefix` : The directory prefix to calculate relative offsets against.\n+ * Pass NULL to default to the current working directory where applicable.\n+ * `format` : The formatting behavior rule to execute.\n+ */\n+void append_formatted_path(struct strbuf *dest, const char *path,\n+\t\t\t   const char *prefix, enum path_format format);\n+\n # ifdef USE_THE_REPOSITORY_VARIABLE\n #  include \"strbuf.h\"\n #  include \"repository.h\"\n-- \n2.54.0\n\n"},{"id":"545622","messageId":"20260616044953.184806-3-jayatheerthkulkarni2005@gmail.com","threadId":"65725","inReplyTo":"20260616044953.184806-1-jayatheerthkulkarni2005@gmail.com","subject":"[GSoC Patch v5 2/4] rev-parse: use append_formatted_path() for path formatting","fromName":"K Jayatheerth","fromEmail":"jayatheerthkulkarni2005@gmail.com","sentAt":"2026-06-16T04:49:51Z","receivedAt":"2026-06-16T04:52:01Z","isPatch":true,"body":"Now that path formatting logic lives in a shared helper, keeping a\nduplicate implementation in rev-parse is unnecessary and risks the\ntwo diverging over time.\n\nReplace the local format_type and default_type enums and the\nhand-rolled formatting logic with a call to append_formatted_path().\nIntroduce PATH_FORMAT_DEFAULT as the initial value of arg_path_format\nso that per-path fallback behavior is resolved in print_path() rather\nthan leaked into the shared helper.\n\nMentored-by: Justin Tobler <jltobler@gmail.com>\nMentored-by: Lucas Seiki Oshiro <lucasseikioshiro@gmail.com>\nSigned-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>\n---\n builtin/rev-parse.c | 104 ++++++++++----------------------------------\n 1 file changed, 24 insertions(+), 80 deletions(-)\n\ndiff --git a/builtin/rev-parse.c b/builtin/rev-parse.c\nindex 218b5f34d6..1fdcb946a7 100644\n--- a/builtin/rev-parse.c\n+++ b/builtin/rev-parse.c\n@@ -632,73 +632,17 @@ static void handle_ref_opt(const char *pattern, const char *prefix)\n \tclear_ref_exclusions(&ref_excludes);\n }\n \n-enum format_type {\n-\t/* We would like a relative path. */\n-\tFORMAT_RELATIVE,\n-\t/* We would like a canonical absolute path. */\n-\tFORMAT_CANONICAL,\n-\t/* We would like the default behavior. */\n-\tFORMAT_DEFAULT,\n-};\n-\n-enum default_type {\n-\t/* Our default is a relative path. */\n-\tDEFAULT_RELATIVE,\n-\t/* Our default is a relative path if there's a shared root. */\n-\tDEFAULT_RELATIVE_IF_SHARED,\n-\t/* Our default is a canonical absolute path. */\n-\tDEFAULT_CANONICAL,\n-\t/* Our default is not to modify the item. */\n-\tDEFAULT_UNMODIFIED,\n-};\n-\n-static void print_path(const char *path, const char *prefix, enum format_type format, enum default_type def)\n+static void print_path(const char *path, const char *prefix,\n+\t\t       enum path_format arg_path_format, enum path_format def_format)\n {\n-\tchar *cwd = NULL;\n-\t/*\n-\t * We don't ever produce a relative path if prefix is NULL, so set the\n-\t * prefix to the current directory so that we can produce a relative\n-\t * path whenever possible.  If we're using RELATIVE_IF_SHARED mode, then\n-\t * we want an absolute path unless the two share a common prefix, so don't\n-\t * set it in that case, since doing so causes a relative path to always\n-\t * be produced if possible.\n-\t */\n-\tif (!prefix && (format != FORMAT_DEFAULT || def != DEFAULT_RELATIVE_IF_SHARED))\n-\t\tprefix = cwd = xgetcwd();\n-\tif (format == FORMAT_DEFAULT && def == DEFAULT_UNMODIFIED) {\n-\t\tputs(path);\n-\t} else if (format == FORMAT_RELATIVE ||\n-\t\t  (format == FORMAT_DEFAULT && def == DEFAULT_RELATIVE)) {\n-\t\t/*\n-\t\t * In order for relative_path to work as expected, we need to\n-\t\t * make sure that both paths are absolute paths.  If we don't,\n-\t\t * we can end up with an unexpected absolute path that the user\n-\t\t * didn't want.\n-\t\t */\n-\t\tstruct strbuf buf = STRBUF_INIT, realbuf = STRBUF_INIT, prefixbuf = STRBUF_INIT;\n-\t\tif (!is_absolute_path(path)) {\n-\t\t\tstrbuf_realpath_forgiving(&realbuf, path,  1);\n-\t\t\tpath = realbuf.buf;\n-\t\t}\n-\t\tif (!is_absolute_path(prefix)) {\n-\t\t\tstrbuf_realpath_forgiving(&prefixbuf, prefix, 1);\n-\t\t\tprefix = prefixbuf.buf;\n-\t\t}\n-\t\tputs(relative_path(path, prefix, &buf));\n-\t\tstrbuf_release(&buf);\n-\t\tstrbuf_release(&realbuf);\n-\t\tstrbuf_release(&prefixbuf);\n-\t} else if (format == FORMAT_DEFAULT && def == DEFAULT_RELATIVE_IF_SHARED) {\n-\t\tstruct strbuf buf = STRBUF_INIT;\n-\t\tputs(relative_path(path, prefix, &buf));\n-\t\tstrbuf_release(&buf);\n-\t} else {\n-\t\tstruct strbuf buf = STRBUF_INIT;\n-\t\tstrbuf_realpath_forgiving(&buf, path, 1);\n-\t\tputs(buf.buf);\n-\t\tstrbuf_release(&buf);\n-\t}\n-\tfree(cwd);\n+\tstruct strbuf sb = STRBUF_INIT;\n+\t/* If the user didn't explicitly specify a format, fallback to the path-specific default. */\n+\tenum path_format fmt = (arg_path_format != PATH_FORMAT_DEFAULT) ? arg_path_format : def_format;\n+\n+\tappend_formatted_path(&sb, path, prefix, fmt);\n+\tputs(sb.buf);\n+\n+\tstrbuf_release(&sb);\n }\n \n int cmd_rev_parse(int argc,\n@@ -717,7 +661,7 @@ int cmd_rev_parse(int argc,\n \tconst char *name = NULL;\n \tstruct strbuf buf = STRBUF_INIT;\n \tint seen_end_of_options = 0;\n-\tenum format_type format = FORMAT_DEFAULT;\n+\tenum path_format arg_path_format = PATH_FORMAT_DEFAULT;\n \n \tshow_usage_if_asked(argc, argv, builtin_rev_parse_usage);\n \n@@ -797,8 +741,8 @@ int cmd_rev_parse(int argc,\n \t\t\t\t\tdie(_(\"--git-path requires an argument\"));\n \t\t\t\tprint_path(repo_git_path_replace(the_repository, &buf,\n \t\t\t\t\t\t\t\t \"%s\", argv[i + 1]), prefix,\n-\t\t\t\t\t\tformat,\n-\t\t\t\t\t\tDEFAULT_RELATIVE_IF_SHARED);\n+\t\t\t\t\t\targ_path_format,\n+\t\t\t\t\t\tPATH_FORMAT_RELATIVE_IF_SHARED);\n \t\t\t\ti++;\n \t\t\t\tcontinue;\n \t\t\t}\n@@ -820,9 +764,9 @@ int cmd_rev_parse(int argc,\n \t\t\t\tif (!arg)\n \t\t\t\t\tdie(_(\"--path-format requires an argument\"));\n \t\t\t\tif (!strcmp(arg, \"absolute\")) {\n-\t\t\t\t\tformat = FORMAT_CANONICAL;\n+\t\t\t\t\targ_path_format = PATH_FORMAT_CANONICAL;\n \t\t\t\t} else if (!strcmp(arg, \"relative\")) {\n-\t\t\t\t\tformat = FORMAT_RELATIVE;\n+\t\t\t\t\targ_path_format = PATH_FORMAT_RELATIVE;\n \t\t\t\t} else {\n \t\t\t\t\tdie(_(\"unknown argument to --path-format: %s\"), arg);\n \t\t\t\t}\n@@ -985,7 +929,7 @@ int cmd_rev_parse(int argc,\n \t\t\tif (!strcmp(arg, \"--show-toplevel\")) {\n \t\t\t\tconst char *work_tree = repo_get_work_tree(the_repository);\n \t\t\t\tif (work_tree)\n-\t\t\t\t\tprint_path(work_tree, prefix, format, DEFAULT_UNMODIFIED);\n+\t\t\t\t\tprint_path(work_tree, prefix, arg_path_format, PATH_FORMAT_UNMODIFIED);\n \t\t\t\telse\n \t\t\t\t\tdie(_(\"this operation must be run in a work tree\"));\n \t\t\t\tcontinue;\n@@ -993,7 +937,7 @@ int cmd_rev_parse(int argc,\n \t\t\tif (!strcmp(arg, \"--show-superproject-working-tree\")) {\n \t\t\t\tstruct strbuf superproject = STRBUF_INIT;\n \t\t\t\tif (get_superproject_working_tree(&superproject))\n-\t\t\t\t\tprint_path(superproject.buf, prefix, format, DEFAULT_UNMODIFIED);\n+\t\t\t\t\tprint_path(superproject.buf, prefix, arg_path_format, PATH_FORMAT_UNMODIFIED);\n \t\t\t\tstrbuf_release(&superproject);\n \t\t\t\tcontinue;\n \t\t\t}\n@@ -1028,18 +972,18 @@ int cmd_rev_parse(int argc,\n \t\t\t\tconst char *gitdir = getenv(GIT_DIR_ENVIRONMENT);\n \t\t\t\tchar *cwd;\n \t\t\t\tint len;\n-\t\t\t\tenum format_type wanted = format;\n+\t\t\t\tenum path_format wanted = arg_path_format;\n \t\t\t\tif (arg[2] == 'g') {\t/* --git-dir */\n \t\t\t\t\tif (gitdir) {\n-\t\t\t\t\t\tprint_path(gitdir, prefix, format, DEFAULT_UNMODIFIED);\n+\t\t\t\t\t\tprint_path(gitdir, prefix, arg_path_format, PATH_FORMAT_UNMODIFIED);\n \t\t\t\t\t\tcontinue;\n \t\t\t\t\t}\n \t\t\t\t\tif (!prefix) {\n-\t\t\t\t\t\tprint_path(\".git\", prefix, format, DEFAULT_UNMODIFIED);\n+\t\t\t\t\t\tprint_path(\".git\", prefix, arg_path_format, PATH_FORMAT_UNMODIFIED);\n \t\t\t\t\t\tcontinue;\n \t\t\t\t\t}\n \t\t\t\t} else {\t\t/* --absolute-git-dir */\n-\t\t\t\t\twanted = FORMAT_CANONICAL;\n+\t\t\t\t\twanted = PATH_FORMAT_CANONICAL;\n \t\t\t\t\tif (!gitdir && !prefix)\n \t\t\t\t\t\tgitdir = \".git\";\n \t\t\t\t\tif (gitdir) {\n@@ -1055,11 +999,11 @@ int cmd_rev_parse(int argc,\n \t\t\t\tstrbuf_reset(&buf);\n \t\t\t\tstrbuf_addf(&buf, \"%s%s.git\", cwd, len && cwd[len-1] != '/' ? \"/\" : \"\");\n \t\t\t\tfree(cwd);\n-\t\t\t\tprint_path(buf.buf, prefix, wanted, DEFAULT_CANONICAL);\n+\t\t\t\tprint_path(buf.buf, prefix, wanted, PATH_FORMAT_CANONICAL);\n \t\t\t\tcontinue;\n \t\t\t}\n \t\t\tif (!strcmp(arg, \"--git-common-dir\")) {\n-\t\t\t\tprint_path(repo_get_common_dir(the_repository), prefix, format, DEFAULT_RELATIVE_IF_SHARED);\n+\t\t\t\tprint_path(repo_get_common_dir(the_repository), prefix, arg_path_format, PATH_FORMAT_RELATIVE_IF_SHARED);\n \t\t\t\tcontinue;\n \t\t\t}\n \t\t\tif (!strcmp(arg, \"--is-inside-git-dir\")) {\n@@ -1089,7 +1033,7 @@ int cmd_rev_parse(int argc,\n \t\t\t\tif (the_repository->index->split_index) {\n \t\t\t\t\tconst struct object_id *oid = &the_repository->index->split_index->base_oid;\n \t\t\t\t\tconst char *path = repo_git_path_replace(the_repository, &buf, \"sharedindex.%s\", oid_to_hex(oid));\n-\t\t\t\t\tprint_path(path, prefix, format, DEFAULT_RELATIVE);\n+\t\t\t\t\tprint_path(path, prefix, arg_path_format, PATH_FORMAT_RELATIVE);\n \t\t\t\t}\n \t\t\t\tcontinue;\n \t\t\t}\n-- \n2.54.0\n\n"},{"id":"545623","messageId":"20260616044953.184806-4-jayatheerthkulkarni2005@gmail.com","threadId":"65725","inReplyTo":"20260616044953.184806-1-jayatheerthkulkarni2005@gmail.com","subject":"[GSoC Patch v5 3/4] repo: add path.commondir with absolute and relative suffix formatting","fromName":"K Jayatheerth","fromEmail":"jayatheerthkulkarni2005@gmail.com","sentAt":"2026-06-16T04:49:52Z","receivedAt":"2026-06-16T04:52:28Z","isPatch":true,"body":"Scripts working with worktree setups need a reliable way to discover\nthe common directory, which diverges from the git directory when\nmultiple worktrees are in use. There is no way to retrieve this path\nfrom git repo info today.\n\nIntroduce path.commondir.absolute and path.commondir.relative keys.\nExposing explicit format variants rather than a single key with a\ndefault avoids ambiguity for scripts that require predictable output.\n\nMentored-by: Justin Tobler <jltobler@gmail.com>\nMentored-by: Lucas Seiki Oshiro <lucasseikioshiro@gmail.com>\nSigned-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>\n---\n Documentation/git-repo.adoc |  9 +++++++\n builtin/repo.c              | 26 +++++++++++++++++++\n t/t1900-repo-info.sh        | 52 +++++++++++++++++++++++++++++++++++++\n 3 files changed, 87 insertions(+)\n\ndiff --git a/Documentation/git-repo.adoc b/Documentation/git-repo.adoc\nindex 42262c1983..890c34051d 100644\n--- a/Documentation/git-repo.adoc\n+++ b/Documentation/git-repo.adoc\n@@ -104,6 +104,15 @@ values that they return:\n `object.format`::\n \tThe object format (hash algorithm) used in the repository.\n \n+`path.commondir.absolute`::\n+\tThe canonical absolute path to the Git repository's common\n+\tdirectory (the shared `.git` directory containing objects,\n+\trefs, and global configuration).\n+\n+`path.commondir.relative`::\n+\tThe path to the Git repository's common directory relative to\n+\tthe current working directory.\n+\n `references.format`::\n \tThe reference storage format. The valid values are:\n +\ndiff --git a/builtin/repo.c b/builtin/repo.c\nindex 71a5c1c29c..c4cc3bf3fc 100644\n--- a/builtin/repo.c\n+++ b/builtin/repo.c\n@@ -7,12 +7,14 @@\n #include \"hex.h\"\n #include \"odb.h\"\n #include \"parse-options.h\"\n+#include \"path.h\"\n #include \"path-walk.h\"\n #include \"progress.h\"\n #include \"quote.h\"\n #include \"ref-filter.h\"\n #include \"refs.h\"\n #include \"revision.h\"\n+#include \"setup.h\"\n #include \"strbuf.h\"\n #include \"string-list.h\"\n #include \"shallow.h\"\n@@ -75,6 +77,28 @@ static int get_object_format(struct repository *repo, struct strbuf *buf)\n \treturn 0;\n }\n \n+static int get_path_commondir_absolute(struct repository *repo, struct strbuf *buf)\n+{\n+\tconst char *common_dir = repo_get_common_dir(repo);\n+\n+\tif (!common_dir)\n+\t\treturn error(_(\"unable to get common directory\"));\n+\n+\tappend_formatted_path(buf, common_dir, startup_info->prefix, PATH_FORMAT_CANONICAL);\n+\treturn 0;\n+}\n+\n+static int get_path_commondir_relative(struct repository *repo, struct strbuf *buf)\n+{\n+\tconst char *common_dir = repo_get_common_dir(repo);\n+\n+\tif (!common_dir)\n+\t\treturn error(_(\"unable to get common directory\"));\n+\n+\tappend_formatted_path(buf, common_dir, startup_info->prefix, PATH_FORMAT_RELATIVE);\n+\treturn 0;\n+}\n+\n static int get_references_format(struct repository *repo, struct strbuf *buf)\n {\n \tstrbuf_addstr(buf,\n@@ -87,6 +111,8 @@ static const struct repo_info_field repo_info_field[] = {\n \t{ \"layout.bare\", get_layout_bare },\n \t{ \"layout.shallow\", get_layout_shallow },\n \t{ \"object.format\", get_object_format },\n+\t{ \"path.commondir.absolute\", get_path_commondir_absolute },\n+\t{ \"path.commondir.relative\", get_path_commondir_relative },\n \t{ \"references.format\", get_references_format },\n };\n \ndiff --git a/t/t1900-repo-info.sh b/t/t1900-repo-info.sh\nindex 39bb77dda0..09158d29f9 100755\n--- a/t/t1900-repo-info.sh\n+++ b/t/t1900-repo-info.sh\n@@ -155,4 +155,56 @@ test_expect_success 'git repo info -h shows only repo info usage' '\n \ttest_grep ! \"git repo structure\" actual\n '\n \n+# Helper function to test path keys in both absolute and relative formats.\n+# $1: label for the test\n+# $2: field_name (e.g., commondir)\n+# $3: expected_dir (the directory name, e.g., .git or custom-common)\n+# $4: init_command (extra setup like exporting env vars)\n+test_repo_info_path () {\n+\tlabel=$1\n+\tfield_name=$2\n+\texpected_dir=$3\n+\tinit_command=$4\n+\n+\ttest_expect_success \"absolute: $label\" '\n+\t\ttest_when_finished \"rm -rf repo\" &&\n+\t\tgit init repo &&\n+\t\t(\n+\t\t\tmkdir -p repo/sub &&\n+\t\t\tcd repo/sub &&\n+\t\t\tROOT=\"$(test-tool path-utils real_path ..)\" && export ROOT &&\n+\t\t\teval \"$init_command\" &&\n+\t\t\techo \"path.$field_name.absolute=$ROOT/$expected_dir\" >expect &&\n+\t\t\tgit repo info \"path.$field_name.absolute\" >actual &&\n+\t\t\ttest_cmp expect actual\n+\t\t)\n+\t'\n+\n+\ttest_expect_success \"relative: $label\" '\n+\t\ttest_when_finished \"rm -rf repo\" &&\n+\t\tgit init repo &&\n+\t\t(\n+\t\t\tmkdir -p repo/sub &&\n+\t\t\tcd repo/sub &&\n+\t\t\tROOT=\"$(test-tool path-utils real_path ..)\" && export ROOT &&\n+\t\t\teval \"$init_command\" &&\n+\t\t\techo \"path.$field_name.relative=../$expected_dir\" >expect &&\n+\t\t\tgit repo info \"path.$field_name.relative\" >actual &&\n+\t\t\ttest_cmp expect actual\n+\t\t)\n+\t'\n+}\n+\n+test_repo_info_path 'commondir standard' 'commondir' '.git'\n+\n+test_repo_info_path 'commondir with GIT_COMMON_DIR and GIT_DIR' 'commondir' \\\n+\t'custom-common' \\\n+\t'GIT_COMMON_DIR=\"$ROOT/custom-common\" && export GIT_COMMON_DIR &&\n+\t GIT_DIR=\"../.git\" && export GIT_DIR &&\n+\t git init --bare \"$ROOT/custom-common\"'\n+\n+test_repo_info_path 'commondir with only GIT_DIR' 'commondir' \\\n+\t'.git' \\\n+\t'GIT_DIR=\"../.git\" && export GIT_DIR'\n+\n test_done\n-- \n2.54.0\n\n"},{"id":"545624","messageId":"20260616044953.184806-5-jayatheerthkulkarni2005@gmail.com","threadId":"65725","inReplyTo":"20260616044953.184806-1-jayatheerthkulkarni2005@gmail.com","subject":"[GSoC Patch v5 4/4] repo: add path.gitdir with absolute and relative suffix formatting","fromName":"K Jayatheerth","fromEmail":"jayatheerthkulkarni2005@gmail.com","sentAt":"2026-06-16T04:49:53Z","receivedAt":"2026-06-16T04:52:45Z","isPatch":true,"body":"Scripts need a stable way to locate the git directory without\nparsing rev-parse output or relying on its flag-driven path format\nselection. There is no way to retrieve this path from git repo info\ntoday.\n\nIntroduce path.gitdir.absolute and path.gitdir.relative keys,\nconsistent with the path.commondir keys added in the previous patch.\nReuse the test_repo_info_path helper introduced there to validate\nboth variants.\n\nMentored-by: Justin Tobler <jltobler@gmail.com>\nMentored-by: Lucas Seiki Oshiro <lucasseikioshiro@gmail.com>\nSigned-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>\n---\n Documentation/git-repo.adoc |  6 ++++++\n builtin/repo.c              | 24 ++++++++++++++++++++++++\n t/t1900-repo-info.sh        |  6 ++++++\n 3 files changed, 36 insertions(+)\n\ndiff --git a/Documentation/git-repo.adoc b/Documentation/git-repo.adoc\nindex 890c34051d..ed7d80c690 100644\n--- a/Documentation/git-repo.adoc\n+++ b/Documentation/git-repo.adoc\n@@ -113,6 +113,12 @@ values that they return:\n \tThe path to the Git repository's common directory relative to\n \tthe current working directory.\n \n+`path.gitdir.absolute`::\n+\tThe canonical absolute path to the Git repository directory (the `.git` directory).\n+\n+`path.gitdir.relative`::\n+\tThe path to the Git repository directory relative to the current working directory.\n+\n `references.format`::\n \tThe reference storage format. The valid values are:\n +\ndiff --git a/builtin/repo.c b/builtin/repo.c\nindex c4cc3bf3fc..9a312d127a 100644\n--- a/builtin/repo.c\n+++ b/builtin/repo.c\n@@ -99,6 +99,28 @@ static int get_path_commondir_relative(struct repository *repo, struct strbuf *b\n \treturn 0;\n }\n \n+static int get_path_gitdir_absolute(struct repository *repo, struct strbuf *buf)\n+{\n+\tconst char *git_dir = repo_get_git_dir(repo);\n+\n+\tif (!git_dir)\n+\t\treturn error(_(\"unable to get git directory\"));\n+\n+\tappend_formatted_path(buf, git_dir, startup_info->prefix, PATH_FORMAT_CANONICAL);\n+\treturn 0;\n+}\n+\n+static int get_path_gitdir_relative(struct repository *repo, struct strbuf *buf)\n+{\n+\tconst char *git_dir = repo_get_git_dir(repo);\n+\n+\tif (!git_dir)\n+\t\treturn error(_(\"unable to get git directory\"));\n+\n+\tappend_formatted_path(buf, git_dir, startup_info->prefix, PATH_FORMAT_RELATIVE);\n+\treturn 0;\n+}\n+\n static int get_references_format(struct repository *repo, struct strbuf *buf)\n {\n \tstrbuf_addstr(buf,\n@@ -113,6 +135,8 @@ static const struct repo_info_field repo_info_field[] = {\n \t{ \"object.format\", get_object_format },\n \t{ \"path.commondir.absolute\", get_path_commondir_absolute },\n \t{ \"path.commondir.relative\", get_path_commondir_relative },\n+\t{ \"path.gitdir.absolute\", get_path_gitdir_absolute },\n+\t{ \"path.gitdir.relative\", get_path_gitdir_relative },\n \t{ \"references.format\", get_references_format },\n };\n \ndiff --git a/t/t1900-repo-info.sh b/t/t1900-repo-info.sh\nindex 09158d29f9..ae8c22c817 100755\n--- a/t/t1900-repo-info.sh\n+++ b/t/t1900-repo-info.sh\n@@ -207,4 +207,10 @@ test_repo_info_path 'commondir with only GIT_DIR' 'commondir' \\\n \t'.git' \\\n \t'GIT_DIR=\"../.git\" && export GIT_DIR'\n \n+test_repo_info_path 'gitdir standard' 'gitdir' '.git'\n+\n+test_repo_info_path 'gitdir with explicit GIT_DIR' 'gitdir' \\\n+\t'.git' \\\n+\t'GIT_DIR=\"../.git\" && export GIT_DIR'\n+\n test_done\n-- \n2.54.0\n\n"},{"id":"545653","messageId":"c58d3b78-985e-4fce-a605-14e693ebef61@gmail.com","threadId":"65725","inReplyTo":"20260616044953.184806-2-jayatheerthkulkarni2005@gmail.com","subject":"Re: [GSoC Patch v5 1/4] path: introduce append_formatted_path() for shared path formatting","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2026-06-16T13:08:32Z","receivedAt":"2026-06-16T13:08:35Z","isPatch":true,"body":"On 16/06/2026 05:49, K Jayatheerth wrote:\n> The path-formatting logic in builtin/rev-parse.c is tightly coupled\n> to that command and writes directly to stdout, making it impossible\n> for other builtins to reuse.\n> \n> Extract the core algorithm into append_formatted_path() in path.c\n> and expose a path_format enum in path.h so that any builtin can\n> format paths consistently without duplicating logic.\n\nSorry I haven't had time to look at this series recently, it is looking \nmuch nicer now that we have a single enum. It would be helpful to \nexplain why we need PATH_FORMAT_DEFAULT that acts exactly like \nPATH_FORMAT_UNMODIFIED. Looking at the next patch it seems this is still \na wart in the api due to rev-parse wanting needing to distinguish the \nunmodified case from the default case.\n\nThanks\n\nPhillip\n\n> Mentored-by: Justin Tobler <jltobler@gmail.com>\n> Mentored-by: Lucas Seiki Oshiro <lucasseikioshiro@gmail.com>\n> Signed-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>\n> ---\n>   path.c | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n>   path.h | 36 ++++++++++++++++++++++++++++++\n>   2 files changed, 106 insertions(+)\n> \n> diff --git a/path.c b/path.c\n> index d7e17bf174..5e83e3e4f6 100644\n> --- a/path.c\n> +++ b/path.c\n> @@ -1579,6 +1579,76 @@ char *xdg_cache_home(const char *filename)\n>   \treturn NULL;\n>   }\n>   \n> +void append_formatted_path(struct strbuf *dest, const char *path,\n> +\t\t\t   const char *prefix, enum path_format format)\n> +{\n> +\tswitch (format) {\n> +\tcase PATH_FORMAT_DEFAULT:\n> +\tcase PATH_FORMAT_UNMODIFIED:\n> +\t\tstrbuf_addstr(dest, path);\n> +\t\tbreak;\n> +\n> +\tcase PATH_FORMAT_RELATIVE: {\n> +\t\tstruct strbuf relative_buf = STRBUF_INIT;\n> +\t\tstruct strbuf real_path = STRBUF_INIT;\n> +\t\tstruct strbuf real_prefix = STRBUF_INIT;\n> +\t\tchar *cwd = NULL;\n> +\n> +\t\t/*\n> +\t\t * We don't ever produce a relative path if prefix is NULL,\n> +\t\t * so set the prefix to the current directory so that we can\n> +\t\t * produce a relative path whenever possible.\n> +\t\t */\n> +\t\tif (!prefix)\n> +\t\t\tprefix = cwd = xgetcwd();\n> +\n> +\t\tif (!is_absolute_path(path)) {\n> +\t\t\tstrbuf_realpath_forgiving(&real_path, path, 1);\n> +\t\t\tpath = real_path.buf;\n> +\t\t}\n> +\t\tif (!is_absolute_path(prefix)) {\n> +\t\t\tstrbuf_realpath_forgiving(&real_prefix, prefix, 1);\n> +\t\t\tprefix = real_prefix.buf;\n> +\t\t}\n> +\n> +\t\tstrbuf_addstr(dest, relative_path(path, prefix, &relative_buf));\n> +\n> +\t\tstrbuf_release(&relative_buf);\n> +\t\tstrbuf_release(&real_path);\n> +\t\tstrbuf_release(&real_prefix);\n> +\t\tfree(cwd);\n> +\t\tbreak;\n> +\t}\n> +\n> +\tcase PATH_FORMAT_RELATIVE_IF_SHARED: {\n> +\t\tstruct strbuf relative_buf = STRBUF_INIT;\n> +\n> +\t\t/*\n> +\t\t * If we're using RELATIVE_IF_SHARED mode, then we want an\n> +\t\t * absolute path unless the two share a common prefix, so don't\n> +\t\t * default the prefix to the current working directory. Doing so\n> +\t\t * would cause a relative path to always be produced if possible.\n> +\t\t */\n> +\t\tstrbuf_addstr(dest, relative_path(path, prefix, &relative_buf));\n> +\t\tstrbuf_release(&relative_buf);\n> +\t\tbreak;\n> +\t}\n> +\n> +\tcase PATH_FORMAT_CANONICAL: {\n> +\t\tstruct strbuf canonical_buf = STRBUF_INIT;\n> +\n> +\t\tstrbuf_realpath_forgiving(&canonical_buf, path, 1);\n> +\t\tstrbuf_addbuf(dest, &canonical_buf);\n> +\n> +\t\tstrbuf_release(&canonical_buf);\n> +\t\tbreak;\n> +\t}\n> +\n> +\tdefault:\n> +\t\tBUG(\"unknown path_format value %d\", format);\n> +\t}\n> +}\n> +\n>   REPO_GIT_PATH_FUNC(squash_msg, \"SQUASH_MSG\")\n>   REPO_GIT_PATH_FUNC(merge_msg, \"MERGE_MSG\")\n>   REPO_GIT_PATH_FUNC(merge_rr, \"MERGE_RR\")\n> diff --git a/path.h b/path.h\n> index 0434ba5e07..6aca53b100 100644\n> --- a/path.h\n> +++ b/path.h\n> @@ -262,6 +262,42 @@ enum scld_error safe_create_leading_directories_no_share(char *path);\n>   int safe_create_file_with_leading_directories(struct repository *repo,\n>   \t\t\t\t\t      const char *path);\n>   \n> +/**\n> + * The formatting strategy to apply when writing a path into a buffer.\n> + */\n> +enum path_format {\n> +\t/*\n> +\t * Represents the default formatting behavior. Treated as\n> +\t * PATH_FORMAT_UNMODIFIED by append_formatted_path().\n> +\t */\n> +\tPATH_FORMAT_DEFAULT,\n> +\n> +\t/* Output the path exactly as-is without any modifications. */\n> +\tPATH_FORMAT_UNMODIFIED,\n> +\n> +\t/* Output a path relative to the provided directory prefix. */\n> +\tPATH_FORMAT_RELATIVE,\n> +\n> +\t/* Output a relative path only if the path shares a root with the prefix. */\n> +\tPATH_FORMAT_RELATIVE_IF_SHARED,\n> +\n> +\t/* Output a fully resolved, absolute canonical path. */\n> +\tPATH_FORMAT_CANONICAL\n> +};\n> +\n> +/**\n> + * Format a path according to the specified formatting strategy and append\n> + * the result to the given strbuf.\n> + *\n> + * `dest`   : The string buffer to append the formatted path to.\n> + * `path`   : The path string that needs to be formatted.\n> + * `prefix` : The directory prefix to calculate relative offsets against.\n> + * Pass NULL to default to the current working directory where applicable.\n> + * `format` : The formatting behavior rule to execute.\n> + */\n> +void append_formatted_path(struct strbuf *dest, const char *path,\n> +\t\t\t   const char *prefix, enum path_format format);\n> +\n>   # ifdef USE_THE_REPOSITORY_VARIABLE\n>   #  include \"strbuf.h\"\n>   #  include \"repository.h\"\n\n"},{"id":"545654","messageId":"0077b1ae-3c85-4b34-a0ac-766395157c4f@gmail.com","threadId":"65725","inReplyTo":"20260616044953.184806-3-jayatheerthkulkarni2005@gmail.com","subject":"Re: [GSoC Patch v5 2/4] rev-parse: use append_formatted_path() for path formatting","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2026-06-16T13:08:37Z","receivedAt":"2026-06-16T13:08:40Z","isPatch":true,"body":"On 16/06/2026 05:49, K Jayatheerth wrote:\n> -static void print_path(const char *path, const char *prefix, enum format_type format, enum default_type def)\n> +static void print_path(const char *path, const char *prefix,\n> +\t\t       enum path_format arg_path_format, enum path_format def_format)\n>   {\n> -\tchar *cwd = NULL;\n> -\t/*\n> -\t * We don't ever produce a relative path if prefix is NULL, so set the\n> -\t * prefix to the current directory so that we can produce a relative\n> -\t * path whenever possible.  If we're using RELATIVE_IF_SHARED mode, then\n> -\t * we want an absolute path unless the two share a common prefix, so don't\n> -\t * set it in that case, since doing so causes a relative path to always\n> -\t * be produced if possible.\n> -\t */\n> -\tif (!prefix && (format != FORMAT_DEFAULT || def != DEFAULT_RELATIVE_IF_SHARED))\n> -\t\tprefix = cwd = xgetcwd();\n> -\tif (format == FORMAT_DEFAULT && def == DEFAULT_UNMODIFIED) {\n> -\t\tputs(path);\n> -\t} else if (format == FORMAT_RELATIVE ||\n> -\t\t  (format == FORMAT_DEFAULT && def == DEFAULT_RELATIVE)) {\n> -\t\t/*\n> -\t\t * In order for relative_path to work as expected, we need to\n> -\t\t * make sure that both paths are absolute paths.  If we don't,\n> -\t\t * we can end up with an unexpected absolute path that the user\n> -\t\t * didn't want.\n> -\t\t */\n> -\t\tstruct strbuf buf = STRBUF_INIT, realbuf = STRBUF_INIT, prefixbuf = STRBUF_INIT;\n> -\t\tif (!is_absolute_path(path)) {\n> -\t\t\tstrbuf_realpath_forgiving(&realbuf, path,  1);\n> -\t\t\tpath = realbuf.buf;\n> -\t\t}\n> -\t\tif (!is_absolute_path(prefix)) {\n> -\t\t\tstrbuf_realpath_forgiving(&prefixbuf, prefix, 1);\n> -\t\t\tprefix = prefixbuf.buf;\n> -\t\t}\n> -\t\tputs(relative_path(path, prefix, &buf));\n> -\t\tstrbuf_release(&buf);\n> -\t\tstrbuf_release(&realbuf);\n> -\t\tstrbuf_release(&prefixbuf);\n> -\t} else if (format == FORMAT_DEFAULT && def == DEFAULT_RELATIVE_IF_SHARED) {\n> -\t\tstruct strbuf buf = STRBUF_INIT;\n> -\t\tputs(relative_path(path, prefix, &buf));\n> -\t\tstrbuf_release(&buf);\n> -\t} else {\n> -\t\tstruct strbuf buf = STRBUF_INIT;\n> -\t\tstrbuf_realpath_forgiving(&buf, path, 1);\n> -\t\tputs(buf.buf);\n> -\t\tstrbuf_release(&buf);\n> -\t}\n> -\tfree(cwd);\n> +\tstruct strbuf sb = STRBUF_INIT;\n> +\t/* If the user didn't explicitly specify a format, fallback to the path-specific default. */\n> +\tenum path_format fmt = (arg_path_format != PATH_FORMAT_DEFAULT) ? arg_path_format : def_format;\n> +\n> +\tappend_formatted_path(&sb, path, prefix, fmt);\n> +\tputs(sb.buf);\n> +\n> +\tstrbuf_release(&sb);\n>   }\n>   \n>   int cmd_rev_parse(int argc,\n> @@ -717,7 +661,7 @@ int cmd_rev_parse(int argc,\n>   \tconst char *name = NULL;\n>   \tstruct strbuf buf = STRBUF_INIT;\n>   \tint seen_end_of_options = 0;\n> -\tenum format_type format = FORMAT_DEFAULT;\n> +\tenum path_format arg_path_format = PATH_FORMAT_DEFAULT;\n\nThis is the source of the api wart I referred to in the previous patch. \nCould we keep the existing enums and convert them into the appropriate \nPATH_FORMAT_* flag in print_path() above? I think we already have the \nlogic to do that in the existing code. That would mean that other users \nof append_formatted_path() don't have to worry about the extra flag.\n\nThanks\n\nPhillip\n\n>   \n>   \tshow_usage_if_asked(argc, argv, builtin_rev_parse_usage);\n>   \n> @@ -797,8 +741,8 @@ int cmd_rev_parse(int argc,\n>   \t\t\t\t\tdie(_(\"--git-path requires an argument\"));\n>   \t\t\t\tprint_path(repo_git_path_replace(the_repository, &buf,\n>   \t\t\t\t\t\t\t\t \"%s\", argv[i + 1]), prefix,\n> -\t\t\t\t\t\tformat,\n> -\t\t\t\t\t\tDEFAULT_RELATIVE_IF_SHARED);\n> +\t\t\t\t\t\targ_path_format,\n> +\t\t\t\t\t\tPATH_FORMAT_RELATIVE_IF_SHARED);\n>   \t\t\t\ti++;\n>   \t\t\t\tcontinue;\n>   \t\t\t}\n> @@ -820,9 +764,9 @@ int cmd_rev_parse(int argc,\n>   \t\t\t\tif (!arg)\n>   \t\t\t\t\tdie(_(\"--path-format requires an argument\"));\n>   \t\t\t\tif (!strcmp(arg, \"absolute\")) {\n> -\t\t\t\t\tformat = FORMAT_CANONICAL;\n> +\t\t\t\t\targ_path_format = PATH_FORMAT_CANONICAL;\n>   \t\t\t\t} else if (!strcmp(arg, \"relative\")) {\n> -\t\t\t\t\tformat = FORMAT_RELATIVE;\n> +\t\t\t\t\targ_path_format = PATH_FORMAT_RELATIVE;\n>   \t\t\t\t} else {\n>   \t\t\t\t\tdie(_(\"unknown argument to --path-format: %s\"), arg);\n>   \t\t\t\t}\n> @@ -985,7 +929,7 @@ int cmd_rev_parse(int argc,\n>   \t\t\tif (!strcmp(arg, \"--show-toplevel\")) {\n>   \t\t\t\tconst char *work_tree = repo_get_work_tree(the_repository);\n>   \t\t\t\tif (work_tree)\n> -\t\t\t\t\tprint_path(work_tree, prefix, format, DEFAULT_UNMODIFIED);\n> +\t\t\t\t\tprint_path(work_tree, prefix, arg_path_format, PATH_FORMAT_UNMODIFIED);\n>   \t\t\t\telse\n>   \t\t\t\t\tdie(_(\"this operation must be run in a work tree\"));\n>   \t\t\t\tcontinue;\n> @@ -993,7 +937,7 @@ int cmd_rev_parse(int argc,\n>   \t\t\tif (!strcmp(arg, \"--show-superproject-working-tree\")) {\n>   \t\t\t\tstruct strbuf superproject = STRBUF_INIT;\n>   \t\t\t\tif (get_superproject_working_tree(&superproject))\n> -\t\t\t\t\tprint_path(superproject.buf, prefix, format, DEFAULT_UNMODIFIED);\n> +\t\t\t\t\tprint_path(superproject.buf, prefix, arg_path_format, PATH_FORMAT_UNMODIFIED);\n>   \t\t\t\tstrbuf_release(&superproject);\n>   \t\t\t\tcontinue;\n>   \t\t\t}\n> @@ -1028,18 +972,18 @@ int cmd_rev_parse(int argc,\n>   \t\t\t\tconst char *gitdir = getenv(GIT_DIR_ENVIRONMENT);\n>   \t\t\t\tchar *cwd;\n>   \t\t\t\tint len;\n> -\t\t\t\tenum format_type wanted = format;\n> +\t\t\t\tenum path_format wanted = arg_path_format;\n>   \t\t\t\tif (arg[2] == 'g') {\t/* --git-dir */\n>   \t\t\t\t\tif (gitdir) {\n> -\t\t\t\t\t\tprint_path(gitdir, prefix, format, DEFAULT_UNMODIFIED);\n> +\t\t\t\t\t\tprint_path(gitdir, prefix, arg_path_format, PATH_FORMAT_UNMODIFIED);\n>   \t\t\t\t\t\tcontinue;\n>   \t\t\t\t\t}\n>   \t\t\t\t\tif (!prefix) {\n> -\t\t\t\t\t\tprint_path(\".git\", prefix, format, DEFAULT_UNMODIFIED);\n> +\t\t\t\t\t\tprint_path(\".git\", prefix, arg_path_format, PATH_FORMAT_UNMODIFIED);\n>   \t\t\t\t\t\tcontinue;\n>   \t\t\t\t\t}\n>   \t\t\t\t} else {\t\t/* --absolute-git-dir */\n> -\t\t\t\t\twanted = FORMAT_CANONICAL;\n> +\t\t\t\t\twanted = PATH_FORMAT_CANONICAL;\n>   \t\t\t\t\tif (!gitdir && !prefix)\n>   \t\t\t\t\t\tgitdir = \".git\";\n>   \t\t\t\t\tif (gitdir) {\n> @@ -1055,11 +999,11 @@ int cmd_rev_parse(int argc,\n>   \t\t\t\tstrbuf_reset(&buf);\n>   \t\t\t\tstrbuf_addf(&buf, \"%s%s.git\", cwd, len && cwd[len-1] != '/' ? \"/\" : \"\");\n>   \t\t\t\tfree(cwd);\n> -\t\t\t\tprint_path(buf.buf, prefix, wanted, DEFAULT_CANONICAL);\n> +\t\t\t\tprint_path(buf.buf, prefix, wanted, PATH_FORMAT_CANONICAL);\n>   \t\t\t\tcontinue;\n>   \t\t\t}\n>   \t\t\tif (!strcmp(arg, \"--git-common-dir\")) {\n> -\t\t\t\tprint_path(repo_get_common_dir(the_repository), prefix, format, DEFAULT_RELATIVE_IF_SHARED);\n> +\t\t\t\tprint_path(repo_get_common_dir(the_repository), prefix, arg_path_format, PATH_FORMAT_RELATIVE_IF_SHARED);\n>   \t\t\t\tcontinue;\n>   \t\t\t}\n>   \t\t\tif (!strcmp(arg, \"--is-inside-git-dir\")) {\n> @@ -1089,7 +1033,7 @@ int cmd_rev_parse(int argc,\n>   \t\t\t\tif (the_repository->index->split_index) {\n>   \t\t\t\t\tconst struct object_id *oid = &the_repository->index->split_index->base_oid;\n>   \t\t\t\t\tconst char *path = repo_git_path_replace(the_repository, &buf, \"sharedindex.%s\", oid_to_hex(oid));\n> -\t\t\t\t\tprint_path(path, prefix, format, DEFAULT_RELATIVE);\n> +\t\t\t\t\tprint_path(path, prefix, arg_path_format, PATH_FORMAT_RELATIVE);\n>   \t\t\t\t}\n>   \t\t\t\tcontinue;\n>   \t\t\t}\n\n"},{"id":"545673","messageId":"CA+rGoLfhhRNrSReeJ1grhy+2K3BSrikTCNgGpCaGqc4fFp3Lfg@mail.gmail.com","threadId":"65725","inReplyTo":"0077b1ae-3c85-4b34-a0ac-766395157c4f@gmail.com","subject":"Re: [GSoC Patch v5 2/4] rev-parse: use append_formatted_path() for path formatting","fromName":"K Jayatheerth","fromEmail":"jayatheerthkulkarni2005@gmail.com","sentAt":"2026-06-16T17:04:28Z","receivedAt":"2026-06-16T17:04:42Z","isPatch":true,"body":"Hi Phillip,\nThanks for taking a look!\n\n> On 16/06/2026 05:49, K Jayatheerth wrote:\n> > The path-formatting logic in builtin/rev-parse.c is tightly coupled\n> > to that command and writes directly to stdout, making it impossible\n> > for other builtins to reuse.\n> >\n> > Extract the core algorithm into append_formatted_path() in path.c\n> > and expose a path_format enum in path.h so that any builtin can\n> > format paths consistently without duplicating logic.\n>\n> Sorry I haven't had time to look at this series recently, it is looking\n> much nicer now that we have a single enum. It would be helpful to\n> explain why we need PATH_FORMAT_DEFAULT that acts exactly like\n> PATH_FORMAT_UNMODIFIED. Looking at the next patch it seems this is still\n> a wart in the api due to rev-parse wanting needing to distinguish the\n> unmodified case from the default case.\nt);\n> > +\n> >   # ifdef USE_THE_REPOSITORY_VARIABLE\n> >   #  include \"strbuf.h\"\n> >   #  include \"repository.h\"\n>\n\n\n> >   int cmd_rev_parse(int argc,\n> > @@ -717,7 +661,7 @@ int cmd_rev_parse(int argc,\n> >       const char *name = NULL;\n> >       struct strbuf buf = STRBUF_INIT;\n> >       int seen_end_of_options = 0;\n> > -     enum format_type format = FORMAT_DEFAULT;\n> > +     enum path_format arg_path_format = PATH_FORMAT_DEFAULT;\n>\n> This is the source of the api wart I referred to in the previous patch.\n> Could we keep the existing enums and convert them into the appropriate\n> PATH_FORMAT_* flag in print_path() above? I think we already have the\n> logic to do that in the existing code. That would mean that other users\n> of append_formatted_path() don't have to worry about the extra flag.\n>\n\nThat is a much more elegant solution than the current one.\n\nFor v6, I will clean this up by keeping the fallback logic\nlocalized within builtin/rev-parse.c and removing\nPATH_FORMAT_DEFAULT entirely from enum path_format in path.h.\n\nInstead, I'll re-introduce a small local enum (e.g., enum\nrev_parse_format) inside rev-parse.c to handle the\ncommand-line parsing state (tracking whether the user\nexplicitly provided a flag or if we are still in a\nneutral/default state).\n\nAs you said, most of the logic is already present. In\nprint_path(), we will check that local tracking enum. If it’s\nset to the local default, we can map it directly to the\npath-specific def_format before invoking append_formatted_path().\nThis ensures other users of the function don't have to worry\nabout the extra flag.\n\nI will send out the v6 series with these fixes shortly.\n\nRegards,\n- K Jayatheerth\n"},{"id":"545676","messageId":"204a4160-f913-4121-8eee-1f42103afca1@gmail.com","threadId":"65725","inReplyTo":"CA+rGoLfhhRNrSReeJ1grhy+2K3BSrikTCNgGpCaGqc4fFp3Lfg@mail.gmail.com","subject":"Re: [GSoC Patch v5 2/4] rev-parse: use append_formatted_path() for path formatting","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2026-06-16T18:26:43Z","receivedAt":"2026-06-16T18:26:47Z","isPatch":true,"body":"\n\nOn 16/06/2026 18:04, K Jayatheerth wrote:\n> Hi Phillip,\n> Thanks for taking a look!\n> \n>> On 16/06/2026 05:49, K Jayatheerth wrote:\n>>> The path-formatting logic in builtin/rev-parse.c is tightly coupled\n>>> to that command and writes directly to stdout, making it impossible\n>>> for other builtins to reuse.\n>>>\n>>> Extract the core algorithm into append_formatted_path() in path.c\n>>> and expose a path_format enum in path.h so that any builtin can\n>>> format paths consistently without duplicating logic.\n>>\n>> Sorry I haven't had time to look at this series recently, it is looking\n>> much nicer now that we have a single enum. It would be helpful to\n>> explain why we need PATH_FORMAT_DEFAULT that acts exactly like\n>> PATH_FORMAT_UNMODIFIED. Looking at the next patch it seems this is still\n>> a wart in the api due to rev-parse wanting needing to distinguish the\n>> unmodified case from the default case.\n> t);\n>>> +\n>>>    # ifdef USE_THE_REPOSITORY_VARIABLE\n>>>    #  include \"strbuf.h\"\n>>>    #  include \"repository.h\"\n>>\n> \n> \n>>>    int cmd_rev_parse(int argc,\n>>> @@ -717,7 +661,7 @@ int cmd_rev_parse(int argc,\n>>>        const char *name = NULL;\n>>>        struct strbuf buf = STRBUF_INIT;\n>>>        int seen_end_of_options = 0;\n>>> -     enum format_type format = FORMAT_DEFAULT;\n>>> +     enum path_format arg_path_format = PATH_FORMAT_DEFAULT;\n>>\n>> This is the source of the api wart I referred to in the previous patch.\n>> Could we keep the existing enums and convert them into the appropriate\n>> PATH_FORMAT_* flag in print_path() above? I think we already have the\n>> logic to do that in the existing code. That would mean that other users\n>> of append_formatted_path() don't have to worry about the extra flag.\n>>\n> \n> That is a much more elegant solution than the current one.\n> \n> For v6, I will clean this up by keeping the fallback logic\n> localized within builtin/rev-parse.c and removing\n> PATH_FORMAT_DEFAULT entirely from enum path_format in path.h.\n> \n> Instead, I'll re-introduce a small local enum (e.g., enum\n> rev_parse_format) inside rev-parse.c to handle the\n> command-line parsing state (tracking whether the user\n> explicitly provided a flag or if we are still in a\n> neutral/default state).\n\nI think it is probably simplest to keep the existing enums and modify \nprint_path() to convert them to the appropriate PATH_FORMAT_*. That way \nwe can keep the option parsing code as is.\n\nThanks\n\nPhillip\n\n> As you said, most of the logic is already present. In\n> print_path(), we will check that local tracking enum. If it’s\n> set to the local default, we can map it directly to the\n> path-specific def_format before invoking append_formatted_path().\n> This ensures other users of the function don't have to worry\n> about the extra flag.\n> \n> I will send out the v6 series with these fixes shortly.\n> \n> Regards,\n> - K Jayatheerth\n\n"},{"id":"546010","messageId":"20260620031644.353772-1-jayatheerthkulkarni2005@gmail.com","threadId":"65725","inReplyTo":"20260601151950.30686-1-jayatheerthkulkarni2005@gmail.com","subject":"[GSoC Patch v6 0/4] teach git repo info to handle path keys","fromName":"K Jayatheerth","fromEmail":"jayatheerthkulkarni2005@gmail.com","sentAt":"2026-06-20T03:16:40Z","receivedAt":"2026-06-20T03:17:33Z","isPatch":true,"body":"Hi!\n\nThis series teaches `git repo info` to handle `path.*`\nkeys, allowing scripts to reliably discover core\nrepository paths without resorting to `git rev-parse`.\n\nThe patches are structured as follows:\n\n1. path: Extract the localized path-formatting logic\n   out of `rev-parse` and expose it globally via\n   `path.h` using clear append semantics.\n\n2. rev-parse: Delegate the command's path-printing\n   helper to the newly shared path engine, while\n   leaving its existing option-parsing untouched.\n\n3. repo: Introduce `path.commondir.absolute` and\n   `path.commondir.relative` alongside a robust,\n   isolated test helper.\n\n4. repo: Introduce `path.gitdir.absolute` and\n   `path.gitdir.relative` using the same standardized\n   formatting rules.\n\nChanges since v5:\n\n* Dropped `PATH_FORMAT_DEFAULT` from the shared\n  `path_format` enum in path.h. It only existed to let\n  rev-parse track \"no format was requested\", which is a\n  rev-parse-specific concern that other callers of\n  `append_formatted_path()` shouldn't need to reason\n  about (Phillip).\n\n* Reverted `print_path()` in builtin/rev-parse.c to keep\n  its original `format_type` and `default_type` local\n  enums completely untouched (Phillip).\n\n* As a result, patch 2 is now much smaller: it only\n  touches the body of `print_path()`.\n\nTagging Justin Tobler, Lucas Seiki Oshiro, Junio,\nPhillip Wood, brian m. carlson, and Ayush Jha.\n\nThanks again for the careful review!\n\nK Jayatheerth (4):\n  path: introduce append_formatted_path() for shared path formatting\n  rev-parse: use append_formatted_path() for path formatting\n  repo: add path.commondir with absolute and relative suffix formatting\n  repo: add path.gitdir with absolute and relative suffix formatting\n\n Documentation/git-repo.adoc | 15 ++++++++\n builtin/repo.c              | 50 +++++++++++++++++++++++++\n builtin/rev-parse.c         | 73 +++++++++++++++----------------------\n path.c                      | 69 +++++++++++++++++++++++++++++++++++\n path.h                      | 30 +++++++++++++++\n t/t1900-repo-info.sh        | 58 +++++++++++++++++++++++++++++\n 6 files changed, 251 insertions(+), 44 deletions(-)\n\nRange-diff against v5:\n1:  31bc2c96e9 ! 1:  bb8bb40030 path: introduce append_formatted_path() for shared path formatting\n    @@ path.c: char *xdg_cache_home(const char *filename)\n     +\t\t\t   const char *prefix, enum path_format format)\n     +{\n     +\tswitch (format) {\n    -+\tcase PATH_FORMAT_DEFAULT:\n     +\tcase PATH_FORMAT_UNMODIFIED:\n     +\t\tstrbuf_addstr(dest, path);\n     +\t\tbreak;\n    @@ path.h: enum scld_error safe_create_leading_directories_no_share(char *path);\n     + * The formatting strategy to apply when writing a path into a buffer.\n     + */\n     +enum path_format {\n    -+\t/*\n    -+\t * Represents the default formatting behavior. Treated as\n    -+\t * PATH_FORMAT_UNMODIFIED by append_formatted_path().\n    -+\t */\n    -+\tPATH_FORMAT_DEFAULT,\n    -+\n     +\t/* Output the path exactly as-is without any modifications. */\n     +\tPATH_FORMAT_UNMODIFIED,\n     +\n2:  12af24ffc3 ! 2:  0ab0e4bde3 rev-parse: use append_formatted_path() for path formatting\n    @@ Metadata\n      ## Commit message ##\n         rev-parse: use append_formatted_path() for path formatting\n     \n    -    Now that path formatting logic lives in a shared helper, keeping a\n    -    duplicate implementation in rev-parse is unnecessary and risks the\n    -    two diverging over time.\n    +    Now that the core path-formatting algorithm lives in\n    +    append_formatted_path(), print_path() doesn't need to duplicate it.\n     \n    -    Replace the local format_type and default_type enums and the\n    -    hand-rolled formatting logic with a call to append_formatted_path().\n    -    Introduce PATH_FORMAT_DEFAULT as the initial value of arg_path_format\n    -    so that per-path fallback behavior is resolved in print_path() rather\n    -    than leaked into the shared helper.\n    +    Replace the body of print_path() with a small mapping from rev-parse's\n    +    existing format_type/default_type pair to the shared path_format enum,\n    +    then delegate to append_formatted_path(). The two local enums, and\n    +    every call site that uses them throughout cmd_rev_parse(), are left\n    +    untouched.\n     \n         Mentored-by: Justin Tobler <jltobler@gmail.com>\n         Mentored-by: Lucas Seiki Oshiro <lucasseikioshiro@gmail.com>\n         Signed-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>\n     \n      ## builtin/rev-parse.c ##\n    -@@ builtin/rev-parse.c: static void handle_ref_opt(const char *pattern, const char *prefix)\n    - \tclear_ref_exclusions(&ref_excludes);\n    - }\n    +@@ builtin/rev-parse.c: enum default_type {\n    + \tDEFAULT_UNMODIFIED,\n    + };\n      \n    --enum format_type {\n    --\t/* We would like a relative path. */\n    --\tFORMAT_RELATIVE,\n    --\t/* We would like a canonical absolute path. */\n    --\tFORMAT_CANONICAL,\n    --\t/* We would like the default behavior. */\n    --\tFORMAT_DEFAULT,\n    --};\n    --\n    --enum default_type {\n    --\t/* Our default is a relative path. */\n    --\tDEFAULT_RELATIVE,\n    --\t/* Our default is a relative path if there's a shared root. */\n    --\tDEFAULT_RELATIVE_IF_SHARED,\n    --\t/* Our default is a canonical absolute path. */\n    --\tDEFAULT_CANONICAL,\n    --\t/* Our default is not to modify the item. */\n    --\tDEFAULT_UNMODIFIED,\n    --};\n    --\n     -static void print_path(const char *path, const char *prefix, enum format_type format, enum default_type def)\n     +static void print_path(const char *path, const char *prefix,\n    -+\t\t       enum path_format arg_path_format, enum path_format def_format)\n    ++\t\t       enum format_type format, enum default_type def)\n      {\n     -\tchar *cwd = NULL;\n     -\t/*\n    @@ builtin/rev-parse.c: static void handle_ref_opt(const char *pattern, const char\n     -\t\tif (!is_absolute_path(prefix)) {\n     -\t\t\tstrbuf_realpath_forgiving(&prefixbuf, prefix, 1);\n     -\t\t\tprefix = prefixbuf.buf;\n    --\t\t}\n    ++\tstruct strbuf sb = STRBUF_INIT;\n    ++\tenum path_format fmt;\n    ++\n    ++\tif (format == FORMAT_RELATIVE) {\n    ++\t\tfmt = PATH_FORMAT_RELATIVE;\n    ++\t} else if (format == FORMAT_CANONICAL) {\n    ++\t\tfmt = PATH_FORMAT_CANONICAL;\n    ++\t} else /* FORMAT_DEFAULT */ {\n    ++\t\tswitch (def) {\n    ++\t\tcase DEFAULT_RELATIVE:\n    ++\t\t\tfmt = PATH_FORMAT_RELATIVE;\n    ++\t\t\tbreak;\n    ++\t\tcase DEFAULT_RELATIVE_IF_SHARED:\n    ++\t\t\tfmt = PATH_FORMAT_RELATIVE_IF_SHARED;\n    ++\t\t\tbreak;\n    ++\t\tcase DEFAULT_CANONICAL:\n    ++\t\t\tfmt = PATH_FORMAT_CANONICAL;\n    ++\t\t\tbreak;\n    ++\t\tcase DEFAULT_UNMODIFIED:\n    ++\t\tdefault:\n    ++\t\t\tfmt = PATH_FORMAT_UNMODIFIED;\n    ++\t\t\tbreak;\n    + \t\t}\n     -\t\tputs(relative_path(path, prefix, &buf));\n     -\t\tstrbuf_release(&buf);\n     -\t\tstrbuf_release(&realbuf);\n    @@ builtin/rev-parse.c: static void handle_ref_opt(const char *pattern, const char\n     -\t\tstrbuf_realpath_forgiving(&buf, path, 1);\n     -\t\tputs(buf.buf);\n     -\t\tstrbuf_release(&buf);\n    --\t}\n    + \t}\n     -\tfree(cwd);\n    -+\tstruct strbuf sb = STRBUF_INIT;\n    -+\t/* If the user didn't explicitly specify a format, fallback to the path-specific default. */\n    -+\tenum path_format fmt = (arg_path_format != PATH_FORMAT_DEFAULT) ? arg_path_format : def_format;\n     +\n     +\tappend_formatted_path(&sb, path, prefix, fmt);\n     +\tputs(sb.buf);\n    @@ builtin/rev-parse.c: static void handle_ref_opt(const char *pattern, const char\n      }\n      \n      int cmd_rev_parse(int argc,\n    -@@ builtin/rev-parse.c: int cmd_rev_parse(int argc,\n    - \tconst char *name = NULL;\n    - \tstruct strbuf buf = STRBUF_INIT;\n    - \tint seen_end_of_options = 0;\n    --\tenum format_type format = FORMAT_DEFAULT;\n    -+\tenum path_format arg_path_format = PATH_FORMAT_DEFAULT;\n    - \n    - \tshow_usage_if_asked(argc, argv, builtin_rev_parse_usage);\n    - \n    -@@ builtin/rev-parse.c: int cmd_rev_parse(int argc,\n    - \t\t\t\t\tdie(_(\"--git-path requires an argument\"));\n    - \t\t\t\tprint_path(repo_git_path_replace(the_repository, &buf,\n    - \t\t\t\t\t\t\t\t \"%s\", argv[i + 1]), prefix,\n    --\t\t\t\t\t\tformat,\n    --\t\t\t\t\t\tDEFAULT_RELATIVE_IF_SHARED);\n    -+\t\t\t\t\t\targ_path_format,\n    -+\t\t\t\t\t\tPATH_FORMAT_RELATIVE_IF_SHARED);\n    - \t\t\t\ti++;\n    - \t\t\t\tcontinue;\n    - \t\t\t}\n    -@@ builtin/rev-parse.c: int cmd_rev_parse(int argc,\n    - \t\t\t\tif (!arg)\n    - \t\t\t\t\tdie(_(\"--path-format requires an argument\"));\n    - \t\t\t\tif (!strcmp(arg, \"absolute\")) {\n    --\t\t\t\t\tformat = FORMAT_CANONICAL;\n    -+\t\t\t\t\targ_path_format = PATH_FORMAT_CANONICAL;\n    - \t\t\t\t} else if (!strcmp(arg, \"relative\")) {\n    --\t\t\t\t\tformat = FORMAT_RELATIVE;\n    -+\t\t\t\t\targ_path_format = PATH_FORMAT_RELATIVE;\n    - \t\t\t\t} else {\n    - \t\t\t\t\tdie(_(\"unknown argument to --path-format: %s\"), arg);\n    - \t\t\t\t}\n    -@@ builtin/rev-parse.c: int cmd_rev_parse(int argc,\n    - \t\t\tif (!strcmp(arg, \"--show-toplevel\")) {\n    - \t\t\t\tconst char *work_tree = repo_get_work_tree(the_repository);\n    - \t\t\t\tif (work_tree)\n    --\t\t\t\t\tprint_path(work_tree, prefix, format, DEFAULT_UNMODIFIED);\n    -+\t\t\t\t\tprint_path(work_tree, prefix, arg_path_format, PATH_FORMAT_UNMODIFIED);\n    - \t\t\t\telse\n    - \t\t\t\t\tdie(_(\"this operation must be run in a work tree\"));\n    - \t\t\t\tcontinue;\n    -@@ builtin/rev-parse.c: int cmd_rev_parse(int argc,\n    - \t\t\tif (!strcmp(arg, \"--show-superproject-working-tree\")) {\n    - \t\t\t\tstruct strbuf superproject = STRBUF_INIT;\n    - \t\t\t\tif (get_superproject_working_tree(&superproject))\n    --\t\t\t\t\tprint_path(superproject.buf, prefix, format, DEFAULT_UNMODIFIED);\n    -+\t\t\t\t\tprint_path(superproject.buf, prefix, arg_path_format, PATH_FORMAT_UNMODIFIED);\n    - \t\t\t\tstrbuf_release(&superproject);\n    - \t\t\t\tcontinue;\n    - \t\t\t}\n    -@@ builtin/rev-parse.c: int cmd_rev_parse(int argc,\n    - \t\t\t\tconst char *gitdir = getenv(GIT_DIR_ENVIRONMENT);\n    - \t\t\t\tchar *cwd;\n    - \t\t\t\tint len;\n    --\t\t\t\tenum format_type wanted = format;\n    -+\t\t\t\tenum path_format wanted = arg_path_format;\n    - \t\t\t\tif (arg[2] == 'g') {\t/* --git-dir */\n    - \t\t\t\t\tif (gitdir) {\n    --\t\t\t\t\t\tprint_path(gitdir, prefix, format, DEFAULT_UNMODIFIED);\n    -+\t\t\t\t\t\tprint_path(gitdir, prefix, arg_path_format, PATH_FORMAT_UNMODIFIED);\n    - \t\t\t\t\t\tcontinue;\n    - \t\t\t\t\t}\n    - \t\t\t\t\tif (!prefix) {\n    --\t\t\t\t\t\tprint_path(\".git\", prefix, format, DEFAULT_UNMODIFIED);\n    -+\t\t\t\t\t\tprint_path(\".git\", prefix, arg_path_format, PATH_FORMAT_UNMODIFIED);\n    - \t\t\t\t\t\tcontinue;\n    - \t\t\t\t\t}\n    - \t\t\t\t} else {\t\t/* --absolute-git-dir */\n    --\t\t\t\t\twanted = FORMAT_CANONICAL;\n    -+\t\t\t\t\twanted = PATH_FORMAT_CANONICAL;\n    - \t\t\t\t\tif (!gitdir && !prefix)\n    - \t\t\t\t\t\tgitdir = \".git\";\n    - \t\t\t\t\tif (gitdir) {\n    -@@ builtin/rev-parse.c: int cmd_rev_parse(int argc,\n    - \t\t\t\tstrbuf_reset(&buf);\n    - \t\t\t\tstrbuf_addf(&buf, \"%s%s.git\", cwd, len && cwd[len-1] != '/' ? \"/\" : \"\");\n    - \t\t\t\tfree(cwd);\n    --\t\t\t\tprint_path(buf.buf, prefix, wanted, DEFAULT_CANONICAL);\n    -+\t\t\t\tprint_path(buf.buf, prefix, wanted, PATH_FORMAT_CANONICAL);\n    - \t\t\t\tcontinue;\n    - \t\t\t}\n    - \t\t\tif (!strcmp(arg, \"--git-common-dir\")) {\n    --\t\t\t\tprint_path(repo_get_common_dir(the_repository), prefix, format, DEFAULT_RELATIVE_IF_SHARED);\n    -+\t\t\t\tprint_path(repo_get_common_dir(the_repository), prefix, arg_path_format, PATH_FORMAT_RELATIVE_IF_SHARED);\n    - \t\t\t\tcontinue;\n    - \t\t\t}\n    - \t\t\tif (!strcmp(arg, \"--is-inside-git-dir\")) {\n    -@@ builtin/rev-parse.c: int cmd_rev_parse(int argc,\n    - \t\t\t\tif (the_repository->index->split_index) {\n    - \t\t\t\t\tconst struct object_id *oid = &the_repository->index->split_index->base_oid;\n    - \t\t\t\t\tconst char *path = repo_git_path_replace(the_repository, &buf, \"sharedindex.%s\", oid_to_hex(oid));\n    --\t\t\t\t\tprint_path(path, prefix, format, DEFAULT_RELATIVE);\n    -+\t\t\t\t\tprint_path(path, prefix, arg_path_format, PATH_FORMAT_RELATIVE);\n    - \t\t\t\t}\n    - \t\t\t\tcontinue;\n    - \t\t\t}\n3:  7aecf1e806 = 3:  a50c75a55b repo: add path.commondir with absolute and relative suffix formatting\n4:  f30010b76c = 4:  1dd22e5cd4 repo: add path.gitdir with absolute and relative suffix formatting\n-- \n2.54.0\n\n"},{"id":"546011","messageId":"20260620031644.353772-2-jayatheerthkulkarni2005@gmail.com","threadId":"65725","inReplyTo":"20260620031644.353772-1-jayatheerthkulkarni2005@gmail.com","subject":"[GSoC Patch v6 1/4] path: introduce append_formatted_path() for shared path formatting","fromName":"K Jayatheerth","fromEmail":"jayatheerthkulkarni2005@gmail.com","sentAt":"2026-06-20T03:16:41Z","receivedAt":"2026-06-20T03:17:51Z","isPatch":true,"body":"The path-formatting logic in builtin/rev-parse.c is tightly coupled\nto that command and writes directly to stdout, making it impossible\nfor other builtins to reuse.\n\nExtract the core algorithm into append_formatted_path() in path.c\nand expose a path_format enum in path.h so that any builtin can\nformat paths consistently without duplicating logic.\n\nMentored-by: Justin Tobler <jltobler@gmail.com>\nMentored-by: Lucas Seiki Oshiro <lucasseikioshiro@gmail.com>\nSigned-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>\n---\n path.c | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n path.h | 30 +++++++++++++++++++++++++\n 2 files changed, 99 insertions(+)\n\ndiff --git a/path.c b/path.c\nindex d7e17bf174..6d8e892ada 100644\n--- a/path.c\n+++ b/path.c\n@@ -1579,6 +1579,75 @@ char *xdg_cache_home(const char *filename)\n \treturn NULL;\n }\n \n+void append_formatted_path(struct strbuf *dest, const char *path,\n+\t\t\t   const char *prefix, enum path_format format)\n+{\n+\tswitch (format) {\n+\tcase PATH_FORMAT_UNMODIFIED:\n+\t\tstrbuf_addstr(dest, path);\n+\t\tbreak;\n+\n+\tcase PATH_FORMAT_RELATIVE: {\n+\t\tstruct strbuf relative_buf = STRBUF_INIT;\n+\t\tstruct strbuf real_path = STRBUF_INIT;\n+\t\tstruct strbuf real_prefix = STRBUF_INIT;\n+\t\tchar *cwd = NULL;\n+\n+\t\t/*\n+\t\t * We don't ever produce a relative path if prefix is NULL,\n+\t\t * so set the prefix to the current directory so that we can\n+\t\t * produce a relative path whenever possible.\n+\t\t */\n+\t\tif (!prefix)\n+\t\t\tprefix = cwd = xgetcwd();\n+\n+\t\tif (!is_absolute_path(path)) {\n+\t\t\tstrbuf_realpath_forgiving(&real_path, path, 1);\n+\t\t\tpath = real_path.buf;\n+\t\t}\n+\t\tif (!is_absolute_path(prefix)) {\n+\t\t\tstrbuf_realpath_forgiving(&real_prefix, prefix, 1);\n+\t\t\tprefix = real_prefix.buf;\n+\t\t}\n+\n+\t\tstrbuf_addstr(dest, relative_path(path, prefix, &relative_buf));\n+\n+\t\tstrbuf_release(&relative_buf);\n+\t\tstrbuf_release(&real_path);\n+\t\tstrbuf_release(&real_prefix);\n+\t\tfree(cwd);\n+\t\tbreak;\n+\t}\n+\n+\tcase PATH_FORMAT_RELATIVE_IF_SHARED: {\n+\t\tstruct strbuf relative_buf = STRBUF_INIT;\n+\n+\t\t/*\n+\t\t * If we're using RELATIVE_IF_SHARED mode, then we want an\n+\t\t * absolute path unless the two share a common prefix, so don't\n+\t\t * default the prefix to the current working directory. Doing so\n+\t\t * would cause a relative path to always be produced if possible.\n+\t\t */\n+\t\tstrbuf_addstr(dest, relative_path(path, prefix, &relative_buf));\n+\t\tstrbuf_release(&relative_buf);\n+\t\tbreak;\n+\t}\n+\n+\tcase PATH_FORMAT_CANONICAL: {\n+\t\tstruct strbuf canonical_buf = STRBUF_INIT;\n+\n+\t\tstrbuf_realpath_forgiving(&canonical_buf, path, 1);\n+\t\tstrbuf_addbuf(dest, &canonical_buf);\n+\n+\t\tstrbuf_release(&canonical_buf);\n+\t\tbreak;\n+\t}\n+\n+\tdefault:\n+\t\tBUG(\"unknown path_format value %d\", format);\n+\t}\n+}\n+\n REPO_GIT_PATH_FUNC(squash_msg, \"SQUASH_MSG\")\n REPO_GIT_PATH_FUNC(merge_msg, \"MERGE_MSG\")\n REPO_GIT_PATH_FUNC(merge_rr, \"MERGE_RR\")\ndiff --git a/path.h b/path.h\nindex 4c2958a903..4d982a2c8e 100644\n--- a/path.h\n+++ b/path.h\n@@ -262,6 +262,36 @@ enum scld_error safe_create_leading_directories_no_share(char *path);\n int safe_create_file_with_leading_directories(struct repository *repo,\n \t\t\t\t\t      const char *path);\n \n+/**\n+ * The formatting strategy to apply when writing a path into a buffer.\n+ */\n+enum path_format {\n+\t/* Output the path exactly as-is without any modifications. */\n+\tPATH_FORMAT_UNMODIFIED,\n+\n+\t/* Output a path relative to the provided directory prefix. */\n+\tPATH_FORMAT_RELATIVE,\n+\n+\t/* Output a relative path only if the path shares a root with the prefix. */\n+\tPATH_FORMAT_RELATIVE_IF_SHARED,\n+\n+\t/* Output a fully resolved, absolute canonical path. */\n+\tPATH_FORMAT_CANONICAL\n+};\n+\n+/**\n+ * Format a path according to the specified formatting strategy and append\n+ * the result to the given strbuf.\n+ *\n+ * `dest`   : The string buffer to append the formatted path to.\n+ * `path`   : The path string that needs to be formatted.\n+ * `prefix` : The directory prefix to calculate relative offsets against.\n+ * Pass NULL to default to the current working directory where applicable.\n+ * `format` : The formatting behavior rule to execute.\n+ */\n+void append_formatted_path(struct strbuf *dest, const char *path,\n+\t\t\t   const char *prefix, enum path_format format);\n+\n # ifdef USE_THE_REPOSITORY_VARIABLE\n #  include \"strbuf.h\"\n #  include \"repository.h\"\n-- \n2.54.0\n\n"},{"id":"546012","messageId":"20260620031644.353772-3-jayatheerthkulkarni2005@gmail.com","threadId":"65725","inReplyTo":"20260620031644.353772-1-jayatheerthkulkarni2005@gmail.com","subject":"[GSoC Patch v6 2/4] rev-parse: use append_formatted_path() for path formatting","fromName":"K Jayatheerth","fromEmail":"jayatheerthkulkarni2005@gmail.com","sentAt":"2026-06-20T03:16:42Z","receivedAt":"2026-06-20T03:18:02Z","isPatch":true,"body":"Now that the core path-formatting algorithm lives in\nappend_formatted_path(), print_path() doesn't need to duplicate it.\n\nReplace the body of print_path() with a small mapping from rev-parse's\nexisting format_type/default_type pair to the shared path_format enum,\nthen delegate to append_formatted_path(). The two local enums, and\nevery call site that uses them throughout cmd_rev_parse(), are left\nuntouched.\n\nMentored-by: Justin Tobler <jltobler@gmail.com>\nMentored-by: Lucas Seiki Oshiro <lucasseikioshiro@gmail.com>\nSigned-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>\n---\n builtin/rev-parse.c | 73 ++++++++++++++++++---------------------------\n 1 file changed, 29 insertions(+), 44 deletions(-)\n\ndiff --git a/builtin/rev-parse.c b/builtin/rev-parse.c\nindex bb882678fe..6de01466db 100644\n--- a/builtin/rev-parse.c\n+++ b/builtin/rev-parse.c\n@@ -653,53 +653,38 @@ enum default_type {\n \tDEFAULT_UNMODIFIED,\n };\n \n-static void print_path(const char *path, const char *prefix, enum format_type format, enum default_type def)\n+static void print_path(const char *path, const char *prefix,\n+\t\t       enum format_type format, enum default_type def)\n {\n-\tchar *cwd = NULL;\n-\t/*\n-\t * We don't ever produce a relative path if prefix is NULL, so set the\n-\t * prefix to the current directory so that we can produce a relative\n-\t * path whenever possible.  If we're using RELATIVE_IF_SHARED mode, then\n-\t * we want an absolute path unless the two share a common prefix, so don't\n-\t * set it in that case, since doing so causes a relative path to always\n-\t * be produced if possible.\n-\t */\n-\tif (!prefix && (format != FORMAT_DEFAULT || def != DEFAULT_RELATIVE_IF_SHARED))\n-\t\tprefix = cwd = xgetcwd();\n-\tif (format == FORMAT_DEFAULT && def == DEFAULT_UNMODIFIED) {\n-\t\tputs(path);\n-\t} else if (format == FORMAT_RELATIVE ||\n-\t\t  (format == FORMAT_DEFAULT && def == DEFAULT_RELATIVE)) {\n-\t\t/*\n-\t\t * In order for relative_path to work as expected, we need to\n-\t\t * make sure that both paths are absolute paths.  If we don't,\n-\t\t * we can end up with an unexpected absolute path that the user\n-\t\t * didn't want.\n-\t\t */\n-\t\tstruct strbuf buf = STRBUF_INIT, realbuf = STRBUF_INIT, prefixbuf = STRBUF_INIT;\n-\t\tif (!is_absolute_path(path)) {\n-\t\t\tstrbuf_realpath_forgiving(&realbuf, path,  1);\n-\t\t\tpath = realbuf.buf;\n-\t\t}\n-\t\tif (!is_absolute_path(prefix)) {\n-\t\t\tstrbuf_realpath_forgiving(&prefixbuf, prefix, 1);\n-\t\t\tprefix = prefixbuf.buf;\n+\tstruct strbuf sb = STRBUF_INIT;\n+\tenum path_format fmt;\n+\n+\tif (format == FORMAT_RELATIVE) {\n+\t\tfmt = PATH_FORMAT_RELATIVE;\n+\t} else if (format == FORMAT_CANONICAL) {\n+\t\tfmt = PATH_FORMAT_CANONICAL;\n+\t} else /* FORMAT_DEFAULT */ {\n+\t\tswitch (def) {\n+\t\tcase DEFAULT_RELATIVE:\n+\t\t\tfmt = PATH_FORMAT_RELATIVE;\n+\t\t\tbreak;\n+\t\tcase DEFAULT_RELATIVE_IF_SHARED:\n+\t\t\tfmt = PATH_FORMAT_RELATIVE_IF_SHARED;\n+\t\t\tbreak;\n+\t\tcase DEFAULT_CANONICAL:\n+\t\t\tfmt = PATH_FORMAT_CANONICAL;\n+\t\t\tbreak;\n+\t\tcase DEFAULT_UNMODIFIED:\n+\t\tdefault:\n+\t\t\tfmt = PATH_FORMAT_UNMODIFIED;\n+\t\t\tbreak;\n \t\t}\n-\t\tputs(relative_path(path, prefix, &buf));\n-\t\tstrbuf_release(&buf);\n-\t\tstrbuf_release(&realbuf);\n-\t\tstrbuf_release(&prefixbuf);\n-\t} else if (format == FORMAT_DEFAULT && def == DEFAULT_RELATIVE_IF_SHARED) {\n-\t\tstruct strbuf buf = STRBUF_INIT;\n-\t\tputs(relative_path(path, prefix, &buf));\n-\t\tstrbuf_release(&buf);\n-\t} else {\n-\t\tstruct strbuf buf = STRBUF_INIT;\n-\t\tstrbuf_realpath_forgiving(&buf, path, 1);\n-\t\tputs(buf.buf);\n-\t\tstrbuf_release(&buf);\n \t}\n-\tfree(cwd);\n+\n+\tappend_formatted_path(&sb, path, prefix, fmt);\n+\tputs(sb.buf);\n+\n+\tstrbuf_release(&sb);\n }\n \n int cmd_rev_parse(int argc,\n-- \n2.54.0\n\n"},{"id":"546013","messageId":"20260620031644.353772-4-jayatheerthkulkarni2005@gmail.com","threadId":"65725","inReplyTo":"20260620031644.353772-1-jayatheerthkulkarni2005@gmail.com","subject":"[GSoC Patch v6 3/4] repo: add path.commondir with absolute and relative suffix formatting","fromName":"K Jayatheerth","fromEmail":"jayatheerthkulkarni2005@gmail.com","sentAt":"2026-06-20T03:16:43Z","receivedAt":"2026-06-20T03:18:18Z","isPatch":true,"body":"Scripts working with worktree setups need a reliable way to discover\nthe common directory, which diverges from the git directory when\nmultiple worktrees are in use. There is no way to retrieve this path\nfrom git repo info today.\n\nIntroduce path.commondir.absolute and path.commondir.relative keys.\nExposing explicit format variants rather than a single key with a\ndefault avoids ambiguity for scripts that require predictable output.\n\nMentored-by: Justin Tobler <jltobler@gmail.com>\nMentored-by: Lucas Seiki Oshiro <lucasseikioshiro@gmail.com>\nSigned-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>\n---\n Documentation/git-repo.adoc |  9 +++++++\n builtin/repo.c              | 26 +++++++++++++++++++\n t/t1900-repo-info.sh        | 52 +++++++++++++++++++++++++++++++++++++\n 3 files changed, 87 insertions(+)\n\ndiff --git a/Documentation/git-repo.adoc b/Documentation/git-repo.adoc\nindex 42262c1983..890c34051d 100644\n--- a/Documentation/git-repo.adoc\n+++ b/Documentation/git-repo.adoc\n@@ -104,6 +104,15 @@ values that they return:\n `object.format`::\n \tThe object format (hash algorithm) used in the repository.\n \n+`path.commondir.absolute`::\n+\tThe canonical absolute path to the Git repository's common\n+\tdirectory (the shared `.git` directory containing objects,\n+\trefs, and global configuration).\n+\n+`path.commondir.relative`::\n+\tThe path to the Git repository's common directory relative to\n+\tthe current working directory.\n+\n `references.format`::\n \tThe reference storage format. The valid values are:\n +\ndiff --git a/builtin/repo.c b/builtin/repo.c\nindex 71a5c1c29c..c4cc3bf3fc 100644\n--- a/builtin/repo.c\n+++ b/builtin/repo.c\n@@ -7,12 +7,14 @@\n #include \"hex.h\"\n #include \"odb.h\"\n #include \"parse-options.h\"\n+#include \"path.h\"\n #include \"path-walk.h\"\n #include \"progress.h\"\n #include \"quote.h\"\n #include \"ref-filter.h\"\n #include \"refs.h\"\n #include \"revision.h\"\n+#include \"setup.h\"\n #include \"strbuf.h\"\n #include \"string-list.h\"\n #include \"shallow.h\"\n@@ -75,6 +77,28 @@ static int get_object_format(struct repository *repo, struct strbuf *buf)\n \treturn 0;\n }\n \n+static int get_path_commondir_absolute(struct repository *repo, struct strbuf *buf)\n+{\n+\tconst char *common_dir = repo_get_common_dir(repo);\n+\n+\tif (!common_dir)\n+\t\treturn error(_(\"unable to get common directory\"));\n+\n+\tappend_formatted_path(buf, common_dir, startup_info->prefix, PATH_FORMAT_CANONICAL);\n+\treturn 0;\n+}\n+\n+static int get_path_commondir_relative(struct repository *repo, struct strbuf *buf)\n+{\n+\tconst char *common_dir = repo_get_common_dir(repo);\n+\n+\tif (!common_dir)\n+\t\treturn error(_(\"unable to get common directory\"));\n+\n+\tappend_formatted_path(buf, common_dir, startup_info->prefix, PATH_FORMAT_RELATIVE);\n+\treturn 0;\n+}\n+\n static int get_references_format(struct repository *repo, struct strbuf *buf)\n {\n \tstrbuf_addstr(buf,\n@@ -87,6 +111,8 @@ static const struct repo_info_field repo_info_field[] = {\n \t{ \"layout.bare\", get_layout_bare },\n \t{ \"layout.shallow\", get_layout_shallow },\n \t{ \"object.format\", get_object_format },\n+\t{ \"path.commondir.absolute\", get_path_commondir_absolute },\n+\t{ \"path.commondir.relative\", get_path_commondir_relative },\n \t{ \"references.format\", get_references_format },\n };\n \ndiff --git a/t/t1900-repo-info.sh b/t/t1900-repo-info.sh\nindex 39bb77dda0..09158d29f9 100755\n--- a/t/t1900-repo-info.sh\n+++ b/t/t1900-repo-info.sh\n@@ -155,4 +155,56 @@ test_expect_success 'git repo info -h shows only repo info usage' '\n \ttest_grep ! \"git repo structure\" actual\n '\n \n+# Helper function to test path keys in both absolute and relative formats.\n+# $1: label for the test\n+# $2: field_name (e.g., commondir)\n+# $3: expected_dir (the directory name, e.g., .git or custom-common)\n+# $4: init_command (extra setup like exporting env vars)\n+test_repo_info_path () {\n+\tlabel=$1\n+\tfield_name=$2\n+\texpected_dir=$3\n+\tinit_command=$4\n+\n+\ttest_expect_success \"absolute: $label\" '\n+\t\ttest_when_finished \"rm -rf repo\" &&\n+\t\tgit init repo &&\n+\t\t(\n+\t\t\tmkdir -p repo/sub &&\n+\t\t\tcd repo/sub &&\n+\t\t\tROOT=\"$(test-tool path-utils real_path ..)\" && export ROOT &&\n+\t\t\teval \"$init_command\" &&\n+\t\t\techo \"path.$field_name.absolute=$ROOT/$expected_dir\" >expect &&\n+\t\t\tgit repo info \"path.$field_name.absolute\" >actual &&\n+\t\t\ttest_cmp expect actual\n+\t\t)\n+\t'\n+\n+\ttest_expect_success \"relative: $label\" '\n+\t\ttest_when_finished \"rm -rf repo\" &&\n+\t\tgit init repo &&\n+\t\t(\n+\t\t\tmkdir -p repo/sub &&\n+\t\t\tcd repo/sub &&\n+\t\t\tROOT=\"$(test-tool path-utils real_path ..)\" && export ROOT &&\n+\t\t\teval \"$init_command\" &&\n+\t\t\techo \"path.$field_name.relative=../$expected_dir\" >expect &&\n+\t\t\tgit repo info \"path.$field_name.relative\" >actual &&\n+\t\t\ttest_cmp expect actual\n+\t\t)\n+\t'\n+}\n+\n+test_repo_info_path 'commondir standard' 'commondir' '.git'\n+\n+test_repo_info_path 'commondir with GIT_COMMON_DIR and GIT_DIR' 'commondir' \\\n+\t'custom-common' \\\n+\t'GIT_COMMON_DIR=\"$ROOT/custom-common\" && export GIT_COMMON_DIR &&\n+\t GIT_DIR=\"../.git\" && export GIT_DIR &&\n+\t git init --bare \"$ROOT/custom-common\"'\n+\n+test_repo_info_path 'commondir with only GIT_DIR' 'commondir' \\\n+\t'.git' \\\n+\t'GIT_DIR=\"../.git\" && export GIT_DIR'\n+\n test_done\n-- \n2.54.0\n\n"},{"id":"546014","messageId":"20260620031644.353772-5-jayatheerthkulkarni2005@gmail.com","threadId":"65725","inReplyTo":"20260620031644.353772-1-jayatheerthkulkarni2005@gmail.com","subject":"[GSoC Patch v6 4/4] repo: add path.gitdir with absolute and relative suffix formatting","fromName":"K Jayatheerth","fromEmail":"jayatheerthkulkarni2005@gmail.com","sentAt":"2026-06-20T03:16:44Z","receivedAt":"2026-06-20T03:18:38Z","isPatch":true,"body":"Scripts need a stable way to locate the git directory without\nparsing rev-parse output or relying on its flag-driven path format\nselection. There is no way to retrieve this path from git repo info\ntoday.\n\nIntroduce path.gitdir.absolute and path.gitdir.relative keys,\nconsistent with the path.commondir keys added in the previous patch.\nReuse the test_repo_info_path helper introduced there to validate\nboth variants.\n\nMentored-by: Justin Tobler <jltobler@gmail.com>\nMentored-by: Lucas Seiki Oshiro <lucasseikioshiro@gmail.com>\nSigned-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>\n---\n Documentation/git-repo.adoc |  6 ++++++\n builtin/repo.c              | 24 ++++++++++++++++++++++++\n t/t1900-repo-info.sh        |  6 ++++++\n 3 files changed, 36 insertions(+)\n\ndiff --git a/Documentation/git-repo.adoc b/Documentation/git-repo.adoc\nindex 890c34051d..ed7d80c690 100644\n--- a/Documentation/git-repo.adoc\n+++ b/Documentation/git-repo.adoc\n@@ -113,6 +113,12 @@ values that they return:\n \tThe path to the Git repository's common directory relative to\n \tthe current working directory.\n \n+`path.gitdir.absolute`::\n+\tThe canonical absolute path to the Git repository directory (the `.git` directory).\n+\n+`path.gitdir.relative`::\n+\tThe path to the Git repository directory relative to the current working directory.\n+\n `references.format`::\n \tThe reference storage format. The valid values are:\n +\ndiff --git a/builtin/repo.c b/builtin/repo.c\nindex c4cc3bf3fc..9a312d127a 100644\n--- a/builtin/repo.c\n+++ b/builtin/repo.c\n@@ -99,6 +99,28 @@ static int get_path_commondir_relative(struct repository *repo, struct strbuf *b\n \treturn 0;\n }\n \n+static int get_path_gitdir_absolute(struct repository *repo, struct strbuf *buf)\n+{\n+\tconst char *git_dir = repo_get_git_dir(repo);\n+\n+\tif (!git_dir)\n+\t\treturn error(_(\"unable to get git directory\"));\n+\n+\tappend_formatted_path(buf, git_dir, startup_info->prefix, PATH_FORMAT_CANONICAL);\n+\treturn 0;\n+}\n+\n+static int get_path_gitdir_relative(struct repository *repo, struct strbuf *buf)\n+{\n+\tconst char *git_dir = repo_get_git_dir(repo);\n+\n+\tif (!git_dir)\n+\t\treturn error(_(\"unable to get git directory\"));\n+\n+\tappend_formatted_path(buf, git_dir, startup_info->prefix, PATH_FORMAT_RELATIVE);\n+\treturn 0;\n+}\n+\n static int get_references_format(struct repository *repo, struct strbuf *buf)\n {\n \tstrbuf_addstr(buf,\n@@ -113,6 +135,8 @@ static const struct repo_info_field repo_info_field[] = {\n \t{ \"object.format\", get_object_format },\n \t{ \"path.commondir.absolute\", get_path_commondir_absolute },\n \t{ \"path.commondir.relative\", get_path_commondir_relative },\n+\t{ \"path.gitdir.absolute\", get_path_gitdir_absolute },\n+\t{ \"path.gitdir.relative\", get_path_gitdir_relative },\n \t{ \"references.format\", get_references_format },\n };\n \ndiff --git a/t/t1900-repo-info.sh b/t/t1900-repo-info.sh\nindex 09158d29f9..ae8c22c817 100755\n--- a/t/t1900-repo-info.sh\n+++ b/t/t1900-repo-info.sh\n@@ -207,4 +207,10 @@ test_repo_info_path 'commondir with only GIT_DIR' 'commondir' \\\n \t'.git' \\\n \t'GIT_DIR=\"../.git\" && export GIT_DIR'\n \n+test_repo_info_path 'gitdir standard' 'gitdir' '.git'\n+\n+test_repo_info_path 'gitdir with explicit GIT_DIR' 'gitdir' \\\n+\t'.git' \\\n+\t'GIT_DIR=\"../.git\" && export GIT_DIR'\n+\n test_done\n-- \n2.54.0\n\n"},{"id":"546031","messageId":"xmqqbjd5guci.fsf@gitster.g","threadId":"65725","inReplyTo":"20260620031644.353772-2-jayatheerthkulkarni2005@gmail.com","subject":"Re: [GSoC Patch v6 1/4] path: introduce append_formatted_path() for shared path formatting","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-06-20T14:27:41Z","receivedAt":"2026-06-20T14:27:44Z","isPatch":true,"body":"K Jayatheerth <jayatheerthkulkarni2005@gmail.com> writes:\n\n> The path-formatting logic in builtin/rev-parse.c is tightly coupled\n> to that command and writes directly to stdout, making it impossible\n> for other builtins to reuse.\n>\n> Extract the core algorithm into append_formatted_path() in path.c\n> and expose a path_format enum in path.h so that any builtin can\n> format paths consistently without duplicating logic.\n>\n> Mentored-by: Justin Tobler <jltobler@gmail.com>\n> Mentored-by: Lucas Seiki Oshiro <lucasseikioshiro@gmail.com>\n> Signed-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>\n> ---\n>  path.c | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n>  path.h | 30 +++++++++++++++++++++++++\n>  2 files changed, 99 insertions(+)\n\nIt often, even though not always, is a sign of a bad topic structure\nto have an insertion-only patch without any removal of existing\ncode, that adds totally unused code.\n\nIf the step is to \"extract the core algorithm\", shouldn't it be able\nto replace existing code already?\n\nWe may want to add new features to this helper function near the end\nof the topic, but wouldn't it make sense for the topic to first\nconsolidate various path formatting logic already present in the\nexisting code into a single helper for ease of extending it (which\nmeans replacing open-coded logic in existing code paths with a call\nto the new helper, which would have a code that may look very\nsimilar to the original code that was replaced with a single call to\nthe helper function), and then expose the helper for use by new\ncallers, and finally further add new features that existing code\npaths wouldn't have needed but the new callers would want?\n\nHow else can we make sure this new implementation added by the first\nstep in the series is (1) capable enough to reproduce what we\nalready have in different parts of the system, (2) does not bring in\nwhat the current codebase does not need, and (3) bug-to-bug\ncompatible with the existing code paths?\n\n> diff --git a/path.c b/path.c\n> index d7e17bf174..6d8e892ada 100644\n> --- a/path.c\n> +++ b/path.c\n> @@ -1579,6 +1579,75 @@ char *xdg_cache_home(const char *filename)\n>  \treturn NULL;\n>  }\n>  \n> +void append_formatted_path(struct strbuf *dest, const char *path,\n> +\t\t\t   const char *prefix, enum path_format format)\n> +{\n> +\tswitch (format) {\n> +\tcase PATH_FORMAT_UNMODIFIED:\n> +\t\tstrbuf_addstr(dest, path);\n> +\t\tbreak;\n> +\n> +\tcase PATH_FORMAT_RELATIVE: {\n> +\t\tstruct strbuf relative_buf = STRBUF_INIT;\n> +\t\tstruct strbuf real_path = STRBUF_INIT;\n> +\t\tstruct strbuf real_prefix = STRBUF_INIT;\n> +\t\tchar *cwd = NULL;\n> +\n> +\t\t/*\n> +\t\t * We don't ever produce a relative path if prefix is NULL,\n> +\t\t * so set the prefix to the current directory so that we can\n> +\t\t * produce a relative path whenever possible.\n> +\t\t */\n> +\t\tif (!prefix)\n> +\t\t\tprefix = cwd = xgetcwd();\n> +\n> +\t\tif (!is_absolute_path(path)) {\n> +\t\t\tstrbuf_realpath_forgiving(&real_path, path, 1);\n> +\t\t\tpath = real_path.buf;\n> +\t\t}\n> +\t\tif (!is_absolute_path(prefix)) {\n> +\t\t\tstrbuf_realpath_forgiving(&real_prefix, prefix, 1);\n> +\t\t\tprefix = real_prefix.buf;\n> +\t\t}\n> +\n> +\t\tstrbuf_addstr(dest, relative_path(path, prefix, &relative_buf));\n> +\n> +\t\tstrbuf_release(&relative_buf);\n> +\t\tstrbuf_release(&real_path);\n> +\t\tstrbuf_release(&real_prefix);\n> +\t\tfree(cwd);\n> +\t\tbreak;\n> +\t}\n> +\n> +\tcase PATH_FORMAT_RELATIVE_IF_SHARED: {\n> +\t\tstruct strbuf relative_buf = STRBUF_INIT;\n> +\n> +\t\t/*\n> +\t\t * If we're using RELATIVE_IF_SHARED mode, then we want an\n> +\t\t * absolute path unless the two share a common prefix, so don't\n> +\t\t * default the prefix to the current working directory. Doing so\n> +\t\t * would cause a relative path to always be produced if possible.\n> +\t\t */\n> +\t\tstrbuf_addstr(dest, relative_path(path, prefix, &relative_buf));\n> +\t\tstrbuf_release(&relative_buf);\n> +\t\tbreak;\n> +\t}\n> +\n> +\tcase PATH_FORMAT_CANONICAL: {\n> +\t\tstruct strbuf canonical_buf = STRBUF_INIT;\n> +\n> +\t\tstrbuf_realpath_forgiving(&canonical_buf, path, 1);\n> +\t\tstrbuf_addbuf(dest, &canonical_buf);\n> +\n> +\t\tstrbuf_release(&canonical_buf);\n> +\t\tbreak;\n> +\t}\n> +\n> +\tdefault:\n> +\t\tBUG(\"unknown path_format value %d\", format);\n> +\t}\n> +}\n> +\n>  REPO_GIT_PATH_FUNC(squash_msg, \"SQUASH_MSG\")\n>  REPO_GIT_PATH_FUNC(merge_msg, \"MERGE_MSG\")\n>  REPO_GIT_PATH_FUNC(merge_rr, \"MERGE_RR\")\n> diff --git a/path.h b/path.h\n> index 4c2958a903..4d982a2c8e 100644\n> --- a/path.h\n> +++ b/path.h\n> @@ -262,6 +262,36 @@ enum scld_error safe_create_leading_directories_no_share(char *path);\n>  int safe_create_file_with_leading_directories(struct repository *repo,\n>  \t\t\t\t\t      const char *path);\n>  \n> +/**\n> + * The formatting strategy to apply when writing a path into a buffer.\n> + */\n> +enum path_format {\n> +\t/* Output the path exactly as-is without any modifications. */\n> +\tPATH_FORMAT_UNMODIFIED,\n> +\n> +\t/* Output a path relative to the provided directory prefix. */\n> +\tPATH_FORMAT_RELATIVE,\n> +\n> +\t/* Output a relative path only if the path shares a root with the prefix. */\n> +\tPATH_FORMAT_RELATIVE_IF_SHARED,\n> +\n> +\t/* Output a fully resolved, absolute canonical path. */\n> +\tPATH_FORMAT_CANONICAL\n> +};\n> +\n> +/**\n> + * Format a path according to the specified formatting strategy and append\n> + * the result to the given strbuf.\n> + *\n> + * `dest`   : The string buffer to append the formatted path to.\n> + * `path`   : The path string that needs to be formatted.\n> + * `prefix` : The directory prefix to calculate relative offsets against.\n> + * Pass NULL to default to the current working directory where applicable.\n> + * `format` : The formatting behavior rule to execute.\n> + */\n> +void append_formatted_path(struct strbuf *dest, const char *path,\n> +\t\t\t   const char *prefix, enum path_format format);\n> +\n>  # ifdef USE_THE_REPOSITORY_VARIABLE\n>  #  include \"strbuf.h\"\n>  #  include \"repository.h\"\n"},{"id":"546038","messageId":"CA+rGoLcibxaEs7KzS8a=A9kxV8+3KCqVXOK+zoiFtNvJkVHvCA@mail.gmail.com","threadId":"65725","inReplyTo":"xmqqbjd5guci.fsf@gitster.g","subject":"Re: [GSoC Patch v6 1/4] path: introduce append_formatted_path() for shared path formatting","fromName":"K Jayatheerth","fromEmail":"jayatheerthkulkarni2005@gmail.com","sentAt":"2026-06-20T16:30:09Z","receivedAt":"2026-06-20T16:30:22Z","isPatch":true,"body":"Hi Junio,\n\n> It often, even though not always, is a sign of a bad topic structure\n> to have an insertion-only patch without any removal of existing\n> code, that adds totally unused code.\n>\n> If the step is to \"extract the core algorithm\", shouldn't it be able\n> to replace existing code already?\n\nGiving the helper and converting its\nfirst caller (`rev-parse`) in the same step proves the implementation\navoids leaving unused code lingering in the tree, even temporarily.\n\n> We may want to add new features to this helper function near the end\n> of the topic, but wouldn't it make sense for the topic to first\n> consolidate various path formatting logic already present in the\n> existing code into a single helper for ease of extending it (which\n> means replacing open-coded logic in existing code paths with a call\n> to the new helper, which would have a code that may look very\n> similar to the original code that was replaced with a single call to\n> the helper function), and then expose the helper for use by new\n> callers, and finally further add new features that existing code\n> paths wouldn't have needed but the new callers would want?\n\nConsolidating the existing logic first ensures\nwe aren't introducing unnecessary complexity up front. I agree with\nrestructuring the topic this way.\n\n> How else can we make sure this new implementation added by the first\n> step in the series is (1) capable enough to reproduce what we\n> already have in different parts of the system, (2) does not bring in\n> what the current codebase does not need, and (3) bug-to-bug\n> compatible with the existing code paths?\n\nIntroducing the helper and swapping out the `rev-parse`\nimplementation in the same step is the best way to prove bug-to-bug\ncompatibility and demonstrate its immediate utility.\n\nFor v7, I will squash patches 1 and 2 together so that the extraction\nand the replacement happen simultaneously, guaranteeing that the new\n`append_formatted_path()` perfectly mirrors the old behavior before\nwe introduce the new `path.*` callers.\n\nThanks for taking the time to explain the rationale!\n\n- K Jayatheerth\n"},{"id":"546058","messageId":"20260621055534.46798-1-jayatheerthkulkarni2005@gmail.com","threadId":"65725","inReplyTo":"20260601151950.30686-1-jayatheerthkulkarni2005@gmail.com","subject":"[GSoC Patch v7 0/3] teach git repo info to handle path keys","fromName":"K Jayatheerth","fromEmail":"jayatheerthkulkarni2005@gmail.com","sentAt":"2026-06-21T05:55:31Z","receivedAt":"2026-06-21T05:57:21Z","isPatch":true,"body":"Hi!\n\nThis series teaches `git repo info` to handle `path.*`\nkeys, allowing scripts to reliably discover core\nrepository paths without resorting to `git rev-parse`.\n\nThe patches are structured as follows:\n\n1. path: Extract the localized path-formatting logic\n   out of `rev-parse` and expose it globally via\n   `path.h` using clear append semantics.\n\n2. repo: Introduce `path.commondir.absolute` and\n   `path.commondir.relative` alongside a robust,\n   isolated test helper.\n\n3. repo: Introduce `path.gitdir.absolute` and\n   `path.gitdir.relative` using the same standardized\n   formatting rules.\n\nChanges since v6:\n\nSquashed patches 1 and 2 to avoid dead code in the tree.\n\nTagging Justin Tobler, Lucas Seiki Oshiro, Junio,\nPhillip Wood, brian m. carlson, and Ayush Jha.\n\nThanks for helping improve this series!\n\nK Jayatheerth (3):\n  path: extract append_formatted_path() and use in rev-parse\n  repo: add path.commondir with absolute and relative suffix formatting\n  repo: add path.gitdir with absolute and relative suffix formatting\n\n Documentation/git-repo.adoc | 15 ++++++++\n builtin/repo.c              | 50 +++++++++++++++++++++++++\n builtin/rev-parse.c         | 73 +++++++++++++++----------------------\n path.c                      | 69 +++++++++++++++++++++++++++++++++++\n path.h                      | 30 +++++++++++++++\n t/t1900-repo-info.sh        | 58 +++++++++++++++++++++++++++++\n 6 files changed, 251 insertions(+), 44 deletions(-)\n\n-- \n2.55.0-rc1\n"},{"id":"546059","messageId":"20260621055534.46798-2-jayatheerthkulkarni2005@gmail.com","threadId":"65725","inReplyTo":"20260621055534.46798-1-jayatheerthkulkarni2005@gmail.com","subject":"[GSoC Patch v7 1/3] path: extract append_formatted_path() and use in rev-parse","fromName":"K Jayatheerth","fromEmail":"jayatheerthkulkarni2005@gmail.com","sentAt":"2026-06-21T05:55:32Z","receivedAt":"2026-06-21T05:58:03Z","isPatch":true,"body":"Path formatting logic in builtin/rev-parse.c writes directly to\nstdout. Other builtins cannot reuse it.\n\nExtract this logic into append_formatted_path() in path.c and expose\na path_format enum in path.h.\n\nConvert rev-parse to use the new helper in the same step to validate\nthe API against existing tests and avoid introducing dead code.\n\nMentored-by: Justin Tobler <jltobler@gmail.com>\nMentored-by: Lucas Seiki Oshiro <lucasseikioshiro@gmail.com>\nSigned-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>\n---\n builtin/rev-parse.c | 73 ++++++++++++++++++---------------------------\n path.c              | 69 ++++++++++++++++++++++++++++++++++++++++++\n path.h              | 30 +++++++++++++++++++\n 3 files changed, 128 insertions(+), 44 deletions(-)\n\ndiff --git a/builtin/rev-parse.c b/builtin/rev-parse.c\nindex bb882678fe..6de01466db 100644\n--- a/builtin/rev-parse.c\n+++ b/builtin/rev-parse.c\n@@ -653,53 +653,38 @@ enum default_type {\n \tDEFAULT_UNMODIFIED,\n };\n \n-static void print_path(const char *path, const char *prefix, enum format_type format, enum default_type def)\n+static void print_path(const char *path, const char *prefix,\n+\t\t       enum format_type format, enum default_type def)\n {\n-\tchar *cwd = NULL;\n-\t/*\n-\t * We don't ever produce a relative path if prefix is NULL, so set the\n-\t * prefix to the current directory so that we can produce a relative\n-\t * path whenever possible.  If we're using RELATIVE_IF_SHARED mode, then\n-\t * we want an absolute path unless the two share a common prefix, so don't\n-\t * set it in that case, since doing so causes a relative path to always\n-\t * be produced if possible.\n-\t */\n-\tif (!prefix && (format != FORMAT_DEFAULT || def != DEFAULT_RELATIVE_IF_SHARED))\n-\t\tprefix = cwd = xgetcwd();\n-\tif (format == FORMAT_DEFAULT && def == DEFAULT_UNMODIFIED) {\n-\t\tputs(path);\n-\t} else if (format == FORMAT_RELATIVE ||\n-\t\t  (format == FORMAT_DEFAULT && def == DEFAULT_RELATIVE)) {\n-\t\t/*\n-\t\t * In order for relative_path to work as expected, we need to\n-\t\t * make sure that both paths are absolute paths.  If we don't,\n-\t\t * we can end up with an unexpected absolute path that the user\n-\t\t * didn't want.\n-\t\t */\n-\t\tstruct strbuf buf = STRBUF_INIT, realbuf = STRBUF_INIT, prefixbuf = STRBUF_INIT;\n-\t\tif (!is_absolute_path(path)) {\n-\t\t\tstrbuf_realpath_forgiving(&realbuf, path,  1);\n-\t\t\tpath = realbuf.buf;\n-\t\t}\n-\t\tif (!is_absolute_path(prefix)) {\n-\t\t\tstrbuf_realpath_forgiving(&prefixbuf, prefix, 1);\n-\t\t\tprefix = prefixbuf.buf;\n+\tstruct strbuf sb = STRBUF_INIT;\n+\tenum path_format fmt;\n+\n+\tif (format == FORMAT_RELATIVE) {\n+\t\tfmt = PATH_FORMAT_RELATIVE;\n+\t} else if (format == FORMAT_CANONICAL) {\n+\t\tfmt = PATH_FORMAT_CANONICAL;\n+\t} else /* FORMAT_DEFAULT */ {\n+\t\tswitch (def) {\n+\t\tcase DEFAULT_RELATIVE:\n+\t\t\tfmt = PATH_FORMAT_RELATIVE;\n+\t\t\tbreak;\n+\t\tcase DEFAULT_RELATIVE_IF_SHARED:\n+\t\t\tfmt = PATH_FORMAT_RELATIVE_IF_SHARED;\n+\t\t\tbreak;\n+\t\tcase DEFAULT_CANONICAL:\n+\t\t\tfmt = PATH_FORMAT_CANONICAL;\n+\t\t\tbreak;\n+\t\tcase DEFAULT_UNMODIFIED:\n+\t\tdefault:\n+\t\t\tfmt = PATH_FORMAT_UNMODIFIED;\n+\t\t\tbreak;\n \t\t}\n-\t\tputs(relative_path(path, prefix, &buf));\n-\t\tstrbuf_release(&buf);\n-\t\tstrbuf_release(&realbuf);\n-\t\tstrbuf_release(&prefixbuf);\n-\t} else if (format == FORMAT_DEFAULT && def == DEFAULT_RELATIVE_IF_SHARED) {\n-\t\tstruct strbuf buf = STRBUF_INIT;\n-\t\tputs(relative_path(path, prefix, &buf));\n-\t\tstrbuf_release(&buf);\n-\t} else {\n-\t\tstruct strbuf buf = STRBUF_INIT;\n-\t\tstrbuf_realpath_forgiving(&buf, path, 1);\n-\t\tputs(buf.buf);\n-\t\tstrbuf_release(&buf);\n \t}\n-\tfree(cwd);\n+\n+\tappend_formatted_path(&sb, path, prefix, fmt);\n+\tputs(sb.buf);\n+\n+\tstrbuf_release(&sb);\n }\n \n int cmd_rev_parse(int argc,\ndiff --git a/path.c b/path.c\nindex d7e17bf174..6d8e892ada 100644\n--- a/path.c\n+++ b/path.c\n@@ -1579,6 +1579,75 @@ char *xdg_cache_home(const char *filename)\n \treturn NULL;\n }\n \n+void append_formatted_path(struct strbuf *dest, const char *path,\n+\t\t\t   const char *prefix, enum path_format format)\n+{\n+\tswitch (format) {\n+\tcase PATH_FORMAT_UNMODIFIED:\n+\t\tstrbuf_addstr(dest, path);\n+\t\tbreak;\n+\n+\tcase PATH_FORMAT_RELATIVE: {\n+\t\tstruct strbuf relative_buf = STRBUF_INIT;\n+\t\tstruct strbuf real_path = STRBUF_INIT;\n+\t\tstruct strbuf real_prefix = STRBUF_INIT;\n+\t\tchar *cwd = NULL;\n+\n+\t\t/*\n+\t\t * We don't ever produce a relative path if prefix is NULL,\n+\t\t * so set the prefix to the current directory so that we can\n+\t\t * produce a relative path whenever possible.\n+\t\t */\n+\t\tif (!prefix)\n+\t\t\tprefix = cwd = xgetcwd();\n+\n+\t\tif (!is_absolute_path(path)) {\n+\t\t\tstrbuf_realpath_forgiving(&real_path, path, 1);\n+\t\t\tpath = real_path.buf;\n+\t\t}\n+\t\tif (!is_absolute_path(prefix)) {\n+\t\t\tstrbuf_realpath_forgiving(&real_prefix, prefix, 1);\n+\t\t\tprefix = real_prefix.buf;\n+\t\t}\n+\n+\t\tstrbuf_addstr(dest, relative_path(path, prefix, &relative_buf));\n+\n+\t\tstrbuf_release(&relative_buf);\n+\t\tstrbuf_release(&real_path);\n+\t\tstrbuf_release(&real_prefix);\n+\t\tfree(cwd);\n+\t\tbreak;\n+\t}\n+\n+\tcase PATH_FORMAT_RELATIVE_IF_SHARED: {\n+\t\tstruct strbuf relative_buf = STRBUF_INIT;\n+\n+\t\t/*\n+\t\t * If we're using RELATIVE_IF_SHARED mode, then we want an\n+\t\t * absolute path unless the two share a common prefix, so don't\n+\t\t * default the prefix to the current working directory. Doing so\n+\t\t * would cause a relative path to always be produced if possible.\n+\t\t */\n+\t\tstrbuf_addstr(dest, relative_path(path, prefix, &relative_buf));\n+\t\tstrbuf_release(&relative_buf);\n+\t\tbreak;\n+\t}\n+\n+\tcase PATH_FORMAT_CANONICAL: {\n+\t\tstruct strbuf canonical_buf = STRBUF_INIT;\n+\n+\t\tstrbuf_realpath_forgiving(&canonical_buf, path, 1);\n+\t\tstrbuf_addbuf(dest, &canonical_buf);\n+\n+\t\tstrbuf_release(&canonical_buf);\n+\t\tbreak;\n+\t}\n+\n+\tdefault:\n+\t\tBUG(\"unknown path_format value %d\", format);\n+\t}\n+}\n+\n REPO_GIT_PATH_FUNC(squash_msg, \"SQUASH_MSG\")\n REPO_GIT_PATH_FUNC(merge_msg, \"MERGE_MSG\")\n REPO_GIT_PATH_FUNC(merge_rr, \"MERGE_RR\")\ndiff --git a/path.h b/path.h\nindex 4c2958a903..4d982a2c8e 100644\n--- a/path.h\n+++ b/path.h\n@@ -262,6 +262,36 @@ enum scld_error safe_create_leading_directories_no_share(char *path);\n int safe_create_file_with_leading_directories(struct repository *repo,\n \t\t\t\t\t      const char *path);\n \n+/**\n+ * The formatting strategy to apply when writing a path into a buffer.\n+ */\n+enum path_format {\n+\t/* Output the path exactly as-is without any modifications. */\n+\tPATH_FORMAT_UNMODIFIED,\n+\n+\t/* Output a path relative to the provided directory prefix. */\n+\tPATH_FORMAT_RELATIVE,\n+\n+\t/* Output a relative path only if the path shares a root with the prefix. */\n+\tPATH_FORMAT_RELATIVE_IF_SHARED,\n+\n+\t/* Output a fully resolved, absolute canonical path. */\n+\tPATH_FORMAT_CANONICAL\n+};\n+\n+/**\n+ * Format a path according to the specified formatting strategy and append\n+ * the result to the given strbuf.\n+ *\n+ * `dest`   : The string buffer to append the formatted path to.\n+ * `path`   : The path string that needs to be formatted.\n+ * `prefix` : The directory prefix to calculate relative offsets against.\n+ * Pass NULL to default to the current working directory where applicable.\n+ * `format` : The formatting behavior rule to execute.\n+ */\n+void append_formatted_path(struct strbuf *dest, const char *path,\n+\t\t\t   const char *prefix, enum path_format format);\n+\n # ifdef USE_THE_REPOSITORY_VARIABLE\n #  include \"strbuf.h\"\n #  include \"repository.h\"\n-- \n2.55.0-rc1\n\n"},{"id":"546060","messageId":"20260621055534.46798-3-jayatheerthkulkarni2005@gmail.com","threadId":"65725","inReplyTo":"20260621055534.46798-1-jayatheerthkulkarni2005@gmail.com","subject":"[GSoC Patch v7 2/3] repo: add path.commondir with absolute and relative suffix formatting","fromName":"K Jayatheerth","fromEmail":"jayatheerthkulkarni2005@gmail.com","sentAt":"2026-06-21T05:55:33Z","receivedAt":"2026-06-21T05:58:35Z","isPatch":true,"body":"Scripts working with worktree setups need a reliable way to discover\nthe common directory, which diverges from the git directory when\nmultiple worktrees are in use. There is no way to retrieve this path\nfrom git repo info today.\n\nIntroduce path.commondir.absolute and path.commondir.relative keys.\nExposing explicit format variants rather than a single key with a\ndefault avoids ambiguity for scripts that require predictable output.\n\nMentored-by: Justin Tobler <jltobler@gmail.com>\nMentored-by: Lucas Seiki Oshiro <lucasseikioshiro@gmail.com>\nSigned-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>\n---\n Documentation/git-repo.adoc |  9 +++++++\n builtin/repo.c              | 26 +++++++++++++++++++\n t/t1900-repo-info.sh        | 52 +++++++++++++++++++++++++++++++++++++\n 3 files changed, 87 insertions(+)\n\ndiff --git a/Documentation/git-repo.adoc b/Documentation/git-repo.adoc\nindex 42262c1983..890c34051d 100644\n--- a/Documentation/git-repo.adoc\n+++ b/Documentation/git-repo.adoc\n@@ -104,6 +104,15 @@ values that they return:\n `object.format`::\n \tThe object format (hash algorithm) used in the repository.\n \n+`path.commondir.absolute`::\n+\tThe canonical absolute path to the Git repository's common\n+\tdirectory (the shared `.git` directory containing objects,\n+\trefs, and global configuration).\n+\n+`path.commondir.relative`::\n+\tThe path to the Git repository's common directory relative to\n+\tthe current working directory.\n+\n `references.format`::\n \tThe reference storage format. The valid values are:\n +\ndiff --git a/builtin/repo.c b/builtin/repo.c\nindex 71a5c1c29c..c4cc3bf3fc 100644\n--- a/builtin/repo.c\n+++ b/builtin/repo.c\n@@ -7,12 +7,14 @@\n #include \"hex.h\"\n #include \"odb.h\"\n #include \"parse-options.h\"\n+#include \"path.h\"\n #include \"path-walk.h\"\n #include \"progress.h\"\n #include \"quote.h\"\n #include \"ref-filter.h\"\n #include \"refs.h\"\n #include \"revision.h\"\n+#include \"setup.h\"\n #include \"strbuf.h\"\n #include \"string-list.h\"\n #include \"shallow.h\"\n@@ -75,6 +77,28 @@ static int get_object_format(struct repository *repo, struct strbuf *buf)\n \treturn 0;\n }\n \n+static int get_path_commondir_absolute(struct repository *repo, struct strbuf *buf)\n+{\n+\tconst char *common_dir = repo_get_common_dir(repo);\n+\n+\tif (!common_dir)\n+\t\treturn error(_(\"unable to get common directory\"));\n+\n+\tappend_formatted_path(buf, common_dir, startup_info->prefix, PATH_FORMAT_CANONICAL);\n+\treturn 0;\n+}\n+\n+static int get_path_commondir_relative(struct repository *repo, struct strbuf *buf)\n+{\n+\tconst char *common_dir = repo_get_common_dir(repo);\n+\n+\tif (!common_dir)\n+\t\treturn error(_(\"unable to get common directory\"));\n+\n+\tappend_formatted_path(buf, common_dir, startup_info->prefix, PATH_FORMAT_RELATIVE);\n+\treturn 0;\n+}\n+\n static int get_references_format(struct repository *repo, struct strbuf *buf)\n {\n \tstrbuf_addstr(buf,\n@@ -87,6 +111,8 @@ static const struct repo_info_field repo_info_field[] = {\n \t{ \"layout.bare\", get_layout_bare },\n \t{ \"layout.shallow\", get_layout_shallow },\n \t{ \"object.format\", get_object_format },\n+\t{ \"path.commondir.absolute\", get_path_commondir_absolute },\n+\t{ \"path.commondir.relative\", get_path_commondir_relative },\n \t{ \"references.format\", get_references_format },\n };\n \ndiff --git a/t/t1900-repo-info.sh b/t/t1900-repo-info.sh\nindex 39bb77dda0..09158d29f9 100755\n--- a/t/t1900-repo-info.sh\n+++ b/t/t1900-repo-info.sh\n@@ -155,4 +155,56 @@ test_expect_success 'git repo info -h shows only repo info usage' '\n \ttest_grep ! \"git repo structure\" actual\n '\n \n+# Helper function to test path keys in both absolute and relative formats.\n+# $1: label for the test\n+# $2: field_name (e.g., commondir)\n+# $3: expected_dir (the directory name, e.g., .git or custom-common)\n+# $4: init_command (extra setup like exporting env vars)\n+test_repo_info_path () {\n+\tlabel=$1\n+\tfield_name=$2\n+\texpected_dir=$3\n+\tinit_command=$4\n+\n+\ttest_expect_success \"absolute: $label\" '\n+\t\ttest_when_finished \"rm -rf repo\" &&\n+\t\tgit init repo &&\n+\t\t(\n+\t\t\tmkdir -p repo/sub &&\n+\t\t\tcd repo/sub &&\n+\t\t\tROOT=\"$(test-tool path-utils real_path ..)\" && export ROOT &&\n+\t\t\teval \"$init_command\" &&\n+\t\t\techo \"path.$field_name.absolute=$ROOT/$expected_dir\" >expect &&\n+\t\t\tgit repo info \"path.$field_name.absolute\" >actual &&\n+\t\t\ttest_cmp expect actual\n+\t\t)\n+\t'\n+\n+\ttest_expect_success \"relative: $label\" '\n+\t\ttest_when_finished \"rm -rf repo\" &&\n+\t\tgit init repo &&\n+\t\t(\n+\t\t\tmkdir -p repo/sub &&\n+\t\t\tcd repo/sub &&\n+\t\t\tROOT=\"$(test-tool path-utils real_path ..)\" && export ROOT &&\n+\t\t\teval \"$init_command\" &&\n+\t\t\techo \"path.$field_name.relative=../$expected_dir\" >expect &&\n+\t\t\tgit repo info \"path.$field_name.relative\" >actual &&\n+\t\t\ttest_cmp expect actual\n+\t\t)\n+\t'\n+}\n+\n+test_repo_info_path 'commondir standard' 'commondir' '.git'\n+\n+test_repo_info_path 'commondir with GIT_COMMON_DIR and GIT_DIR' 'commondir' \\\n+\t'custom-common' \\\n+\t'GIT_COMMON_DIR=\"$ROOT/custom-common\" && export GIT_COMMON_DIR &&\n+\t GIT_DIR=\"../.git\" && export GIT_DIR &&\n+\t git init --bare \"$ROOT/custom-common\"'\n+\n+test_repo_info_path 'commondir with only GIT_DIR' 'commondir' \\\n+\t'.git' \\\n+\t'GIT_DIR=\"../.git\" && export GIT_DIR'\n+\n test_done\n-- \n2.55.0-rc1\n\n"},{"id":"546061","messageId":"20260621055534.46798-4-jayatheerthkulkarni2005@gmail.com","threadId":"65725","inReplyTo":"20260621055534.46798-1-jayatheerthkulkarni2005@gmail.com","subject":"[GSoC Patch v7 3/3] repo: add path.gitdir with absolute and relative suffix formatting","fromName":"K Jayatheerth","fromEmail":"jayatheerthkulkarni2005@gmail.com","sentAt":"2026-06-21T05:55:34Z","receivedAt":"2026-06-21T05:59:04Z","isPatch":true,"body":"Scripts need a stable way to locate the git directory without\nparsing rev-parse output or relying on its flag-driven path format\nselection. There is no way to retrieve this path from git repo info\ntoday.\n\nIntroduce path.gitdir.absolute and path.gitdir.relative keys,\nconsistent with the path.commondir keys added in the previous patch.\nReuse the test_repo_info_path helper introduced there to validate\nboth variants.\n\nMentored-by: Justin Tobler <jltobler@gmail.com>\nMentored-by: Lucas Seiki Oshiro <lucasseikioshiro@gmail.com>\nSigned-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>\n---\n Documentation/git-repo.adoc |  6 ++++++\n builtin/repo.c              | 24 ++++++++++++++++++++++++\n t/t1900-repo-info.sh        |  6 ++++++\n 3 files changed, 36 insertions(+)\n\ndiff --git a/Documentation/git-repo.adoc b/Documentation/git-repo.adoc\nindex 890c34051d..ed7d80c690 100644\n--- a/Documentation/git-repo.adoc\n+++ b/Documentation/git-repo.adoc\n@@ -113,6 +113,12 @@ values that they return:\n \tThe path to the Git repository's common directory relative to\n \tthe current working directory.\n \n+`path.gitdir.absolute`::\n+\tThe canonical absolute path to the Git repository directory (the `.git` directory).\n+\n+`path.gitdir.relative`::\n+\tThe path to the Git repository directory relative to the current working directory.\n+\n `references.format`::\n \tThe reference storage format. The valid values are:\n +\ndiff --git a/builtin/repo.c b/builtin/repo.c\nindex c4cc3bf3fc..9a312d127a 100644\n--- a/builtin/repo.c\n+++ b/builtin/repo.c\n@@ -99,6 +99,28 @@ static int get_path_commondir_relative(struct repository *repo, struct strbuf *b\n \treturn 0;\n }\n \n+static int get_path_gitdir_absolute(struct repository *repo, struct strbuf *buf)\n+{\n+\tconst char *git_dir = repo_get_git_dir(repo);\n+\n+\tif (!git_dir)\n+\t\treturn error(_(\"unable to get git directory\"));\n+\n+\tappend_formatted_path(buf, git_dir, startup_info->prefix, PATH_FORMAT_CANONICAL);\n+\treturn 0;\n+}\n+\n+static int get_path_gitdir_relative(struct repository *repo, struct strbuf *buf)\n+{\n+\tconst char *git_dir = repo_get_git_dir(repo);\n+\n+\tif (!git_dir)\n+\t\treturn error(_(\"unable to get git directory\"));\n+\n+\tappend_formatted_path(buf, git_dir, startup_info->prefix, PATH_FORMAT_RELATIVE);\n+\treturn 0;\n+}\n+\n static int get_references_format(struct repository *repo, struct strbuf *buf)\n {\n \tstrbuf_addstr(buf,\n@@ -113,6 +135,8 @@ static const struct repo_info_field repo_info_field[] = {\n \t{ \"object.format\", get_object_format },\n \t{ \"path.commondir.absolute\", get_path_commondir_absolute },\n \t{ \"path.commondir.relative\", get_path_commondir_relative },\n+\t{ \"path.gitdir.absolute\", get_path_gitdir_absolute },\n+\t{ \"path.gitdir.relative\", get_path_gitdir_relative },\n \t{ \"references.format\", get_references_format },\n };\n \ndiff --git a/t/t1900-repo-info.sh b/t/t1900-repo-info.sh\nindex 09158d29f9..ae8c22c817 100755\n--- a/t/t1900-repo-info.sh\n+++ b/t/t1900-repo-info.sh\n@@ -207,4 +207,10 @@ test_repo_info_path 'commondir with only GIT_DIR' 'commondir' \\\n \t'.git' \\\n \t'GIT_DIR=\"../.git\" && export GIT_DIR'\n \n+test_repo_info_path 'gitdir standard' 'gitdir' '.git'\n+\n+test_repo_info_path 'gitdir with explicit GIT_DIR' 'gitdir' \\\n+\t'.git' \\\n+\t'GIT_DIR=\"../.git\" && export GIT_DIR'\n+\n test_done\n-- \n2.55.0-rc1\n\n"},{"id":"546089","messageId":"xmqqtsqv6204.fsf@gitster.g","threadId":"65725","inReplyTo":"20260621055534.46798-2-jayatheerthkulkarni2005@gmail.com","subject":"Re: [GSoC Patch v7 1/3] path: extract append_formatted_path() and use in rev-parse","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-06-21T21:02:19Z","receivedAt":"2026-06-21T21:02:22Z","isPatch":true,"body":"K Jayatheerth <jayatheerthkulkarni2005@gmail.com> writes:\n\nSo, for the existing user of this logic, the preimage ...\n\n> -static void print_path(const char *path, const char *prefix, enum format_type format, enum default_type def)\n>  {\n> -\tchar *cwd = NULL;\n> -\t/*\n> -\t * We don't ever produce a relative path if prefix is NULL, so set the\n> -\t * prefix to the current directory so that we can produce a relative\n> -\t * path whenever possible.  If we're using RELATIVE_IF_SHARED mode, then\n> -\t * we want an absolute path unless the two share a common prefix, so don't\n> -\t * set it in that case, since doing so causes a relative path to always\n> -\t * be produced if possible.\n> -\t */\n> -\tif (!prefix && (format != FORMAT_DEFAULT || def != DEFAULT_RELATIVE_IF_SHARED))\n> -\t\tprefix = cwd = xgetcwd();\n> -\tif (format == FORMAT_DEFAULT && def == DEFAULT_UNMODIFIED) {\n> -\t\tputs(path);\n> -\t} else if (format == FORMAT_RELATIVE ||\n> -\t\t  (format == FORMAT_DEFAULT && def == DEFAULT_RELATIVE)) {\n> -\t\t/*\n> -\t\t * In order for relative_path to work as expected, we need to\n> -\t\t * make sure that both paths are absolute paths.  If we don't,\n> -\t\t * we can end up with an unexpected absolute path that the user\n> -\t\t * didn't want.\n> -\t\t */\n> -\t\tstruct strbuf buf = STRBUF_INIT, realbuf = STRBUF_INIT, prefixbuf = STRBUF_INIT;\n> -\t\tif (!is_absolute_path(path)) {\n> -\t\t\tstrbuf_realpath_forgiving(&realbuf, path,  1);\n> -\t\t\tpath = realbuf.buf;\n> -\t\t}\n> -\t\tif (!is_absolute_path(prefix)) {\n> -\t\t\tstrbuf_realpath_forgiving(&prefixbuf, prefix, 1);\n> -\t\t\tprefix = prefixbuf.buf;\n>  \t\t}\n> -\t\tputs(relative_path(path, prefix, &buf));\n> -\t\tstrbuf_release(&buf);\n> -\t\tstrbuf_release(&realbuf);\n> -\t\tstrbuf_release(&prefixbuf);\n> -\t} else if (format == FORMAT_DEFAULT && def == DEFAULT_RELATIVE_IF_SHARED) {\n> -\t\tstruct strbuf buf = STRBUF_INIT;\n> -\t\tputs(relative_path(path, prefix, &buf));\n> -\t\tstrbuf_release(&buf);\n> -\t} else {\n> -\t\tstruct strbuf buf = STRBUF_INIT;\n> -\t\tstrbuf_realpath_forgiving(&buf, path, 1);\n> -\t\tputs(buf.buf);\n> -\t\tstrbuf_release(&buf);\n>  \t}\n> -\tfree(cwd);\n>  }\n\n... now becomes this postimage.\n\n> +static void print_path(const char *path, const char *prefix,\n> +\t\t       enum format_type format, enum default_type def)\n>  {\n> +\tstruct strbuf sb = STRBUF_INIT;\n> +\tenum path_format fmt;\n> +\n> +\tif (format == FORMAT_RELATIVE) {\n> +\t\tfmt = PATH_FORMAT_RELATIVE;\n> +\t} else if (format == FORMAT_CANONICAL) {\n> +\t\tfmt = PATH_FORMAT_CANONICAL;\n> +\t} else /* FORMAT_DEFAULT */ {\n> +\t\tswitch (def) {\n> +\t\tcase DEFAULT_RELATIVE:\n> +\t\t\tfmt = PATH_FORMAT_RELATIVE;\n> +\t\t\tbreak;\n> +\t\tcase DEFAULT_RELATIVE_IF_SHARED:\n> +\t\t\tfmt = PATH_FORMAT_RELATIVE_IF_SHARED;\n> +\t\t\tbreak;\n> +\t\tcase DEFAULT_CANONICAL:\n> +\t\t\tfmt = PATH_FORMAT_CANONICAL;\n> +\t\t\tbreak;\n> +\t\tcase DEFAULT_UNMODIFIED:\n> +\t\tdefault:\n> +\t\t\tfmt = PATH_FORMAT_UNMODIFIED;\n> +\t\t\tbreak;\n>  \t\t}\n>  \t}\n> +\n> +\tappend_formatted_path(&sb, path, prefix, fmt);\n> +\tputs(sb.buf);\n> +\n> +\tstrbuf_release(&sb);\n>  }\n\nMostly, the code translates FORMAT_FOO constants into the new\nPATH_FORMAT_FOO constants, and lets append_formatted_path() do the\nheavy lifting.\n\nIt is a minor point, but wouldn't it make it simpler to handle\nformat_default first?  I.e.,\n\n\tif (format == FORMAT_DEFAULT)\n\t\tswitch (def) {\n\t\tcase DEFAULT_RELATIVE:\n\t\t\tformat = DEFAULT_RELATIVE;\n\t\t\tbreak;\n\t\t...\n\t\tcase DEFAULT_UNMODIFIED:\n\t\tdefault:\n\t\t\tformat = DEFAULT_UNMODIFIED; \n\t\t\tbreak;\n\t}\n\tswitch (format) {\n        case FORMAT_RELATIVE: fmt = PATH_FORMAT_RELATIVE; break;\n\tcase FORMAT_CANONICAL: fmt = PATH_FORMAT_CANONICAL; break;\n\t...\n\t}\n\nPerhaps yes, perhaps not.  I dunno.\n\n> diff --git a/path.c b/path.c\n> index d7e17bf174..6d8e892ada 100644\n> --- a/path.c\n> +++ b/path.c\n> @@ -1579,6 +1579,75 @@ char *xdg_cache_home(const char *filename)\n>  \treturn NULL;\n>  }\n>  \n> +void append_formatted_path(struct strbuf *dest, const char *path,\n> +\t\t\t   const char *prefix, enum path_format format)\n> +{\n> +\tswitch (format) {\n> +\tcase PATH_FORMAT_UNMODIFIED:\n> +\t\tstrbuf_addstr(dest, path);\n> +\t\tbreak;\n\nIn the orignal \"print_path()\", DEFAULT/UNMODIFIED did this \"show\nunmodified\".  OK.\n\n> +\tcase PATH_FORMAT_RELATIVE: {\n> +\t\tstruct strbuf relative_buf = STRBUF_INIT;\n> +\t\tstruct strbuf real_path = STRBUF_INIT;\n> +\t\tstruct strbuf real_prefix = STRBUF_INIT;\n> +\t\tchar *cwd = NULL;\n> +\n> +\t\t/*\n> +\t\t * We don't ever produce a relative path if prefix is NULL,\n> +\t\t * so set the prefix to the current directory so that we can\n> +\t\t * produce a relative path whenever possible.\n> +\t\t */\n> +\t\tif (!prefix)\n> +\t\t\tprefix = cwd = xgetcwd();\n\nThis is what was done in the original \"print_path()\" upfront, with\na similar comment to explay why this happens.  Looking good.  Also\nwe no longer call xgetcwd() when we do not need to, which is goodd.\n\n> +\t\tif (!is_absolute_path(path)) {\n> +\t\t\tstrbuf_realpath_forgiving(&real_path, path, 1);\n> +\t\t\tpath = real_path.buf;\n> +\t\t}\n> +\t\tif (!is_absolute_path(prefix)) {\n> +\t\t\tstrbuf_realpath_forgiving(&real_prefix, prefix, 1);\n> +\t\t\tprefix = real_prefix.buf;\n> +\t\t}\n\nThere used to be a comment explaining why we make realpath calls,\nwhich is now lost.  Perhaps what the comment said was so obvious\nthat we are better off without it?  I offhand do not know.\n\nWhat is done to make the paths real is the same as before, which is\ngood.\n\n> +\t\tstrbuf_addstr(dest, relative_path(path, prefix, &relative_buf));\n> +\n> +\t\tstrbuf_release(&relative_buf);\n> +\t\tstrbuf_release(&real_path);\n> +\t\tstrbuf_release(&real_prefix);\n> +\t\tfree(cwd);\n> +\t\tbreak;\n> +\t}\n\nOK.\n\n> +\tcase PATH_FORMAT_RELATIVE_IF_SHARED: {\n> +\t\tstruct strbuf relative_buf = STRBUF_INIT;\n> +\n> +\t\t/*\n> +\t\t * If we're using RELATIVE_IF_SHARED mode, then we want an\n> +\t\t * absolute path unless the two share a common prefix, so don't\n> +\t\t * default the prefix to the current working directory. Doing so\n> +\t\t * would cause a relative path to always be produced if possible.\n> +\t\t */\n> +\t\tstrbuf_addstr(dest, relative_path(path, prefix, &relative_buf));\n> +\t\tstrbuf_release(&relative_buf);\n> +\t\tbreak;\n> +\t}\n\nIdentical to the original, which is good.\n\n> +\n> +\tcase PATH_FORMAT_CANONICAL: {\n> +\t\tstruct strbuf canonical_buf = STRBUF_INIT;\n> +\n> +\t\tstrbuf_realpath_forgiving(&canonical_buf, path, 1);\n> +\t\tstrbuf_addbuf(dest, &canonical_buf);\n> +\n> +\t\tstrbuf_release(&canonical_buf);\n> +\t\tbreak;\n> +\t}\n> +\n> +\tdefault:\n> +\t\tBUG(\"unknown path_format value %d\", format);\n> +\t}\n> +}\n\nOK.\n\n> +/**\n> + * The formatting strategy to apply when writing a path into a buffer.\n> + */\n> +enum path_format {\n> +\t/* Output the path exactly as-is without any modifications. */\n> +\tPATH_FORMAT_UNMODIFIED,\n> +\n> +\t/* Output a path relative to the provided directory prefix. */\n> +\tPATH_FORMAT_RELATIVE,\n> +\n> +\t/* Output a relative path only if the path shares a root with the prefix. */\n> +\tPATH_FORMAT_RELATIVE_IF_SHARED,\n> +\n> +\t/* Output a fully resolved, absolute canonical path. */\n> +\tPATH_FORMAT_CANONICAL\n> +};\n> +\n> +/**\n> + * Format a path according to the specified formatting strategy and append\n> + * the result to the given strbuf.\n> + *\n> + * `dest`   : The string buffer to append the formatted path to.\n> + * `path`   : The path string that needs to be formatted.\n> + * `prefix` : The directory prefix to calculate relative offsets against.\n> + * Pass NULL to default to the current working directory where applicable.\n> + * `format` : The formatting behavior rule to execute.\n> + */\n> +void append_formatted_path(struct strbuf *dest, const char *path,\n> +\t\t\t   const char *prefix, enum path_format format);\n> +\n\nIt is slightly unsatisfying that this function is defined to\n\"append\" to any existing value in the dest strbuf, rather than\nstoring the result in the dest strbuf.  The original caller\nprint_path() passes an empty strbuf to this helper, so it can let\nstrbuf_realpath_*() functions to strbuf_reset() it (e.g.,\nabspath.c:get_root_part() called by strbuf_realpath_1(), wihch in\nturn is called by strbuf_realpath() and strbuf_realpath_forgiving())\nit freely, which means that use of temporary strbuf like\ncanonical_buf only to copy it out to dest is wasteful and unneeded.\nBut other callers we will have for this helper later may want to\nappend to what they already have, so perhaps it is OK (on the other\nhand, we could say that preserving and appending is what these\ncallers can do themselves).\n\nOtherwise, looking good as a no-op bug-to-bug compatible rewrite,\nwith a slight optimization (to skip xgetcwd()).\n\nThanks.\n"},{"id":"546190","messageId":"xmqq1pdy36me.fsf@gitster.g","threadId":"65725","inReplyTo":"xmqqtsqv6204.fsf@gitster.g","subject":"Re: [GSoC Patch v7 1/3] path: extract append_formatted_path() and use in rev-parse","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-06-22T16:03:05Z","receivedAt":"2026-06-22T16:03:08Z","isPatch":true,"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> K Jayatheerth <jayatheerthkulkarni2005@gmail.com> writes:\n> ...\n> It is a minor point, but wouldn't it make it simpler to handle\n> format_default first?  I.e.,\n>\n> \tif (format == FORMAT_DEFAULT)\n> \t\tswitch (def) {\n> \t\tcase DEFAULT_RELATIVE:\n> \t\t\tformat = DEFAULT_RELATIVE;\n> \t\t\tbreak;\n> \t\t...\n> \t\tcase DEFAULT_UNMODIFIED:\n> \t\tdefault:\n> \t\t\tformat = DEFAULT_UNMODIFIED; \n> \t\t\tbreak;\n> \t}\n> \tswitch (format) {\n>         case FORMAT_RELATIVE: fmt = PATH_FORMAT_RELATIVE; break;\n> \tcase FORMAT_CANONICAL: fmt = PATH_FORMAT_CANONICAL; break;\n> \t...\n> \t}\n>\n> Perhaps yes, perhaps not.  I dunno.\n\nI do not consider the above an blocker, but it might make a\ndifference if we are going to acquire more modes and formats, so\nonce somebody tries to rewrite the logic and finds the resulting\ncode harder to follow (or not easier to follow), I would be happy to\nsee the above discarded ;-)\n\n>> +/**\n>> + * Format a path according to the specified formatting strategy and append\n>> + * the result to the given strbuf.\n>> + *\n>> + * `dest`   : The string buffer to append the formatted path to.\n>> + * `path`   : The path string that needs to be formatted.\n>> + * `prefix` : The directory prefix to calculate relative offsets against.\n>> + * Pass NULL to default to the current working directory where applicable.\n>> + * `format` : The formatting behavior rule to execute.\n>> + */\n>> +void append_formatted_path(struct strbuf *dest, const char *path,\n>> +\t\t\t   const char *prefix, enum path_format format);\n>> +\n>\n> It is slightly unsatisfying that this function is defined to\n> \"append\" to any existing value in the dest strbuf, rather than\n> storing the result in the dest strbuf.  The original caller\n> print_path() passes an empty strbuf to this helper, so it can let\n> strbuf_realpath_*() functions to strbuf_reset() it (e.g.,\n> abspath.c:get_root_part() called by strbuf_realpath_1(), wihch in\n> turn is called by strbuf_realpath() and strbuf_realpath_forgiving())\n> it freely, which means that use of temporary strbuf like\n> canonical_buf only to copy it out to dest is wasteful and unneeded.\n> But other callers we will have for this helper later may want to\n> append to what they already have, so perhaps it is OK (on the other\n> hand, we could say that preserving and appending is what these\n> callers can do themselves).\n\nThis one we may want to consider a bit more seriously, but it is\nentirely up to the future callers of the helper.  If it would make\nthe callers much easier to write for this helper to have \"append\"\nsemantics, I'd be happy to accept the semantics of the above as-is,\nbut otherwise, I suspect it would be simpler to use if the helper is\ndefined to replase dest with the result, instead of appending the\nresult to dest.\n\n> Otherwise, looking good as a no-op bug-to-bug compatible rewrite,\n> with a slight optimization (to skip xgetcwd()).\n\nThis part of the review does not change in any case.  The\nrefactoring looks good.\n\nThanks.\n"},{"id":"546195","messageId":"CA+rGoLcahV9pPqkSAKvz9o3g2cw2PsYXxzzwAC8XoseFzMB5rA@mail.gmail.com","threadId":"65725","inReplyTo":"xmqq1pdy36me.fsf@gitster.g","subject":"Re: [GSoC Patch v7 1/3] path: extract append_formatted_path() and use in rev-parse","fromName":"K Jayatheerth","fromEmail":"jayatheerthkulkarni2005@gmail.com","sentAt":"2026-06-22T17:41:23Z","receivedAt":"2026-06-22T17:41:37Z","isPatch":true,"body":"Hey Junio,\n\nOn Mon, Jun 22, 2026 at 2:32 AM Junio C Hamano <gitster@pobox.com> wrote:\n\n> So, for the existing user of this logic, the preimage ...\n>\n> > -static void print_path(const char *path, const char *prefix, enum format_type format, enum default_type def)\n> >  {\n\n...\n\n> > -     free(cwd);\n> >  }\n>\n> ... now becomes this postimage.\n>\n\nYes that's right!\n\n> > +static void print_path(const char *path, const char *prefix,\n> > +                    enum format_type format, enum default_type def)\n> >  {\n> > +     struct strbuf sb = STRBUF_INIT;\n> > +     enum path_format fmt;\n> > +\n> > +     if (format == FORMAT_RELATIVE) {\n> > +             fmt = PATH_FORMAT_RELATIVE;\n> > +     } else if (format == FORMAT_CANONICAL) {\n> > +             fmt = PATH_FORMAT_CANONICAL;\n> > +     } else /* FORMAT_DEFAULT */ {\n> > +             switch (def) {\n> > +             case DEFAULT_RELATIVE:\n> > +                     fmt = PATH_FORMAT_RELATIVE;\n> > +                     break;\n> > +             case DEFAULT_RELATIVE_IF_SHARED:\n> > +                     fmt = PATH_FORMAT_RELATIVE_IF_SHARED;\n> > +                     break;\n> > +             case DEFAULT_CANONICAL:\n> > +                     fmt = PATH_FORMAT_CANONICAL;\n> > +                     break;\n> > +             case DEFAULT_UNMODIFIED:\n> > +             default:\n> > +                     fmt = PATH_FORMAT_UNMODIFIED;\n> > +                     break;\n> >               }\n> >       }\n> > +\n> > +     append_formatted_path(&sb, path, prefix, fmt);\n> > +     puts(sb.buf);\n> > +\n> > +     strbuf_release(&sb);\n> >  }\n>\n> Mostly, the code translates FORMAT_FOO constants into the new\n> PATH_FORMAT_FOO constants, and lets append_formatted_path() do the\n> heavy lifting.\n>\n> It is a minor point, but wouldn't it make it simpler to handle\n> format_default first?  I.e.,\n>\n>         if (format == FORMAT_DEFAULT)\n>                 switch (def) {\n>                 case DEFAULT_RELATIVE:\n>                         format = DEFAULT_RELATIVE;\n>                         break;\n>                 ...\n>                 case DEFAULT_UNMODIFIED:\n>                 default:\n>                         format = DEFAULT_UNMODIFIED;\n>                         break;\n>         }\n>         switch (format) {\n>         case FORMAT_RELATIVE: fmt = PATH_FORMAT_RELATIVE; break;\n>         case FORMAT_CANONICAL: fmt = PATH_FORMAT_CANONICAL; break;\n>         ...\n>         }\n>\n> Perhaps yes, perhaps not.  I dunno.\n>\n\nI see you have continued this point further\nI am going to respond to this in detail there.\n\n> > diff --git a/path.c b/path.c\n> > index d7e17bf174..6d8e892ada 100644\n> > --- a/path.c\n> > +++ b/path.c\n> > @@ -1579,6 +1579,75 @@ char *xdg_cache_home(const char *filename)\n> >       return NULL;\n> >  }\n> >\n> > +void append_formatted_path(struct strbuf *dest, const char *path,\n> > +                        const char *prefix, enum path_format format)\n> > +{\n> > +     switch (format) {\n> > +     case PATH_FORMAT_UNMODIFIED:\n> > +             strbuf_addstr(dest, path);\n> > +             break;\n>\n> In the orignal \"print_path()\", DEFAULT/UNMODIFIED did this \"show\n> unmodified\".  OK.\n>\n> > +     case PATH_FORMAT_RELATIVE: {\n> > +             struct strbuf relative_buf = STRBUF_INIT;\n> > +             struct strbuf real_path = STRBUF_INIT;\n> > +             struct strbuf real_prefix = STRBUF_INIT;\n> > +             char *cwd = NULL;\n> > +\n> > +             /*\n> > +              * We don't ever produce a relative path if prefix is NULL,\n> > +              * so set the prefix to the current directory so that we can\n> > +              * produce a relative path whenever possible.\n> > +              */\n> > +             if (!prefix)\n> > +                     prefix = cwd = xgetcwd();\n>\n> This is what was done in the original \"print_path()\" upfront, with\n> a similar comment to explay why this happens.  Looking good.  Also\n> we no longer call xgetcwd() when we do not need to, which is goodd.\n>\n> > +             if (!is_absolute_path(path)) {\n> > +                     strbuf_realpath_forgiving(&real_path, path, 1);\n> > +                     path = real_path.buf;\n> > +             }\n> > +             if (!is_absolute_path(prefix)) {\n> > +                     strbuf_realpath_forgiving(&real_prefix, prefix, 1);\n> > +                     prefix = real_prefix.buf;\n> > +             }\n>\n> There used to be a comment explaining why we make realpath calls,\n> which is now lost.  Perhaps what the comment said was so obvious\n> that we are better off without it?  I offhand do not know.\n>\n\nWhen the logic was a single block, the comment felt necessary to\nexplain the flow.\nBy splitting it into explicit switch cases, the logic became a bit\nmore self-evident, so I removed it to reduce clutter.\nI kept the other comments where the reasoning is less obvious.\n\n\n> What is done to make the paths real is the same as before, which is\n> good.\n>\n> > +             strbuf_addstr(dest, relative_path(path, prefix, &relative_buf));\n> > +\n> > +             strbuf_release(&relative_buf);\n> > +             strbuf_release(&real_path);\n> > +             strbuf_release(&real_prefix);\n> > +             free(cwd);\n> > +             break;\n> > +     }\n>\n> OK.\n>\n> > +     case PATH_FORMAT_RELATIVE_IF_SHARED: {\n> > +             struct strbuf relative_buf = STRBUF_INIT;\n> > +\n> > +             /*\n> > +              * If we're using RELATIVE_IF_SHARED mode, then we want an\n> > +              * absolute path unless the two share a common prefix, so don't\n> > +              * default the prefix to the current working directory. Doing so\n> > +              * would cause a relative path to always be produced if possible.\n> > +              */\n\nI thought this comment made sense keeping in for instance.\n\n> Identical to the original, which is good.\n> > +\n> > +     case PATH_FORMAT_CANONICAL: {\n> > +             struct strbuf canonical_buf = STRBUF_INIT;\n> > +\n> > +             strbuf_realpath_forgiving(&canonical_buf, path, 1);\n> > +             strbuf_addbuf(dest, &canonical_buf);\n> > +\n> > +             strbuf_release(&canonical_buf);\n> > +             break;\n> > +     }\n> > +\n> > +     default:\n> > +             BUG(\"unknown path_format value %d\", format);\n> > +     }\n> > +}\n>\n> OK.\n>\n> > +/**\n> > + * Format a path according to the specified formatting strategy and append\n> > + * the result to the given strbuf.\n> > + *\n> > + * `dest`   : The string buffer to append the formatted path to.\n> > + * `path`   : The path string that needs to be formatted.\n> > + * `prefix` : The directory prefix to calculate relative offsets against.\n> > + * Pass NULL to default to the current working directory where applicable.\n> > + * `format` : The formatting behavior rule to execute.\n> > + */\n> > +void append_formatted_path(struct strbuf *dest, const char *path,\n> > +                        const char *prefix, enum path_format format);\n> > +\n>\n> It is slightly unsatisfying that this function is defined to\n> \"append\" to any existing value in the dest strbuf, rather than\n> storing the result in the dest strbuf.  The original caller\n> print_path() passes an empty strbuf to this helper, so it can let\n> strbuf_realpath_*() functions to strbuf_reset() it (e.g.,\n> abspath.c:get_root_part() called by strbuf_realpath_1(), wihch in\n> turn is called by strbuf_realpath() and strbuf_realpath_forgiving())\n> it freely, which means that use of temporary strbuf like\n> canonical_buf only to copy it out to dest is wasteful and unneeded.\n> But other callers we will have for this helper later may want to\n> append to what they already have, so perhaps it is OK (on the other\n> hand, we could say that preserving and appending is what these\n> callers can do themselves).\n>\n\nHmm, I thought about this for a while.\n\nThen I looked at what ls-tree.c does(using an accumulator).\nThey already routinely use temporary `strbuf`s to calculate\nrelative/absolute paths before\nappending them to their main output string.\n\nBecause callers who need to accumulate can easily do the preserving\nand appending\nthemselves with a temporary buffer, there is no reason to force that\noverhead into our helper.\n\nI will change the semantics from \"append\" to \"replace\", rename the\nhelper back to `format_path()`.\nI hope I am looking at ls-tree.c correctly here : )\n\nEliminate the wasteful `canonical_buf` allocations so we can pass the\ndestination buffer directly to functions like\n`strbuf_realpath_forgiving()`.\nThis is a good suggestion actually, thanks!\n\n> Otherwise, looking good as a no-op bug-to-bug compatible rewrite,\n> with a slight optimization (to skip xgetcwd()).\n>\n> Thanks.\n\nOn Mon, Jun 22, 2026 at 9:33 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Junio C Hamano <gitster@pobox.com> writes:\n\n> > ...\n> > It is a minor point, but wouldn't it make it simpler to handle\n> > format_default first?  I.e.,\n> >\n> >       if (format == FORMAT_DEFAULT)\n> >               switch (def) {\n> >               case DEFAULT_RELATIVE:\n> >                       format = DEFAULT_RELATIVE;\n> >                       break;\n> >               ...\n> >               case DEFAULT_UNMODIFIED:\n> >               default:\n> >                       format = DEFAULT_UNMODIFIED;\n> >                       break;\n> >       }\n> >       switch (format) {\n> >         case FORMAT_RELATIVE: fmt = PATH_FORMAT_RELATIVE; break;\n> >       case FORMAT_CANONICAL: fmt = PATH_FORMAT_CANONICAL; break;\n> >       ...\n> >       }\n> >\n> > Perhaps yes, perhaps not.  I dunno.\n>\n> I do not consider the above an blocker, but it might make a\n> difference if we are going to acquire more modes and formats, so\n> once somebody tries to rewrite the logic and finds the resulting\n> code harder to follow (or not easier to follow), I would be happy to\n> see the above discarded ;-)\n>\n\nTrue, if new formats are introduced\nthis would instantly become sloppy.\n\nI will change it to future proof since I am\nlooking to send v8 for append_formatted_path().\n\nAlthough I would be surprised to see an example for a new format.\n\n> >> +/**\n> >> + * Format a path according to the specified formatting strategy and append\n> >> + * the result to the given strbuf.\n> >> + *\n> >> + * `dest`   : The string buffer to append the formatted path to.\n> >> + * `path`   : The path string that needs to be formatted.\n> >> + * `prefix` : The directory prefix to calculate relative offsets against.\n> >> + * Pass NULL to default to the current working directory where applicable.\n> >> + * `format` : The formatting behavior rule to execute.\n> >> + */\n> >> +void append_formatted_path(struct strbuf *dest, const char *path,\n> >> +                       const char *prefix, enum path_format format);\n> >> +\n> >\n> > It is slightly unsatisfying that this function is defined to\n> > \"append\" to any existing value in the dest strbuf, rather than\n> > storing the result in the dest strbuf.  The original caller\n> > print_path() passes an empty strbuf to this helper, so it can let\n> > strbuf_realpath_*() functions to strbuf_reset() it (e.g.,\n> > abspath.c:get_root_part() called by strbuf_realpath_1(), wihch in\n> > turn is called by strbuf_realpath() and strbuf_realpath_forgiving())\n> > it freely, which means that use of temporary strbuf like\n> > canonical_buf only to copy it out to dest is wasteful and unneeded.\n> > But other callers we will have for this helper later may want to\n> > append to what they already have, so perhaps it is OK (on the other\n> > hand, we could say that preserving and appending is what these\n> > callers can do themselves).\n>\n> This one we may want to consider a bit more seriously, but it is\n> entirely up to the future callers of the helper.  If it would make\n> the callers much easier to write for this helper to have \"append\"\n> semantics, I'd be happy to accept the semantics of the above as-is,\n> but otherwise, I suspect it would be simpler to use if the helper is\n> defined to replase dest with the result, instead of appending the\n> result to dest.\n>\n\nI am still unsure if I am following ls-tree.c correctly.\nIf I am then I think it is a very good change to have for v8 as I\nspecified above.\n\n> > Otherwise, looking good as a no-op bug-to-bug compatible rewrite,\n> > with a slight optimization (to skip xgetcwd()).\n>\n> This part of the review does not change in any case.  The\n> refactoring looks good.\n\nThank you ; )\n\nRegards,\n- K Jayatheerth\n"},{"id":"546245","messageId":"084ad4d0-d872-4c7f-94a8-ec2383c7a8ca@gmail.com","threadId":"65725","inReplyTo":"20260621055534.46798-2-jayatheerthkulkarni2005@gmail.com","subject":"Re: [GSoC Patch v7 1/3] path: extract append_formatted_path() and use in rev-parse","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2026-06-23T15:57:09Z","receivedAt":"2026-06-23T15:57:12Z","isPatch":true,"body":"On 21/06/2026 06:55, K Jayatheerth wrote:\n> Path formatting logic in builtin/rev-parse.c writes directly to\n> stdout. Other builtins cannot reuse it.\n> \n> Extract this logic into append_formatted_path() in path.c and expose\n> a path_format enum in path.h.\n> \n> Convert rev-parse to use the new helper in the same step to validate\n> the API against existing tests and avoid introducing dead code.\n\nThe new API looks good now, and so does the conversion of the existing \ncode. I'm very happy with this version and don't have anything to add to \nJunio's comments\n\nThanks\n\nPhillip\n\n> Mentored-by: Justin Tobler <jltobler@gmail.com>\n> Mentored-by: Lucas Seiki Oshiro <lucasseikioshiro@gmail.com>\n> Signed-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>\n> ---\n>   builtin/rev-parse.c | 73 ++++++++++++++++++---------------------------\n>   path.c              | 69 ++++++++++++++++++++++++++++++++++++++++++\n>   path.h              | 30 +++++++++++++++++++\n>   3 files changed, 128 insertions(+), 44 deletions(-)\n> \n> diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c\n> index bb882678fe..6de01466db 100644\n> --- a/builtin/rev-parse.c\n> +++ b/builtin/rev-parse.c\n> @@ -653,53 +653,38 @@ enum default_type {\n>   \tDEFAULT_UNMODIFIED,\n>   };\n>   \n> -static void print_path(const char *path, const char *prefix, enum format_type format, enum default_type def)\n> +static void print_path(const char *path, const char *prefix,\n> +\t\t       enum format_type format, enum default_type def)\n>   {\n> -\tchar *cwd = NULL;\n> -\t/*\n> -\t * We don't ever produce a relative path if prefix is NULL, so set the\n> -\t * prefix to the current directory so that we can produce a relative\n> -\t * path whenever possible.  If we're using RELATIVE_IF_SHARED mode, then\n> -\t * we want an absolute path unless the two share a common prefix, so don't\n> -\t * set it in that case, since doing so causes a relative path to always\n> -\t * be produced if possible.\n> -\t */\n> -\tif (!prefix && (format != FORMAT_DEFAULT || def != DEFAULT_RELATIVE_IF_SHARED))\n> -\t\tprefix = cwd = xgetcwd();\n> -\tif (format == FORMAT_DEFAULT && def == DEFAULT_UNMODIFIED) {\n> -\t\tputs(path);\n> -\t} else if (format == FORMAT_RELATIVE ||\n> -\t\t  (format == FORMAT_DEFAULT && def == DEFAULT_RELATIVE)) {\n> -\t\t/*\n> -\t\t * In order for relative_path to work as expected, we need to\n> -\t\t * make sure that both paths are absolute paths.  If we don't,\n> -\t\t * we can end up with an unexpected absolute path that the user\n> -\t\t * didn't want.\n> -\t\t */\n> -\t\tstruct strbuf buf = STRBUF_INIT, realbuf = STRBUF_INIT, prefixbuf = STRBUF_INIT;\n> -\t\tif (!is_absolute_path(path)) {\n> -\t\t\tstrbuf_realpath_forgiving(&realbuf, path,  1);\n> -\t\t\tpath = realbuf.buf;\n> -\t\t}\n> -\t\tif (!is_absolute_path(prefix)) {\n> -\t\t\tstrbuf_realpath_forgiving(&prefixbuf, prefix, 1);\n> -\t\t\tprefix = prefixbuf.buf;\n> +\tstruct strbuf sb = STRBUF_INIT;\n> +\tenum path_format fmt;\n> +\n> +\tif (format == FORMAT_RELATIVE) {\n> +\t\tfmt = PATH_FORMAT_RELATIVE;\n> +\t} else if (format == FORMAT_CANONICAL) {\n> +\t\tfmt = PATH_FORMAT_CANONICAL;\n> +\t} else /* FORMAT_DEFAULT */ {\n> +\t\tswitch (def) {\n> +\t\tcase DEFAULT_RELATIVE:\n> +\t\t\tfmt = PATH_FORMAT_RELATIVE;\n> +\t\t\tbreak;\n> +\t\tcase DEFAULT_RELATIVE_IF_SHARED:\n> +\t\t\tfmt = PATH_FORMAT_RELATIVE_IF_SHARED;\n> +\t\t\tbreak;\n> +\t\tcase DEFAULT_CANONICAL:\n> +\t\t\tfmt = PATH_FORMAT_CANONICAL;\n> +\t\t\tbreak;\n> +\t\tcase DEFAULT_UNMODIFIED:\n> +\t\tdefault:\n> +\t\t\tfmt = PATH_FORMAT_UNMODIFIED;\n> +\t\t\tbreak;\n>   \t\t}\n> -\t\tputs(relative_path(path, prefix, &buf));\n> -\t\tstrbuf_release(&buf);\n> -\t\tstrbuf_release(&realbuf);\n> -\t\tstrbuf_release(&prefixbuf);\n> -\t} else if (format == FORMAT_DEFAULT && def == DEFAULT_RELATIVE_IF_SHARED) {\n> -\t\tstruct strbuf buf = STRBUF_INIT;\n> -\t\tputs(relative_path(path, prefix, &buf));\n> -\t\tstrbuf_release(&buf);\n> -\t} else {\n> -\t\tstruct strbuf buf = STRBUF_INIT;\n> -\t\tstrbuf_realpath_forgiving(&buf, path, 1);\n> -\t\tputs(buf.buf);\n> -\t\tstrbuf_release(&buf);\n>   \t}\n> -\tfree(cwd);\n> +\n> +\tappend_formatted_path(&sb, path, prefix, fmt);\n> +\tputs(sb.buf);\n> +\n> +\tstrbuf_release(&sb);\n>   }\n>   \n>   int cmd_rev_parse(int argc,\n> diff --git a/path.c b/path.c\n> index d7e17bf174..6d8e892ada 100644\n> --- a/path.c\n> +++ b/path.c\n> @@ -1579,6 +1579,75 @@ char *xdg_cache_home(const char *filename)\n>   \treturn NULL;\n>   }\n>   \n> +void append_formatted_path(struct strbuf *dest, const char *path,\n> +\t\t\t   const char *prefix, enum path_format format)\n> +{\n> +\tswitch (format) {\n> +\tcase PATH_FORMAT_UNMODIFIED:\n> +\t\tstrbuf_addstr(dest, path);\n> +\t\tbreak;\n> +\n> +\tcase PATH_FORMAT_RELATIVE: {\n> +\t\tstruct strbuf relative_buf = STRBUF_INIT;\n> +\t\tstruct strbuf real_path = STRBUF_INIT;\n> +\t\tstruct strbuf real_prefix = STRBUF_INIT;\n> +\t\tchar *cwd = NULL;\n> +\n> +\t\t/*\n> +\t\t * We don't ever produce a relative path if prefix is NULL,\n> +\t\t * so set the prefix to the current directory so that we can\n> +\t\t * produce a relative path whenever possible.\n> +\t\t */\n> +\t\tif (!prefix)\n> +\t\t\tprefix = cwd = xgetcwd();\n> +\n> +\t\tif (!is_absolute_path(path)) {\n> +\t\t\tstrbuf_realpath_forgiving(&real_path, path, 1);\n> +\t\t\tpath = real_path.buf;\n> +\t\t}\n> +\t\tif (!is_absolute_path(prefix)) {\n> +\t\t\tstrbuf_realpath_forgiving(&real_prefix, prefix, 1);\n> +\t\t\tprefix = real_prefix.buf;\n> +\t\t}\n> +\n> +\t\tstrbuf_addstr(dest, relative_path(path, prefix, &relative_buf));\n> +\n> +\t\tstrbuf_release(&relative_buf);\n> +\t\tstrbuf_release(&real_path);\n> +\t\tstrbuf_release(&real_prefix);\n> +\t\tfree(cwd);\n> +\t\tbreak;\n> +\t}\n> +\n> +\tcase PATH_FORMAT_RELATIVE_IF_SHARED: {\n> +\t\tstruct strbuf relative_buf = STRBUF_INIT;\n> +\n> +\t\t/*\n> +\t\t * If we're using RELATIVE_IF_SHARED mode, then we want an\n> +\t\t * absolute path unless the two share a common prefix, so don't\n> +\t\t * default the prefix to the current working directory. Doing so\n> +\t\t * would cause a relative path to always be produced if possible.\n> +\t\t */\n> +\t\tstrbuf_addstr(dest, relative_path(path, prefix, &relative_buf));\n> +\t\tstrbuf_release(&relative_buf);\n> +\t\tbreak;\n> +\t}\n> +\n> +\tcase PATH_FORMAT_CANONICAL: {\n> +\t\tstruct strbuf canonical_buf = STRBUF_INIT;\n> +\n> +\t\tstrbuf_realpath_forgiving(&canonical_buf, path, 1);\n> +\t\tstrbuf_addbuf(dest, &canonical_buf);\n> +\n> +\t\tstrbuf_release(&canonical_buf);\n> +\t\tbreak;\n> +\t}\n> +\n> +\tdefault:\n> +\t\tBUG(\"unknown path_format value %d\", format);\n> +\t}\n> +}\n> +\n>   REPO_GIT_PATH_FUNC(squash_msg, \"SQUASH_MSG\")\n>   REPO_GIT_PATH_FUNC(merge_msg, \"MERGE_MSG\")\n>   REPO_GIT_PATH_FUNC(merge_rr, \"MERGE_RR\")\n> diff --git a/path.h b/path.h\n> index 4c2958a903..4d982a2c8e 100644\n> --- a/path.h\n> +++ b/path.h\n> @@ -262,6 +262,36 @@ enum scld_error safe_create_leading_directories_no_share(char *path);\n>   int safe_create_file_with_leading_directories(struct repository *repo,\n>   \t\t\t\t\t      const char *path);\n>   \n> +/**\n> + * The formatting strategy to apply when writing a path into a buffer.\n> + */\n> +enum path_format {\n> +\t/* Output the path exactly as-is without any modifications. */\n> +\tPATH_FORMAT_UNMODIFIED,\n> +\n> +\t/* Output a path relative to the provided directory prefix. */\n> +\tPATH_FORMAT_RELATIVE,\n> +\n> +\t/* Output a relative path only if the path shares a root with the prefix. */\n> +\tPATH_FORMAT_RELATIVE_IF_SHARED,\n> +\n> +\t/* Output a fully resolved, absolute canonical path. */\n> +\tPATH_FORMAT_CANONICAL\n> +};\n> +\n> +/**\n> + * Format a path according to the specified formatting strategy and append\n> + * the result to the given strbuf.\n> + *\n> + * `dest`   : The string buffer to append the formatted path to.\n> + * `path`   : The path string that needs to be formatted.\n> + * `prefix` : The directory prefix to calculate relative offsets against.\n> + * Pass NULL to default to the current working directory where applicable.\n> + * `format` : The formatting behavior rule to execute.\n> + */\n> +void append_formatted_path(struct strbuf *dest, const char *path,\n> +\t\t\t   const char *prefix, enum path_format format);\n> +\n>   # ifdef USE_THE_REPOSITORY_VARIABLE\n>   #  include \"strbuf.h\"\n>   #  include \"repository.h\"\n\n"},{"id":"546251","messageId":"20260624033748.108281-1-jayatheerthkulkarni2005@gmail.com","threadId":"65725","inReplyTo":"20260601151950.30686-1-jayatheerthkulkarni2005@gmail.com","subject":"[GSoC Patch v8 0/3] teach git repo info to handle path keys","fromName":"K Jayatheerth","fromEmail":"jayatheerthkulkarni2005@gmail.com","sentAt":"2026-06-24T03:37:45Z","receivedAt":"2026-06-24T03:39:47Z","isPatch":true,"body":"Hi!\n\nThis series teaches `git repo info` to handle `path.*`\nkeys, allowing scripts to reliably discover core\nrepository paths without resorting to `git rev-parse`.\n\nThe patches are structured as follows:\n\n1. path: Extract the localized path-formatting logic\n   out of `rev-parse` and expose it globally via\n   `path.h` using clear append semantics.\n\n2. repo: Introduce `path.commondir.absolute` and\n   `path.commondir.relative` alongside a robust,\n   isolated test helper.\n\n3. repo: Introduce `path.gitdir.absolute` and\n   `path.gitdir.relative` using the same standardized\n   formatting rules.\n\n   Changes since v7:\n\n   * Renamed the helper to format_path() and changed semantics to replace/reset\n     the destination buffer instead of appending (Junio).\n   * Eliminated wasteful intermediate strbuf allocations (e.g., canonical_buf)\n     by passing the destination buffer directly where safe (Junio).\n   * Refactored the print_path() switch logic in rev-parse.c to evaluate\n     FORMAT_DEFAULT first for better readability and future-proofing (Junio).\n\n   Keeping the name as format_path() made sense to me. I understand we already\n   had a discussion stating format_path() wasn't a good name back then because\n   we were clearly appending to the buffer.\n\n   I believe it is a good name now. Although, if there are any other name\n   suggestions, I am happy to change it.\n\n   P.S: I have thought of:\n     replace_formatted_path\n     strbuf_format_path\n     populate_formatted_path\n\n   In the end, I came to the conclusion that format_path() is simply better.\n\nTagging Justin Tobler, Lucas Seiki Oshiro, Junio,\nPhillip Wood, brian m. carlson, and Ayush Jha.\n\nThanks for helping improve this series!\n\nK Jayatheerth (3):\n  path: extract format_path() and use in rev-parse\n  repo: add path.commondir with absolute and relative suffix formatting\n  repo: add path.gitdir with absolute and relative suffix formatting\n\n Documentation/git-repo.adoc | 15 +++++++\n builtin/repo.c              | 50 +++++++++++++++++++++++\n builtin/rev-parse.c         | 79 +++++++++++++++++--------------------\n path.c                      | 69 ++++++++++++++++++++++++++++++++\n path.h                      | 30 ++++++++++++++\n t/t1900-repo-info.sh        | 58 +++++++++++++++++++++++++++\n 6 files changed, 258 insertions(+), 43 deletions(-)\n\nRange-diff against v7:\n1:  bb1d3fd06f ! 1:  287281935e path: extract append_formatted_path() and use in rev-parse\n    @@ Metadata\n     Author: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>\n     \n      ## Commit message ##\n    -    path: extract append_formatted_path() and use in rev-parse\n    +    path: extract format_path() and use in rev-parse\n     \n         Path formatting logic in builtin/rev-parse.c writes directly to\n         stdout. Other builtins cannot reuse it.\n     \n    -    Extract this logic into append_formatted_path() in path.c and expose\n    +    Extract this logic into format_path() in path.c and expose\n         a path_format enum in path.h.\n     \n         Convert rev-parse to use the new helper in the same step to validate\n    @@ builtin/rev-parse.c: enum default_type {\n     +\tstruct strbuf sb = STRBUF_INIT;\n     +\tenum path_format fmt;\n     +\n    -+\tif (format == FORMAT_RELATIVE) {\n    -+\t\tfmt = PATH_FORMAT_RELATIVE;\n    -+\t} else if (format == FORMAT_CANONICAL) {\n    -+\t\tfmt = PATH_FORMAT_CANONICAL;\n    -+\t} else /* FORMAT_DEFAULT */ {\n    ++\tif (format == FORMAT_DEFAULT) {\n     +\t\tswitch (def) {\n     +\t\tcase DEFAULT_RELATIVE:\n     +\t\t\tfmt = PATH_FORMAT_RELATIVE;\n    @@ builtin/rev-parse.c: enum default_type {\n     -\t\tstruct strbuf buf = STRBUF_INIT;\n     -\t\tputs(relative_path(path, prefix, &buf));\n     -\t\tstrbuf_release(&buf);\n    --\t} else {\n    + \t} else {\n     -\t\tstruct strbuf buf = STRBUF_INIT;\n     -\t\tstrbuf_realpath_forgiving(&buf, path, 1);\n     -\t\tputs(buf.buf);\n     -\t\tstrbuf_release(&buf);\n    ++\t\tswitch (format) {\n    ++\t\tcase FORMAT_RELATIVE:\n    ++\t\t\tfmt = PATH_FORMAT_RELATIVE;\n    ++\t\t\tbreak;\n    ++\t\tcase FORMAT_CANONICAL:\n    ++\t\t\tfmt = PATH_FORMAT_CANONICAL;\n    ++\t\t\tbreak;\n    ++\t\tdefault:\n    ++\t\t\tfmt = PATH_FORMAT_UNMODIFIED;\n    ++\t\t\tbreak;\n    ++\t\t}\n      \t}\n     -\tfree(cwd);\n     +\n    -+\tappend_formatted_path(&sb, path, prefix, fmt);\n    ++\tformat_path(&sb, path, prefix, fmt);\n     +\tputs(sb.buf);\n     +\n     +\tstrbuf_release(&sb);\n    @@ path.c: char *xdg_cache_home(const char *filename)\n      \treturn NULL;\n      }\n      \n    -+void append_formatted_path(struct strbuf *dest, const char *path,\n    -+\t\t\t   const char *prefix, enum path_format format)\n    ++void format_path(struct strbuf *dest, const char *path,\n    ++\t\t const char *prefix, enum path_format format)\n     +{\n    ++\tstrbuf_reset(dest);\n    ++\n     +\tswitch (format) {\n     +\tcase PATH_FORMAT_UNMODIFIED:\n     +\t\tstrbuf_addstr(dest, path);\n    @@ path.c: char *xdg_cache_home(const char *filename)\n     +\t\tbreak;\n     +\t}\n     +\n    -+\tcase PATH_FORMAT_CANONICAL: {\n    -+\t\tstruct strbuf canonical_buf = STRBUF_INIT;\n    -+\n    -+\t\tstrbuf_realpath_forgiving(&canonical_buf, path, 1);\n    -+\t\tstrbuf_addbuf(dest, &canonical_buf);\n    -+\n    -+\t\tstrbuf_release(&canonical_buf);\n    ++\tcase PATH_FORMAT_CANONICAL:\n    ++\t\t/*\n    ++\t\t * strbuf_realpath_forgiving inherently resets the destination\n    ++\t\t * buffer, safely aligning with our replace semantics.\n    ++\t\t */\n    ++\t\tstrbuf_realpath_forgiving(dest, path, 1);\n     +\t\tbreak;\n    -+\t}\n     +\n     +\tdefault:\n     +\t\tBUG(\"unknown path_format value %d\", format);\n    @@ path.h: enum scld_error safe_create_leading_directories_no_share(char *path);\n     +};\n     +\n     +/**\n    -+ * Format a path according to the specified formatting strategy and append\n    -+ * the result to the given strbuf.\n    ++ * Format a path according to the specified formatting strategy and store\n    ++ * the result in the given strbuf, replacing any existing contents.\n     + *\n    -+ * `dest`   : The string buffer to append the formatted path to.\n    ++ * `dest`   : The string buffer to store the formatted path into.\n     + * `path`   : The path string that needs to be formatted.\n     + * `prefix` : The directory prefix to calculate relative offsets against.\n     + * Pass NULL to default to the current working directory where applicable.\n     + * `format` : The formatting behavior rule to execute.\n     + */\n    -+void append_formatted_path(struct strbuf *dest, const char *path,\n    -+\t\t\t   const char *prefix, enum path_format format);\n    ++void format_path(struct strbuf *dest, const char *path,\n    ++\t\t const char *prefix, enum path_format format);\n     +\n      # ifdef USE_THE_REPOSITORY_VARIABLE\n      #  include \"strbuf.h\"\n2:  d2414bee58 ! 2:  69517f1a08 repo: add path.commondir with absolute and relative suffix formatting\n    @@ builtin/repo.c: static int get_object_format(struct repository *repo, struct str\n     +\tif (!common_dir)\n     +\t\treturn error(_(\"unable to get common directory\"));\n     +\n    -+\tappend_formatted_path(buf, common_dir, startup_info->prefix, PATH_FORMAT_CANONICAL);\n    ++\tformat_path(buf, common_dir, startup_info->prefix, PATH_FORMAT_CANONICAL);\n     +\treturn 0;\n     +}\n     +\n    @@ builtin/repo.c: static int get_object_format(struct repository *repo, struct str\n     +\tif (!common_dir)\n     +\t\treturn error(_(\"unable to get common directory\"));\n     +\n    -+\tappend_formatted_path(buf, common_dir, startup_info->prefix, PATH_FORMAT_RELATIVE);\n    ++\tformat_path(buf, common_dir, startup_info->prefix, PATH_FORMAT_RELATIVE);\n     +\treturn 0;\n     +}\n     +\n3:  9962c7d530 ! 3:  ce43453975 repo: add path.gitdir with absolute and relative suffix formatting\n    @@ builtin/repo.c: static int get_path_commondir_relative(struct repository *repo,\n     +\tif (!git_dir)\n     +\t\treturn error(_(\"unable to get git directory\"));\n     +\n    -+\tappend_formatted_path(buf, git_dir, startup_info->prefix, PATH_FORMAT_CANONICAL);\n    ++\tformat_path(buf, git_dir, startup_info->prefix, PATH_FORMAT_CANONICAL);\n     +\treturn 0;\n     +}\n     +\n    @@ builtin/repo.c: static int get_path_commondir_relative(struct repository *repo,\n     +\tif (!git_dir)\n     +\t\treturn error(_(\"unable to get git directory\"));\n     +\n    -+\tappend_formatted_path(buf, git_dir, startup_info->prefix, PATH_FORMAT_RELATIVE);\n    ++\tformat_path(buf, git_dir, startup_info->prefix, PATH_FORMAT_RELATIVE);\n     +\treturn 0;\n     +}\n     +\n-- \n2.55.0-rc1\n"},{"id":"546252","messageId":"20260624033748.108281-2-jayatheerthkulkarni2005@gmail.com","threadId":"65725","inReplyTo":"20260624033748.108281-1-jayatheerthkulkarni2005@gmail.com","subject":"[GSoC Patch v8 1/3] path: extract format_path() and use in rev-parse","fromName":"K Jayatheerth","fromEmail":"jayatheerthkulkarni2005@gmail.com","sentAt":"2026-06-24T03:37:46Z","receivedAt":"2026-06-24T03:40:06Z","isPatch":true,"body":"Path formatting logic in builtin/rev-parse.c writes directly to\nstdout. Other builtins cannot reuse it.\n\nExtract this logic into format_path() in path.c and expose\na path_format enum in path.h.\n\nConvert rev-parse to use the new helper in the same step to validate\nthe API against existing tests and avoid introducing dead code.\n\nMentored-by: Justin Tobler <jltobler@gmail.com>\nMentored-by: Lucas Seiki Oshiro <lucasseikioshiro@gmail.com>\nSigned-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>\n---\n builtin/rev-parse.c | 79 +++++++++++++++++++++------------------------\n path.c              | 69 +++++++++++++++++++++++++++++++++++++++\n path.h              | 30 +++++++++++++++++\n 3 files changed, 135 insertions(+), 43 deletions(-)\n\ndiff --git a/builtin/rev-parse.c b/builtin/rev-parse.c\nindex bb882678fe..7d6ac92038 100644\n--- a/builtin/rev-parse.c\n+++ b/builtin/rev-parse.c\n@@ -653,53 +653,46 @@ enum default_type {\n \tDEFAULT_UNMODIFIED,\n };\n \n-static void print_path(const char *path, const char *prefix, enum format_type format, enum default_type def)\n+static void print_path(const char *path, const char *prefix,\n+\t\t       enum format_type format, enum default_type def)\n {\n-\tchar *cwd = NULL;\n-\t/*\n-\t * We don't ever produce a relative path if prefix is NULL, so set the\n-\t * prefix to the current directory so that we can produce a relative\n-\t * path whenever possible.  If we're using RELATIVE_IF_SHARED mode, then\n-\t * we want an absolute path unless the two share a common prefix, so don't\n-\t * set it in that case, since doing so causes a relative path to always\n-\t * be produced if possible.\n-\t */\n-\tif (!prefix && (format != FORMAT_DEFAULT || def != DEFAULT_RELATIVE_IF_SHARED))\n-\t\tprefix = cwd = xgetcwd();\n-\tif (format == FORMAT_DEFAULT && def == DEFAULT_UNMODIFIED) {\n-\t\tputs(path);\n-\t} else if (format == FORMAT_RELATIVE ||\n-\t\t  (format == FORMAT_DEFAULT && def == DEFAULT_RELATIVE)) {\n-\t\t/*\n-\t\t * In order for relative_path to work as expected, we need to\n-\t\t * make sure that both paths are absolute paths.  If we don't,\n-\t\t * we can end up with an unexpected absolute path that the user\n-\t\t * didn't want.\n-\t\t */\n-\t\tstruct strbuf buf = STRBUF_INIT, realbuf = STRBUF_INIT, prefixbuf = STRBUF_INIT;\n-\t\tif (!is_absolute_path(path)) {\n-\t\t\tstrbuf_realpath_forgiving(&realbuf, path,  1);\n-\t\t\tpath = realbuf.buf;\n-\t\t}\n-\t\tif (!is_absolute_path(prefix)) {\n-\t\t\tstrbuf_realpath_forgiving(&prefixbuf, prefix, 1);\n-\t\t\tprefix = prefixbuf.buf;\n+\tstruct strbuf sb = STRBUF_INIT;\n+\tenum path_format fmt;\n+\n+\tif (format == FORMAT_DEFAULT) {\n+\t\tswitch (def) {\n+\t\tcase DEFAULT_RELATIVE:\n+\t\t\tfmt = PATH_FORMAT_RELATIVE;\n+\t\t\tbreak;\n+\t\tcase DEFAULT_RELATIVE_IF_SHARED:\n+\t\t\tfmt = PATH_FORMAT_RELATIVE_IF_SHARED;\n+\t\t\tbreak;\n+\t\tcase DEFAULT_CANONICAL:\n+\t\t\tfmt = PATH_FORMAT_CANONICAL;\n+\t\t\tbreak;\n+\t\tcase DEFAULT_UNMODIFIED:\n+\t\tdefault:\n+\t\t\tfmt = PATH_FORMAT_UNMODIFIED;\n+\t\t\tbreak;\n \t\t}\n-\t\tputs(relative_path(path, prefix, &buf));\n-\t\tstrbuf_release(&buf);\n-\t\tstrbuf_release(&realbuf);\n-\t\tstrbuf_release(&prefixbuf);\n-\t} else if (format == FORMAT_DEFAULT && def == DEFAULT_RELATIVE_IF_SHARED) {\n-\t\tstruct strbuf buf = STRBUF_INIT;\n-\t\tputs(relative_path(path, prefix, &buf));\n-\t\tstrbuf_release(&buf);\n \t} else {\n-\t\tstruct strbuf buf = STRBUF_INIT;\n-\t\tstrbuf_realpath_forgiving(&buf, path, 1);\n-\t\tputs(buf.buf);\n-\t\tstrbuf_release(&buf);\n+\t\tswitch (format) {\n+\t\tcase FORMAT_RELATIVE:\n+\t\t\tfmt = PATH_FORMAT_RELATIVE;\n+\t\t\tbreak;\n+\t\tcase FORMAT_CANONICAL:\n+\t\t\tfmt = PATH_FORMAT_CANONICAL;\n+\t\t\tbreak;\n+\t\tdefault:\n+\t\t\tfmt = PATH_FORMAT_UNMODIFIED;\n+\t\t\tbreak;\n+\t\t}\n \t}\n-\tfree(cwd);\n+\n+\tformat_path(&sb, path, prefix, fmt);\n+\tputs(sb.buf);\n+\n+\tstrbuf_release(&sb);\n }\n \n int cmd_rev_parse(int argc,\ndiff --git a/path.c b/path.c\nindex d7e17bf174..c3a709a928 100644\n--- a/path.c\n+++ b/path.c\n@@ -1579,6 +1579,75 @@ char *xdg_cache_home(const char *filename)\n \treturn NULL;\n }\n \n+void format_path(struct strbuf *dest, const char *path,\n+\t\t const char *prefix, enum path_format format)\n+{\n+\tstrbuf_reset(dest);\n+\n+\tswitch (format) {\n+\tcase PATH_FORMAT_UNMODIFIED:\n+\t\tstrbuf_addstr(dest, path);\n+\t\tbreak;\n+\n+\tcase PATH_FORMAT_RELATIVE: {\n+\t\tstruct strbuf relative_buf = STRBUF_INIT;\n+\t\tstruct strbuf real_path = STRBUF_INIT;\n+\t\tstruct strbuf real_prefix = STRBUF_INIT;\n+\t\tchar *cwd = NULL;\n+\n+\t\t/*\n+\t\t * We don't ever produce a relative path if prefix is NULL,\n+\t\t * so set the prefix to the current directory so that we can\n+\t\t * produce a relative path whenever possible.\n+\t\t */\n+\t\tif (!prefix)\n+\t\t\tprefix = cwd = xgetcwd();\n+\n+\t\tif (!is_absolute_path(path)) {\n+\t\t\tstrbuf_realpath_forgiving(&real_path, path, 1);\n+\t\t\tpath = real_path.buf;\n+\t\t}\n+\t\tif (!is_absolute_path(prefix)) {\n+\t\t\tstrbuf_realpath_forgiving(&real_prefix, prefix, 1);\n+\t\t\tprefix = real_prefix.buf;\n+\t\t}\n+\n+\t\tstrbuf_addstr(dest, relative_path(path, prefix, &relative_buf));\n+\n+\t\tstrbuf_release(&relative_buf);\n+\t\tstrbuf_release(&real_path);\n+\t\tstrbuf_release(&real_prefix);\n+\t\tfree(cwd);\n+\t\tbreak;\n+\t}\n+\n+\tcase PATH_FORMAT_RELATIVE_IF_SHARED: {\n+\t\tstruct strbuf relative_buf = STRBUF_INIT;\n+\n+\t\t/*\n+\t\t * If we're using RELATIVE_IF_SHARED mode, then we want an\n+\t\t * absolute path unless the two share a common prefix, so don't\n+\t\t * default the prefix to the current working directory. Doing so\n+\t\t * would cause a relative path to always be produced if possible.\n+\t\t */\n+\t\tstrbuf_addstr(dest, relative_path(path, prefix, &relative_buf));\n+\t\tstrbuf_release(&relative_buf);\n+\t\tbreak;\n+\t}\n+\n+\tcase PATH_FORMAT_CANONICAL:\n+\t\t/*\n+\t\t * strbuf_realpath_forgiving inherently resets the destination\n+\t\t * buffer, safely aligning with our replace semantics.\n+\t\t */\n+\t\tstrbuf_realpath_forgiving(dest, path, 1);\n+\t\tbreak;\n+\n+\tdefault:\n+\t\tBUG(\"unknown path_format value %d\", format);\n+\t}\n+}\n+\n REPO_GIT_PATH_FUNC(squash_msg, \"SQUASH_MSG\")\n REPO_GIT_PATH_FUNC(merge_msg, \"MERGE_MSG\")\n REPO_GIT_PATH_FUNC(merge_rr, \"MERGE_RR\")\ndiff --git a/path.h b/path.h\nindex 4c2958a903..7e7408dd05 100644\n--- a/path.h\n+++ b/path.h\n@@ -262,6 +262,36 @@ enum scld_error safe_create_leading_directories_no_share(char *path);\n int safe_create_file_with_leading_directories(struct repository *repo,\n \t\t\t\t\t      const char *path);\n \n+/**\n+ * The formatting strategy to apply when writing a path into a buffer.\n+ */\n+enum path_format {\n+\t/* Output the path exactly as-is without any modifications. */\n+\tPATH_FORMAT_UNMODIFIED,\n+\n+\t/* Output a path relative to the provided directory prefix. */\n+\tPATH_FORMAT_RELATIVE,\n+\n+\t/* Output a relative path only if the path shares a root with the prefix. */\n+\tPATH_FORMAT_RELATIVE_IF_SHARED,\n+\n+\t/* Output a fully resolved, absolute canonical path. */\n+\tPATH_FORMAT_CANONICAL\n+};\n+\n+/**\n+ * Format a path according to the specified formatting strategy and store\n+ * the result in the given strbuf, replacing any existing contents.\n+ *\n+ * `dest`   : The string buffer to store the formatted path into.\n+ * `path`   : The path string that needs to be formatted.\n+ * `prefix` : The directory prefix to calculate relative offsets against.\n+ * Pass NULL to default to the current working directory where applicable.\n+ * `format` : The formatting behavior rule to execute.\n+ */\n+void format_path(struct strbuf *dest, const char *path,\n+\t\t const char *prefix, enum path_format format);\n+\n # ifdef USE_THE_REPOSITORY_VARIABLE\n #  include \"strbuf.h\"\n #  include \"repository.h\"\n-- \n2.55.0-rc1\n\n"},{"id":"546253","messageId":"20260624033748.108281-3-jayatheerthkulkarni2005@gmail.com","threadId":"65725","inReplyTo":"20260624033748.108281-1-jayatheerthkulkarni2005@gmail.com","subject":"[GSoC Patch v8 2/3] repo: add path.commondir with absolute and relative suffix formatting","fromName":"K Jayatheerth","fromEmail":"jayatheerthkulkarni2005@gmail.com","sentAt":"2026-06-24T03:37:47Z","receivedAt":"2026-06-24T03:41:15Z","isPatch":true,"body":"Scripts working with worktree setups need a reliable way to discover\nthe common directory, which diverges from the git directory when\nmultiple worktrees are in use. There is no way to retrieve this path\nfrom git repo info today.\n\nIntroduce path.commondir.absolute and path.commondir.relative keys.\nExposing explicit format variants rather than a single key with a\ndefault avoids ambiguity for scripts that require predictable output.\n\nMentored-by: Justin Tobler <jltobler@gmail.com>\nMentored-by: Lucas Seiki Oshiro <lucasseikioshiro@gmail.com>\nSigned-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>\n---\n Documentation/git-repo.adoc |  9 +++++++\n builtin/repo.c              | 26 +++++++++++++++++++\n t/t1900-repo-info.sh        | 52 +++++++++++++++++++++++++++++++++++++\n 3 files changed, 87 insertions(+)\n\ndiff --git a/Documentation/git-repo.adoc b/Documentation/git-repo.adoc\nindex 42262c1983..890c34051d 100644\n--- a/Documentation/git-repo.adoc\n+++ b/Documentation/git-repo.adoc\n@@ -104,6 +104,15 @@ values that they return:\n `object.format`::\n \tThe object format (hash algorithm) used in the repository.\n \n+`path.commondir.absolute`::\n+\tThe canonical absolute path to the Git repository's common\n+\tdirectory (the shared `.git` directory containing objects,\n+\trefs, and global configuration).\n+\n+`path.commondir.relative`::\n+\tThe path to the Git repository's common directory relative to\n+\tthe current working directory.\n+\n `references.format`::\n \tThe reference storage format. The valid values are:\n +\ndiff --git a/builtin/repo.c b/builtin/repo.c\nindex 71a5c1c29c..4c3fbc26b9 100644\n--- a/builtin/repo.c\n+++ b/builtin/repo.c\n@@ -7,12 +7,14 @@\n #include \"hex.h\"\n #include \"odb.h\"\n #include \"parse-options.h\"\n+#include \"path.h\"\n #include \"path-walk.h\"\n #include \"progress.h\"\n #include \"quote.h\"\n #include \"ref-filter.h\"\n #include \"refs.h\"\n #include \"revision.h\"\n+#include \"setup.h\"\n #include \"strbuf.h\"\n #include \"string-list.h\"\n #include \"shallow.h\"\n@@ -75,6 +77,28 @@ static int get_object_format(struct repository *repo, struct strbuf *buf)\n \treturn 0;\n }\n \n+static int get_path_commondir_absolute(struct repository *repo, struct strbuf *buf)\n+{\n+\tconst char *common_dir = repo_get_common_dir(repo);\n+\n+\tif (!common_dir)\n+\t\treturn error(_(\"unable to get common directory\"));\n+\n+\tformat_path(buf, common_dir, startup_info->prefix, PATH_FORMAT_CANONICAL);\n+\treturn 0;\n+}\n+\n+static int get_path_commondir_relative(struct repository *repo, struct strbuf *buf)\n+{\n+\tconst char *common_dir = repo_get_common_dir(repo);\n+\n+\tif (!common_dir)\n+\t\treturn error(_(\"unable to get common directory\"));\n+\n+\tformat_path(buf, common_dir, startup_info->prefix, PATH_FORMAT_RELATIVE);\n+\treturn 0;\n+}\n+\n static int get_references_format(struct repository *repo, struct strbuf *buf)\n {\n \tstrbuf_addstr(buf,\n@@ -87,6 +111,8 @@ static const struct repo_info_field repo_info_field[] = {\n \t{ \"layout.bare\", get_layout_bare },\n \t{ \"layout.shallow\", get_layout_shallow },\n \t{ \"object.format\", get_object_format },\n+\t{ \"path.commondir.absolute\", get_path_commondir_absolute },\n+\t{ \"path.commondir.relative\", get_path_commondir_relative },\n \t{ \"references.format\", get_references_format },\n };\n \ndiff --git a/t/t1900-repo-info.sh b/t/t1900-repo-info.sh\nindex 39bb77dda0..09158d29f9 100755\n--- a/t/t1900-repo-info.sh\n+++ b/t/t1900-repo-info.sh\n@@ -155,4 +155,56 @@ test_expect_success 'git repo info -h shows only repo info usage' '\n \ttest_grep ! \"git repo structure\" actual\n '\n \n+# Helper function to test path keys in both absolute and relative formats.\n+# $1: label for the test\n+# $2: field_name (e.g., commondir)\n+# $3: expected_dir (the directory name, e.g., .git or custom-common)\n+# $4: init_command (extra setup like exporting env vars)\n+test_repo_info_path () {\n+\tlabel=$1\n+\tfield_name=$2\n+\texpected_dir=$3\n+\tinit_command=$4\n+\n+\ttest_expect_success \"absolute: $label\" '\n+\t\ttest_when_finished \"rm -rf repo\" &&\n+\t\tgit init repo &&\n+\t\t(\n+\t\t\tmkdir -p repo/sub &&\n+\t\t\tcd repo/sub &&\n+\t\t\tROOT=\"$(test-tool path-utils real_path ..)\" && export ROOT &&\n+\t\t\teval \"$init_command\" &&\n+\t\t\techo \"path.$field_name.absolute=$ROOT/$expected_dir\" >expect &&\n+\t\t\tgit repo info \"path.$field_name.absolute\" >actual &&\n+\t\t\ttest_cmp expect actual\n+\t\t)\n+\t'\n+\n+\ttest_expect_success \"relative: $label\" '\n+\t\ttest_when_finished \"rm -rf repo\" &&\n+\t\tgit init repo &&\n+\t\t(\n+\t\t\tmkdir -p repo/sub &&\n+\t\t\tcd repo/sub &&\n+\t\t\tROOT=\"$(test-tool path-utils real_path ..)\" && export ROOT &&\n+\t\t\teval \"$init_command\" &&\n+\t\t\techo \"path.$field_name.relative=../$expected_dir\" >expect &&\n+\t\t\tgit repo info \"path.$field_name.relative\" >actual &&\n+\t\t\ttest_cmp expect actual\n+\t\t)\n+\t'\n+}\n+\n+test_repo_info_path 'commondir standard' 'commondir' '.git'\n+\n+test_repo_info_path 'commondir with GIT_COMMON_DIR and GIT_DIR' 'commondir' \\\n+\t'custom-common' \\\n+\t'GIT_COMMON_DIR=\"$ROOT/custom-common\" && export GIT_COMMON_DIR &&\n+\t GIT_DIR=\"../.git\" && export GIT_DIR &&\n+\t git init --bare \"$ROOT/custom-common\"'\n+\n+test_repo_info_path 'commondir with only GIT_DIR' 'commondir' \\\n+\t'.git' \\\n+\t'GIT_DIR=\"../.git\" && export GIT_DIR'\n+\n test_done\n-- \n2.55.0-rc1\n\n"},{"id":"546254","messageId":"20260624033748.108281-4-jayatheerthkulkarni2005@gmail.com","threadId":"65725","inReplyTo":"20260624033748.108281-1-jayatheerthkulkarni2005@gmail.com","subject":"[GSoC Patch v8 3/3] repo: add path.gitdir with absolute and relative suffix formatting","fromName":"K Jayatheerth","fromEmail":"jayatheerthkulkarni2005@gmail.com","sentAt":"2026-06-24T03:37:48Z","receivedAt":"2026-06-24T03:41:30Z","isPatch":true,"body":"Scripts need a stable way to locate the git directory without\nparsing rev-parse output or relying on its flag-driven path format\nselection. There is no way to retrieve this path from git repo info\ntoday.\n\nIntroduce path.gitdir.absolute and path.gitdir.relative keys,\nconsistent with the path.commondir keys added in the previous patch.\nReuse the test_repo_info_path helper introduced there to validate\nboth variants.\n\nMentored-by: Justin Tobler <jltobler@gmail.com>\nMentored-by: Lucas Seiki Oshiro <lucasseikioshiro@gmail.com>\nSigned-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>\n---\n Documentation/git-repo.adoc |  6 ++++++\n builtin/repo.c              | 24 ++++++++++++++++++++++++\n t/t1900-repo-info.sh        |  6 ++++++\n 3 files changed, 36 insertions(+)\n\ndiff --git a/Documentation/git-repo.adoc b/Documentation/git-repo.adoc\nindex 890c34051d..ed7d80c690 100644\n--- a/Documentation/git-repo.adoc\n+++ b/Documentation/git-repo.adoc\n@@ -113,6 +113,12 @@ values that they return:\n \tThe path to the Git repository's common directory relative to\n \tthe current working directory.\n \n+`path.gitdir.absolute`::\n+\tThe canonical absolute path to the Git repository directory (the `.git` directory).\n+\n+`path.gitdir.relative`::\n+\tThe path to the Git repository directory relative to the current working directory.\n+\n `references.format`::\n \tThe reference storage format. The valid values are:\n +\ndiff --git a/builtin/repo.c b/builtin/repo.c\nindex 4c3fbc26b9..27c8caff38 100644\n--- a/builtin/repo.c\n+++ b/builtin/repo.c\n@@ -99,6 +99,28 @@ static int get_path_commondir_relative(struct repository *repo, struct strbuf *b\n \treturn 0;\n }\n \n+static int get_path_gitdir_absolute(struct repository *repo, struct strbuf *buf)\n+{\n+\tconst char *git_dir = repo_get_git_dir(repo);\n+\n+\tif (!git_dir)\n+\t\treturn error(_(\"unable to get git directory\"));\n+\n+\tformat_path(buf, git_dir, startup_info->prefix, PATH_FORMAT_CANONICAL);\n+\treturn 0;\n+}\n+\n+static int get_path_gitdir_relative(struct repository *repo, struct strbuf *buf)\n+{\n+\tconst char *git_dir = repo_get_git_dir(repo);\n+\n+\tif (!git_dir)\n+\t\treturn error(_(\"unable to get git directory\"));\n+\n+\tformat_path(buf, git_dir, startup_info->prefix, PATH_FORMAT_RELATIVE);\n+\treturn 0;\n+}\n+\n static int get_references_format(struct repository *repo, struct strbuf *buf)\n {\n \tstrbuf_addstr(buf,\n@@ -113,6 +135,8 @@ static const struct repo_info_field repo_info_field[] = {\n \t{ \"object.format\", get_object_format },\n \t{ \"path.commondir.absolute\", get_path_commondir_absolute },\n \t{ \"path.commondir.relative\", get_path_commondir_relative },\n+\t{ \"path.gitdir.absolute\", get_path_gitdir_absolute },\n+\t{ \"path.gitdir.relative\", get_path_gitdir_relative },\n \t{ \"references.format\", get_references_format },\n };\n \ndiff --git a/t/t1900-repo-info.sh b/t/t1900-repo-info.sh\nindex 09158d29f9..ae8c22c817 100755\n--- a/t/t1900-repo-info.sh\n+++ b/t/t1900-repo-info.sh\n@@ -207,4 +207,10 @@ test_repo_info_path 'commondir with only GIT_DIR' 'commondir' \\\n \t'.git' \\\n \t'GIT_DIR=\"../.git\" && export GIT_DIR'\n \n+test_repo_info_path 'gitdir standard' 'gitdir' '.git'\n+\n+test_repo_info_path 'gitdir with explicit GIT_DIR' 'gitdir' \\\n+\t'.git' \\\n+\t'GIT_DIR=\"../.git\" && export GIT_DIR'\n+\n test_done\n-- \n2.55.0-rc1\n\n"},{"id":"546255","messageId":"CA+rGoLd4MLp5RuLMCJ3R+UDNtpY0UdKnxokz=z=QCz6mpCE0Ag@mail.gmail.com","threadId":"65725","inReplyTo":"084ad4d0-d872-4c7f-94a8-ec2383c7a8ca@gmail.com","subject":"Re: [GSoC Patch v7 1/3] path: extract append_formatted_path() and use in rev-parse","fromName":"K Jayatheerth","fromEmail":"jayatheerthkulkarni2005@gmail.com","sentAt":"2026-06-24T03:49:07Z","receivedAt":"2026-06-24T03:49:21Z","isPatch":true,"body":"Hey Phillip,\n\nOn Tue, Jun 23, 2026 at 9:27 PM Phillip Wood <phillip.wood123@gmail.com> wrote:\n>\n> On 21/06/2026 06:55, K Jayatheerth wrote:\n> > Path formatting logic in builtin/rev-parse.c writes directly to\n> > stdout. Other builtins cannot reuse it.\n> >\n> > Extract this logic into append_formatted_path() in path.c and expose\n> > a path_format enum in path.h.\n> >\n> > Convert rev-parse to use the new helper in the same step to validate\n> > the API against existing tests and avoid introducing dead code.\n>\n> The new API looks good now, and so does the conversion of the existing\n> code. I'm very happy with this version and don't have anything to add to\n> Junio's comments\n>\n> Thanks\n>\n> Phillip\n>\n\nI have sent a v8 with Junio's feedback addressed.\nI wouldn't have a problem with either of the versions getting merged.\n\nBoth of them are good in their own ways.\n\nThank you,\n- K Jayatheerth\n"},{"id":"546339","messageId":"xmqqy0g3iz38.fsf@gitster.g","threadId":"65725","inReplyTo":"20260624033748.108281-2-jayatheerthkulkarni2005@gmail.com","subject":"Re: [GSoC Patch v8 1/3] path: extract format_path() and use in rev-parse","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-06-24T18:15:55Z","receivedAt":"2026-06-24T18:15:58Z","isPatch":true,"body":"K Jayatheerth <jayatheerthkulkarni2005@gmail.com> writes:\n\n> +void format_path(struct strbuf *dest, const char *path,\n> +\t\t const char *prefix, enum path_format format)\n> +{\n> +\tstrbuf_reset(dest);\n> +\n> +\tswitch (format) {\n> +\tcase PATH_FORMAT_UNMODIFIED:\n> +\t\tstrbuf_addstr(dest, path);\n> +\t\tbreak;\n> +\n> +\tcase PATH_FORMAT_RELATIVE: {\n> ...\n> +\t\tstrbuf_addstr(dest, relative_path(path, prefix, &relative_buf));\n> +\n> +\t\tstrbuf_release(&relative_buf);\n> +\t\tstrbuf_release(&real_path);\n> +\t\tstrbuf_release(&real_prefix);\n> +\t\tfree(cwd);\n> +\t\tbreak;\n> +\t}\n> +\n> +\tcase PATH_FORMAT_RELATIVE_IF_SHARED: {\n> ...\n> +\t\tstrbuf_addstr(dest, relative_path(path, prefix, &relative_buf));\n> +\t\tstrbuf_release(&relative_buf);\n> +\t\tbreak;\n> +\t}\n> +\n> +\tcase PATH_FORMAT_CANONICAL:\n> +\t\t/*\n> +\t\t * strbuf_realpath_forgiving inherently resets the destination\n> +\t\t * buffer, safely aligning with our replace semantics.\n> +\t\t */\n> +\t\tstrbuf_realpath_forgiving(dest, path, 1);\n> +\t\tbreak;\n> +\n> +\tdefault:\n> +\t\tBUG(\"unknown path_format value %d\", format);\n> +\t}\n> +}\n\nHmph.\n\nI was hoping that we could lose even more strbuf, but since\nrelative_path() does not always leave its result in the strbuf that\nis passed to it as its third parameter, we do need addstr() into\ndest, which is a bit unsatisfying but not a fault of this patch at\nall.  At least, we lost extra copy in the canonical codepath ;-)\n\nLooking good.  Thanks.\n"}]}