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 <jltobler@gmail.com>
Mentored-by: Lucas Seiki Oshiro <lucasseikioshiro@gmail.com>
Signed-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
seen
K Jayatheerth 2026-09-11 20:15:14 +05:30 committed by Junio C Hamano
parent 998c3eacc3
commit 2f09f13d73
6 changed files with 122 additions and 23 deletions

View File

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

View File

@ -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 },

View File

@ -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;

View File

@ -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;
}


View File

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

View File

@ -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 &&
@ -248,4 +287,22 @@ test_expect_success 'path.toplevel absolute and relative in a bare repository' '
)
'

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