Merge branch 'kj/repo-info-more-path-keys' into seen

The 'git repo info' command has been taught more keys to output
paths of various repository components (such as the working tree
root, superproject working tree, object database, etc.), supporting
both absolute and relative path formats.

* kj/repo-info-more-path-keys:
  repo: add path.git-prefix path key
  repo: add path.grafts with absolute and relative suffix formatting
  repo: add path.index with absolute and relative suffix formatting
  repo: add path.hooks with absolute and relative suffix formatting
  repo: add path.objects with absolute and relative suffix formatting
  repo: add path.superproject-working-tree with absolute and relative suffixes
  repo: add path.toplevel with absolute and relative suffix formatting
This commit is contained in:
Junio C Hamano
2026-08-03 09:32:08 -07:00
3 changed files with 355 additions and 0 deletions

View File

@@ -113,12 +113,76 @@ values that they return:
The path to the Git repository's common directory relative to
the current working directory.
`path.git-prefix`::
The relative path from the top-level directory of the working tree to
the current working directory (including a trailing slash). Outputs an
empty string if executed at the root of the working tree.
`path.gitdir.absolute`::
The canonical absolute path to the Git repository directory (the `.git` directory).
`path.gitdir.relative`::
The path to the Git repository directory relative to the current working directory.
`path.grafts.absolute`::
The canonical absolute path to the repository grafts 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 grafts 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 `core.hooksPath` configuration adjustments.
`path.hooks.relative`::
The path to the repository's hooks directory relative to the current
working directory. Respects `core.hooksPath` configuration adjustments.
`path.index.absolute`::
The canonical absolute path to the repository's current index file.
Respects the `GIT_INDEX_FILE` environment override. The returned path
reflects the configured/default index location regardless of whether the
repository is bare or whether the file currently exists.
`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.
The returned path reflects the configured/default index location regardless
of whether the repository is bare or whether the file currently exists.
`path.objects.absolute`::
The canonical absolute path to the repository's object database directory.
Respects the `GIT_OBJECT_DIRECTORY` environment override.
`path.objects.relative`::
The path to the repository's object database directory relative to the
current working directory. Respects the `GIT_OBJECT_DIRECTORY`
environment override.
`path.superproject-working-tree.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-working-tree.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
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:
+

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"
@@ -99,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 if we are at the working tree root.
* We add an empty string to ensure the buffer is cleanly initialized.
*/
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);
@@ -121,6 +132,148 @@ 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, repo->prefix, 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;
repo_git_path_replace(repo, &hooks_path, "hooks");
format_path(buf, hooks_path.buf, repo->prefix, 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_index_absolute(struct repository *repo, struct strbuf *buf)
{
const char *index_file = repo_get_index_file(repo);
if (!index_file)
return error(_("unable to get index file"));
format_path(buf, index_file, repo->prefix, 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);
if (!index_file)
return error(_("unable to get index file"));
format_path(buf, index_file, repo->prefix, PATH_FORMAT_RELATIVE);
return 0;
}
static int get_path_objects_absolute(struct repository *repo, struct strbuf *buf)
{
const char *obj_dir = repo_get_object_directory(repo);
if (!obj_dir)
return error(_("unable to get object directory"));
format_path(buf, obj_dir, repo->prefix, PATH_FORMAT_CANONICAL);
return 0;
}
static int get_path_objects_relative(struct repository *repo, struct strbuf *buf)
{
const char *obj_dir = repo_get_object_directory(repo);
if (!obj_dir)
return error(_("unable to get object directory"));
format_path(buf, obj_dir, repo->prefix, PATH_FORMAT_RELATIVE);
return 0;
}
static int get_path_superproject_absolute(struct repository *repo, struct strbuf *buf)
{
struct strbuf superproject = STRBUF_INIT;
if (!get_superproject_working_tree(&superproject)) {
strbuf_release(&superproject);
strbuf_addstr(buf, "");
return 0;
}
format_path(buf, superproject.buf, repo->prefix, 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(&superproject)) {
strbuf_release(&superproject);
strbuf_addstr(buf, "");
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);
if (!work_tree) {
strbuf_addstr(buf, "");
return 0;
}
format_path(buf, work_tree, repo->prefix, 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) {
strbuf_addstr(buf, "");
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,
@@ -135,8 +288,21 @@ 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 },
{ "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 },
{ "path.index.relative", get_path_index_relative },
{ "path.objects.absolute", get_path_objects_absolute },
{ "path.objects.relative", get_path_objects_relative },
{ "path.superproject-working-tree.absolute", get_path_superproject_absolute },
{ "path.superproject-working-tree.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

@@ -207,10 +207,135 @@ test_repo_info_path 'commondir with only GIT_DIR' 'commondir' \
'.git' \
'GIT_DIR="../.git" && export GIT_DIR'
test_expect_success 'path.git-prefix at root and in a subdirectory' '
test_when_finished "rm -rf repo" &&
git init repo &&
(
cd repo &&
echo "path.git-prefix=" >expect.root &&
git repo info path.git-prefix >actual.root &&
test_cmp expect.root actual.root &&
mkdir -p sub/dir &&
cd sub/dir &&
echo "path.git-prefix=sub/dir/" >expect.sub &&
git repo info path.git-prefix >actual.sub &&
test_cmp expect.sub actual.sub
)
'
test_repo_info_path 'gitdir standard' 'gitdir' '.git'
test_repo_info_path 'gitdir with explicit GIT_DIR' 'gitdir' \
'.git' \
'GIT_DIR="../.git" && export GIT_DIR'
test_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 fallback' '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"'
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_repo_info_path 'objects standard' 'objects' '.git/objects'
test_repo_info_path 'objects with GIT_OBJECT_DIRECTORY override' 'objects' \
'custom-objects' \
'GIT_OBJECT_DIRECTORY="$ROOT/custom-objects" && export GIT_OBJECT_DIRECTORY &&
mkdir -p "$ROOT/custom-objects"'
test_expect_success 'path.superproject-working-tree 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-working-tree.absolute=$ROOT" >expect.abs &&
git repo info path.superproject-working-tree.absolute >actual.abs &&
test_cmp expect.abs actual.abs &&
echo "path.superproject-working-tree.relative=../" >expect.rel &&
git repo info path.superproject-working-tree.relative >actual.rel &&
test_cmp expect.rel actual.rel
)
'
test_expect_success 'path.superproject-working-tree returns empty when not in a submodule' '
test_when_finished "rm -rf repo" &&
git init repo &&
(
cd repo &&
echo "path.superproject-working-tree.absolute=" >expect &&
git repo info path.superproject-working-tree.absolute >actual &&
test_cmp expect actual
)
'
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 returns empty in a bare repository' '
test_when_finished "rm -rf bare.git" &&
git init --bare bare.git &&
(
cd bare.git &&
echo "path.toplevel.absolute=" >expect &&
git repo info path.toplevel.absolute >actual &&
test_cmp expect actual
)
'
test_done