From 608ec22eb8242fbfab58ee6c18f1d2a9d7c0bc96 Mon Sep 17 00:00:00 2001 From: K Jayatheerth Date: Sun, 27 Sep 2026 17:14:13 +0530 Subject: [PATCH 1/8] repo: add path.toplevel with absolute and relative suffix formatting Scripts frequently need to find the root directory of a repository's working tree. Currently, this requires using `git rev-parse --show-toplevel` or inferring it from other repository information. Introduce `path.toplevel.absolute` and `path.toplevel.relative` keys to `git repo info`. This allows scripts to retrieve the top-level working tree path in a predictable, strictly formatted manner without relying on `rev-parse`. If requested in a bare repository where no working tree exists, the command returns an empty string. Mentored-by: Justin Tobler Mentored-by: Lucas Seiki Oshiro Signed-off-by: K Jayatheerth Signed-off-by: Junio C Hamano --- Documentation/git-repo.adoc | 10 ++++++++++ builtin/repo.c | 24 ++++++++++++++++++++++++ t/t1900-repo-info.sh | 35 +++++++++++++++++++++++++++++++++++ 3 files changed, 69 insertions(+) diff --git a/Documentation/git-repo.adoc b/Documentation/git-repo.adoc index ed7d80c690..e34abe5fea 100644 --- a/Documentation/git-repo.adoc +++ b/Documentation/git-repo.adoc @@ -119,6 +119,16 @@ values that they return: `path.gitdir.relative`:: The path to the Git repository directory relative to the current working directory. +`path.toplevel.absolute`:: + The canonical absolute path to the top-level directory of the + repository's working tree. Outputs an empty string if the repository + is bare. + +`path.toplevel.relative`:: + The path to the top-level directory of the repository's working + tree relative to the current working directory. Outputs an empty + string if the repository is bare. + `references.format`:: The reference storage format. The valid values are: + diff --git a/builtin/repo.c b/builtin/repo.c index 84e012f83f..c31e9cfa70 100644 --- a/builtin/repo.c +++ b/builtin/repo.c @@ -121,6 +121,28 @@ static int get_path_gitdir_relative(struct repository *repo, struct strbuf *buf) return 0; } +static int get_path_toplevel_absolute(struct repository *repo, struct strbuf *buf) +{ + const char *work_tree = repo_get_work_tree(repo); + + if (!work_tree) + return 0; + + format_path(buf, work_tree, "", PATH_FORMAT_CANONICAL); + return 0; +} + +static int get_path_toplevel_relative(struct repository *repo, struct strbuf *buf) +{ + const char *work_tree = repo_get_work_tree(repo); + + if (!work_tree) + return 0; + + format_path(buf, work_tree, repo->prefix, PATH_FORMAT_RELATIVE); + return 0; +} + static int get_references_format(struct repository *repo, struct strbuf *buf) { strbuf_addstr(buf, @@ -137,6 +159,8 @@ static const struct repo_info_field repo_info_field[] = { { "path.commondir.relative", get_path_commondir_relative }, { "path.gitdir.absolute", get_path_gitdir_absolute }, { "path.gitdir.relative", get_path_gitdir_relative }, + { "path.toplevel.absolute", get_path_toplevel_absolute }, + { "path.toplevel.relative", get_path_toplevel_relative }, { "references.format", get_references_format }, }; diff --git a/t/t1900-repo-info.sh b/t/t1900-repo-info.sh index c85d390f43..9417d1ab65 100755 --- a/t/t1900-repo-info.sh +++ b/t/t1900-repo-info.sh @@ -213,4 +213,39 @@ test_repo_info_path 'gitdir with explicit GIT_DIR' 'gitdir' \ '.git' \ 'GIT_DIR="../.git" && export GIT_DIR' +test_expect_success 'path.toplevel absolute and relative' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + mkdir -p repo/sub && + cd repo/sub && + + ROOT="$(test-tool path-utils real_path ..)" && + + echo "path.toplevel.absolute=$ROOT" >expect.abs && + git repo info path.toplevel.absolute >actual.abs && + test_cmp expect.abs actual.abs && + + echo "path.toplevel.relative=../" >expect.rel && + git repo info path.toplevel.relative >actual.rel && + test_cmp expect.rel actual.rel + ) +' + +test_expect_success 'path.toplevel absolute and relative in a bare repository' ' + test_when_finished "rm -rf bare.git" && + git init --bare bare.git && + ( + cd bare.git && + + echo "path.toplevel.absolute=" >expect.abs && + git repo info path.toplevel.absolute >actual.abs && + test_cmp expect.abs actual.abs && + + echo "path.toplevel.relative=" >expect.rel && + git repo info path.toplevel.relative >actual.rel && + test_cmp expect.rel actual.rel + ) +' + test_done From 81ee59205acf9194cd6e4f76b5bfa08e10b310ef Mon Sep 17 00:00:00 2001 From: K Jayatheerth Date: Sun, 27 Sep 2026 17:14:14 +0530 Subject: [PATCH 2/8] submodule: use repository to find superproject `get_superproject_working_tree()` currently uses `xgetcwd()` to determine the repository whose superproject should be checked. This is incorrect when `--git-dir` points to a repository different from the one associated with the current working directory. In that case, the current working directory may cause the function to return the wrong superproject or an empty result. Pass the `repository` to `get_superproject_working_tree()` so that the superproject is determined from the repository being inspected rather than the current working directory. Mentored-by: Justin Tobler Mentored-by: Lucas Seiki Oshiro Signed-off-by: K Jayatheerth Signed-off-by: Junio C Hamano --- builtin/rev-parse.c | 2 +- submodule.c | 43 +++++++++++++++++++------------------- submodule.h | 2 +- t/t7400-submodule-basic.sh | 19 +++++++++++++++++ 4 files changed, 43 insertions(+), 23 deletions(-) diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c index 43693454d5..e1a6da0076 100644 --- a/builtin/rev-parse.c +++ b/builtin/rev-parse.c @@ -997,7 +997,7 @@ int cmd_rev_parse(int argc, } if (!strcmp(arg, "--show-superproject-working-tree")) { struct strbuf superproject = STRBUF_INIT; - if (get_superproject_working_tree(&superproject)) + if (get_superproject_working_tree(the_repository, &superproject)) print_path(superproject.buf, prefix, format, DEFAULT_UNMODIFIED); strbuf_release(&superproject); continue; diff --git a/submodule.c b/submodule.c index 5c92575888..78856f9840 100644 --- a/submodule.c +++ b/submodule.c @@ -2610,34 +2610,35 @@ void absorb_git_dir_into_superproject(const char *path, absorb_git_dir_into_superproject_recurse(path, super_prefix); } -int get_superproject_working_tree(struct strbuf *buf) +int get_superproject_working_tree(struct repository *r, struct strbuf *buf) { struct child_process cp = CHILD_PROCESS_INIT; struct strbuf sb = STRBUF_INIT; struct strbuf one_up = STRBUF_INIT; - char *cwd = xgetcwd(); + struct strbuf target_wt = STRBUF_INIT; + const char *worktree; int ret = 0; const char *subpath; int code; ssize_t len; - if (!is_inside_work_tree(the_repository)) - /* - * FIXME: - * We might have a superproject, but it is harder - * to determine. - */ + worktree = repo_get_work_tree(r); + if (!worktree) goto out; - if (!strbuf_realpath(&one_up, "../", 0)) + if (!strbuf_realpath(&target_wt, worktree, 0)) goto out; - subpath = relative_path(cwd, one_up.buf, &sb); + strbuf_addf(&one_up, "%s/..", target_wt.buf); + if (!strbuf_realpath(&one_up, one_up.buf, 0)) + goto out; + + subpath = relative_path(target_wt.buf, one_up.buf, &sb); prepare_submodule_repo_env(&cp.env); strvec_pop(&cp.env); - strvec_pushl(&cp.args, "--literal-pathspecs", "-C", "..", + strvec_pushl(&cp.args, "--literal-pathspecs", "-C", one_up.buf, "ls-files", "-z", "--stage", "--full-name", "--", subpath, NULL); strbuf_reset(&sb); @@ -2648,14 +2649,14 @@ int get_superproject_working_tree(struct strbuf *buf) cp.git_cmd = 1; if (start_command(&cp)) - die(_("could not start ls-files in ..")); + die(_("could not start ls-files in %s"), one_up.buf); len = strbuf_read(&sb, cp.out, PATH_MAX); close(cp.out); if (starts_with(sb.buf, "160000")) { int super_sub_len; - int cwd_len = strlen(cwd); + int wt_len = target_wt.len; char *super_sub, *super_wt; /* @@ -2666,12 +2667,12 @@ int get_superproject_working_tree(struct strbuf *buf) super_sub = strchr(sb.buf, '\t') + 1; super_sub_len = strlen(super_sub); - if (super_sub_len > cwd_len || - strcmp(&cwd[cwd_len - super_sub_len], super_sub)) - BUG("returned path string doesn't match cwd?"); + if (super_sub_len > wt_len || + strcmp(&target_wt.buf[wt_len - super_sub_len], super_sub)) + BUG("returned path string doesn't match worktree?"); - super_wt = xstrdup(cwd); - super_wt[cwd_len - super_sub_len] = '\0'; + super_wt = xstrdup(target_wt.buf); + super_wt[wt_len - super_sub_len] = '\0'; strbuf_realpath(buf, super_wt, 1); ret = 1; @@ -2681,10 +2682,10 @@ int get_superproject_working_tree(struct strbuf *buf) code = finish_command(&cp); if (code == 128) - /* '../' is not a git repository */ + /* parent directory is not a git repository */ ret = 0; else if (code == 0 && len == 0) - /* There is an unrelated git repository at '../' */ + /* There is an unrelated git repository at parent directory */ ret = 0; else if (code) die(_("ls-tree returned unexpected return code %d"), code); @@ -2692,7 +2693,7 @@ int get_superproject_working_tree(struct strbuf *buf) out: strbuf_release(&sb); strbuf_release(&one_up); - free(cwd); + strbuf_release(&target_wt); return ret; } diff --git a/submodule.h b/submodule.h index b10e16e6c0..1a465a1208 100644 --- a/submodule.h +++ b/submodule.h @@ -170,6 +170,6 @@ void absorb_git_dir_into_superproject(const char *path, * project is a submodule of. If this repository is not a submodule of * another repository, return 0. */ -int get_superproject_working_tree(struct strbuf *buf); +int get_superproject_working_tree(struct repository *r, struct strbuf *buf); #endif diff --git a/t/t7400-submodule-basic.sh b/t/t7400-submodule-basic.sh index eefdecb0bd..5c3d9f2829 100755 --- a/t/t7400-submodule-basic.sh +++ b/t/t7400-submodule-basic.sh @@ -1549,4 +1549,23 @@ test_expect_success 'submodule add fails when name is reused' ' ) ' +test_expect_success 'path.superproject-root works with --git-dir' ' + test_when_finished "rm -rf sub super" && + git init sub && + test_commit -C sub initial && + git init super && + ( + cd super && + git -c protocol.file.allow=always submodule add "../sub" sub && + git commit -m "add submodule" && + + SUPER_ROOT="$(test-tool path-utils real_path .)" && + MODULE_DIR="$SUPER_ROOT/.git/modules/sub" && + + echo "path.superproject-root.absolute=$SUPER_ROOT" >expect && + git --git-dir="$MODULE_DIR" repo info path.superproject-root.absolute >actual && + test_cmp expect actual + ) +' + test_done From 86c88d9b3634affd26523edf376150369b923920 Mon Sep 17 00:00:00 2001 From: K Jayatheerth Date: Sun, 27 Sep 2026 17:14:15 +0530 Subject: [PATCH 3/8] repo: add path.superproject-root with absolute and relative suffixes Scripts working in multi-repository setups often need to identify the top-level working tree of a superproject from within a submodule. Currently, this is only exposed via `git rev-parse --show-superproject-working-tree`. Introduce `path.superproject-root.absolute` and `path.superproject-root.relative` keys to `git repo info`. This exposes the core submodule context via a scriptable config-like key using standard format rules. If requested when not inside a submodule, the command returns an empty string. Mentored-by: Justin Tobler Mentored-by: Lucas Seiki Oshiro Signed-off-by: K Jayatheerth Signed-off-by: Junio C Hamano --- Documentation/git-repo.adoc | 10 ++++++++++ builtin/repo.c | 31 +++++++++++++++++++++++++++++ t/t1900-repo-info.sh | 39 +++++++++++++++++++++++++++++++++++++ 3 files changed, 80 insertions(+) diff --git a/Documentation/git-repo.adoc b/Documentation/git-repo.adoc index e34abe5fea..e524a07f53 100644 --- a/Documentation/git-repo.adoc +++ b/Documentation/git-repo.adoc @@ -119,6 +119,16 @@ values that they return: `path.gitdir.relative`:: The path to the Git repository directory relative to the current working directory. +`path.superproject-root.absolute`:: + The canonical absolute path to the working tree root of the superproject + if the current repository is an initialized submodule. Outputs an empty + string if not in a submodule. + +`path.superproject-root.relative`:: + The path to the working tree root of the superproject relative to the + current working directory if the current repository is an initialized + submodule. Outputs an empty string if not in a submodule. + `path.toplevel.absolute`:: The canonical absolute path to the top-level directory of the repository's working tree. Outputs an empty string if the repository diff --git a/builtin/repo.c b/builtin/repo.c index c31e9cfa70..27ebb7a1c9 100644 --- a/builtin/repo.c +++ b/builtin/repo.c @@ -18,6 +18,7 @@ #include "strbuf.h" #include "string-list.h" #include "shallow.h" +#include "submodule.h" #include "tree.h" #include "tree-walk.h" #include "utf8.h" @@ -121,6 +122,34 @@ static int get_path_gitdir_relative(struct repository *repo, struct strbuf *buf) return 0; } +static int get_path_superproject_absolute(struct repository *repo, struct strbuf *buf) +{ + struct strbuf superproject = STRBUF_INIT; + + if (!get_superproject_working_tree(repo, &superproject)) { + strbuf_release(&superproject); + return 0; + } + + format_path(buf, superproject.buf, "", PATH_FORMAT_CANONICAL); + strbuf_release(&superproject); + return 0; +} + +static int get_path_superproject_relative(struct repository *repo, struct strbuf *buf) +{ + struct strbuf superproject = STRBUF_INIT; + + if (!get_superproject_working_tree(repo, &superproject)) { + strbuf_release(&superproject); + return 0; + } + + format_path(buf, superproject.buf, repo->prefix, PATH_FORMAT_RELATIVE); + strbuf_release(&superproject); + return 0; +} + static int get_path_toplevel_absolute(struct repository *repo, struct strbuf *buf) { const char *work_tree = repo_get_work_tree(repo); @@ -159,6 +188,8 @@ static const struct repo_info_field repo_info_field[] = { { "path.commondir.relative", get_path_commondir_relative }, { "path.gitdir.absolute", get_path_gitdir_absolute }, { "path.gitdir.relative", get_path_gitdir_relative }, + { "path.superproject-root.absolute", get_path_superproject_absolute }, + { "path.superproject-root.relative", get_path_superproject_relative }, { "path.toplevel.absolute", get_path_toplevel_absolute }, { "path.toplevel.relative", get_path_toplevel_relative }, { "references.format", get_references_format }, diff --git a/t/t1900-repo-info.sh b/t/t1900-repo-info.sh index 9417d1ab65..eec576a1d9 100755 --- a/t/t1900-repo-info.sh +++ b/t/t1900-repo-info.sh @@ -213,6 +213,45 @@ test_repo_info_path 'gitdir with explicit GIT_DIR' 'gitdir' \ '.git' \ 'GIT_DIR="../.git" && export GIT_DIR' +test_expect_success 'path.superproject-root absolute and relative' ' + test_when_finished "rm -rf sub super" && + git init sub && + test_commit -C sub initial && + git init super && + ( + cd super && + git -c protocol.file.allow=always submodule add "../sub" sub && + git commit -m "add submodule" && + + cd sub && + ROOT="$(test-tool path-utils real_path ..)" && + + echo "path.superproject-root.absolute=$ROOT" >expect.abs && + git repo info path.superproject-root.absolute >actual.abs && + test_cmp expect.abs actual.abs && + + echo "path.superproject-root.relative=../" >expect.rel && + git repo info path.superproject-root.relative >actual.rel && + test_cmp expect.rel actual.rel + ) +' + +test_expect_success 'path.superproject-root returns empty when not in a submodule' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + + echo "path.superproject-root.absolute=" >expect.abs && + git repo info path.superproject-root.absolute >actual.abs && + test_cmp expect.abs actual.abs && + + echo "path.superproject-root.relative=" >expect.rel && + git repo info path.superproject-root.relative >actual.rel && + test_cmp expect.rel actual.rel + ) +' + test_expect_success 'path.toplevel absolute and relative' ' test_when_finished "rm -rf repo" && git init repo && From ea076162e54ccb7c26e695d347a6c041db77ff2b Mon Sep 17 00:00:00 2001 From: K Jayatheerth Date: Sun, 27 Sep 2026 17:14:16 +0530 Subject: [PATCH 4/8] repo: add path.hooks with absolute and relative suffixes Hooks are an integral part of a repository's configuration and are commonly used by tooling to automate repository-specific workflows. Currently, scripts typically retrieve the hooks directory by invoking `git rev-parse --git-path hooks`. Introduce `path.hooks.absolute` and `path.hooks.relative` keys to `git repo info`. This exposes the hooks directory as a scriptable config-like key using standard format rules, allowing scripts to retrieve it through the same interface as other repository path information. Mentored-by: Justin Tobler Mentored-by: Lucas Seiki Oshiro Signed-off-by: K Jayatheerth Signed-off-by: Junio C Hamano --- Documentation/git-repo.adoc | 9 +++++++++ builtin/repo.c | 22 ++++++++++++++++++++++ t/t1900-repo-info.sh | 28 ++++++++++++++++++++++++++-- 3 files changed, 57 insertions(+), 2 deletions(-) diff --git a/Documentation/git-repo.adoc b/Documentation/git-repo.adoc index e524a07f53..20836cf8f6 100644 --- a/Documentation/git-repo.adoc +++ b/Documentation/git-repo.adoc @@ -119,6 +119,15 @@ values that they return: `path.gitdir.relative`:: The path to the Git repository directory relative to the current working directory. +`path.hooks.absolute`:: + The canonical absolute path to the repository's hooks directory. + Respects the `core.hooksPath` configuration. If `core.hooksPath` is + set to `/dev/null`, that value is returned unchanged. + +`path.hooks.relative`:: + The path to the repository's hooks directory relative to the current + working directory. Respects the `core.hooksPath` configuration. + `path.superproject-root.absolute`:: The canonical absolute path to the working tree root of the superproject if the current repository is an initialized submodule. Outputs an empty diff --git a/builtin/repo.c b/builtin/repo.c index 27ebb7a1c9..01666dc17c 100644 --- a/builtin/repo.c +++ b/builtin/repo.c @@ -122,6 +122,26 @@ static int get_path_gitdir_relative(struct repository *repo, struct strbuf *buf) return 0; } +static int get_path_hooks_absolute(struct repository *repo, struct strbuf *buf) +{ + struct strbuf hooks_path = STRBUF_INIT; + + repo_git_path_replace(repo, &hooks_path, "hooks"); + format_path(buf, hooks_path.buf, "", PATH_FORMAT_CANONICAL); + strbuf_release(&hooks_path); + return 0; +} + +static int get_path_hooks_relative(struct repository *repo, struct strbuf *buf) +{ + struct strbuf hooks_path = STRBUF_INIT; + + repo_git_path_replace(repo, &hooks_path, "hooks"); + format_path(buf, hooks_path.buf, repo->prefix, PATH_FORMAT_RELATIVE); + strbuf_release(&hooks_path); + return 0; +} + static int get_path_superproject_absolute(struct repository *repo, struct strbuf *buf) { struct strbuf superproject = STRBUF_INIT; @@ -188,6 +208,8 @@ static const struct repo_info_field repo_info_field[] = { { "path.commondir.relative", get_path_commondir_relative }, { "path.gitdir.absolute", get_path_gitdir_absolute }, { "path.gitdir.relative", get_path_gitdir_relative }, + { "path.hooks.absolute", get_path_hooks_absolute }, + { "path.hooks.relative", get_path_hooks_relative }, { "path.superproject-root.absolute", get_path_superproject_absolute }, { "path.superproject-root.relative", get_path_superproject_relative }, { "path.toplevel.absolute", get_path_toplevel_absolute }, diff --git a/t/t1900-repo-info.sh b/t/t1900-repo-info.sh index eec576a1d9..1da5db4942 100755 --- a/t/t1900-repo-info.sh +++ b/t/t1900-repo-info.sh @@ -174,7 +174,11 @@ test_repo_info_path () { cd repo/sub && ROOT="$(test-tool path-utils real_path ..)" && export ROOT && eval "$init_command" && - echo "path.$field_name.absolute=$ROOT/$expected_dir" >expect && + case "$expected_dir" in + /*) EXPECT_ABS="$expected_dir" ;; + *) EXPECT_ABS="$ROOT/$expected_dir" ;; + esac && + echo "path.$field_name.absolute=$EXPECT_ABS" >expect && git repo info "path.$field_name.absolute" >actual && test_cmp expect actual ) @@ -188,7 +192,11 @@ test_repo_info_path () { cd repo/sub && ROOT="$(test-tool path-utils real_path ..)" && export ROOT && eval "$init_command" && - echo "path.$field_name.relative=../$expected_dir" >expect && + case "$expected_dir" in + /*) EXPECT_REL="$(test-tool path-utils relative_path "$expected_dir" "$PWD")" ;; + *) EXPECT_REL="../$expected_dir" ;; + esac && + echo "path.$field_name.relative=$EXPECT_REL" >expect && git repo info "path.$field_name.relative" >actual && test_cmp expect actual ) @@ -213,6 +221,22 @@ test_repo_info_path 'gitdir with explicit GIT_DIR' 'gitdir' \ '.git' \ 'GIT_DIR="../.git" && export GIT_DIR' +test_repo_info_path 'hooks standard' 'hooks' '.git/hooks' + +test_repo_info_path 'hooks with core.hooksPath override' 'hooks' \ + 'custom-hooks' \ + 'git config core.hooksPath "$ROOT/custom-hooks" && mkdir -p "$ROOT/custom-hooks"' + +# /dev/null is not a real, canonicalizable filesystem path on Windows, +# so path resolution for core.hooksPath=/dev/null cannot be expected to +# produce a literal "/dev/null" the way it does on POSIX systems. +if ! test_have_prereq MINGW +then + test_repo_info_path 'hooks with core.hooksPath=/dev/null' 'hooks' \ + '/dev/null' \ + 'git config core.hooksPath /dev/null' +fi + test_expect_success 'path.superproject-root absolute and relative' ' test_when_finished "rm -rf sub super" && git init sub && From f8ca76b3df6b1a802ba37cf2ddb50cb7ea349b49 Mon Sep 17 00:00:00 2001 From: K Jayatheerth Date: Sun, 27 Sep 2026 17:14:17 +0530 Subject: [PATCH 5/8] repo: add path.index with absolute and relative suffixes The repository index is a fundamental component used by Git and related tooling to track the working tree state. Scripts that interact with the index currently retrieve its location by invoking `git rev-parse --git-path index`. Introduce `path.index.absolute` and `path.index.relative` keys to `git repo info`. This exposes the index file location as a scriptable config-like key using standard format rules, allowing scripts to retrieve it through the same interface as other repository path information. Mentored-by: Justin Tobler Mentored-by: Lucas Seiki Oshiro Signed-off-by: K Jayatheerth Signed-off-by: Junio C Hamano --- Documentation/git-repo.adoc | 12 ++++++++++++ builtin/repo.c | 18 ++++++++++++++++++ t/t1900-repo-info.sh | 23 +++++++++++++++++++++++ 3 files changed, 53 insertions(+) diff --git a/Documentation/git-repo.adoc b/Documentation/git-repo.adoc index 20836cf8f6..08ef47750c 100644 --- a/Documentation/git-repo.adoc +++ b/Documentation/git-repo.adoc @@ -128,6 +128,18 @@ values that they return: The path to the repository's hooks directory relative to the current working directory. Respects the `core.hooksPath` configuration. +`path.index.absolute`:: + The canonical absolute path to the repository's current index file. + Respects the `GIT_INDEX_FILE` environment override. Returns the + configured index path even if the repository is bare or the file does + not exist. + +`path.index.relative`:: + The path to the repository's current index file relative to the current + working directory. Respects the `GIT_INDEX_FILE` environment override. + Returns the configured index path even if the repository is bare or the + file does not exist. + `path.superproject-root.absolute`:: The canonical absolute path to the working tree root of the superproject if the current repository is an initialized submodule. Outputs an empty diff --git a/builtin/repo.c b/builtin/repo.c index 01666dc17c..a4ea2022eb 100644 --- a/builtin/repo.c +++ b/builtin/repo.c @@ -142,6 +142,22 @@ static int get_path_hooks_relative(struct repository *repo, struct strbuf *buf) return 0; } +static int get_path_index_absolute(struct repository *repo, struct strbuf *buf) +{ + const char *index_file = repo_get_index_file(repo); + + format_path(buf, index_file, "", PATH_FORMAT_CANONICAL); + return 0; +} + +static int get_path_index_relative(struct repository *repo, struct strbuf *buf) +{ + const char *index_file = repo_get_index_file(repo); + + format_path(buf, index_file, repo->prefix, PATH_FORMAT_RELATIVE); + return 0; +} + static int get_path_superproject_absolute(struct repository *repo, struct strbuf *buf) { struct strbuf superproject = STRBUF_INIT; @@ -210,6 +226,8 @@ static const struct repo_info_field repo_info_field[] = { { "path.gitdir.relative", get_path_gitdir_relative }, { "path.hooks.absolute", get_path_hooks_absolute }, { "path.hooks.relative", get_path_hooks_relative }, + { "path.index.absolute", get_path_index_absolute }, + { "path.index.relative", get_path_index_relative }, { "path.superproject-root.absolute", get_path_superproject_absolute }, { "path.superproject-root.relative", get_path_superproject_relative }, { "path.toplevel.absolute", get_path_toplevel_absolute }, diff --git a/t/t1900-repo-info.sh b/t/t1900-repo-info.sh index 1da5db4942..431a4842d4 100755 --- a/t/t1900-repo-info.sh +++ b/t/t1900-repo-info.sh @@ -237,6 +237,29 @@ then 'git config core.hooksPath /dev/null' fi +test_repo_info_path 'index standard' 'index' '.git/index' + +test_repo_info_path 'index with GIT_INDEX_FILE override' 'index' \ + 'custom-index-file' \ + 'GIT_INDEX_FILE="$ROOT/custom-index-file" && export GIT_INDEX_FILE' + +test_expect_success 'path.index in a bare repository returns default index location' ' + test_when_finished "rm -rf bare.git" && + git init --bare bare.git && + ( + cd bare.git && + ROOT="$(test-tool path-utils real_path .)" && + + echo "path.index.absolute=$ROOT/index" >expect.abs && + git repo info path.index.absolute >actual.abs && + test_cmp expect.abs actual.abs && + + echo "path.index.relative=index" >expect.rel && + git repo info path.index.relative >actual.rel && + test_cmp expect.rel actual.rel + ) +' + test_expect_success 'path.superproject-root absolute and relative' ' test_when_finished "rm -rf sub super" && git init sub && From d7a45a67472709dc462f428dc3baa4c126f3967f Mon Sep 17 00:00:00 2001 From: K Jayatheerth Date: Sun, 27 Sep 2026 17:14:18 +0530 Subject: [PATCH 6/8] repo: add path.grafts with absolute and relative suffixes The repository grafts file specifies alternate parent relationships for commits and may be used by repository tooling that needs to inspect or manage grafts. Scripts currently retrieve its location by invoking `git rev-parse --git-path info/grafts`. Introduce `path.grafts.absolute` and `path.grafts.relative` keys to `git repo info`. This exposes the grafts file location as a scriptable config-like key using standard format rules, allowing scripts to retrieve it through the same interface as other repository path information. Mentored-by: Justin Tobler Mentored-by: Lucas Seiki Oshiro Signed-off-by: K Jayatheerth Signed-off-by: Junio C Hamano --- Documentation/git-repo.adoc | 11 +++++++++++ builtin/repo.c | 24 ++++++++++++++++++++++++ t/t1900-repo-info.sh | 6 ++++++ 3 files changed, 41 insertions(+) diff --git a/Documentation/git-repo.adoc b/Documentation/git-repo.adoc index 08ef47750c..868ab0ed9f 100644 --- a/Documentation/git-repo.adoc +++ b/Documentation/git-repo.adoc @@ -119,6 +119,17 @@ values that they return: `path.gitdir.relative`:: The path to the Git repository directory relative to the current working directory. +`path.grafts.absolute`:: + The canonical absolute path to the repository's graft file. + Respects the `GIT_GRAFT_FILE` environment override. The path is + returned regardless of whether the file currently exists on disk. + +`path.grafts.relative`:: + The path to the repository's graft file relative to the current + working directory. Respects the `GIT_GRAFT_FILE` environment + override. The path is returned regardless of whether the file + currently exists on disk. + `path.hooks.absolute`:: The canonical absolute path to the repository's hooks directory. Respects the `core.hooksPath` configuration. If `core.hooksPath` is diff --git a/builtin/repo.c b/builtin/repo.c index a4ea2022eb..8144b74361 100644 --- a/builtin/repo.c +++ b/builtin/repo.c @@ -122,6 +122,28 @@ static int get_path_gitdir_relative(struct repository *repo, struct strbuf *buf) return 0; } +static int get_path_grafts_absolute(struct repository *repo, struct strbuf *buf) +{ + const char *graft_file = repo_get_graft_file(repo); + + if (!graft_file) + return error(_("unable to get graft file")); + + format_path(buf, graft_file, "", PATH_FORMAT_CANONICAL); + return 0; +} + +static int get_path_grafts_relative(struct repository *repo, struct strbuf *buf) +{ + const char *graft_file = repo_get_graft_file(repo); + + if (!graft_file) + return error(_("unable to get graft file")); + + format_path(buf, graft_file, repo->prefix, PATH_FORMAT_RELATIVE); + return 0; +} + static int get_path_hooks_absolute(struct repository *repo, struct strbuf *buf) { struct strbuf hooks_path = STRBUF_INIT; @@ -224,6 +246,8 @@ static const struct repo_info_field repo_info_field[] = { { "path.commondir.relative", get_path_commondir_relative }, { "path.gitdir.absolute", get_path_gitdir_absolute }, { "path.gitdir.relative", get_path_gitdir_relative }, + { "path.grafts.absolute", get_path_grafts_absolute }, + { "path.grafts.relative", get_path_grafts_relative }, { "path.hooks.absolute", get_path_hooks_absolute }, { "path.hooks.relative", get_path_hooks_relative }, { "path.index.absolute", get_path_index_absolute }, diff --git a/t/t1900-repo-info.sh b/t/t1900-repo-info.sh index 431a4842d4..adc4a92487 100755 --- a/t/t1900-repo-info.sh +++ b/t/t1900-repo-info.sh @@ -221,6 +221,12 @@ test_repo_info_path 'gitdir with explicit GIT_DIR' 'gitdir' \ '.git' \ 'GIT_DIR="../.git" && export GIT_DIR' +test_repo_info_path 'grafts standard' 'grafts' '.git/info/grafts' + +test_repo_info_path 'grafts with GIT_GRAFT_FILE override' 'grafts' \ + 'custom-graft-file' \ + 'GIT_GRAFT_FILE="$ROOT/custom-graft-file" && export GIT_GRAFT_FILE' + test_repo_info_path 'hooks standard' 'hooks' '.git/hooks' test_repo_info_path 'hooks with core.hooksPath override' 'hooks' \ From 42235267e4871257e2ac92c37b7052ce62d7b097 Mon Sep 17 00:00:00 2001 From: K Jayatheerth Date: Sun, 27 Sep 2026 17:14:19 +0530 Subject: [PATCH 7/8] repo: add path.git-prefix Scripts sometimes need the path from the repository's working tree root to the current working directory. While this information can be derived through existing Git commands, `git repo info` does not currently expose it as a scriptable key. Introduce the `path.git-prefix` key to `git repo info`. The key returns the path from the working tree root to the current working directory, returning the empty string when invoked from the working tree root. Mentored-by: Justin Tobler Mentored-by: Lucas Seiki Oshiro Signed-off-by: K Jayatheerth Signed-off-by: Junio C Hamano --- Documentation/git-repo.adoc | 5 +++++ builtin/repo.c | 11 +++++++++++ t/t1900-repo-info.sh | 23 +++++++++++++++++++++++ 3 files changed, 39 insertions(+) diff --git a/Documentation/git-repo.adoc b/Documentation/git-repo.adoc index 868ab0ed9f..fb5aceae8f 100644 --- a/Documentation/git-repo.adoc +++ b/Documentation/git-repo.adoc @@ -113,6 +113,11 @@ values that they return: The path to the Git repository's common directory relative to the current working directory. +`path.git-prefix`:: + The path from the root of the working tree to the current working + directory. Returns the empty string when the current working directory + is the root of the working tree. + `path.gitdir.absolute`:: The canonical absolute path to the Git repository directory (the `.git` directory). diff --git a/builtin/repo.c b/builtin/repo.c index 8144b74361..ce78cdc44e 100644 --- a/builtin/repo.c +++ b/builtin/repo.c @@ -100,6 +100,16 @@ static int get_path_commondir_relative(struct repository *repo, struct strbuf *b return 0; } +static int get_path_git_prefix(struct repository *repo, struct strbuf *buf) +{ + /* + * repo->prefix is NULL when the current working directory is + * the worktree root. + */ + strbuf_addstr(buf, repo->prefix ? repo->prefix : ""); + return 0; +} + static int get_path_gitdir_absolute(struct repository *repo, struct strbuf *buf) { const char *git_dir = repo_get_git_dir(repo); @@ -244,6 +254,7 @@ static const struct repo_info_field repo_info_field[] = { { "object.format", get_object_format }, { "path.commondir.absolute", get_path_commondir_absolute }, { "path.commondir.relative", get_path_commondir_relative }, + { "path.git-prefix", get_path_git_prefix }, { "path.gitdir.absolute", get_path_gitdir_absolute }, { "path.gitdir.relative", get_path_gitdir_relative }, { "path.grafts.absolute", get_path_grafts_absolute }, diff --git a/t/t1900-repo-info.sh b/t/t1900-repo-info.sh index adc4a92487..b689445b7a 100755 --- a/t/t1900-repo-info.sh +++ b/t/t1900-repo-info.sh @@ -215,6 +215,29 @@ test_repo_info_path 'commondir with only GIT_DIR' 'commondir' \ '.git' \ 'GIT_DIR="../.git" && export GIT_DIR' +test_expect_success 'path.git-prefix at repository root' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + echo "path.git-prefix=" >expect && + git repo info path.git-prefix >actual && + test_cmp expect actual + ) +' + +test_expect_success 'path.git-prefix in subdirectory' ' + test_when_finished "rm -rf repo" && + git init repo && + mkdir -p repo/sub/dir && + ( + cd repo/sub/dir && + echo "path.git-prefix=sub/dir/" >expect && + git repo info path.git-prefix >actual && + test_cmp expect actual + ) +' + test_repo_info_path 'gitdir standard' 'gitdir' '.git' test_repo_info_path 'gitdir with explicit GIT_DIR' 'gitdir' \ From 2db363c17a96b54c23eae00b1e763bc41c8e83d1 Mon Sep 17 00:00:00 2001 From: K Jayatheerth Date: Sun, 27 Sep 2026 17:14:20 +0530 Subject: [PATCH 8/8] repo: add path.cdup Scripts sometimes need the relative path from the current working directory to the repository's working tree root (cdup). While this information can be retrieved through `git rev-parse --show-cdup`, `git repo info` does not currently expose it as a scriptable key. Introduce the `path.cdup` key to `git repo info`. The key returns the path from the current working directory to the root of the working tree, returning the empty string when invoked from the working tree root. Mentored-by: Justin Tobler Mentored-by: Lucas Seiki Oshiro Signed-off-by: K Jayatheerth Signed-off-by: Junio C Hamano --- Documentation/git-repo.adoc | 5 +++++ builtin/repo.c | 23 +++++++++++++++++++++ t/t1900-repo-info.sh | 40 +++++++++++++++++++++++++++++++++++++ 3 files changed, 68 insertions(+) diff --git a/Documentation/git-repo.adoc b/Documentation/git-repo.adoc index fb5aceae8f..7e14aac244 100644 --- a/Documentation/git-repo.adoc +++ b/Documentation/git-repo.adoc @@ -104,6 +104,11 @@ values that they return: `object.format`:: The object format (hash algorithm) used in the repository. +`path.cdup`:: + When the command is invoked from a subdirectory, show the + path of the top-level directory relative to the current + directory (typically a sequence of "../", or an empty string). + `path.commondir.absolute`:: The canonical absolute path to the Git repository's common directory (the shared `.git` directory containing objects, diff --git a/builtin/repo.c b/builtin/repo.c index ce78cdc44e..d4083abedc 100644 --- a/builtin/repo.c +++ b/builtin/repo.c @@ -78,6 +78,28 @@ static int get_object_format(struct repository *repo, struct strbuf *buf) return 0; } +static int get_path_cdup(struct repository *repo, struct strbuf *buf) +{ + const char *pfx = repo->prefix; + + if (!is_inside_work_tree(repo)) { + const char *worktree = repo_get_work_tree(repo); + + if (worktree) { + strbuf_addstr(buf, worktree); + } + } + + while (pfx) { + pfx = strchr(pfx, '/'); + if (pfx) { + pfx++; + strbuf_addstr(buf, "../"); + } + } + return 0; +} + static int get_path_commondir_absolute(struct repository *repo, struct strbuf *buf) { const char *common_dir = repo_get_common_dir(repo); @@ -252,6 +274,7 @@ static const struct repo_info_field repo_info_field[] = { { "layout.bare", get_layout_bare }, { "layout.shallow", get_layout_shallow }, { "object.format", get_object_format }, + { "path.cdup", get_path_cdup }, { "path.commondir.absolute", get_path_commondir_absolute }, { "path.commondir.relative", get_path_commondir_relative }, { "path.git-prefix", get_path_git_prefix }, diff --git a/t/t1900-repo-info.sh b/t/t1900-repo-info.sh index b689445b7a..c600074c24 100755 --- a/t/t1900-repo-info.sh +++ b/t/t1900-repo-info.sh @@ -215,6 +215,46 @@ test_repo_info_path 'commondir with only GIT_DIR' 'commondir' \ '.git' \ 'GIT_DIR="../.git" && export GIT_DIR' +test_expect_success 'path.cdup at repository root' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + echo "path.cdup=" >expect && + git repo info path.cdup >actual && + test_cmp expect actual + ) +' + +test_expect_success 'path.cdup in subdirectory' ' + test_when_finished "rm -rf repo" && + git init repo && + mkdir -p repo/sub/dir && + ( + cd repo/sub/dir && + echo "path.cdup=../../" >expect && + git repo info path.cdup >actual && + test_cmp expect actual + ) +' + +test_expect_success 'path.cdup cwd outside the working tree' ' + test_when_finished "rm -rf repo" && + mkdir -p repo/tmp/x && + cd repo && + git init test && + ( + echo path.cdup=$(pwd)/tmp/x >./test/expect && + cd test && + GIT_WORK_TREE=../tmp/x && + export GIT_WORK_TREE && + GIT_DIR=$(pwd)/.git && + export GIT_DIR && + git repo info path.cdup >actual && + test_cmp expect actual + ) +' + test_expect_success 'path.git-prefix at repository root' ' test_when_finished "rm -rf repo" && git init repo &&