From: K Jayatheerth Date: Sun, 27 Sep 2026 11:44:12 GMT Subject: [GSoC Patch v7 0/8] add more path keys to git repo info Message-ID: <20260927114420.59724-1-jayatheerthkulkarni2005@gmail.com> In-Reply-To: <20260716012138.6714-1-jayatheerthkulkarni2005@gmail.com> Series adds keys to git repo info. Keys output paths of repository components: * path.toplevel: repository tree. * path.superproject-root: superproject tree from submodules. * path.hooks: repository hooks. * path.index: repository index. * path.grafts: repository grafts. * path.git-prefix: prefix offset. * path.cdup: relative path to top level from subdirectory. Keys support suffixes for format. Commits contain documentation and tests. fix a superproject bug where it now takes repo parameter instead of just depending on cwd. changes since v6: * The superproject bug fix is now a commit of its own. * The dead code in index is removed, we simply rely on the helper itself and not write any new conditions. * cdup now works even when the worktree is outside the current working directory. K Jayatheerth (8): repo: add path.toplevel with absolute and relative suffix formatting submodule: use repository to find superproject repo: add path.superproject-root with absolute and relative suffixes repo: add path.hooks with absolute and relative suffixes repo: add path.index with absolute and relative suffixes repo: add path.grafts with absolute and relative suffixes repo: add path.git-prefix repo: add path.cdup Documentation/git-repo.adoc | 62 ++++++++++++ builtin/repo.c | 153 ++++++++++++++++++++++++++++ builtin/rev-parse.c | 2 +- submodule.c | 43 ++++---- submodule.h | 2 +- t/t1900-repo-info.sh | 194 +++++++++++++++++++++++++++++++++++- t/t7400-submodule-basic.sh | 19 ++++ 7 files changed, 450 insertions(+), 25 deletions(-) Range-diff against v6: -: ---------- > 1: 27b9811163 repo: add path.toplevel with absolute and relative suffix formatting 1: d4f3253256 ! 2: 7c90603a1b repo: add path.superproject-root with absolute and relative suffixes @@ Metadata Author: K Jayatheerth ## Commit message ## - repo: add path.superproject-root with absolute and relative suffixes + submodule: use repository to find superproject - 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`. + `get_superproject_working_tree()` currently uses `xgetcwd()` to + determine the repository whose superproject should be checked. - 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. + 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. - If requested when not inside a submodule, the command returns an empty - string. + 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 - ## Documentation/git-repo.adoc ## -@@ Documentation/git-repo.adoc: 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 - - ## builtin/repo.c ## -@@ - #include "strbuf.h" - #include "string-list.h" - #include "shallow.h" -+#include "submodule.h" - #include "tree.h" - #include "tree-walk.h" - #include "utf8.h" -@@ builtin/repo.c: 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); -@@ builtin/repo.c: 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 }, - ## builtin/rev-parse.c ## @@ builtin/rev-parse.c: int cmd_rev_parse(int argc, } @@ submodule.h: void absorb_git_dir_into_superproject(const char *path, #endif - ## t/t1900-repo-info.sh ## -@@ t/t1900-repo-info.sh: 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 && -@@ t/t1900-repo-info.sh: test_expect_success 'path.toplevel absolute and relative in a bare repository' ' + ## t/t7400-submodule-basic.sh ## +@@ t/t7400-submodule-basic.sh: test_expect_success 'submodule add fails when name is reused' ' ) ' @@ t/t1900-repo-info.sh: test_expect_success 'path.toplevel absolute and relative i + test_cmp expect actual + ) +' ++ test_done 3: 1860d19d01 ! 3: 1bfb0c8c1d repo: add path.index with absolute and relative suffixes @@ Metadata Author: K Jayatheerth ## Commit message ## - repo: add path.index with absolute and relative suffixes + repo: add path.superproject-root 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`. + 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.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. + 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 @@ Commit message ## Documentation/git-repo.adoc ## @@ Documentation/git-repo.adoc: values that they return: - The path to the repository's hooks directory relative to the current - working directory. Respects the `core.hooksPath` configuration. + `path.gitdir.relative`:: + The path to the Git repository directory relative to the current working directory. -+`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.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.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.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.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 + `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 ## builtin/repo.c ## -@@ builtin/repo.c: static int get_path_hooks_relative(struct repository *repo, struct strbuf *buf) +@@ + #include "strbuf.h" + #include "string-list.h" + #include "shallow.h" ++#include "submodule.h" + #include "tree.h" + #include "tree-walk.h" + #include "utf8.h" +@@ builtin/repo.c: static int get_path_gitdir_relative(struct repository *repo, struct strbuf *buf) return 0; } -+static int get_path_index_absolute(struct repository *repo, struct strbuf *buf) ++static int get_path_superproject_absolute(struct repository *repo, struct strbuf *buf) +{ -+ const char *index_file = repo_get_index_file(repo); ++ struct strbuf superproject = STRBUF_INIT; + -+ if (!index_file) -+ return error(_("unable to get index file")); ++ if (!get_superproject_working_tree(repo, &superproject)) { ++ strbuf_release(&superproject); ++ return 0; ++ } + -+ format_path(buf, index_file, "", PATH_FORMAT_CANONICAL); ++ format_path(buf, superproject.buf, "", PATH_FORMAT_CANONICAL); ++ strbuf_release(&superproject); + return 0; +} + -+static int get_path_index_relative(struct repository *repo, struct strbuf *buf) ++static int get_path_superproject_relative(struct repository *repo, struct strbuf *buf) +{ -+ const char *index_file = repo_get_index_file(repo); ++ struct strbuf superproject = STRBUF_INIT; + -+ if (!index_file) -+ return error(_("unable to get index file")); ++ if (!get_superproject_working_tree(repo, &superproject)) { ++ strbuf_release(&superproject); ++ return 0; ++ } + -+ format_path(buf, index_file, repo->prefix, PATH_FORMAT_RELATIVE); ++ format_path(buf, superproject.buf, repo->prefix, PATH_FORMAT_RELATIVE); ++ strbuf_release(&superproject); + return 0; +} + - static int get_path_superproject_absolute(struct repository *repo, struct strbuf *buf) + static int get_path_toplevel_absolute(struct repository *repo, struct strbuf *buf) { - struct strbuf superproject = STRBUF_INIT; + const char *work_tree = repo_get_work_tree(repo); @@ builtin/repo.c: 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.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.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 }, ## t/t1900-repo-info.sh ## -@@ t/t1900-repo-info.sh: then - 'git config core.hooksPath /dev/null' - fi +@@ t/t1900-repo-info.sh: test_repo_info_path 'gitdir with explicit GIT_DIR' 'gitdir' \ + '.git' \ + 'GIT_DIR="../.git" && export GIT_DIR' -+test_repo_info_path 'index standard' 'index' '.git/index' ++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 && + -+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' ++ 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.index in a bare repository returns default index location' ' -+ test_when_finished "rm -rf bare.git" && -+ git init --bare bare.git && ++test_expect_success 'path.superproject-root returns empty when not in a submodule' ' ++ test_when_finished "rm -rf repo" && ++ git init repo && + ( -+ cd bare.git && -+ ROOT="$(test-tool path-utils real_path .)" && ++ cd repo && + -+ echo "path.index.absolute=$ROOT/index" >expect.abs && -+ git repo info path.index.absolute >actual.abs && ++ echo "path.superproject-root.absolute=" >expect.abs && ++ git repo info path.superproject-root.absolute >actual.abs && + test_cmp expect.abs actual.abs && + -+ echo "path.index.relative=index" >expect.rel && -+ git repo info path.index.relative >actual.rel && ++ 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 absolute and relative' ' - test_when_finished "rm -rf sub super" && - git init sub && + test_expect_success 'path.toplevel absolute and relative' ' + test_when_finished "rm -rf repo" && + git init repo && 2: dcbaf1cb96 = 4: 09d2937fcd repo: add path.hooks with absolute and relative suffixes -: ---------- > 5: d0c186f150 repo: add path.index with absolute and relative suffixes 4: ced5b0cb8b = 6: 351b4b7de7 repo: add path.grafts with absolute and relative suffixes 5: ac766e49e3 = 7: ef69d4aa8c repo: add path.git-prefix 6: 0a0c3dd924 ! 8: 3e61728dba repo: add path.cdup @@ Documentation/git-repo.adoc: values that they return: The object format (hash algorithm) used in the repository. +`path.cdup`:: -+ The path to the root of the working tree relative to the current -+ working directory. Returns the empty string when the current -+ working directory is the root of the working tree. ++ 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 @@ builtin/repo.c: static int get_object_format(struct repository *repo, struct str +{ + 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) { @@ t/t1900-repo-info.sh: test_repo_info_path 'commondir with only GIT_DIR' 'commond + 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" && -- 2.56.0-rc2