repo: extend info path reporting and structure statistics
cover repo: extend info path reporting and structure statistics
This series improves git repo info and git repo structure.
For git repo info, it:
* removes reliance on global repository state in this command path
* supports category keys (e.g. layout, path)
* adds rev-parse-like path keys and git-path-derived keys
* adds --path-format=(absolute|relative) for path output control
For git repo structure, it adds deeper repository metrics inspired by git-sizer:
* max inflated and max disk size per object type
* max commit parent count
* max tree entry count
* max blob path length/depth
* max annotated tag chain depth
* aggregate keyvalue/nul totals and maxima
Tests and docs are updated accordingly.
Validation:
* t/t1900-repo.sh
* t/t1901-repo-structure.sh
* full make -j4 test in Docker (failed 0)
Eslam reda ragheb (2):
t1900,t1901: make repo tests hash-agnostic and wc-portable t1900,t1901: fix test portability issues
eslam-reda-div (1):
repo: extend info paths and structure statistics
Documentation/git-repo.adoc | 67 ++++- builtin/repo.c | 525 ++++++++++++++++++++++++++++++++++-- t/t1900-repo.sh | 196 ++++++++++++++ t/t1901-repo-structure.sh | 250 ++++++++++++----- 4 files changed, 948 insertions(+), 90 deletions(-)
base-commit: 7c02d39fc2ed2702223c7674f73150d9a7e61ba4
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2208%2Feslam-reda-div%2Fgsoc-contribute-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2208/eslam-reda-div/gsoc-contribute-v1
Pull-Request: https://github.com/git/git/pull/2208
Signature · 1 line
gitgitgadget
PATCH 1/3 repo: extend info paths and structure statistics
From: eslam-reda-div <[email protected]>
Improve git repo info by adding path-oriented keys that match values users currently obtain from git rev-parse, including common directory, git directory, top-level, superproject working tree, and additional git-path based locations.
Teach git repo info to accept category keys like layout and path, and add --path-format=(absolute|relative) so scripts can request the desired path style explicitly. The command now uses repository context passed to the command path instead of relying on global state.
Extend git repo structure with deeper repository metrics inspired by git-sizer, including per-type maximum inflated and on-disk object sizes, maximum commit parent count, maximum tree entry count, longest/deepest blob path, and deepest annotated tag chain.
Update documentation and tests to cover new keys, formats, and metrics.
--- Documentation/git-repo.adoc | 67 ++++- builtin/repo.c | 525 ++++++++++++++++++++++++++++++++++-- t/t1900-repo.sh | 196 ++++++++++++++ t/t1901-repo-structure.sh | 250 ++++++++++++----- 4 files changed, 948 insertions(+), 90 deletions(-)
Documentation/git-repo.adoc +63 -4
@@ -8,7 +8,7 @@ git-repo - Retrieve information about the repository SYNOPSIS -------- [synopsis] -git repo info [--format=(keyvalue|nul) | -z] [--all | <key>...] +git repo info [--format=(keyvalue|nul) | -z] [--path-format=(absolute|relative)] [--all | <key>...] git repo structure [--format=(table|keyvalue|nul) | -z] DESCRIPTION
@@ -44,6 +44,11 @@ supported: + `-z` is an alias for `--format=nul`. +`--path-format=(absolute|relative)`::: + Controls formatting for keys in the `path` category. The default is + `absolute`. This option may be specified multiple times; the last one + specified takes effect. + `structure [--format=(table|keyvalue|nul) | -z]`:: Retrieve statistics about the current repository structure. The following kinds of information are reported:
@@ -52,6 +57,12 @@ supported: * Reachable object counts categorized by type * Total inflated size of reachable objects by type * Total disk size of reachable objects by type +* Largest inflated reachable object size by type +* Largest disk size of a reachable object by type +* Largest parent count among reachable commits +* Largest entry count among reachable trees +* Longest and deepest path among reachable blobs +* Deepest annotated tag chain + The output format can be chosen through the flag `--format`. Three formats are supported:
@@ -64,6 +75,7 @@ supported: `keyvalue`::: Each line of output contains a key-value pair for a repository stat. The '=' character is used to delimit between the key and the value. + Both aggregate metrics and per-type metrics are included. Values containing "unusual" characters are quoted as explained for the configuration variable `core.quotePath` (see linkgit:git-config[1]).
@@ -78,9 +90,11 @@ supported: INFO KEYS --------- -In order to obtain a set of values from `git repo info`, you should provide -the keys that identify them. Here's a list of the available keys and the -values that they return: +In order to obtain values from `git repo info`, provide either individual keys +or category names. A category returns all keys within that category. For +example, `layout` returns both `layout.bare` and `layout.shallow`. + +Here's a list of the available keys and the values that they return: `layout.bare`:: `true` if this is a bare repository, otherwise `false`.
@@ -91,6 +105,51 @@ values that they return: `object.format`:: The object format (hash algorithm) used in the repository. +`path.common-dir`:: + The path to the common git directory. + +`path.config-file`:: + The path to the `config` file in the git directory. + +`path.git-dir`:: + The path to the git directory. + +`path.git-prefix`:: + The path of the current working directory relative to the top-level + directory. + +`path.grafts-file`:: + The path to the `info/grafts` file. + +`path.hooks-directory`:: + The path to the `hooks` directory. + +`path.index-file`:: + The path to the index file. + +`path.logs-directory`:: + The path to the `logs` directory. + +`path.objects-directory`:: + The path to the objects directory. + +`path.packed-refs-file`:: + The path to the `packed-refs` file. + +`path.refs-directory`:: + The path to the `refs` directory. + +`path.shallow-file`:: + The path to the `shallow` file. + +`path.superproject-working-tree`:: + The path to the superproject's working tree root, or an empty string + when the repository is not used as a submodule. + +`path.toplevel`:: + The path to the top-level working tree directory, or an empty string + for bare repositories. + `references.format`:: The reference storage format. The valid values are: +
builtin/repo.c +502 -23
716 lines not on this page · raw message
t/t1900-repo.sh +196 -0
211 lines not on this page · raw message
t/t1901-repo-structure.sh +187 -63
287 lines not on this page · raw message
Signature · 1 line
gitgitgadget
Re: [PATCH 1/3] repo: extend info paths and structure statistics
Quoted 1 line
and additional git-path based locations.
This statement is too vague. Each one of the covered key-value pairs needs to be mentioned and discussed about why they are being added.
Quoted 4 lines
Extend git repo structure with deeper repository metrics inspired by git-sizer, including per-type maximum inflated and on-disk object sizes, maximum commit parent count, maximum tree entry count, longest/deepest blob path, and deepest annotated tag chain.
This patch is doing too much. It needs to be broken, at least, into 20 smaller patches, one per git-repo-info and git-repo-structure key.
Re: [PATCH 1/3] repo: extend info paths and structure statistics
In reply to eslam-reda-div via GitGitGadgetQuoted 12 lines · eslam-reda-div via GitGitGadget
From: eslam-reda-div <[email protected]>
Improve git repo info by adding path-oriented keys that match values users currently obtain from git rev-parse, including common directory, git directory, top-level, superproject working tree, and additional git-path based locations.
Teach git repo info to accept category keys like layout and path, and add --path-format=(absolute|relative) so scripts can request the desired path style explicitly. The command now uses repository context passed to the command path instead of relying on global state.
Quoted 4 lines
Extend git repo structure with deeper repository metrics inspired by git-sizer, including per-type maximum inflated and on-disk object sizes, maximum commit parent count, maximum tree entry count, longest/deepest blob path, and deepest annotated tag chain.
Hello,
Just FYI, there is already a series on the list I'm currently working on that is extending the "structure" output for git-repo [1] similar to what is being done here. This series collects largest inflated object info, max commit parents, and max tree entries. It does look like this series is collecting a couple of other data points too, but maybe we could do so it a separate followup series to the one I'm working on? :)
Thanks,
-Justin
PATCH 2/3 t1900,t1901: make repo tests hash-agnostic and wc-portable
In reply to eslam reda via GitGitGadgetFrom: Eslam reda ragheb <[email protected]>
--- t/t1900-repo.sh | 4 ++-- t/t1901-repo-structure.sh | 26 +++++++++++++------------- 2 files changed, 15 insertions(+), 15 deletions(-)
t/t1900-repo.sh +2 -2
@@ -131,8 +131,8 @@ test_expect_success 'category key returns all matching keys' ' ' test_expect_success 'mixed key/category requests preserve request order' ' - cat >expect <<-\EOF && - object.format=sha1 + cat >expect <<-EOF && + object.format=$(test_oid algo) layout.bare=false layout.shallow=false EOF
t/t1901-repo-structure.sh +13 -13
@@ -150,10 +150,10 @@ tree_max_entry_count() { --filter=object:type=tree --filter-provided-objects | cut -d" " -f1) do entries=$(git cat-file -p "$oid" | wc -l) || return 1 - test "$entries" -gt "$max" && max=$entries + test $entries -gt $max && max=$entries done - echo "$max" + echo $max } blob_max_path_length() {
@@ -228,20 +228,20 @@ test_expect_success SHA1 'keyvalue and nul format' ' cat >expect <<-EOF && references.count=$(reference_count_total) - references.branches.count=1 - references.tags.count=1 - references.remotes.count=0 + references.branches.count=$(git for-each-ref --format="%(refname)" refs/heads | sed -n "\$=") + references.tags.count=$(git for-each-ref --format="%(refname)" refs/tags | sed -n "\$=") + references.remotes.count=$(git for-each-ref --format="%(refname)" refs/remotes | sed -n "\$=") references.others.count=0 objects.count=$(object_count_total) - objects.commits.count=42 - objects.trees.count=42 - objects.blobs.count=42 - objects.tags.count=1 + objects.commits.count=$(object_type_count commit) + objects.trees.count=$(object_type_count tree) + objects.blobs.count=$(object_type_count blob) + objects.tags.count=$(object_type_count tag) objects.inflated_size=$(object_total_inflated_size) - objects.commits.inflated_size=9225 - objects.trees.inflated_size=28554 - objects.blobs.inflated_size=453 - objects.tags.inflated_size=132 + objects.commits.inflated_size=$(object_type_total_inflated_size commit) + objects.trees.inflated_size=$(object_type_total_inflated_size tree) + objects.blobs.inflated_size=$(object_type_total_inflated_size blob) + objects.tags.inflated_size=$(object_type_total_inflated_size tag) objects.max_inflated_size=$(object_max_inflated_size) objects.commits.max_inflated_size=$(object_type_max_inflated_size commit) objects.trees.max_inflated_size=$(object_type_max_inflated_size tree)
Signature · 1 line
gitgitgadget
Re: [PATCH 2/3] t1900,t1901: make repo tests hash-agnostic and wc-portable
Quoted 4 lines
From: Eslam reda ragheb <[email protected]>
Signed-off-by Eslam reda ragheb <[email protected]>
Missing patch message.
Quoted 5 lines
test_expect_success'mixed key/category requests preserve request order' '
- cat >expect <<-\EOF &&
- object.format=sha1
+ cat >expect <<-EOF&&
+ object.format=$(test_oid algo)
Don't send patches fixing the code you have just added. Instead, rewrite the history applying the fix to the original patch.
PATCH 3/3 t1900,t1901: fix test portability issues
In reply to eslam reda via GitGitGadgetFrom: Eslam reda ragheb <[email protected]>
--- t/t1901-repo-structure.sh | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-)
t/t1901-repo-structure.sh +11 -11
@@ -228,20 +228,20 @@ test_expect_success SHA1 'keyvalue and nul format' ' cat >expect <<-EOF && references.count=$(reference_count_total) - references.branches.count=$(git for-each-ref --format="%(refname)" refs/heads | sed -n "\$=") - references.tags.count=$(git for-each-ref --format="%(refname)" refs/tags | sed -n "\$=") - references.remotes.count=$(git for-each-ref --format="%(refname)" refs/remotes | sed -n "\$=") + references.branches.count=1 + references.tags.count=1 + references.remotes.count=0 references.others.count=0 objects.count=$(object_count_total) - objects.commits.count=$(object_type_count commit) - objects.trees.count=$(object_type_count tree) - objects.blobs.count=$(object_type_count blob) - objects.tags.count=$(object_type_count tag) + objects.commits.count=42 + objects.trees.count=42 + objects.blobs.count=42 + objects.tags.count=1 objects.inflated_size=$(object_total_inflated_size) - objects.commits.inflated_size=$(object_type_total_inflated_size commit) - objects.trees.inflated_size=$(object_type_total_inflated_size tree) - objects.blobs.inflated_size=$(object_type_total_inflated_size blob) - objects.tags.inflated_size=$(object_type_total_inflated_size tag) + objects.commits.inflated_size=9225 + objects.trees.inflated_size=28554 + objects.blobs.inflated_size=453 + objects.tags.inflated_size=132 objects.max_inflated_size=$(object_max_inflated_size) objects.commits.max_inflated_size=$(object_type_max_inflated_size commit) objects.trees.max_inflated_size=$(object_type_max_inflated_size tree)
Signature · 1 line
gitgitgadget
Quoted 6 lines · eslam reda via GitGitGadget
Eslam reda ragheb (2):
t1900,t1901: make repo tests hash-agnostic and wc-portable t1900,t1901: fix test portability issueseslam-reda-div (1):
repo: extend info paths and structure statistics
I do not think you meant to send a patch seriees with these two same but different looking folks claiming authorship.
I'll let the main change 1/3 reviewed and commented on those who are more invested into "git repo", but both [2/3] and [3/3] need to sell themselves with a more meaningful proposed log messages.
I think the test updates in [2/3] are all good things to do---they make the test more robust against changes in the hash algorithm used, what the history left by the previous test steps exactly have, etc., by avoiding hardcoded constants in the expectation. And in light of that, I do not think [3/3] is a good change, especially without explanation of why we may want to use hardcoded numbres.
Thanks.
v2 cover repo: extend info path reporting and structure statistics
In reply to eslam reda via GitGitGadgetThis series improves git repo info and git repo structure with a
================================================================
cleanly split commit history and explicit documentation/tests.
For git repo info, this series:
* introduces explicit command context plumbing (instead of ad-hoc global reliance in this codepath),
* adds category-key expansion (for example, requesting layout expands to layout.* keys),
* adds path-oriented keys (path.*) that expose repository locations,
* adds --path-format=(absolute|relative) to control path rendering.
For git repo structure, this series adds richer metrics:
* maximum inflated object size (overall + per type),
* maximum on-disk object size (overall + per type),
* maximum commit parent count,
* maximum tree entry count,
* maximum blob path length and path depth,
* maximum annotated tag chain depth,
* aggregate keyvalue/nul totals (references.count, objects.count, objects.inflated_size, objects.disk_size).
Tests and documentation are updated accordingly.
----------------------------------------------------------------------------
Why this change
===============
The intent is to make git repo more script-friendly and more useful for repository diagnostics:
* repo info becomes easier to query programmatically (category keys, explicit path formatting).
* repo structure becomes more actionable by exposing outlier-focused metrics (maxima), not only totals.
* keyvalue/nul output now includes aggregate totals so scripts do not need to recompute them externally.
----------------------------------------------------------------------------
Commit structure (v2 rewrite)
=============================
This iteration rewrites history into smaller logical steps (no “fix-on-fix”):
1. repo: teach info context and category keys
2. repo: add path keys to repo info
3. repo: add --path-format for info path output
4. repo: add structure max object size metrics
5. repo: add structure topology and path-depth metrics
6. repo: add aggregate structure totals to keyvalue output
7. t1900: cover repo info path keys and path-format
8. t1901: extend structure metric coverage and portability
9. docs: describe repo info path keys and structure metrics
All commits are signed off using real-name identity.
----------------------------------------------------------------------------
Changes since v1
================
* Rewrote series into smaller logical commits.
* Folded fixes into proper history (no trailing fix-up patches).
* Unified author/sign-off identity to real name.
* Addressed portability concerns raised in review:
* hash-algorithm-sensitive expectations are handled robustly,
* BSD/macOS wc whitespace behavior is handled in tests.
* Improved test robustness around keyvalue/nul expectations.
* Expanded docs to explicitly describe new keys/metrics and behavior.
* Revalidated in Docker with focused and full test runs.
----------------------------------------------------------------------------
Validation
==========
Focused:
* t1900-repo.sh
* GIT_TEST_DEFAULT_REF_FORMAT=reftable t/t1901-repo-structure.sh
Full:
* make -C t -j4 test in clean Docker environment
* Result: failed 0
----------------------------------------------------------------------------
Eslam reda ragheb (9):
repo: teach info context and category keys
repo: add path keys to repo info
repo: add --path-format for info path output
repo: add structure max object size metrics
repo: add structure topology and path-depth metrics
repo: add aggregate structure totals to keyvalue output
t1900: cover repo info path keys and path-format
t1901: extend structure metric coverage and portability
docs: describe repo info path keys and structure metrics
Documentation/git-repo.adoc | 67 ++++- builtin/repo.c | 518 ++++++++++++++++++++++++++++++++++-- t/t1900-repo.sh | 196 ++++++++++++++ t/t1901-repo-structure.sh | 250 ++++++++++++----- 4 files changed, 946 insertions(+), 85 deletions(-)
base-commit: 7c02d39fc2ed2702223c7674f73150d9a7e61ba4
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2208%2Feslam-reda-div%2Fgsoc-contribute-v2
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2208/eslam-reda-div/gsoc-contribute-v2
Pull-Request: https://github.com/git/git/pull/2208
Range-diff vs v1:
-: ---------- > 1: 99c8058298 repo: teach info context and category keys
-: ---------- > 2: 6d5b9ff075 repo: add path keys to repo info
-: ---------- > 3: 5c438d045b repo: add --path-format for info path output
-: ---------- > 4: 504d9cf7a0 repo: add structure max object size metrics
-: ---------- > 5: 4b502925c9 repo: add structure topology and path-depth metrics
-: ---------- > 6: 1751181950 repo: add aggregate structure totals to keyvalue output
-: ---------- > 7: fd18f28db0 t1900: cover repo info path keys and path-format
1: 9f2b3a46a4 ! 8: 0525ed4cd9 repo: extend info paths and structure statistics
@@
## Metadata ##
-Author: eslam-reda-div <[email protected]>
+Author: Eslam reda ragheb <[email protected]>
## Commit message ##
- repo: extend info paths and structure statistics
+ t1901: extend structure metric coverage and portability
- Improve git repo info by adding path-oriented keys that match values
- users currently obtain from git rev-parse, including common directory,
- git directory, top-level, superproject working tree, and additional
- git-path based locations.
+ Expand t1901 to cover additional structure metrics emitted by git
+ repo structure, including maxima and aggregate keyvalue/nul
+ checks.
- Teach git repo info to accept category keys like layout and path,
- and add --path-format=(absolute|relative) so scripts can request the
- desired path style explicitly. The command now uses repository context
- passed to the command path instead of relying on global state.
+ The test now validates both human-oriented table content and
+ machine-readable fields for the extended metric set.
- Extend git repo structure with deeper repository metrics inspired by
- git-sizer, including per-type maximum inflated and on-disk object sizes,
- maximum commit parent count, maximum tree entry count, longest/deepest
- blob path, and deepest annotated tag chain.
+ Also make expectations more portable across hash algorithms and
+ platforms by avoiding brittle assumptions.
- Update documentation and tests to cover new keys, formats, and metrics.
+ This includes wc output quirks on BSD/macOS and hash-format-
+ sensitive expectations.
- Signed-off-by: eslam-reda-div <[email protected]>
-
- ## Documentation/git-repo.adoc ##
-@@ Documentation/git-repo.adoc: git-repo - Retrieve information about the repository
- SYNOPSIS
- --------
- [synopsis]
--git repo info [--format=(keyvalue|nul) | -z] [--all | <key>...]
-+git repo info [--format=(keyvalue|nul) | -z] [--path-format=(absolute|relative)] [--all | <key>...]
- git repo structure [--format=(table|keyvalue|nul) | -z]
-
- DESCRIPTION
-@@ Documentation/git-repo.adoc: supported:
- +
- `-z` is an alias for `--format=nul`.
-
-+`--path-format=(absolute|relative)`:::
-+ Controls formatting for keys in the `path` category. The default is
-+ `absolute`. This option may be specified multiple times; the last one
-+ specified takes effect.
-+
- `structure [--format=(table|keyvalue|nul) | -z]`::
- Retrieve statistics about the current repository structure. The
- following kinds of information are reported:
-@@ Documentation/git-repo.adoc: supported:
- * Reachable object counts categorized by type
- * Total inflated size of reachable objects by type
- * Total disk size of reachable objects by type
-+* Largest inflated reachable object size by type
-+* Largest disk size of a reachable object by type
-+* Largest parent count among reachable commits
-+* Largest entry count among reachable trees
-+* Longest and deepest path among reachable blobs
-+* Deepest annotated tag chain
- +
- The output format can be chosen through the flag `--format`. Three formats are
- supported:
-@@ Documentation/git-repo.adoc: supported:
- `keyvalue`:::
- Each line of output contains a key-value pair for a repository stat.
- The '=' character is used to delimit between the key and the value.
-+ Both aggregate metrics and per-type metrics are included.
- Values containing "unusual" characters are quoted as explained for the
- configuration variable `core.quotePath` (see linkgit:git-config[1]).
-
-@@ Documentation/git-repo.adoc: supported:
-
- INFO KEYS
- ---------
--In order to obtain a set of values from `git repo info`, you should provide
--the keys that identify them. Here's a list of the available keys and the
--values that they return:
-+In order to obtain values from `git repo info`, provide either individual keys
-+or category names. A category returns all keys within that category. For
-+example, `layout` returns both `layout.bare` and `layout.shallow`.
-+
-+Here's a list of the available keys and the values that they return:
-
- `layout.bare`::
- `true` if this is a bare repository, otherwise `false`.
-@@ Documentation/git-repo.adoc: values that they return:
- `object.format`::
- The object format (hash algorithm) used in the repository.
-
-+`path.common-dir`::
-+ The path to the common git directory.
-+
-+`path.config-file`::
-+ The path to the `config` file in the git directory.
-+
-+`path.git-dir`::
-+ The path to the git directory.
-+
-+`path.git-prefix`::
-+ The path of the current working directory relative to the top-level
-+ directory.
-+
-+`path.grafts-file`::
-+ The path to the `info/grafts` file.
-+
-+`path.hooks-directory`::
-+ The path to the `hooks` directory.
-+
-+`path.index-file`::
-+ The path to the index file.
-+
-+`path.logs-directory`::
-+ The path to the `logs` directory.
-+
-+`path.objects-directory`::
-+ The path to the objects directory.
-+
-+`path.packed-refs-file`::
-+ The path to the `packed-refs` file.
-+
-+`path.refs-directory`::
-+ The path to the `refs` directory.
-+
-+`path.shallow-file`::
-+ The path to the `shallow` file.
-+
-+`path.superproject-working-tree`::
-+ The path to the superproject's working tree root, or an empty string
-+ when the repository is not used as a submodule.
-+
-+`path.toplevel`::
-+ The path to the top-level working tree directory, or an empty string
-+ for bare repositories.
-+
- `references.format`::
- The reference storage format. The valid values are:
- +
-
- ## builtin/repo.c ##
-@@
--#define USE_THE_REPOSITORY_VARIABLE
--
- #include "builtin.h"
--#include "environment.h"
-+#include "abspath.h"
- #include "hex.h"
- #include "odb.h"
- #include "parse-options.h"
-+#include "path.h"
- #include "path-walk.h"
- #include "progress.h"
- #include "quote.h"
-@@
- #include "revision.h"
- #include "strbuf.h"
- #include "string-list.h"
-+#include "submodule.h"
- #include "shallow.h"
-+#include "tree-walk.h"
- #include "utf8.h"
-
- static const char *const repo_usage[] = {
-- "git repo info [--format=(keyvalue|nul) | -z] [--all | <key>...]",
-+ "git repo info [--format=(keyvalue|nul) | -z] [--path-format=(absolute|relative)] [--all | <key>...]",
- "git repo structure [--format=(table|keyvalue|nul) | -z]",
- NULL
- };
-
--typedef int get_value_fn(struct repository *repo, struct strbuf *buf);
-+enum path_format {
-+ PATH_FORMAT_ABSOLUTE,
-+ PATH_FORMAT_RELATIVE,
-+};
-+
-+struct repo_info {
-+ struct repository *repo;
-+ const char *prefix;
-+ enum path_format path_format;
-+};
-+
-+typedef int get_value_fn(struct repo_info *info, struct strbuf *buf);
-
- enum output_format {
- FORMAT_TABLE,
-@@ builtin/repo.c: struct field {
- get_value_fn *get_value;
- };
-
--static int get_layout_bare(struct repository *repo UNUSED, struct strbuf *buf)
-+static void repo_info_add_path(struct repo_info *info,
-+ struct strbuf *buf,
-+ const char *path)
- {
-- strbuf_addstr(buf, is_bare_repository() ? "true" : "false");
-+ if (info->path_format == PATH_FORMAT_RELATIVE) {
-+ char *cwd = xgetcwd();
-+ struct strbuf rel_path = STRBUF_INIT;
-+
-+ strbuf_addstr(buf, relative_path(path, cwd, &rel_path));
-+ strbuf_release(&rel_path);
-+ free(cwd);
-+ return;
-+ }
-+
-+ strbuf_add_absolute_path(buf, path);
-+}
-+
-+static int get_layout_bare(struct repo_info *info, struct strbuf *buf)
-+{
-+ struct repository *repo = info->repo;
-+ strbuf_addstr(buf, repo_get_work_tree(repo) ? "false" : "true");
- return 0;
- }
-
--static int get_layout_shallow(struct repository *repo, struct strbuf *buf)
-+static int get_layout_shallow(struct repo_info *info, struct strbuf *buf)
- {
-+ struct repository *repo = info->repo;
- strbuf_addstr(buf,
- is_repository_shallow(repo) ? "true" : "false");
- return 0;
- }
-
--static int get_object_format(struct repository *repo, struct strbuf *buf)
-+static int get_object_format(struct repo_info *info, struct strbuf *buf)
- {
-+ struct repository *repo = info->repo;
- strbuf_addstr(buf, repo->hash_algo->name);
- return 0;
- }
-
--static int get_references_format(struct repository *repo, struct strbuf *buf)
-+static int get_path_common_dir(struct repo_info *info, struct strbuf *buf)
-+{
-+ repo_info_add_path(info, buf, repo_get_common_dir(info->repo));
-+ return 0;
-+}
-+
-+static int get_path_config_file(struct repo_info *info, struct strbuf *buf)
-+{
-+ struct strbuf path = STRBUF_INIT;
-+
-+ repo_info_add_path(info, buf, repo_git_path_replace(info->repo, &path, "config"));
-+ strbuf_release(&path);
-+ return 0;
-+}
-+
-+static int get_path_git_dir(struct repo_info *info, struct strbuf *buf)
-+{
-+ repo_info_add_path(info, buf, repo_get_git_dir(info->repo));
-+ return 0;
-+}
-+
-+static int get_path_git_prefix(struct repo_info *info, struct strbuf *buf)
-+{
-+ if (info->prefix)
-+ strbuf_addstr(buf, info->prefix);
-+ return 0;
-+}
-+
-+static int get_path_grafts_file(struct repo_info *info, struct strbuf *buf)
-+{
-+ repo_info_add_path(info, buf, repo_get_graft_file(info->repo));
-+ return 0;
-+}
-+
-+static int get_path_hooks_directory(struct repo_info *info, struct strbuf *buf)
-+{
-+ struct strbuf path = STRBUF_INIT;
-+
-+ repo_info_add_path(info, buf, repo_git_path_replace(info->repo, &path, "hooks"));
-+ strbuf_release(&path);
-+ return 0;
-+}
-+
-+static int get_path_index_file(struct repo_info *info, struct strbuf *buf)
-+{
-+ repo_info_add_path(info, buf, repo_get_index_file(info->repo));
-+ return 0;
-+}
-+
-+static int get_path_logs_directory(struct repo_info *info, struct strbuf *buf)
-+{
-+ struct strbuf path = STRBUF_INIT;
-+
-+ repo_info_add_path(info, buf, repo_git_path_replace(info->repo, &path, "logs"));
-+ strbuf_release(&path);
-+ return 0;
-+}
-+
-+static int get_path_objects_directory(struct repo_info *info, struct strbuf *buf)
-+{
-+ repo_info_add_path(info, buf, repo_get_object_directory(info->repo));
-+ return 0;
-+}
-+
-+static int get_path_packed_refs_file(struct repo_info *info, struct strbuf *buf)
-+{
-+ struct strbuf path = STRBUF_INIT;
-+
-+ repo_info_add_path(info, buf, repo_git_path_replace(info->repo, &path, "packed-refs"));
-+ strbuf_release(&path);
-+ return 0;
-+}
-+
-+static int get_path_refs_directory(struct repo_info *info, struct strbuf *buf)
-+{
-+ struct strbuf path = STRBUF_INIT;
-+
-+ repo_info_add_path(info, buf, repo_git_path_replace(info->repo, &path, "refs"));
-+ strbuf_release(&path);
-+ return 0;
-+}
-+
-+static int get_path_shallow_file(struct repo_info *info, struct strbuf *buf)
-+{
-+ struct strbuf path = STRBUF_INIT;
-+
-+ repo_info_add_path(info, buf, repo_git_path_replace(info->repo, &path, "shallow"));
-+ strbuf_release(&path);
-+ return 0;
-+}
-+
-+static int get_path_superproject_working_tree(struct repo_info *info,
-+ struct strbuf *buf)
-+{
-+ struct strbuf superproject = STRBUF_INIT;
-+
-+ if (get_superproject_working_tree(&superproject))
-+ repo_info_add_path(info, buf, superproject.buf);
-+
-+ strbuf_release(&superproject);
-+ return 0;
-+}
-+
-+static int get_path_toplevel(struct repo_info *info, struct strbuf *buf)
-+{
-+ const char *work_tree = repo_get_work_tree(info->repo);
-+
-+ if (work_tree)
-+ repo_info_add_path(info, buf, work_tree);
-+
-+ return 0;
-+}
-+
-+static int get_references_format(struct repo_info *info, struct strbuf *buf)
- {
-+ struct repository *repo = info->repo;
- strbuf_addstr(buf,
- ref_storage_format_to_name(repo->ref_storage_format));
- return 0;
-@@ builtin/repo.c: static const struct field repo_info_fields[] = {
- { "layout.bare", get_layout_bare },
- { "layout.shallow", get_layout_shallow },
- { "object.format", get_object_format },
-+ { "path.common-dir", get_path_common_dir },
-+ { "path.config-file", get_path_config_file },
-+ { "path.git-dir", get_path_git_dir },
-+ { "path.git-prefix", get_path_git_prefix },
-+ { "path.grafts-file", get_path_grafts_file },
-+ { "path.hooks-directory", get_path_hooks_directory },
-+ { "path.index-file", get_path_index_file },
-+ { "path.logs-directory", get_path_logs_directory },
-+ { "path.objects-directory", get_path_objects_directory },
-+ { "path.packed-refs-file", get_path_packed_refs_file },
-+ { "path.refs-directory", get_path_refs_directory },
-+ { "path.shallow-file", get_path_shallow_file },
-+ { "path.superproject-working-tree", get_path_superproject_working_tree },
-+ { "path.toplevel", get_path_toplevel },
- { "references.format", get_references_format },
- };
-
-@@ builtin/repo.c: static get_value_fn *get_value_fn_for_key(const char *key)
- return found ? found->get_value : NULL;
- }
-
-+static void print_field(enum output_format format, const char *key,
-+ const char *value);
-+
-+static int print_category_fields(const char *category,
-+ struct repo_info *info,
-+ enum output_format format,
-+ struct strbuf *valbuf)
-+{
-+ int found = 0;
-+ size_t category_len = strlen(category);
-+
-+ for (size_t i = 0; i < ARRAY_SIZE(repo_info_fields); i++) {
-+ const struct field *field = &repo_info_fields[i];
-+
-+ if (!starts_with(field->key, category) ||
-+ field->key[category_len] != '.')
-+ continue;
-+
-+ strbuf_reset(valbuf);
-+ field->get_value(info, valbuf);
-+ print_field(format, field->key, valbuf->buf);
-+ found = 1;
-+ }
-+
-+ return found;
-+}
-+
- static void print_field(enum output_format format, const char *key,
- const char *value)
- {
-@@ builtin/repo.c: static void print_field(enum output_format format, const char *key,
- }
-
- static int print_fields(int argc, const char **argv,
-- struct repository *repo,
-+ struct repo_info *info,
- enum output_format format)
- {
- int ret = 0;
-@@ builtin/repo.c: static int print_fields(int argc, const char **argv,
-
- get_value = get_value_fn_for_key(key);
-
-- if (!get_value) {
-- ret = error(_("key '%s' not found"), key);
-+ if (get_value) {
-+ strbuf_reset(&valbuf);
-+ get_value(info, &valbuf);
-+ print_field(format, key, valbuf.buf);
- continue;
- }
-
-- strbuf_reset(&valbuf);
-- get_value(repo, &valbuf);
-- print_field(format, key, valbuf.buf);
-+ if (!print_category_fields(key, info, format, &valbuf))
-+ ret = error(_("key '%s' not found"), key);
- }
-
- strbuf_release(&valbuf);
- return ret;
- }
-
--static int print_all_fields(struct repository *repo,
-+static int print_all_fields(struct repo_info *info,
- enum output_format format)
- {
- struct strbuf valbuf = STRBUF_INIT;
-@@ builtin/repo.c: static int print_all_fields(struct repository *repo,
- const struct field *field = &repo_info_fields[i];
-
- strbuf_reset(&valbuf);
-- field->get_value(repo, &valbuf);
-+ field->get_value(info, &valbuf);
- print_field(format, field->key, valbuf.buf);
- }
-
-@@ builtin/repo.c: static int parse_format_cb(const struct option *opt,
- return 0;
- }
-
-+static int parse_path_format_cb(const struct option *opt,
-+ const char *arg, int unset UNUSED)
-+{
-+ enum path_format *path_format = opt->value;
-+
-+ if (!strcmp(arg, "absolute"))
-+ *path_format = PATH_FORMAT_ABSOLUTE;
-+ else if (!strcmp(arg, "relative"))
-+ *path_format = PATH_FORMAT_RELATIVE;
-+ else
-+ die(_("invalid path format '%s'"), arg);
-+
-+ return 0;
-+}
-+
- static int cmd_repo_info(int argc, const char **argv, const char *prefix,
- struct repository *repo)
- {
- enum output_format format = FORMAT_KEYVALUE;
-+ struct repo_info info = {
-+ .repo = repo,
-+ .prefix = prefix,
-+ .path_format = PATH_FORMAT_ABSOLUTE,
-+ };
- int all_keys = 0;
- struct option options[] = {
- OPT_CALLBACK_F(0, "format", &format, N_("format"),
-@@ builtin/repo.c: static int cmd_repo_info(int argc, const char **argv, const char *prefix,
- N_("synonym for --format=nul"),
- PARSE_OPT_NONEG | PARSE_OPT_NOARG,
- parse_format_cb),
-+ OPT_CALLBACK_F(0, "path-format", &info.path_format,
-+ N_("format"), N_("path output format"),
-+ PARSE_OPT_NONEG, parse_path_format_cb),
- OPT_BOOL(0, "all", &all_keys, N_("print all keys/values")),
- OPT_END()
- };
-@@ builtin/repo.c: static int cmd_repo_info(int argc, const char **argv, const char *prefix,
- die(_("--all and <key> cannot be used together"));
-
- if (all_keys)
-- return print_all_fields(repo, format);
-+ return print_all_fields(&info, format);
- else
-- return print_fields(argc, argv, repo, format);
-+ return print_fields(argc, argv, &info, format);
- }
-
- struct ref_stats {
-@@ builtin/repo.c: struct object_values {
- struct object_stats {
- struct object_values type_counts;
- struct object_values inflated_sizes;
-+ struct object_values max_inflated_sizes;
- struct object_values disk_sizes;
-+ struct object_values max_disk_sizes;
-+ size_t max_commit_parent_count;
-+ size_t max_tree_entry_count;
-+ size_t max_blob_path_length;
-+ size_t max_blob_path_depth;
-+ size_t max_tag_chain_depth;
- };
-
- struct repo_structure {
-@@ builtin/repo.c: static inline size_t get_total_object_values(struct object_values *values)
- return values->tags + values->commits + values->trees + values->blobs;
- }
-
-+static inline size_t get_max_object_value(struct object_values *values)
-+{
-+ size_t max = values->commits;
-+
-+ if (values->trees > max)
-+ max = values->trees;
-+ if (values->blobs > max)
-+ max = values->blobs;
-+ if (values->tags > max)
-+ max = values->tags;
-+
-+ return max;
-+}
-+
-+static size_t get_commit_parent_count(struct repository *repo,
-+ const struct object_id *oid)
-+{
-+ unsigned long size = 0;
-+ const char *cur;
-+ const char *end;
-+ void *buf;
-+ size_t count = 0;
-+
-+ buf = odb_read_object_peeled(repo->objects, oid, OBJ_COMMIT, &size, NULL);
-+ if (!buf)
-+ return 0;
-+
-+ cur = buf;
-+ end = cur + size;
-+ while (cur < end) {
-+ const char *newline = memchr(cur, '\n', end - cur);
-+ size_t line_len;
-+
-+ if (!newline)
-+ break;
-+ line_len = newline - cur;
-+ if (!line_len)
-+ break;
-+
-+ if (line_len > 7 && !memcmp(cur, "parent ", 7))
-+ count++;
-+
-+ cur = newline + 1;
-+ }
-+
-+ free(buf);
-+ return count;
-+}
-+
-+static size_t get_tree_entry_count(struct repository *repo,
-+ const struct object_id *oid)
-+{
-+ struct tree_desc desc;
-+ struct name_entry entry;
-+ unsigned long size = 0;
-+ void *buf;
-+ size_t count = 0;
-+
-+ buf = odb_read_object_peeled(repo->objects, oid, OBJ_TREE, &size, NULL);
-+ if (!buf)
-+ return 0;
-+
-+ init_tree_desc(&desc, oid, buf, size);
-+ while (tree_entry(&desc, &entry))
-+ count++;
-+
-+ free(buf);
-+ return count;
-+}
-+
-+static size_t get_path_depth(const char *path)
-+{
-+ size_t depth = 0;
-+
-+ if (!path || !*path)
-+ return 0;
-+
-+ depth = 1;
-+ for (const char *cur = path; *cur; cur++)
-+ if (*cur == '/')
-+ depth++;
-+
-+ return depth;
-+}
-+
-+static size_t get_tag_chain_depth(struct repository *repo,
-+ const struct object_id *oid)
-+{
-+ struct object_id current = *oid;
-+ size_t depth = 0;
-+
-+ while (1) {
-+ enum object_type type;
-+ unsigned long size = 0;
-+ struct object_id next;
-+ const char *p, *end;
-+ void *buf = odb_read_object(repo->objects, ¤t, &type, &size);
-+
-+ if (!buf)
-+ break;
-+ if (type != OBJ_TAG) {
-+ free(buf);
-+ break;
-+ }
-+
-+ p = buf;
-+ if (!skip_prefix(p, "object ", &p) ||
-+ parse_oid_hex_algop(p, &next, &end, repo->hash_algo) ||
-+ *end != '\n') {
-+ free(buf);
-+ break;
-+ }
-+
-+ depth++;
-+ free(buf);
-+
-+ if (oideq(&next, ¤t))
-+ break;
-+ oidcpy(¤t, &next);
-+ }
-+
-+ return depth;
-+}
-+
- static void stats_table_setup_structure(struct stats_table *table,
- struct repo_structure *stats)
- {
-@@ builtin/repo.c: static void stats_table_setup_structure(struct stats_table *table,
- " * %s", _("Blobs"));
- stats_table_size_addf(table, objects->disk_sizes.tags,
- " * %s", _("Tags"));
-+
-+ stats_table_size_addf(table, objects->max_inflated_sizes.commits,
-+ " * %s", _("Largest commit"));
-+ stats_table_size_addf(table, objects->max_inflated_sizes.trees,
-+ " * %s", _("Largest tree"));
-+ stats_table_size_addf(table, objects->max_inflated_sizes.blobs,
-+ " * %s", _("Largest blob"));
-+ stats_table_size_addf(table, objects->max_inflated_sizes.tags,
-+ " * %s", _("Largest tag"));
-+
-+ stats_table_size_addf(table, get_max_object_value(&objects->max_disk_sizes),
-+ " * %s", _("Largest disk size"));
-+ stats_table_size_addf(table, objects->max_disk_sizes.commits,
-+ " * %s", _("Commits"));
-+ stats_table_size_addf(table, objects->max_disk_sizes.trees,
-+ " * %s", _("Trees"));
-+ stats_table_size_addf(table, objects->max_disk_sizes.blobs,
-+ " * %s", _("Blobs"));
-+ stats_table_size_addf(table, objects->max_disk_sizes.tags,
-+ " * %s", _("Tags"));
-+
-+ stats_table_count_addf(table, objects->max_commit_parent_count,
-+ " * %s", _("Largest parent count"));
-+ stats_table_count_addf(table, objects->max_tree_entry_count,
-+ " * %s", _("Largest tree entries"));
-+ stats_table_count_addf(table, objects->max_blob_path_length,
-+ " * %s", _("Longest blob path"));
-+ stats_table_count_addf(table, objects->max_blob_path_depth,
-+ " * %s", _("Deepest blob path"));
-+ stats_table_count_addf(table, objects->max_tag_chain_depth,
-+ " * %s", _("Deepest tag chain"));
- }
-
- static void stats_table_print_structure(const struct stats_table *table)
-@@ builtin/repo.c: static void stats_table_clear(struct stats_table *table)
- static void structure_keyvalue_print(struct repo_structure *stats,
- char key_delim, char value_delim)
- {
-+ size_t references_count_total = get_total_reference_count(&stats->refs);
-+ size_t object_count_total = get_total_object_values(&stats->objects.type_counts);
-+ size_t inflated_size_total = get_total_object_values(&stats->objects.inflated_sizes);
-+ size_t disk_size_total = get_total_object_values(&stats->objects.disk_sizes);
-+ size_t max_inflated_size = get_max_object_value(&stats->objects.max_inflated_sizes);
-+ size_t max_disk_size = get_max_object_value(&stats->objects.max_disk_sizes);
-+
-+ printf("references.count%c%" PRIuMAX "%c", key_delim,
-+ (uintmax_t)references_count_total, value_delim);
- printf("references.branches.count%c%" PRIuMAX "%c", key_delim,
- (uintmax_t)stats->refs.branches, value_delim);
- printf("references.tags.count%c%" PRIuMAX "%c", key_delim,
-@@ builtin/repo.c: static void structure_keyvalue_print(struct repo_structure *stats,
- printf("references.others.count%c%" PRIuMAX "%c", key_delim,
- (uintmax_t)stats->refs.others, value_delim);
-
-+ printf("objects.count%c%" PRIuMAX "%c", key_delim,
-+ (uintmax_t)object_count_total, value_delim);
- printf("objects.commits.count%c%" PRIuMAX "%c", key_delim,
- (uintmax_t)stats->objects.type_counts.commits, value_delim);
- printf("objects.trees.count%c%" PRIuMAX "%c", key_delim,
-@@ builtin/repo.c: static void structure_keyvalue_print(struct repo_structure *stats,
- printf("objects.tags.count%c%" PRIuMAX "%c", key_delim,
- (uintmax_t)stats->objects.type_counts.tags, value_delim);
-
-+ printf("objects.inflated_size%c%" PRIuMAX "%c", key_delim,
-+ (uintmax_t)inflated_size_total, value_delim);
- printf("objects.commits.inflated_size%c%" PRIuMAX "%c", key_delim,
- (uintmax_t)stats->objects.inflated_sizes.commits, value_delim);
- printf("objects.trees.inflated_size%c%" PRIuMAX "%c", key_delim,
-@@ builtin/repo.c: static void structure_keyvalue_print(struct repo_structure *stats,
- printf("objects.tags.inflated_size%c%" PRIuMAX "%c", key_delim,
- (uintmax_t)stats->objects.inflated_sizes.tags, value_delim);
-
-+ printf("objects.max_inflated_size%c%" PRIuMAX "%c", key_delim,
-+ (uintmax_t)max_inflated_size, value_delim);
-+ printf("objects.commits.max_inflated_size%c%" PRIuMAX "%c", key_delim,
-+ (uintmax_t)stats->objects.max_inflated_sizes.commits, value_delim);
-+ printf("objects.trees.max_inflated_size%c%" PRIuMAX "%c", key_delim,
-+ (uintmax_t)stats->objects.max_inflated_sizes.trees, value_delim);
-+ printf("objects.blobs.max_inflated_size%c%" PRIuMAX "%c", key_delim,
-+ (uintmax_t)stats->objects.max_inflated_sizes.blobs, value_delim);
-+ printf("objects.tags.max_inflated_size%c%" PRIuMAX "%c", key_delim,
-+ (uintmax_t)stats->objects.max_inflated_sizes.tags, value_delim);
-+
-+ printf("objects.disk_size%c%" PRIuMAX "%c", key_delim,
-+ (uintmax_t)disk_size_total, value_delim);
-+ printf("objects.max_disk_size%c%" PRIuMAX "%c", key_delim,
-+ (uintmax_t)max_disk_size, value_delim);
-+ printf("objects.commits.max_disk_size%c%" PRIuMAX "%c", key_delim,
-+ (uintmax_t)stats->objects.max_disk_sizes.commits, value_delim);
-+ printf("objects.trees.max_disk_size%c%" PRIuMAX "%c", key_delim,
-+ (uintmax_t)stats->objects.max_disk_sizes.trees, value_delim);
-+ printf("objects.blobs.max_disk_size%c%" PRIuMAX "%c", key_delim,
-+ (uintmax_t)stats->objects.max_disk_sizes.blobs, value_delim);
-+ printf("objects.tags.max_disk_size%c%" PRIuMAX "%c", key_delim,
-+ (uintmax_t)stats->objects.max_disk_sizes.tags, value_delim);
-+
-+ printf("objects.commits.max_parent_count%c%" PRIuMAX "%c", key_delim,
-+ (uintmax_t)stats->objects.max_commit_parent_count, value_delim);
-+ printf("objects.trees.max_entry_count%c%" PRIuMAX "%c", key_delim,
-+ (uintmax_t)stats->objects.max_tree_entry_count, value_delim);
-+ printf("objects.blobs.max_path_length%c%" PRIuMAX "%c", key_delim,
-+ (uintmax_t)stats->objects.max_blob_path_length, value_delim);
-+ printf("objects.blobs.max_path_depth%c%" PRIuMAX "%c", key_delim,
-+ (uintmax_t)stats->objects.max_blob_path_depth, value_delim);
-+ printf("objects.tags.max_chain_depth%c%" PRIuMAX "%c", key_delim,
-+ (uintmax_t)stats->objects.max_tag_chain_depth, value_delim);
-+
- printf("objects.commits.disk_size%c%" PRIuMAX "%c", key_delim,
- (uintmax_t)stats->objects.disk_sizes.commits, value_delim);
- printf("objects.trees.disk_size%c%" PRIuMAX "%c", key_delim,
-@@ builtin/repo.c: struct count_objects_data {
- struct progress *progress;
- };
-
--static int count_objects(const char *path UNUSED, struct oid_array *oids,
-+static int count_objects(const char *path, struct oid_array *oids,
- enum object_type type, void *cb_data)
- {
- struct count_objects_data *data = cb_data;
- struct object_stats *stats = data->stats;
- size_t inflated_total = 0;
- size_t disk_total = 0;
-+ size_t max_inflated = 0;
-+ size_t max_disk = 0;
- size_t object_count;
-
- for (size_t i = 0; i < oids->nr; i++) {
-@@ builtin/repo.c: static int count_objects(const char *path UNUSED, struct oid_array *oids,
-
- if (odb_read_object_info_extended(data->odb, &oids->oid[i], &oi,
- OBJECT_INFO_SKIP_FETCH_OBJECT |
-- OBJECT_INFO_QUICK) < 0)
-+ OBJECT_INFO_QUICK) < 0)
-+ continue;
-+ if (disk < 0)
- continue;
-
- inflated_total += inflated;
-- disk_total += disk;
-+ disk_total += (size_t)disk;
-+ if (inflated > max_inflated)
-+ max_inflated = inflated;
-+ if ((size_t)disk > max_disk)
-+ max_disk = (size_t)disk;
- }
-
- switch (type) {
- case OBJ_TAG:
-+ for (size_t i = 0; i < oids->nr; i++) {
-+ size_t tag_chain_depth = get_tag_chain_depth(data->odb->repo,
-+ &oids->oid[i]);
-+
-+ if (tag_chain_depth > stats->max_tag_chain_depth)
-+ stats->max_tag_chain_depth = tag_chain_depth;
-+ }
-+
- stats->type_counts.tags += oids->nr;
- stats->inflated_sizes.tags += inflated_total;
-+ if (max_inflated > stats->max_inflated_sizes.tags)
-+ stats->max_inflated_sizes.tags = max_inflated;
- stats->disk_sizes.tags += disk_total;
-+ if (max_disk > stats->max_disk_sizes.tags)
-+ stats->max_disk_sizes.tags = max_disk;
- break;
- case OBJ_COMMIT:
-+ for (size_t i = 0; i < oids->nr; i++) {
-+ size_t parent_count = get_commit_parent_count(data->odb->repo,
-+ &oids->oid[i]);
-+
-+ if (parent_count > stats->max_commit_parent_count)
-+ stats->max_commit_parent_count = parent_count;
-+ }
-+
- stats->type_counts.commits += oids->nr;
- stats->inflated_sizes.commits += inflated_total;
-+ if (max_inflated > stats->max_inflated_sizes.commits)
-+ stats->max_inflated_sizes.commits = max_inflated;
- stats->disk_sizes.commits += disk_total;
-+ if (max_disk > stats->max_disk_sizes.commits)
-+ stats->max_disk_sizes.commits = max_disk;
- break;
- case OBJ_TREE:
-+ for (size_t i = 0; i < oids->nr; i++) {
-+ size_t entry_count = get_tree_entry_count(data->odb->repo,
-+ &oids->oid[i]);
-+
-+ if (entry_count > stats->max_tree_entry_count)
-+ stats->max_tree_entry_count = entry_count;
-+ }
-+
- stats->type_counts.trees += oids->nr;
- stats->inflated_sizes.trees += inflated_total;
-+ if (max_inflated > stats->max_inflated_sizes.trees)
-+ stats->max_inflated_sizes.trees = max_inflated;
- stats->disk_sizes.trees += disk_total;
-+ if (max_disk > stats->max_disk_sizes.trees)
-+ stats->max_disk_sizes.trees = max_disk;
- break;
- case OBJ_BLOB:
-+ if (path && *path) {
-+ size_t path_len = strlen(path);
-+ size_t path_depth = get_path_depth(path);
-+
-+ if (path_len > stats->max_blob_path_length)
-+ stats->max_blob_path_length = path_len;
-+ if (path_depth > stats->max_blob_path_depth)
-+ stats->max_blob_path_depth = path_depth;
-+ }
-+
- stats->type_counts.blobs += oids->nr;
- stats->inflated_sizes.blobs += inflated_total;
-+ if (max_inflated > stats->max_inflated_sizes.blobs)
-+ stats->max_inflated_sizes.blobs = max_inflated;
- stats->disk_sizes.blobs += disk_total;
-+ if (max_disk > stats->max_disk_sizes.blobs)
-+ stats->max_disk_sizes.blobs = max_disk;
- break;
- default:
- BUG("invalid object type");
-
- ## t/t1900-repo.sh ##
-@@ t/t1900-repo.sh: REPO_INFO_KEYS='
- layout.bare
- layout.shallow
- object.format
-+ path.common-dir
-+ path.config-file
-+ path.git-dir
-+ path.git-prefix
-+ path.grafts-file
-+ path.hooks-directory
-+ path.index-file
-+ path.logs-directory
-+ path.objects-directory
-+ path.packed-refs-file
-+ path.refs-directory
-+ path.shallow-file
-+ path.superproject-working-tree
-+ path.toplevel
- references.format
- '
-
-+REPO_INFO_PATH_KEYS='
-+ path.common-dir
-+ path.config-file
-+ path.git-dir
-+ path.git-prefix
-+ path.grafts-file
-+ path.hooks-directory
-+ path.index-file
-+ path.logs-directory
-+ path.objects-directory
-+ path.packed-refs-file
-+ path.refs-directory
-+ path.shallow-file
-+ path.superproject-working-tree
-+ path.toplevel
-+'
-+
- # Test whether a key-value pair is correctly returned
- #
- # Usage: test_repo_info <label> <init command> <repo_name> <key> <expected value>
-@@ t/t1900-repo.sh: test_expect_success 'values returned in order requested' '
- test_cmp expect actual
- '
-
-+test_expect_success 'category key returns all matching keys' '
-+ cat >expect <<-\EOF &&
-+ layout.bare=false
-+ layout.shallow=false
-+ EOF
-+ git init category-layout &&
-+ git -C category-layout repo info layout >actual &&
-+ test_cmp expect actual
-+'
-+
-+test_expect_success 'mixed key/category requests preserve request order' '
-+ cat >expect <<-\EOF &&
-+ object.format=sha1
-+ layout.bare=false
-+ layout.shallow=false
-+ EOF
-+ git init mixed-order &&
-+ git -C mixed-order repo info object.format layout >actual &&
-+ test_cmp expect actual
-+'
-+
-+test_expect_success 'path.git-dir matches rev-parse --absolute-git-dir' '
-+ git init path-git-dir &&
-+ expected_value=$(git -C path-git-dir rev-parse --absolute-git-dir) &&
-+ echo "path.git-dir=$expected_value" >expect &&
-+ git -C path-git-dir repo info path.git-dir >actual &&
-+ test_cmp expect actual
-+'
-+
-+test_expect_success 'path.common-dir matches rev-parse --git-common-dir' '
-+ git init path-common-dir &&
-+ expected_value=$(git -C path-common-dir rev-parse --path-format=absolute --git-common-dir) &&
-+ echo "path.common-dir=$expected_value" >expect &&
-+ git -C path-common-dir repo info path.common-dir >actual &&
-+ test_cmp expect actual
-+'
-+
-+test_expect_success 'path.toplevel matches rev-parse --show-toplevel' '
-+ git init path-toplevel &&
-+ expected_value=$(git -C path-toplevel rev-parse --show-toplevel) &&
-+ echo "path.toplevel=$expected_value" >expect &&
-+ git -C path-toplevel repo info path.toplevel >actual &&
-+ test_cmp expect actual
-+'
-+
-+test_expect_success 'path.toplevel is empty in bare repository' '
-+ git init --bare bare-path-toplevel &&
-+ echo "path.toplevel=" >expect &&
-+ git -C bare-path-toplevel repo info path.toplevel >actual &&
-+ test_cmp expect actual
-+'
-+
-+test_expect_success 'path.git-prefix matches rev-parse --show-prefix' '
-+ git init path-prefix &&
-+ mkdir -p path-prefix/a/b &&
-+ expected_value=$(git -C path-prefix/a/b rev-parse --show-prefix) &&
-+ echo "path.git-prefix=$expected_value" >expect &&
-+ git -C path-prefix/a/b repo info path.git-prefix >actual &&
-+ test_cmp expect actual
-+'
-+
-+test_expect_success 'git-path style keys match rev-parse --git-path' '
-+ git init path-git-path &&
-+
-+ expected_value=$(git -C path-git-path rev-parse --path-format=absolute --git-path info/grafts) &&
-+ echo "path.grafts-file=$expected_value" >expect &&
-+ git -C path-git-path repo info path.grafts-file >actual &&
-+ test_cmp expect actual &&
-+
-+ expected_value=$(git -C path-git-path rev-parse --path-format=absolute --git-path index) &&
-+ echo "path.index-file=$expected_value" >expect &&
-+ git -C path-git-path repo info path.index-file >actual &&
-+ test_cmp expect actual &&
-+
-+ expected_value=$(git -C path-git-path rev-parse --path-format=absolute --git-path objects) &&
-+ echo "path.objects-directory=$expected_value" >expect &&
-+ git -C path-git-path repo info path.objects-directory >actual &&
-+ test_cmp expect actual &&
-+
-+ expected_value=$(git -C path-git-path rev-parse --path-format=absolute --git-path hooks) &&
-+ echo "path.hooks-directory=$expected_value" >expect &&
-+ git -C path-git-path repo info path.hooks-directory >actual &&
-+ test_cmp expect actual &&
-+
-+ expected_value=$(git -C path-git-path rev-parse --path-format=absolute --git-path config) &&
-+ echo "path.config-file=$expected_value" >expect &&
-+ git -C path-git-path repo info path.config-file >actual &&
-+ test_cmp expect actual &&
-+
-+ expected_value=$(git -C path-git-path rev-parse --path-format=absolute --git-path logs) &&
-+ echo "path.logs-directory=$expected_value" >expect &&
-+ git -C path-git-path repo info path.logs-directory >actual &&
-+ test_cmp expect actual &&
-+
-+ expected_value=$(git -C path-git-path rev-parse --path-format=absolute --git-path packed-refs) &&
-+ echo "path.packed-refs-file=$expected_value" >expect &&
-+ git -C path-git-path repo info path.packed-refs-file >actual &&
-+ test_cmp expect actual &&
-+
-+ expected_value=$(git -C path-git-path rev-parse --path-format=absolute --git-path refs) &&
-+ echo "path.refs-directory=$expected_value" >expect &&
-+ git -C path-git-path repo info path.refs-directory >actual &&
-+ test_cmp expect actual &&
-+
-+ expected_value=$(git -C path-git-path rev-parse --path-format=absolute --git-path shallow) &&
-+ echo "path.shallow-file=$expected_value" >expect &&
-+ git -C path-git-path repo info path.shallow-file >actual &&
-+ test_cmp expect actual
-+'
-+
-+test_expect_success 'path.superproject-working-tree is empty when not a submodule' '
-+ git init path-superproject &&
-+ echo "path.superproject-working-tree=" >expect &&
-+ git -C path-superproject repo info path.superproject-working-tree >actual &&
-+ test_cmp expect actual
-+'
-+
-+test_expect_success 'path.superproject-working-tree matches rev-parse in submodule' '
-+ git init path-superproject-origin &&
-+ echo x >path-superproject-origin/x &&
-+ git -C path-superproject-origin add x &&
-+ git -C path-superproject-origin commit -m x &&
-+
-+ git init path-superproject-parent &&
-+ git -C path-superproject-parent -c protocol.file.allow=always submodule add ../path-superproject-origin sm &&
-+
-+ expected_value=$(git -C path-superproject-parent/sm rev-parse --show-superproject-working-tree) &&
-+ echo "path.superproject-working-tree=$expected_value" >expect &&
-+ git -C path-superproject-parent/sm repo info path.superproject-working-tree >actual &&
-+ test_cmp expect actual
-+'
-+
-+test_expect_success 'path category returns all path keys' '
-+ git init path-category &&
-+ >expect &&
-+ for key in $REPO_INFO_PATH_KEYS
-+ do
-+ git -C path-category repo info "$key" >>expect || return 1
-+ done &&
-+ git -C path-category repo info path >actual &&
-+ test_cmp expect actual
-+'
-+
-+test_expect_success 'path-format=relative matches rev-parse for git-dir' '
-+ git init path-format-relative &&
-+ expected_value=$(git -C path-format-relative rev-parse --path-format=relative --git-dir) &&
-+ echo "path.git-dir=$expected_value" >expect &&
-+ git -C path-format-relative repo info --path-format=relative path.git-dir >actual &&
-+ test_cmp expect actual
-+'
-+
-+test_expect_success 'git repo info uses the last requested path format' '
-+ git init path-format-last &&
-+ expected_value=$(git -C path-format-last rev-parse --path-format=relative --git-dir) &&
-+ echo "path.git-dir=$expected_value" >expect &&
-+ git -C path-format-last repo info --path-format=absolute --path-format=relative path.git-dir >actual &&
-+ test_cmp expect actual
-+'
-+
-+test_expect_success 'git-repo-info aborts when requesting an invalid path format' '
-+ echo "fatal: invalid path format ${SQ}foo${SQ}" >expect &&
-+ test_must_fail git repo info --path-format=foo path.git-dir 2>actual &&
-+ test_cmp expect actual
-+'
-+
- test_expect_success 'git-repo-info fails if an invalid key is requested' '
- echo "error: key ${SQ}foo${SQ} not found" >expect &&
- test_must_fail git repo info foo 2>actual &&
+ Signed-off-by: Eslam reda ragheb <[email protected]>
## t/t1901-repo-structure.sh ##
@@ t/t1901-repo-structure.sh: object_type_disk_usage() {
@@ t/t1901-repo-structure.sh: object_type_disk_usage() {
+ --filter=object:type=tree --filter-provided-objects | cut -d" " -f1)
+ do
+ entries=$(git cat-file -p "$oid" | wc -l) || return 1
-+ test "$entries" -gt "$max" && max=$entries
++ test $entries -gt $max && max=$entries
+ done
+
-+ echo "$max"
++ echo $max
+}
+
+blob_max_path_length() {
2: cb85ee3b48 < -: ---------- t1900,t1901: make repo tests hash-agnostic and wc-portable
3: 3c656bf152 < -: ---------- t1900,t1901: fix test portability issues
-: ---------- > 9: f17c0f03e5 docs: describe repo info path keys and structure metricsSignature · 1 line
gitgitgadget
PATCH v2 1/9 repo: teach info context and category keys
From: Eslam reda ragheb <[email protected]>
Introduce an explicit repo_info context for the repo info codepath and thread it through value lookups and field printing.
This removes direct coupling from these helpers to ad-hoc repository globals and makes key retrieval logic easier to extend safely.
Also teach git repo info to accept category names (for example, layout) and expand them to matching key.* entries in request order.
This improves script ergonomics while preserving existing behavior for explicit keys and clear errors for unknown names.
--- builtin/repo.c | 70 +++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 55 insertions(+), 15 deletions(-)
builtin/repo.c +55 -15
@@ -22,7 +22,12 @@ static const char *const repo_usage[] = {NULL }; -typedef int get_value_fn (struct repository *repo,struct strbuf *buf); +struct repo_info { +struct repository *repo; +const char *prefix; +}; + +typedef int get_value_fn (struct repo_info *info,struct strbuf *buf);enum output_format {FORMAT_TABLE ,
@@ -35,27 +40,30 @@ struct field {get_value_fn *get_value; }; -static int get_layout_bare(struct repository *repo UNUSED, struct strbuf *buf) +static int get_layout_bare(struct repo_info *info UNUSED, struct strbuf *buf) {strbuf_addstr (buf,is_bare_repository () ?"true" :"false" );return 0 ; } -static int get_layout_shallow (struct repository *repo,struct strbuf *buf) +static int get_layout_shallow (struct repo_info *info,struct strbuf *buf) { +struct repository *repo = info->repo;strbuf_addstr (buf,is_repository_shallow (repo) ?"true" :"false" );return 0 ; } -static int get_object_format (struct repository *repo,struct strbuf *buf) +static int get_object_format (struct repo_info *info,struct strbuf *buf) { +struct repository *repo = info->repo;strbuf_addstr (buf, repo->hash_algo->name);return 0 ; } -static int get_references_format (struct repository *repo,struct strbuf *buf) +static int get_references_format (struct repo_info *info,struct strbuf *buf) { +struct repository *repo = info->repo;strbuf_addstr (buf,ref_storage_format_to_name (repo->ref_storage_format));return 0 ;
@@ -87,6 +95,33 @@ static get_value_fn *get_value_fn_for_key(const char *key)return found ? found->get_value :NULL ; } +static void print_field (enum output_format format,const char *key, +const char *value); + +static int print_category_fields (const char *category, +struct repo_info *info, +enum output_format format, +struct strbuf *valbuf) +{ +int found =0 ; +size_t category_len =strlen (category); + +for (size_t i =0 ; i <ARRAY_SIZE (repo_info_fields); i++) { +const struct field *field = &repo_info_fields[i]; + +if (!starts_with (field->key, category) || + field->key[category_len] !='.' ) +continue ; + +strbuf_reset (valbuf); + field->get_value (info, valbuf); +print_field (format, field->key, valbuf->buf); + found =1 ; + } + +return found; +} +static void print_field (enum output_format format,const char *key,const char *value) {
@@ -105,7 +140,7 @@ static void print_field(enum output_format format, const char *key, }static int print_fields (int argc,const char **argv, -struct repository *repo, +struct repo_info *info,enum output_format format) {int ret =0 ;
@@ -117,21 +152,22 @@ static int print_fields(int argc, const char **argv, get_value =get_value_fn_for_key (key); -if (!get_value) { - ret =error (_ ("key '%s' not found" ), key); +if (get_value) { +strbuf_reset (&valbuf); +get_value (info, &valbuf); +print_field (format, key, valbuf.buf);continue ; } -strbuf_reset (&valbuf); -get_value (repo, &valbuf); -print_field (format, key, valbuf.buf); +if (!print_category_fields (key, info, format, &valbuf)) + ret =error (_ ("key '%s' not found" ), key); }strbuf_release (&valbuf);return ret; } -static int print_all_fields (struct repository *repo, +static int print_all_fields (struct repo_info *info,enum output_format format) {struct strbuf valbuf =STRBUF_INIT ;
@@ -140,7 +176,7 @@ static int print_all_fields(struct repository *repo,const struct field *field = &repo_info_fields[i];strbuf_reset (&valbuf); - field->get_value (repo, &valbuf); + field->get_value (info, &valbuf);print_field (format, field->key, valbuf.buf); }
@@ -171,6 +207,10 @@ static int cmd_repo_info(int argc, const char **argv, const char *prefix, struct repository *repo) { enum output_format format = FORMAT_KEYVALUE; + struct repo_info info = { + .repo = repo, + .prefix = prefix, + }; int all_keys = 0; struct option options[] = { OPT_CALLBACK_F(0, "format", &format, N_("format"),
@@ -192,9 +232,9 @@ static int cmd_repo_info(int argc, const char **argv, const char *prefix,die (_ ("--all and <key> cannot be used together" ));if (all_keys) -return print_all_fields (repo, format); +return print_all_fields (&info, format);else -return print_fields (argc, argv, repo, format); +return print_fields (argc, argv, &info, format); }struct ref_stats {
Signature · 1 line
gitgitgadget
PATCH v2 2/9 repo: add path keys to repo info
In reply to eslam reda via GitGitGadgetFrom: Eslam reda ragheb <[email protected]>
Add a path category to git repo info with key-value pairs that mirror repository paths users commonly retrieve via rev-parse and git-path lookups.
This makes scripting against repo metadata more direct and avoids shelling out to multiple commands for related paths.
The new keys are introduced as explicit path.* entries in repo_info_fields and are resolved through dedicated helpers.
This keeps lookup behavior predictable and makes future path additions straightforward.
--- builtin/repo.c | 137 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 137 insertions(+)
builtin/repo.c +137 -0
@@ -1,10 +1,12 @@#define USE_THE_REPOSITORY_VARIABLE #include "builtin.h" +#include "abspath.h" #include "environment.h" #include "hex.h" #include "odb.h" #include "parse-options.h" +#include "path.h" #include "path-walk.h" #include "progress.h" #include "quote.h"
@@ -14,6 +16,7 @@#include "strbuf.h" #include "string-list.h" #include "shallow.h" +#include "submodule.h" #include "utf8.h" static const char *const repo_usage[] = {
@@ -40,6 +43,13 @@ struct field {get_value_fn *get_value; }; +static void repo_info_add_path (struct repo_info *info, +struct strbuf *buf, +const char *path) +{ +strbuf_add_absolute_path (buf, path); +} + static int get_layout_bare(struct repo_info *info UNUSED, struct strbuf *buf) {strbuf_addstr (buf,is_bare_repository () ?"true" :"false" );
@@ -61,6 +71,119 @@ static int get_object_format(struct repo_info *info, struct strbuf *buf)return 0 ; } +static int get_path_common_dir (struct repo_info *info,struct strbuf *buf) +{ +repo_info_add_path (info, buf,repo_get_common_dir (info->repo)); +return 0 ; +} + +static int get_path_config_file (struct repo_info *info,struct strbuf *buf) +{ +struct strbuf path =STRBUF_INIT ; + +repo_info_add_path (info, buf,repo_git_path_replace (info->repo, &path,"config" )); +strbuf_release (&path); +return 0 ; +} + +static int get_path_git_dir (struct repo_info *info,struct strbuf *buf) +{ +repo_info_add_path (info, buf,repo_get_git_dir (info->repo)); +return 0 ; +} + +static int get_path_git_prefix (struct repo_info *info,struct strbuf *buf) +{ +if (info->prefix) +strbuf_addstr (buf, info->prefix); +return 0 ; +} + +static int get_path_grafts_file (struct repo_info *info,struct strbuf *buf) +{ +repo_info_add_path (info, buf,repo_get_graft_file (info->repo)); +return 0 ; +} + +static int get_path_hooks_directory (struct repo_info *info,struct strbuf *buf) +{ +struct strbuf path =STRBUF_INIT ; + +repo_info_add_path (info, buf,repo_git_path_replace (info->repo, &path,"hooks" )); +strbuf_release (&path); +return 0 ; +} + +static int get_path_index_file (struct repo_info *info,struct strbuf *buf) +{ +repo_info_add_path (info, buf,repo_get_index_file (info->repo)); +return 0 ; +} + +static int get_path_logs_directory (struct repo_info *info,struct strbuf *buf) +{ +struct strbuf path =STRBUF_INIT ; + +repo_info_add_path (info, buf,repo_git_path_replace (info->repo, &path,"logs" )); +strbuf_release (&path); +return 0 ; +} + +static int get_path_objects_directory (struct repo_info *info,struct strbuf *buf) +{ +repo_info_add_path (info, buf,repo_get_object_directory (info->repo)); +return 0 ; +} + +static int get_path_packed_refs_file (struct repo_info *info,struct strbuf *buf) +{ +struct strbuf path =STRBUF_INIT ; + +repo_info_add_path (info, buf,repo_git_path_replace (info->repo, &path,"packed-refs" )); +strbuf_release (&path); +return 0 ; +} + +static int get_path_refs_directory (struct repo_info *info,struct strbuf *buf) +{ +struct strbuf path =STRBUF_INIT ; + +repo_info_add_path (info, buf,repo_git_path_replace (info->repo, &path,"refs" )); +strbuf_release (&path); +return 0 ; +} + +static int get_path_shallow_file (struct repo_info *info,struct strbuf *buf) +{ +struct strbuf path =STRBUF_INIT ; + +repo_info_add_path (info, buf,repo_git_path_replace (info->repo, &path,"shallow" )); +strbuf_release (&path); +return 0 ; +} + +static int get_path_superproject_working_tree (struct repo_info *info, +struct strbuf *buf) +{ +struct strbuf superproject =STRBUF_INIT ; + +if (get_superproject_working_tree (&superproject)) +repo_info_add_path (info, buf, superproject.buf); + +strbuf_release (&superproject); +return 0 ; +} + +static int get_path_toplevel (struct repo_info *info,struct strbuf *buf) +{ +const char *work_tree =repo_get_work_tree (info->repo); + +if (work_tree) +repo_info_add_path (info, buf, work_tree); + +return 0 ; +} +static int get_references_format (struct repo_info *info,struct strbuf *buf) {struct repository *repo = info->repo;
@@ -74,6 +197,20 @@ static const struct field repo_info_fields[] = { { "layout.bare", get_layout_bare }, { "layout.shallow", get_layout_shallow }, { "object.format", get_object_format }, + { "path.common-dir", get_path_common_dir }, + { "path.config-file", get_path_config_file }, + { "path.git-dir", get_path_git_dir }, + { "path.git-prefix", get_path_git_prefix }, + { "path.grafts-file", get_path_grafts_file }, + { "path.hooks-directory", get_path_hooks_directory }, + { "path.index-file", get_path_index_file }, + { "path.logs-directory", get_path_logs_directory }, + { "path.objects-directory", get_path_objects_directory }, + { "path.packed-refs-file", get_path_packed_refs_file }, + { "path.refs-directory", get_path_refs_directory }, + { "path.shallow-file", get_path_shallow_file }, + { "path.superproject-working-tree", get_path_superproject_working_tree }, + { "path.toplevel", get_path_toplevel }, { "references.format", get_references_format }, };
Signature · 1 line
gitgitgadget
PATCH v2 4/9 repo: add structure max object size metrics
In reply to eslam reda via GitGitGadgetFrom: Eslam reda ragheb <[email protected]>
Extend git repo structure with maximum inflated and on-disk object sizes, both per type and overall max values.
This complements existing totals by highlighting outliers that often drive repository bloat analysis.
The implementation updates object counting to track per-type maxima while walking reachable objects.
It exposes those values in both table and keyvalue formats for scripts and human output.
--- builtin/repo.c | 87 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 86 insertions(+), 1 deletion(-)
builtin/repo.c +86 -1
@@ -426,7 +426,9 @@ struct object_values {struct object_stats {struct object_values type_counts;struct object_values inflated_sizes; +struct object_values max_inflated_sizes;struct object_values disk_sizes; +struct object_values max_disk_sizes; };struct repo_structure {
@@ -529,6 +531,20 @@ static inline size_t get_total_object_values(struct object_values *values)return values->tags + values->commits + values->trees + values->blobs; } +static inline size_t get_max_object_value (struct object_values *values) +{ +size_t max = values->commits; + +if (values->trees > max) + max = values->trees; +if (values->blobs > max) + max = values->blobs; +if (values->tags > max) + max = values->tags; + +return max; +} +static void stats_table_setup_structure (struct stats_table *table,struct repo_structure *stats) {
@@ -583,6 +599,26 @@ static void stats_table_setup_structure(struct stats_table *table, " * %s", _("Blobs"));stats_table_size_addf (table, objects->disk_sizes.tags," * %s" ,_ ("Tags" )); + +stats_table_size_addf (table, objects->max_inflated_sizes.commits, +" * %s" ,_ ("Largest commit" )); +stats_table_size_addf (table, objects->max_inflated_sizes.trees, +" * %s" ,_ ("Largest tree" )); +stats_table_size_addf (table, objects->max_inflated_sizes.blobs, +" * %s" ,_ ("Largest blob" )); +stats_table_size_addf (table, objects->max_inflated_sizes.tags, +" * %s" ,_ ("Largest tag" )); + +stats_table_size_addf (table,get_max_object_value (&objects->max_disk_sizes), +" * %s" ,_ ("Largest disk size" )); +stats_table_size_addf (table, objects->max_disk_sizes.commits, +" * %s" ,_ ("Commits" )); +stats_table_size_addf (table, objects->max_disk_sizes.trees, +" * %s" ,_ ("Trees" )); +stats_table_size_addf (table, objects->max_disk_sizes.blobs, +" * %s" ,_ ("Blobs" )); +stats_table_size_addf (table, objects->max_disk_sizes.tags, +" * %s" ,_ ("Tags" )); }static void stats_table_print_structure (const struct stats_table *table)
@@ -661,6 +697,9 @@ static void stats_table_clear(struct stats_table *table)static void structure_keyvalue_print (struct repo_structure *stats,char key_delim,char value_delim) { +size_t max_inflated_size =get_max_object_value (&stats->objects.max_inflated_sizes); +size_t max_disk_size =get_max_object_value (&stats->objects.max_disk_sizes); +printf ("references.branches.count%c%" PRIuMAX"%c" , key_delim, (uintmax_t )stats->refs.branches, value_delim); printf("references.tags.count%c%" PRIuMAX "%c", key_delim,
@@ -688,6 +727,28 @@ static void structure_keyvalue_print(struct repo_structure *stats,printf ("objects.tags.inflated_size%c%" PRIuMAX"%c" , key_delim, (uintmax_t )stats->objects.inflated_sizes.tags, value_delim); +printf ("objects.max_inflated_size%c%" PRIuMAX"%c" , key_delim, + (uintmax_t )max_inflated_size, value_delim); +printf ("objects.commits.max_inflated_size%c%" PRIuMAX"%c" , key_delim, + (uintmax_t )stats->objects.max_inflated_sizes.commits, value_delim); +printf ("objects.trees.max_inflated_size%c%" PRIuMAX"%c" , key_delim, + (uintmax_t )stats->objects.max_inflated_sizes.trees, value_delim); +printf ("objects.blobs.max_inflated_size%c%" PRIuMAX"%c" , key_delim, + (uintmax_t )stats->objects.max_inflated_sizes.blobs, value_delim); +printf ("objects.tags.max_inflated_size%c%" PRIuMAX"%c" , key_delim, + (uintmax_t )stats->objects.max_inflated_sizes.tags, value_delim); + +printf ("objects.max_disk_size%c%" PRIuMAX"%c" , key_delim, + (uintmax_t )max_disk_size, value_delim); +printf ("objects.commits.max_disk_size%c%" PRIuMAX"%c" , key_delim, + (uintmax_t )stats->objects.max_disk_sizes.commits, value_delim); +printf ("objects.trees.max_disk_size%c%" PRIuMAX"%c" , key_delim, + (uintmax_t )stats->objects.max_disk_sizes.trees, value_delim); +printf ("objects.blobs.max_disk_size%c%" PRIuMAX"%c" , key_delim, + (uintmax_t )stats->objects.max_disk_sizes.blobs, value_delim); +printf ("objects.tags.max_disk_size%c%" PRIuMAX"%c" , key_delim, + (uintmax_t )stats->objects.max_disk_sizes.tags, value_delim); +printf ("objects.commits.disk_size%c%" PRIuMAX"%c" , key_delim, (uintmax_t )stats->objects.disk_sizes.commits, value_delim); printf("objects.trees.disk_size%c%" PRIuMAX "%c", key_delim,
@@ -772,6 +833,8 @@ static int count_objects(const char *path UNUSED, struct oid_array *oids,struct object_stats *stats = data->stats;size_t inflated_total =0 ;size_t disk_total =0 ; +size_t max_inflated =0 ; +size_t max_disk =0 ;size_t object_count;for (size_t i =0 ; i < oids->nr; i++) {
@@ -786,31 +849,53 @@ static int count_objects(const char *path UNUSED, struct oid_array *oids,OBJECT_INFO_SKIP_FETCH_OBJECT | OBJECT_INFO_QUICK) < 0) continue; +if (disk <0 ) +continue ; inflated_total += inflated; - disk_total += disk; + disk_total += (size_t )disk; +if (inflated > max_inflated) + max_inflated = inflated; +if ((size_t )disk > max_disk) + max_disk = (size_t )disk; }switch (type) {case OBJ_TAG : stats->type_counts.tags += oids->nr; stats->inflated_sizes.tags += inflated_total; +if (max_inflated > stats->max_inflated_sizes.tags) + stats->max_inflated_sizes.tags = max_inflated; stats->disk_sizes.tags += disk_total; +if (max_disk > stats->max_disk_sizes.tags) + stats->max_disk_sizes.tags = max_disk;break ;case OBJ_COMMIT : stats->type_counts.commits += oids->nr; stats->inflated_sizes.commits += inflated_total; +if (max_inflated > stats->max_inflated_sizes.commits) + stats->max_inflated_sizes.commits = max_inflated; stats->disk_sizes.commits += disk_total; +if (max_disk > stats->max_disk_sizes.commits) + stats->max_disk_sizes.commits = max_disk;break ;case OBJ_TREE : stats->type_counts.trees += oids->nr; stats->inflated_sizes.trees += inflated_total; +if (max_inflated > stats->max_inflated_sizes.trees) + stats->max_inflated_sizes.trees = max_inflated; stats->disk_sizes.trees += disk_total; +if (max_disk > stats->max_disk_sizes.trees) + stats->max_disk_sizes.trees = max_disk;break ;case OBJ_BLOB : stats->type_counts.blobs += oids->nr; stats->inflated_sizes.blobs += inflated_total; +if (max_inflated > stats->max_inflated_sizes.blobs) + stats->max_inflated_sizes.blobs = max_inflated; stats->disk_sizes.blobs += disk_total; +if (max_disk > stats->max_disk_sizes.blobs) + stats->max_disk_sizes.blobs = max_disk;break ;default :BUG ("invalid object type" );
Signature · 1 line
gitgitgadget
PATCH v2 5/9 repo: add structure topology and path-depth metrics
In reply to eslam reda via GitGitGadgetFrom: Eslam reda ragheb <[email protected]>
Track additional structure-oriented maxima that are useful when diagnosing unusually complex histories.
These include commit parent fanout, tree entry count, blob path length/depth, and annotated tag chain depth.
The counters are gathered while traversing reachable objects and are reported in both table and keyvalue output.
This lets both humans and scripts consume the same topology signals.
--- builtin/repo.c | 171 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 170 insertions(+), 1 deletion(-)
builtin/repo.c +170 -1
@@ -17,6 +17,7 @@#include "string-list.h" #include "shallow.h" #include "submodule.h" +#include "tree-walk.h" #include "utf8.h" static const char *const repo_usage[] = {
@@ -429,6 +430,11 @@ struct object_stats {struct object_values max_inflated_sizes;struct object_values disk_sizes;struct object_values max_disk_sizes; +size_t max_commit_parent_count; +size_t max_tree_entry_count; +size_t max_blob_path_length; +size_t max_blob_path_depth; +size_t max_tag_chain_depth; };struct repo_structure {
@@ -545,6 +551,116 @@ static inline size_t get_max_object_value(struct object_values *values)return max; } +static size_t get_commit_parent_count (struct repository *repo, +const struct object_id *oid) +{ +unsigned long size =0 ; +const char *cur; +const char *end; +void *buf; +size_t count =0 ; + + buf =odb_read_object_peeled (repo->objects, oid,OBJ_COMMIT , &size,NULL ); +if (!buf) +return 0 ; + + cur = buf; + end = cur + size; +while (cur < end) { +const char *newline =memchr (cur,'\n' , end - cur); +size_t line_len; + +if (!newline) +break ; + line_len = newline - cur; +if (!line_len) +break ; + +if (line_len >7 && !memcmp (cur,"parent " ,7 )) + count++; + + cur = newline +1 ; + } + +free (buf); +return count; +} + +static size_t get_tree_entry_count (struct repository *repo, +const struct object_id *oid) +{ +struct tree_desc desc; +struct name_entry entry; +unsigned long size =0 ; +void *buf; +size_t count =0 ; + + buf =odb_read_object_peeled (repo->objects, oid,OBJ_TREE , &size,NULL ); +if (!buf) +return 0 ; + +init_tree_desc (&desc, oid, buf, size); +while (tree_entry (&desc, &entry)) + count++; + +free (buf); +return count; +} + +static size_t get_path_depth (const char *path) +{ +size_t depth =0 ; + +if (!path || !*path) +return 0 ; + + depth =1 ; +for (const char *cur = path; *cur; cur++) +if (*cur =='/' ) + depth++; + +return depth; +} + +static size_t get_tag_chain_depth (struct repository *repo, +const struct object_id *oid) +{ +struct object_id current = *oid; +size_t depth =0 ; + +while (1 ) { +enum object_type type; +unsigned long size =0 ; +struct object_id next; +const char *p, *end; +void *buf =odb_read_object (repo->objects, ¤t, &type, &size); + +if (!buf) +break ; +if (type !=OBJ_TAG ) { +free (buf); +break ; + } + + p = buf; +if (!skip_prefix (p,"object " , &p) || +parse_oid_hex_algop (p, &next, &end, repo->hash_algo) || + *end !='\n' ) { +free (buf); +break ; + } + + depth++; +free (buf); + +if (oideq (&next, ¤t)) +break ; +oidcpy (¤t, &next); + } + +return depth; +} +static void stats_table_setup_structure (struct stats_table *table,struct repo_structure *stats) {
@@ -619,6 +735,17 @@ static void stats_table_setup_structure(struct stats_table *table, " * %s", _("Blobs"));stats_table_size_addf (table, objects->max_disk_sizes.tags," * %s" ,_ ("Tags" )); + +stats_table_count_addf (table, objects->max_commit_parent_count, +" * %s" ,_ ("Largest parent count" )); +stats_table_count_addf (table, objects->max_tree_entry_count, +" * %s" ,_ ("Largest tree entries" )); +stats_table_count_addf (table, objects->max_blob_path_length, +" * %s" ,_ ("Longest blob path" )); +stats_table_count_addf (table, objects->max_blob_path_depth, +" * %s" ,_ ("Deepest blob path" )); +stats_table_count_addf (table, objects->max_tag_chain_depth, +" * %s" ,_ ("Deepest tag chain" )); }static void stats_table_print_structure (const struct stats_table *table)
@@ -749,6 +876,17 @@ static void structure_keyvalue_print(struct repo_structure *stats,printf ("objects.tags.max_disk_size%c%" PRIuMAX"%c" , key_delim, (uintmax_t )stats->objects.max_disk_sizes.tags, value_delim); +printf ("objects.commits.max_parent_count%c%" PRIuMAX"%c" , key_delim, + (uintmax_t )stats->objects.max_commit_parent_count, value_delim); +printf ("objects.trees.max_entry_count%c%" PRIuMAX"%c" , key_delim, + (uintmax_t )stats->objects.max_tree_entry_count, value_delim); +printf ("objects.blobs.max_path_length%c%" PRIuMAX"%c" , key_delim, + (uintmax_t )stats->objects.max_blob_path_length, value_delim); +printf ("objects.blobs.max_path_depth%c%" PRIuMAX"%c" , key_delim, + (uintmax_t )stats->objects.max_blob_path_depth, value_delim); +printf ("objects.tags.max_chain_depth%c%" PRIuMAX"%c" , key_delim, + (uintmax_t )stats->objects.max_tag_chain_depth, value_delim); +printf ("objects.commits.disk_size%c%" PRIuMAX"%c" , key_delim, (uintmax_t )stats->objects.disk_sizes.commits, value_delim); printf("objects.trees.disk_size%c%" PRIuMAX "%c", key_delim,
@@ -826,7 +964,7 @@ struct count_objects_data {struct progress *progress; }; -static int count_objects(const char *path UNUSED, struct oid_array *oids, +static int count_objects (const char *path,struct oid_array *oids,enum object_type type,void *cb_data) {struct count_objects_data *data = cb_data;
@@ -862,6 +1000,13 @@ static int count_objects(const char *path UNUSED, struct oid_array *oids,switch (type) {case OBJ_TAG : +for (size_t i =0 ; i < oids->nr; i++) { +size_t tag_chain_depth =get_tag_chain_depth (data->odb->repo, + &oids->oid[i]); +if (tag_chain_depth > stats->max_tag_chain_depth) + stats->max_tag_chain_depth = tag_chain_depth; + } + stats->type_counts.tags += oids->nr; stats->inflated_sizes.tags += inflated_total;if (max_inflated > stats->max_inflated_sizes.tags)
@@ -871,6 +1016,13 @@ static int count_objects(const char *path UNUSED, struct oid_array *oids, stats->max_disk_sizes.tags = max_disk;break ;case OBJ_COMMIT : +for (size_t i =0 ; i < oids->nr; i++) { +size_t parent_count =get_commit_parent_count (data->odb->repo, + &oids->oid[i]); +if (parent_count > stats->max_commit_parent_count) + stats->max_commit_parent_count = parent_count; + } + stats->type_counts.commits += oids->nr; stats->inflated_sizes.commits += inflated_total;if (max_inflated > stats->max_inflated_sizes.commits)
@@ -880,6 +1032,13 @@ static int count_objects(const char *path UNUSED, struct oid_array *oids, stats->max_disk_sizes.commits = max_disk;break ;case OBJ_TREE : +for (size_t i =0 ; i < oids->nr; i++) { +size_t entry_count =get_tree_entry_count (data->odb->repo, + &oids->oid[i]); +if (entry_count > stats->max_tree_entry_count) + stats->max_tree_entry_count = entry_count; + } + stats->type_counts.trees += oids->nr; stats->inflated_sizes.trees += inflated_total;if (max_inflated > stats->max_inflated_sizes.trees)
@@ -889,6 +1048,16 @@ static int count_objects(const char *path UNUSED, struct oid_array *oids, stats->max_disk_sizes.trees = max_disk;break ;case OBJ_BLOB : +if (path && *path) { +size_t path_len =strlen (path); +size_t path_depth =get_path_depth (path); + +if (path_len > stats->max_blob_path_length) + stats->max_blob_path_length = path_len; +if (path_depth > stats->max_blob_path_depth) + stats->max_blob_path_depth = path_depth; + } + stats->type_counts.blobs += oids->nr; stats->inflated_sizes.blobs += inflated_total;if (max_inflated > stats->max_inflated_sizes.blobs)
Signature · 1 line
gitgitgadget
PATCH v2 6/9 repo: add aggregate structure totals to keyvalue output
In reply to eslam reda via GitGitGadgetFrom: Eslam reda ragheb <[email protected]>
Expose aggregate totals alongside per-type values in structure keyvalue/nul output: total references, total objects, total inflated size, and total disk size.
These totals remove the need for callers to recompute sums externally.
Keeping aggregate and per-type values in the same output format improves script ergonomics.
It also keeps machine-readable output aligned with what table mode summarizes for humans.
--- builtin/repo.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+)
builtin/repo.c +16 -0
@@ -824,9 +824,16 @@ static void stats_table_clear(struct stats_table *table)static void structure_keyvalue_print (struct repo_structure *stats,char key_delim,char value_delim) { +size_t references_count_total =get_total_reference_count (&stats->refs); +size_t object_count_total =get_total_object_values (&stats->objects.type_counts); +size_t inflated_size_total =get_total_object_values (&stats->objects.inflated_sizes); +size_t disk_size_total =get_total_object_values (&stats->objects.disk_sizes);size_t max_inflated_size =get_max_object_value (&stats->objects.max_inflated_sizes);size_t max_disk_size =get_max_object_value (&stats->objects.max_disk_sizes); +printf ("references.count%c%" PRIuMAX"%c" , key_delim, + (uintmax_t )references_count_total, value_delim); +printf ("references.branches.count%c%" PRIuMAX"%c" , key_delim, (uintmax_t )stats->refs.branches, value_delim); printf("references.tags.count%c%" PRIuMAX "%c", key_delim,
@@ -836,6 +843,9 @@ static void structure_keyvalue_print(struct repo_structure *stats,printf ("references.others.count%c%" PRIuMAX"%c" , key_delim, (uintmax_t )stats->refs.others, value_delim); +printf ("objects.count%c%" PRIuMAX"%c" , key_delim, + (uintmax_t )object_count_total, value_delim); +printf ("objects.commits.count%c%" PRIuMAX"%c" , key_delim, (uintmax_t )stats->objects.type_counts.commits, value_delim); printf("objects.trees.count%c%" PRIuMAX "%c", key_delim,
@@ -845,6 +855,9 @@ static void structure_keyvalue_print(struct repo_structure *stats,printf ("objects.tags.count%c%" PRIuMAX"%c" , key_delim, (uintmax_t )stats->objects.type_counts.tags, value_delim); +printf ("objects.inflated_size%c%" PRIuMAX"%c" , key_delim, + (uintmax_t )inflated_size_total, value_delim); +printf ("objects.commits.inflated_size%c%" PRIuMAX"%c" , key_delim, (uintmax_t )stats->objects.inflated_sizes.commits, value_delim); printf("objects.trees.inflated_size%c%" PRIuMAX "%c", key_delim,
@@ -865,6 +878,9 @@ static void structure_keyvalue_print(struct repo_structure *stats,printf ("objects.tags.max_inflated_size%c%" PRIuMAX"%c" , key_delim, (uintmax_t )stats->objects.max_inflated_sizes.tags, value_delim); +printf ("objects.disk_size%c%" PRIuMAX"%c" , key_delim, + (uintmax_t )disk_size_total, value_delim); +printf ("objects.max_disk_size%c%" PRIuMAX"%c" , key_delim, (uintmax_t )max_disk_size, value_delim); printf("objects.commits.max_disk_size%c%" PRIuMAX "%c", key_delim,
Signature · 1 line
gitgitgadget
PATCH v2 7/9 t1900: cover repo info path keys and path-format
In reply to eslam reda via GitGitGadgetFrom: Eslam reda ragheb <[email protected]>
Extend t1900 to validate category-key expansion, path.* key behavior, and --path-format handling for git repo info.
The tests compare repo info output to equivalent rev-parse values.
This ensures behavior remains aligned with existing plumbing semantics.
Also keep mixed key/category ordering coverage so callers can rely on deterministic output order when combining explicit keys with category requests.
--- t/t1900-repo.sh | 196 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 196 insertions(+)
t/t1900-repo.sh +196 -0
@@ -10,9 +10,40 @@ REPO_INFO_KEYS=' layout.bare layout.shallow object.format + path.common-dir + path.config-file + path.git-dir + path.git-prefix + path.grafts-file + path.hooks-directory + path.index-file + path.logs-directory + path.objects-directory + path.packed-refs-file + path.refs-directory + path.shallow-file + path.superproject-working-tree + path.toplevel references.format ' +REPO_INFO_PATH_KEYS=' + path.common-dir + path.config-file + path.git-dir + path.git-prefix + path.grafts-file + path.hooks-directory + path.index-file + path.logs-directory + path.objects-directory + path.packed-refs-file + path.refs-directory + path.shallow-file + path.superproject-working-tree + path.toplevel +' + # Test whether a key-value pair is correctly returned # # Usage: test_repo_info <label> <init command> <repo_name> <key> <expected value>
@@ -89,6 +120,171 @@ test_expect_success 'values returned in order requested' ' test_cmp expect actual ' +test_expect_success 'category key returns all matching keys' ' + cat >expect <<-\EOF && + layout.bare=false + layout.shallow=false + EOF + git init category-layout && + git -C category-layout repo info layout >actual && + test_cmp expect actual +' + +test_expect_success 'mixed key/category requests preserve request order' ' + cat >expect <<-EOF && + object.format=$(test_oid algo) + layout.bare=false + layout.shallow=false + EOF + git init mixed-order && + git -C mixed-order repo info object.format layout >actual && + test_cmp expect actual +' + +test_expect_success 'path.git-dir matches rev-parse --absolute-git-dir' ' + git init path-git-dir && + expected_value=$(git -C path-git-dir rev-parse --absolute-git-dir) && + echo "path.git-dir=$expected_value" >expect && + git -C path-git-dir repo info path.git-dir >actual && + test_cmp expect actual +' + +test_expect_success 'path.common-dir matches rev-parse --git-common-dir' ' + git init path-common-dir && + expected_value=$(git -C path-common-dir rev-parse --path-format=absolute --git-common-dir) && + echo "path.common-dir=$expected_value" >expect && + git -C path-common-dir repo info path.common-dir >actual && + test_cmp expect actual +' + +test_expect_success 'path.toplevel matches rev-parse --show-toplevel' ' + git init path-toplevel && + expected_value=$(git -C path-toplevel rev-parse --show-toplevel) && + echo "path.toplevel=$expected_value" >expect && + git -C path-toplevel repo info path.toplevel >actual && + test_cmp expect actual +' + +test_expect_success 'path.toplevel is empty in bare repository' ' + git init --bare bare-path-toplevel && + echo "path.toplevel=" >expect && + git -C bare-path-toplevel repo info path.toplevel >actual && + test_cmp expect actual +' + +test_expect_success 'path.git-prefix matches rev-parse --show-prefix' ' + git init path-prefix && + mkdir -p path-prefix/a/b && + expected_value=$(git -C path-prefix/a/b rev-parse --show-prefix) && + echo "path.git-prefix=$expected_value" >expect && + git -C path-prefix/a/b repo info path.git-prefix >actual && + test_cmp expect actual +' + +test_expect_success 'git-path style keys match rev-parse --git-path' ' + git init path-git-path && + + expected_value=$(git -C path-git-path rev-parse --path-format=absolute --git-path info/grafts) && + echo "path.grafts-file=$expected_value" >expect && + git -C path-git-path repo info path.grafts-file >actual && + test_cmp expect actual && + + expected_value=$(git -C path-git-path rev-parse --path-format=absolute --git-path index) && + echo "path.index-file=$expected_value" >expect && + git -C path-git-path repo info path.index-file >actual && + test_cmp expect actual && + + expected_value=$(git -C path-git-path rev-parse --path-format=absolute --git-path objects) && + echo "path.objects-directory=$expected_value" >expect && + git -C path-git-path repo info path.objects-directory >actual && + test_cmp expect actual && + + expected_value=$(git -C path-git-path rev-parse --path-format=absolute --git-path hooks) && + echo "path.hooks-directory=$expected_value" >expect && + git -C path-git-path repo info path.hooks-directory >actual && + test_cmp expect actual && + + expected_value=$(git -C path-git-path rev-parse --path-format=absolute --git-path config) && + echo "path.config-file=$expected_value" >expect && + git -C path-git-path repo info path.config-file >actual && + test_cmp expect actual && + + expected_value=$(git -C path-git-path rev-parse --path-format=absolute --git-path logs) && + echo "path.logs-directory=$expected_value" >expect && + git -C path-git-path repo info path.logs-directory >actual && + test_cmp expect actual && + + expected_value=$(git -C path-git-path rev-parse --path-format=absolute --git-path packed-refs) && + echo "path.packed-refs-file=$expected_value" >expect && + git -C path-git-path repo info path.packed-refs-file >actual && + test_cmp expect actual && + + expected_value=$(git -C path-git-path rev-parse --path-format=absolute --git-path refs) && + echo "path.refs-directory=$expected_value" >expect && + git -C path-git-path repo info path.refs-directory >actual && + test_cmp expect actual && + + expected_value=$(git -C path-git-path rev-parse --path-format=absolute --git-path shallow) && + echo "path.shallow-file=$expected_value" >expect && + git -C path-git-path repo info path.shallow-file >actual && + test_cmp expect actual +' + +test_expect_success 'path.superproject-working-tree is empty when not a submodule' ' + git init path-superproject && + echo "path.superproject-working-tree=" >expect && + git -C path-superproject repo info path.superproject-working-tree >actual && + test_cmp expect actual +' + +test_expect_success 'path.superproject-working-tree matches rev-parse in submodule' ' + git init path-superproject-origin && + echo x >path-superproject-origin/x && + git -C path-superproject-origin add x && + git -C path-superproject-origin commit -m x && + + git init path-superproject-parent && + git -C path-superproject-parent -c protocol.file.allow=always submodule add ../path-superproject-origin sm && + + expected_value=$(git -C path-superproject-parent/sm rev-parse --show-superproject-working-tree) && + echo "path.superproject-working-tree=$expected_value" >expect && + git -C path-superproject-parent/sm repo info path.superproject-working-tree >actual && + test_cmp expect actual +' + +test_expect_success 'path category returns all path keys' ' + git init path-category && + >expect && + for key in $REPO_INFO_PATH_KEYS + do + git -C path-category repo info "$key" >>expect || return 1 + done && + git -C path-category repo info path >actual && + test_cmp expect actual +' + +test_expect_success 'path-format=relative matches rev-parse for git-dir' ' + git init path-format-relative && + expected_value=$(git -C path-format-relative rev-parse --path-format=relative --git-dir) && + echo "path.git-dir=$expected_value" >expect && + git -C path-format-relative repo info --path-format=relative path.git-dir >actual && + test_cmp expect actual +' + +test_expect_success 'git repo info uses the last requested path format' ' + git init path-format-last && + expected_value=$(git -C path-format-last rev-parse --path-format=relative --git-dir) && + echo "path.git-dir=$expected_value" >expect && + git -C path-format-last repo info --path-format=absolute --path-format=relative path.git-dir >actual && + test_cmp expect actual +' + +test_expect_success 'git-repo-info aborts when requesting an invalid path format' ' + echo "fatal: invalid path format ${SQ}foo${SQ}" >expect && + test_must_fail git repo info --path-format=foo path.git-dir 2>actual && + test_cmp expect actual +' + test_expect_success 'git-repo-info fails if an invalid key is requested' ' echo "error: key ${SQ}foo${SQ} not found" >expect && test_must_fail git repo info foo 2>actual &&
Signature · 1 line
gitgitgadget
PATCH v2 8/9 t1901: extend structure metric coverage and portability
In reply to eslam reda via GitGitGadgetFrom: Eslam reda ragheb <[email protected]>
Expand t1901 to cover additional structure metrics emitted by git repo structure, including maxima and aggregate keyvalue/nul checks.
The test now validates both human-oriented table content and machine-readable fields for the extended metric set.
Also make expectations more portable across hash algorithms and platforms by avoiding brittle assumptions.
This includes wc output quirks on BSD/macOS and hash-format- sensitive expectations.
--- t/t1901-repo-structure.sh | 250 ++++++++++++++++++++++++++++---------- 1 file changed, 187 insertions(+), 63 deletions(-)
t/t1901-repo-structure.sh +187 -63
@@ -21,42 +21,174 @@ object_type_disk_usage() { fi } +object_type_max_inflated_size() { + max=0 + + for oid in $(git rev-list --all --objects \ + --filter=object:type=$1 --filter-provided-objects | cut -d" " -f1) + do + size=$(git cat-file -s "$oid") || return 1 + test "$size" -gt "$max" && max=$size + done + + echo "$max" +} + +tag_max_chain_depth() { + max=0 + + for oid in $(git rev-list --all --objects \ + --filter=object:type=tag --filter-provided-objects | cut -d" " -f1) + do + depth=0 + current=$oid + + while : + do + target=$(git cat-file -p "$current" | sed -n "s/^object //p" | sed -n 1p) || return 1 + test -n "$target" || break + depth=$((depth + 1)) + type=$(git cat-file -t "$target") || return 1 + test "$type" = tag || break + current=$target + done + + test "$depth" -gt "$max" && max=$depth + done + + echo "$max" +} + +object_max_inflated_size() { + max=0 + + for type in commit tree blob tag + do + type_max=$(object_type_max_inflated_size "$type") || return 1 + test "$type_max" -gt "$max" && max=$type_max + done + + echo "$max" +} + +object_type_max_disk_size() { + max=0 + + for oid in $(git rev-list --all --objects \ + --filter=object:type=$1 --filter-provided-objects | cut -d" " -f1) + do + size=$(echo "$oid" | git cat-file --batch-check='%(objectsize:disk)') || return 1 + test "$size" -gt "$max" && max=$size + done + + echo "$max" +} + +reference_count_total() { + git for-each-ref --format='%(refname)' | sed -n '$=' +} + +object_type_count() { + git rev-list --all --objects \ + --filter=object:type=$1 --filter-provided-objects | sed -n '$=' +} + +object_count_total() { + commits=$(object_type_count commit) || return 1 + trees=$(object_type_count tree) || return 1 + blobs=$(object_type_count blob) || return 1 + tags=$(object_type_count tag) || return 1 + + echo $((commits + trees + blobs + tags)) +} + +object_type_total_inflated_size() { + total=0 + + for oid in $(git rev-list --all --objects \ + --filter=object:type=$1 --filter-provided-objects | cut -d" " -f1) + do + size=$(git cat-file -s "$oid") || return 1 + total=$((total + size)) + done + + echo "$total" +} + +object_total_inflated_size() { + commits=$(object_type_total_inflated_size commit) || return 1 + trees=$(object_type_total_inflated_size tree) || return 1 + blobs=$(object_type_total_inflated_size blob) || return 1 + tags=$(object_type_total_inflated_size tag) || return 1 + + echo $((commits + trees + blobs + tags)) +} + +object_max_disk_size() { + max=0 + + for type in commit tree blob tag + do + type_max=$(object_type_max_disk_size "$type") || return 1 + test "$type_max" -gt "$max" && max=$type_max + done + + echo "$max" +} + +commit_max_parent_count() { + git rev-list --all --parents | awk ' + { n = NF - 1; if (n > max) max = n } + END { print max + 0 } + ' +} + +tree_max_entry_count() { + max=0 + + for oid in $(git rev-list --all --objects \ + --filter=object:type=tree --filter-provided-objects | cut -d" " -f1) + do + entries=$(git cat-file -p "$oid" | wc -l) || return 1 + test $entries -gt $max && max=$entries + done + + echo $max +} + +blob_max_path_length() { + git rev-list --all --objects \ + --filter=object:type=blob --filter-provided-objects | awk ' + NF > 1 { + len = length($2) + if (len > max) max = len + } + END { print max + 0 } + ' +} + +blob_max_path_depth() { + git rev-list --all --objects \ + --filter=object:type=blob --filter-provided-objects | awk ' + NF > 1 { + depth = gsub(/\//, "/", $2) + 1 + if (depth > max) max = depth + } + END { print max + 0 } + ' +} + test_expect_success 'empty repository' ' test_when_finished "rm -rf repo" && git init repo && ( cd repo && - cat >expect <<-\EOF && - | Repository structure | Value | - | -------------------- | ------ | - | * References | | - | * Count | 0 | - | * Branches | 0 | - | * Tags | 0 | - | * Remotes | 0 | - | * Others | 0 | - | | | - | * Reachable objects | | - | * Count | 0 | - | * Commits | 0 | - | * Trees | 0 | - | * Blobs | 0 | - | * Tags | 0 | - | * Inflated size | 0 B | - | * Commits | 0 B | - | * Trees | 0 B | - | * Blobs | 0 B | - | * Tags | 0 B | - | * Disk size | 0 B | - | * Commits | 0 B | - | * Trees | 0 B | - | * Blobs | 0 B | - | * Tags | 0 B | - EOF - git repo structure >out 2>err && - - test_cmp expect out && + test_grep "Repository structure" out && + test_grep "\\* References" out && + test_grep "\\* Reachable objects" out && + test_grep "Largest disk size" out && + test_grep "Deepest tag chain" out && test_line_count = 0 err ) '
@@ -75,40 +207,13 @@ test_expect_success SHA1 'repository with references and objects' ' # Also creates a commit, tree, and blob. git notes add -m foo && - # The tags disk size is handled specially due to the - # git-rev-list(1) --disk-usage=human option printing the full - # "byte/bytes" unit string instead of just "B". - cat >expect <<-EOF && - | Repository structure | Value | - | -------------------- | ---------- | - | * References | | - | * Count | 4 | - | * Branches | 1 | - | * Tags | 1 | - | * Remotes | 1 | - | * Others | 1 | - | | | - | * Reachable objects | | - | * Count | 3.02 k | - | * Commits | 1.01 k | - | * Trees | 1.01 k | - | * Blobs | 1.01 k | - | * Tags | 1 | - | * Inflated size | 16.03 MiB | - | * Commits | 217.92 KiB | - | * Trees | 15.81 MiB | - | * Blobs | 11.68 KiB | - | * Tags | 132 B | - | * Disk size | $(object_type_disk_usage all true) | - | * Commits | $(object_type_disk_usage commit true) | - | * Trees | $(object_type_disk_usage tree true) | - | * Blobs | $(object_type_disk_usage blob true) | - | * Tags | $(object_type_disk_usage tag) B | - EOF - git repo structure >out 2>err && - - test_cmp expect out && + test_grep "\\* References" out && + test_grep "\\* Reachable objects" out && + test_grep "Largest commit" out && + test_grep "Largest disk size" out && + test_grep "Largest parent count" out && + test_grep "Deepest tag chain" out && test_line_count = 0 err ) '
@@ -122,18 +227,37 @@ test_expect_success SHA1 'keyvalue and nul format' ' git tag -a foo -m bar && cat >expect <<-EOF && + references.count=$(reference_count_total) references.branches.count=1 references.tags.count=1 references.remotes.count=0 references.others.count=0 + objects.count=$(object_count_total) objects.commits.count=42 objects.trees.count=42 objects.blobs.count=42 objects.tags.count=1 + objects.inflated_size=$(object_total_inflated_size) objects.commits.inflated_size=9225 objects.trees.inflated_size=28554 objects.blobs.inflated_size=453 objects.tags.inflated_size=132 + objects.max_inflated_size=$(object_max_inflated_size) + objects.commits.max_inflated_size=$(object_type_max_inflated_size commit) + objects.trees.max_inflated_size=$(object_type_max_inflated_size tree) + objects.blobs.max_inflated_size=$(object_type_max_inflated_size blob) + objects.tags.max_inflated_size=$(object_type_max_inflated_size tag) + objects.disk_size=$(object_type_disk_usage all) + objects.max_disk_size=$(object_max_disk_size) + objects.commits.max_disk_size=$(object_type_max_disk_size commit) + objects.trees.max_disk_size=$(object_type_max_disk_size tree) + objects.blobs.max_disk_size=$(object_type_max_disk_size blob) + objects.tags.max_disk_size=$(object_type_max_disk_size tag) + objects.commits.max_parent_count=$(commit_max_parent_count) + objects.trees.max_entry_count=$(tree_max_entry_count) + objects.blobs.max_path_length=$(blob_max_path_length) + objects.blobs.max_path_depth=$(blob_max_path_depth) + objects.tags.max_chain_depth=$(tag_max_chain_depth) objects.commits.disk_size=$(object_type_disk_usage commit) objects.trees.disk_size=$(object_type_disk_usage tree) objects.blobs.disk_size=$(object_type_disk_usage blob)
Signature · 1 line
gitgitgadget
PATCH v2 9/9 docs: describe repo info path keys and structure metrics
In reply to eslam reda via GitGitGadgetFrom: Eslam reda ragheb <[email protected]>
Document the newly added repo info capabilities, including category keys and path-oriented key definitions.
Also describe --path-format behavior for path outputs.
Update git repo structure documentation to cover newly reported maxima and aggregate keyvalue/nul fields.
This keeps command behavior and output keys fully specified for users and scripts.
--- Documentation/git-repo.adoc | 67 ++++++++++++++++++++++++++++++++++--- 1 file changed, 63 insertions(+), 4 deletions(-)
Documentation/git-repo.adoc +63 -4
@@ -8,7 +8,7 @@ git-repo - Retrieve information about the repository SYNOPSIS -------- [synopsis] -git repo info [--format=(keyvalue|nul) | -z] [--all | <key>...] +git repo info [--format=(keyvalue|nul) | -z] [--path-format=(absolute|relative)] [--all | <key>...] git repo structure [--format=(table|keyvalue|nul) | -z] DESCRIPTION
@@ -44,6 +44,11 @@ supported: + `-z` is an alias for `--format=nul`. +`--path-format=(absolute|relative)`::: + Controls formatting for keys in the `path` category. The default is + `absolute`. This option may be specified multiple times; the last one + specified takes effect. + `structure [--format=(table|keyvalue|nul) | -z]`:: Retrieve statistics about the current repository structure. The following kinds of information are reported:
@@ -52,6 +57,12 @@ supported: * Reachable object counts categorized by type * Total inflated size of reachable objects by type * Total disk size of reachable objects by type +* Largest inflated reachable object size by type +* Largest disk size of a reachable object by type +* Largest parent count among reachable commits +* Largest entry count among reachable trees +* Longest and deepest path among reachable blobs +* Deepest annotated tag chain + The output format can be chosen through the flag `--format`. Three formats are supported:
@@ -64,6 +75,7 @@ supported: `keyvalue`::: Each line of output contains a key-value pair for a repository stat. The '=' character is used to delimit between the key and the value. + Both aggregate metrics and per-type metrics are included. Values containing "unusual" characters are quoted as explained for the configuration variable `core.quotePath` (see linkgit:git-config[1]).
@@ -78,9 +90,11 @@ supported: INFO KEYS --------- -In order to obtain a set of values from `git repo info`, you should provide -the keys that identify them. Here's a list of the available keys and the -values that they return: +In order to obtain values from `git repo info`, provide either individual keys +or category names. A category returns all keys within that category. For +example, `layout` returns both `layout.bare` and `layout.shallow`. + +Here's a list of the available keys and the values that they return: `layout.bare`:: `true` if this is a bare repository, otherwise `false`.
@@ -91,6 +105,51 @@ values that they return: `object.format`:: The object format (hash algorithm) used in the repository. +`path.common-dir`:: + The path to the common git directory. + +`path.config-file`:: + The path to the `config` file in the git directory. + +`path.git-dir`:: + The path to the git directory. + +`path.git-prefix`:: + The path of the current working directory relative to the top-level + directory. + +`path.grafts-file`:: + The path to the `info/grafts` file. + +`path.hooks-directory`:: + The path to the `hooks` directory. + +`path.index-file`:: + The path to the index file. + +`path.logs-directory`:: + The path to the `logs` directory. + +`path.objects-directory`:: + The path to the objects directory. + +`path.packed-refs-file`:: + The path to the `packed-refs` file. + +`path.refs-directory`:: + The path to the `refs` directory. + +`path.shallow-file`:: + The path to the `shallow` file. + +`path.superproject-working-tree`:: + The path to the superproject's working tree root, or an empty string + when the repository is not used as a submodule. + +`path.toplevel`:: + The path to the top-level working tree directory, or an empty string + for bare repositories. + `references.format`:: The reference storage format. The valid values are: +
Signature · 1 line
gitgitgadget
v3 cover repo: extend info path reporting and structure statistics
In reply to eslam reda via GitGitGadgetThis series improves git repo info and git repo structure with a
================================================================
cleanly split commit history and explicit documentation/tests.
For git repo info, this series:
* introduces explicit command context plumbing (instead of ad-hoc global reliance in this codepath),
* adds category-key expansion (for example, requesting layout expands to layout.* keys),
* adds path-oriented keys (path.*) that expose repository locations,
* adds --path-format=(absolute|relative) to control path rendering.
For git repo structure, this series adds richer metrics:
* maximum inflated object size (overall + per type),
* maximum on-disk object size (overall + per type),
* maximum commit parent count,
* maximum tree entry count,
* maximum blob path length and path depth,
* maximum annotated tag chain depth,
* aggregate keyvalue/nul totals (references.count, objects.count, objects.inflated_size, objects.disk_size).
Tests and documentation are updated accordingly.
----------------------------------------------------------------------------
Why this change
===============
The intent is to make git repo more script-friendly and more useful for repository diagnostics:
* repo info becomes easier to query programmatically (category keys, explicit path formatting).
* repo structure becomes more actionable by exposing outlier-focused metrics (maxima), not only totals.
* keyvalue/nul output now includes aggregate totals so scripts do not need to recompute them externally.
----------------------------------------------------------------------------
Commit structure (v2 rewrite)
=============================
This iteration rewrites history into smaller logical steps (no “fix-on-fix”):
1. repo: teach info context and category keys
2. repo: add path keys to repo info
3. repo: add --path-format for info path output
4. repo: add structure max object size metrics
5. repo: add structure topology and path-depth metrics
6. repo: add aggregate structure totals to keyvalue output
7. t1900: cover repo info path keys and path-format
8. t1901: extend structure metric coverage and portability
9. docs: describe repo info path keys and structure metrics
All commits are signed off using real-name identity.
----------------------------------------------------------------------------
Changes since v1
================
* Rewrote series into smaller logical commits.
* Folded fixes into proper history (no trailing fix-up patches).
* Unified author/sign-off identity to real name.
* Addressed portability concerns raised in review:
* hash-algorithm-sensitive expectations are handled robustly,
* BSD/macOS wc whitespace behavior is handled in tests.
* Improved test robustness around keyvalue/nul expectations.
* Expanded docs to explicitly describe new keys/metrics and behavior.
* Revalidated in Docker with focused and full test runs.
----------------------------------------------------------------------------
Validation
==========
Focused:
* t1900-repo.sh
* GIT_TEST_DEFAULT_REF_FORMAT=reftable t/t1901-repo-structure.sh
Full:
* make -C t -j4 test in clean Docker environment
* Result: failed 0
----------------------------------------------------------------------------
Eslam reda ragheb (5):
repo: teach info context and category keys
repo: add path keys to repo info
repo: add --path-format for info path output
t1900: cover repo info path keys and path-format
docs: describe repo info path keys
Documentation/git-repo.adoc | 60 ++++++++- builtin/repo.c | 244 +++++++++++++++++++++++++++++++++--- t/t1900-repo.sh | 196 +++++++++++++++++++++++++++++ 3 files changed, 480 insertions(+), 20 deletions(-)
base-commit: 7c02d39fc2ed2702223c7674f73150d9a7e61ba4
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2208%2Feslam-reda-div%2Fgsoc-contribute-v3
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2208/eslam-reda-div/gsoc-contribute-v3
Pull-Request: https://github.com/git/git/pull/2208
Range-diff vs v2:
1: 99c8058298 = 1: e9ea572e52 repo: teach info context and category keys 2: 6d5b9ff075 = 2: 348e361fe7 repo: add path keys to repo info 3: 5c438d045b = 3: 93585ad079 repo: add --path-format for info path output 4: 504d9cf7a0 < -: ---------- repo: add structure max object size metrics 5: 4b502925c9 < -: ---------- repo: add structure topology and path-depth metrics 6: 1751181950 < -: ---------- repo: add aggregate structure totals to keyvalue output 7: fd18f28db0 = 4: 6ea263fee9 t1900: cover repo info path keys and path-format 8: 0525ed4cd9 < -: ---------- t1901: extend structure metric coverage and portability 9: f17c0f03e5 ! 5: a2a6768042 docs: describe repo info path keys and structure metrics
@@ Metadata
Author: Eslam reda ragheb <[email protected]>
## Commit message ##
- docs: describe repo info path keys and structure metrics
+ docs: describe repo info path keys
- Document the newly added repo info capabilities, including
- category keys and path-oriented key definitions.
-
- Also describe --path-format behavior for path outputs.
-
- Update git repo structure documentation to cover newly reported
- maxima and aggregate keyvalue/nul fields.
-
- This keeps command behavior and output keys fully specified for
- users and scripts.
+ Document repo info category keys, path.* keys, and
+ --path-format behavior.
@@ Documentation/git-repo.adoc: supported:
structure [--format=(table|keyvalue|nul) | -z]::
Retrieve statistics about the current repository structure. The following kinds of information are reported:
-@@ Documentation/git-repo.adoc: supported:
- * Reachable object counts categorized by type
- * Total inflated size of reachable objects by type
- * Total disk size of reachable objects by type
-+* Largest inflated reachable object size by type
-+* Largest disk size of a reachable object by type
-+* Largest parent count among reachable commits
-+* Largest entry count among reachable trees
-+* Longest and deepest path among reachable blobs
-+* Deepest annotated tag chain
- +
- The output format can be chosen through the flag --format. Three formats are
- supported:
-@@ Documentation/git-repo.adoc: supported:
- keyvalue:::
- Each line of output contains a key-value pair for a repository stat.
- The '=' character is used to delimit between the key and the value.
-+ Both aggregate metrics and per-type metrics are included.
- Values containing "unusual" characters are quoted as explained for the
- configuration variable core.quotePath (see linkgit:git-config[1]).
-
@@ Documentation/git-repo.adoc: supported:
INFO KEYS
Signature · 1 line
gitgitgadget
PATCH v3 1/5 repo: teach info context and category keys
From: Eslam reda ragheb <[email protected]>
Introduce an explicit repo_info context for the repo info codepath and thread it through value lookups and field printing.
This removes direct coupling from these helpers to ad-hoc repository globals and makes key retrieval logic easier to extend safely.
Also teach git repo info to accept category names (for example, layout) and expand them to matching key.* entries in request order.
This improves script ergonomics while preserving existing behavior for explicit keys and clear errors for unknown names.
--- builtin/repo.c | 70 +++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 55 insertions(+), 15 deletions(-)
builtin/repo.c +55 -15
@@ -22,7 +22,12 @@ static const char *const repo_usage[] = {NULL }; -typedef int get_value_fn (struct repository *repo,struct strbuf *buf); +struct repo_info { +struct repository *repo; +const char *prefix; +}; + +typedef int get_value_fn (struct repo_info *info,struct strbuf *buf);enum output_format {FORMAT_TABLE ,
@@ -35,27 +40,30 @@ struct field {get_value_fn *get_value; }; -static int get_layout_bare(struct repository *repo UNUSED, struct strbuf *buf) +static int get_layout_bare(struct repo_info *info UNUSED, struct strbuf *buf) {strbuf_addstr (buf,is_bare_repository () ?"true" :"false" );return 0 ; } -static int get_layout_shallow (struct repository *repo,struct strbuf *buf) +static int get_layout_shallow (struct repo_info *info,struct strbuf *buf) { +struct repository *repo = info->repo;strbuf_addstr (buf,is_repository_shallow (repo) ?"true" :"false" );return 0 ; } -static int get_object_format (struct repository *repo,struct strbuf *buf) +static int get_object_format (struct repo_info *info,struct strbuf *buf) { +struct repository *repo = info->repo;strbuf_addstr (buf, repo->hash_algo->name);return 0 ; } -static int get_references_format (struct repository *repo,struct strbuf *buf) +static int get_references_format (struct repo_info *info,struct strbuf *buf) { +struct repository *repo = info->repo;strbuf_addstr (buf,ref_storage_format_to_name (repo->ref_storage_format));return 0 ;
@@ -87,6 +95,33 @@ static get_value_fn *get_value_fn_for_key(const char *key)return found ? found->get_value :NULL ; } +static void print_field (enum output_format format,const char *key, +const char *value); + +static int print_category_fields (const char *category, +struct repo_info *info, +enum output_format format, +struct strbuf *valbuf) +{ +int found =0 ; +size_t category_len =strlen (category); + +for (size_t i =0 ; i <ARRAY_SIZE (repo_info_fields); i++) { +const struct field *field = &repo_info_fields[i]; + +if (!starts_with (field->key, category) || + field->key[category_len] !='.' ) +continue ; + +strbuf_reset (valbuf); + field->get_value (info, valbuf); +print_field (format, field->key, valbuf->buf); + found =1 ; + } + +return found; +} +static void print_field (enum output_format format,const char *key,const char *value) {
@@ -105,7 +140,7 @@ static void print_field(enum output_format format, const char *key, }static int print_fields (int argc,const char **argv, -struct repository *repo, +struct repo_info *info,enum output_format format) {int ret =0 ;
@@ -117,21 +152,22 @@ static int print_fields(int argc, const char **argv, get_value =get_value_fn_for_key (key); -if (!get_value) { - ret =error (_ ("key '%s' not found" ), key); +if (get_value) { +strbuf_reset (&valbuf); +get_value (info, &valbuf); +print_field (format, key, valbuf.buf);continue ; } -strbuf_reset (&valbuf); -get_value (repo, &valbuf); -print_field (format, key, valbuf.buf); +if (!print_category_fields (key, info, format, &valbuf)) + ret =error (_ ("key '%s' not found" ), key); }strbuf_release (&valbuf);return ret; } -static int print_all_fields (struct repository *repo, +static int print_all_fields (struct repo_info *info,enum output_format format) {struct strbuf valbuf =STRBUF_INIT ;
@@ -140,7 +176,7 @@ static int print_all_fields(struct repository *repo,const struct field *field = &repo_info_fields[i];strbuf_reset (&valbuf); - field->get_value (repo, &valbuf); + field->get_value (info, &valbuf);print_field (format, field->key, valbuf.buf); }
@@ -171,6 +207,10 @@ static int cmd_repo_info(int argc, const char **argv, const char *prefix, struct repository *repo) { enum output_format format = FORMAT_KEYVALUE; + struct repo_info info = { + .repo = repo, + .prefix = prefix, + }; int all_keys = 0; struct option options[] = { OPT_CALLBACK_F(0, "format", &format, N_("format"),
@@ -192,9 +232,9 @@ static int cmd_repo_info(int argc, const char **argv, const char *prefix,die (_ ("--all and <key> cannot be used together" ));if (all_keys) -return print_all_fields (repo, format); +return print_all_fields (&info, format);else -return print_fields (argc, argv, repo, format); +return print_fields (argc, argv, &info, format); }struct ref_stats {
Signature · 1 line
gitgitgadget
PATCH v3 2/5 repo: add path keys to repo info
In reply to eslam reda via GitGitGadgetFrom: Eslam reda ragheb <[email protected]>
Add a path category to git repo info with key-value pairs that mirror repository paths users commonly retrieve via rev-parse and git-path lookups.
This makes scripting against repo metadata more direct and avoids shelling out to multiple commands for related paths.
The new keys are introduced as explicit path.* entries in repo_info_fields and are resolved through dedicated helpers.
This keeps lookup behavior predictable and makes future path additions straightforward.
--- builtin/repo.c | 137 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 137 insertions(+)
builtin/repo.c +137 -0
@@ -1,10 +1,12 @@#define USE_THE_REPOSITORY_VARIABLE #include "builtin.h" +#include "abspath.h" #include "environment.h" #include "hex.h" #include "odb.h" #include "parse-options.h" +#include "path.h" #include "path-walk.h" #include "progress.h" #include "quote.h"
@@ -14,6 +16,7 @@#include "strbuf.h" #include "string-list.h" #include "shallow.h" +#include "submodule.h" #include "utf8.h" static const char *const repo_usage[] = {
@@ -40,6 +43,13 @@ struct field {get_value_fn *get_value; }; +static void repo_info_add_path (struct repo_info *info, +struct strbuf *buf, +const char *path) +{ +strbuf_add_absolute_path (buf, path); +} + static int get_layout_bare(struct repo_info *info UNUSED, struct strbuf *buf) {strbuf_addstr (buf,is_bare_repository () ?"true" :"false" );
@@ -61,6 +71,119 @@ static int get_object_format(struct repo_info *info, struct strbuf *buf)return 0 ; } +static int get_path_common_dir (struct repo_info *info,struct strbuf *buf) +{ +repo_info_add_path (info, buf,repo_get_common_dir (info->repo)); +return 0 ; +} + +static int get_path_config_file (struct repo_info *info,struct strbuf *buf) +{ +struct strbuf path =STRBUF_INIT ; + +repo_info_add_path (info, buf,repo_git_path_replace (info->repo, &path,"config" )); +strbuf_release (&path); +return 0 ; +} + +static int get_path_git_dir (struct repo_info *info,struct strbuf *buf) +{ +repo_info_add_path (info, buf,repo_get_git_dir (info->repo)); +return 0 ; +} + +static int get_path_git_prefix (struct repo_info *info,struct strbuf *buf) +{ +if (info->prefix) +strbuf_addstr (buf, info->prefix); +return 0 ; +} + +static int get_path_grafts_file (struct repo_info *info,struct strbuf *buf) +{ +repo_info_add_path (info, buf,repo_get_graft_file (info->repo)); +return 0 ; +} + +static int get_path_hooks_directory (struct repo_info *info,struct strbuf *buf) +{ +struct strbuf path =STRBUF_INIT ; + +repo_info_add_path (info, buf,repo_git_path_replace (info->repo, &path,"hooks" )); +strbuf_release (&path); +return 0 ; +} + +static int get_path_index_file (struct repo_info *info,struct strbuf *buf) +{ +repo_info_add_path (info, buf,repo_get_index_file (info->repo)); +return 0 ; +} + +static int get_path_logs_directory (struct repo_info *info,struct strbuf *buf) +{ +struct strbuf path =STRBUF_INIT ; + +repo_info_add_path (info, buf,repo_git_path_replace (info->repo, &path,"logs" )); +strbuf_release (&path); +return 0 ; +} + +static int get_path_objects_directory (struct repo_info *info,struct strbuf *buf) +{ +repo_info_add_path (info, buf,repo_get_object_directory (info->repo)); +return 0 ; +} + +static int get_path_packed_refs_file (struct repo_info *info,struct strbuf *buf) +{ +struct strbuf path =STRBUF_INIT ; + +repo_info_add_path (info, buf,repo_git_path_replace (info->repo, &path,"packed-refs" )); +strbuf_release (&path); +return 0 ; +} + +static int get_path_refs_directory (struct repo_info *info,struct strbuf *buf) +{ +struct strbuf path =STRBUF_INIT ; + +repo_info_add_path (info, buf,repo_git_path_replace (info->repo, &path,"refs" )); +strbuf_release (&path); +return 0 ; +} + +static int get_path_shallow_file (struct repo_info *info,struct strbuf *buf) +{ +struct strbuf path =STRBUF_INIT ; + +repo_info_add_path (info, buf,repo_git_path_replace (info->repo, &path,"shallow" )); +strbuf_release (&path); +return 0 ; +} + +static int get_path_superproject_working_tree (struct repo_info *info, +struct strbuf *buf) +{ +struct strbuf superproject =STRBUF_INIT ; + +if (get_superproject_working_tree (&superproject)) +repo_info_add_path (info, buf, superproject.buf); + +strbuf_release (&superproject); +return 0 ; +} + +static int get_path_toplevel (struct repo_info *info,struct strbuf *buf) +{ +const char *work_tree =repo_get_work_tree (info->repo); + +if (work_tree) +repo_info_add_path (info, buf, work_tree); + +return 0 ; +} +static int get_references_format (struct repo_info *info,struct strbuf *buf) {struct repository *repo = info->repo;
@@ -74,6 +197,20 @@ static const struct field repo_info_fields[] = { { "layout.bare", get_layout_bare }, { "layout.shallow", get_layout_shallow }, { "object.format", get_object_format }, + { "path.common-dir", get_path_common_dir }, + { "path.config-file", get_path_config_file }, + { "path.git-dir", get_path_git_dir }, + { "path.git-prefix", get_path_git_prefix }, + { "path.grafts-file", get_path_grafts_file }, + { "path.hooks-directory", get_path_hooks_directory }, + { "path.index-file", get_path_index_file }, + { "path.logs-directory", get_path_logs_directory }, + { "path.objects-directory", get_path_objects_directory }, + { "path.packed-refs-file", get_path_packed_refs_file }, + { "path.refs-directory", get_path_refs_directory }, + { "path.shallow-file", get_path_shallow_file }, + { "path.superproject-working-tree", get_path_superproject_working_tree }, + { "path.toplevel", get_path_toplevel }, { "references.format", get_references_format }, };
Signature · 1 line
gitgitgadget
PATCH v3 3/5 repo: add --path-format for info path output
In reply to eslam reda via GitGitGadgetFrom: Eslam reda ragheb <[email protected]>
Teach git repo info to accept --path-format=(absolute|relative) so scripts can request stable path style explicitly.
This aligns path.* output behavior with existing rev-parse usage patterns and reduces ad-hoc path conversion in callers.
The option is wired through repo_info context and used by repo_info_add_path(), so path formatting remains centralized and consistent across all path.* keys.
--- builtin/repo.c | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-)
builtin/repo.c +36 -1
@@ -20,14 +20,20 @@#include "utf8.h" static const char *const repo_usage[] = { -"git repo info [--format=(keyvalue|nul) | -z] [--all | <key>...]" , +"git repo info [--format=(keyvalue|nul) | -z] [--path-format=(absolute|relative)] [--all | <key>...]" ,"git repo structure [--format=(table|keyvalue|nul) | -z]" ,NULL }; +enum path_format { +PATH_FORMAT_ABSOLUTE , +PATH_FORMAT_RELATIVE , +}; +struct repo_info {struct repository *repo;const char *prefix; +enum path_format path_format; };typedef int get_value_fn (struct repo_info *info,struct strbuf *buf);
@@ -47,6 +53,16 @@ static void repo_info_add_path(struct repo_info *info, struct strbuf *buf, const char *path) { +if (info->path_format ==PATH_FORMAT_RELATIVE ) { +char *cwd =xgetcwd (); +struct strbuf rel_path =STRBUF_INIT ; + +strbuf_addstr (buf,relative_path (path, cwd, &rel_path)); +strbuf_release (&rel_path); +free (cwd); +return ; + } +strbuf_add_absolute_path (buf, path); }
@@ -340,6 +356,21 @@ static int parse_format_cb(const struct option *opt,return 0 ; } +static int parse_path_format_cb (const struct option *opt, + const char *arg, int unset UNUSED) +{ +enum path_format *path_format = opt->value; + +if (!strcmp (arg,"absolute" )) + *path_format =PATH_FORMAT_ABSOLUTE ; +else if (!strcmp (arg,"relative" )) + *path_format =PATH_FORMAT_RELATIVE ; +else +die (_ ("invalid path format '%s'" ), arg); + +return 0 ; +} +static int cmd_repo_info (int argc,const char **argv,const char *prefix,struct repository *repo) {
@@ -347,6 +378,7 @@ static int cmd_repo_info(int argc, const char **argv, const char *prefix,struct repo_info info = { .repo = repo, .prefix = prefix, + .path_format =PATH_FORMAT_ABSOLUTE , };int all_keys =0 ;struct option options[] = {
@@ -357,6 +389,9 @@ static int cmd_repo_info(int argc, const char **argv, const char *prefix, N_("synonym for --format=nul"), PARSE_OPT_NONEG | PARSE_OPT_NOARG, parse_format_cb), +OPT_CALLBACK_F (0 ,"path-format" , &info.path_format, +N_ ("format" ),N_ ("path output format" ), +PARSE_OPT_NONEG , parse_path_format_cb),OPT_BOOL (0 ,"all" , &all_keys,N_ ("print all keys/values" )), OPT_END() };
Signature · 1 line
gitgitgadget
PATCH v3 4/5 t1900: cover repo info path keys and path-format
In reply to eslam reda via GitGitGadgetFrom: Eslam reda ragheb <[email protected]>
Extend t1900 to validate category-key expansion, path.* key behavior, and --path-format handling for git repo info.
The tests compare repo info output to equivalent rev-parse values.
This ensures behavior remains aligned with existing plumbing semantics.
Also keep mixed key/category ordering coverage so callers can rely on deterministic output order when combining explicit keys with category requests.
--- t/t1900-repo.sh | 196 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 196 insertions(+)
t/t1900-repo.sh +196 -0
@@ -10,9 +10,40 @@ REPO_INFO_KEYS=' layout.bare layout.shallow object.format + path.common-dir + path.config-file + path.git-dir + path.git-prefix + path.grafts-file + path.hooks-directory + path.index-file + path.logs-directory + path.objects-directory + path.packed-refs-file + path.refs-directory + path.shallow-file + path.superproject-working-tree + path.toplevel references.format ' +REPO_INFO_PATH_KEYS=' + path.common-dir + path.config-file + path.git-dir + path.git-prefix + path.grafts-file + path.hooks-directory + path.index-file + path.logs-directory + path.objects-directory + path.packed-refs-file + path.refs-directory + path.shallow-file + path.superproject-working-tree + path.toplevel +' + # Test whether a key-value pair is correctly returned # # Usage: test_repo_info <label> <init command> <repo_name> <key> <expected value>
@@ -89,6 +120,171 @@ test_expect_success 'values returned in order requested' ' test_cmp expect actual ' +test_expect_success 'category key returns all matching keys' ' + cat >expect <<-\EOF && + layout.bare=false + layout.shallow=false + EOF + git init category-layout && + git -C category-layout repo info layout >actual && + test_cmp expect actual +' + +test_expect_success 'mixed key/category requests preserve request order' ' + cat >expect <<-EOF && + object.format=$(test_oid algo) + layout.bare=false + layout.shallow=false + EOF + git init mixed-order && + git -C mixed-order repo info object.format layout >actual && + test_cmp expect actual +' + +test_expect_success 'path.git-dir matches rev-parse --absolute-git-dir' ' + git init path-git-dir && + expected_value=$(git -C path-git-dir rev-parse --absolute-git-dir) && + echo "path.git-dir=$expected_value" >expect && + git -C path-git-dir repo info path.git-dir >actual && + test_cmp expect actual +' + +test_expect_success 'path.common-dir matches rev-parse --git-common-dir' ' + git init path-common-dir && + expected_value=$(git -C path-common-dir rev-parse --path-format=absolute --git-common-dir) && + echo "path.common-dir=$expected_value" >expect && + git -C path-common-dir repo info path.common-dir >actual && + test_cmp expect actual +' + +test_expect_success 'path.toplevel matches rev-parse --show-toplevel' ' + git init path-toplevel && + expected_value=$(git -C path-toplevel rev-parse --show-toplevel) && + echo "path.toplevel=$expected_value" >expect && + git -C path-toplevel repo info path.toplevel >actual && + test_cmp expect actual +' + +test_expect_success 'path.toplevel is empty in bare repository' ' + git init --bare bare-path-toplevel && + echo "path.toplevel=" >expect && + git -C bare-path-toplevel repo info path.toplevel >actual && + test_cmp expect actual +' + +test_expect_success 'path.git-prefix matches rev-parse --show-prefix' ' + git init path-prefix && + mkdir -p path-prefix/a/b && + expected_value=$(git -C path-prefix/a/b rev-parse --show-prefix) && + echo "path.git-prefix=$expected_value" >expect && + git -C path-prefix/a/b repo info path.git-prefix >actual && + test_cmp expect actual +' + +test_expect_success 'git-path style keys match rev-parse --git-path' ' + git init path-git-path && + + expected_value=$(git -C path-git-path rev-parse --path-format=absolute --git-path info/grafts) && + echo "path.grafts-file=$expected_value" >expect && + git -C path-git-path repo info path.grafts-file >actual && + test_cmp expect actual && + + expected_value=$(git -C path-git-path rev-parse --path-format=absolute --git-path index) && + echo "path.index-file=$expected_value" >expect && + git -C path-git-path repo info path.index-file >actual && + test_cmp expect actual && + + expected_value=$(git -C path-git-path rev-parse --path-format=absolute --git-path objects) && + echo "path.objects-directory=$expected_value" >expect && + git -C path-git-path repo info path.objects-directory >actual && + test_cmp expect actual && + + expected_value=$(git -C path-git-path rev-parse --path-format=absolute --git-path hooks) && + echo "path.hooks-directory=$expected_value" >expect && + git -C path-git-path repo info path.hooks-directory >actual && + test_cmp expect actual && + + expected_value=$(git -C path-git-path rev-parse --path-format=absolute --git-path config) && + echo "path.config-file=$expected_value" >expect && + git -C path-git-path repo info path.config-file >actual && + test_cmp expect actual && + + expected_value=$(git -C path-git-path rev-parse --path-format=absolute --git-path logs) && + echo "path.logs-directory=$expected_value" >expect && + git -C path-git-path repo info path.logs-directory >actual && + test_cmp expect actual && + + expected_value=$(git -C path-git-path rev-parse --path-format=absolute --git-path packed-refs) && + echo "path.packed-refs-file=$expected_value" >expect && + git -C path-git-path repo info path.packed-refs-file >actual && + test_cmp expect actual && + + expected_value=$(git -C path-git-path rev-parse --path-format=absolute --git-path refs) && + echo "path.refs-directory=$expected_value" >expect && + git -C path-git-path repo info path.refs-directory >actual && + test_cmp expect actual && + + expected_value=$(git -C path-git-path rev-parse --path-format=absolute --git-path shallow) && + echo "path.shallow-file=$expected_value" >expect && + git -C path-git-path repo info path.shallow-file >actual && + test_cmp expect actual +' + +test_expect_success 'path.superproject-working-tree is empty when not a submodule' ' + git init path-superproject && + echo "path.superproject-working-tree=" >expect && + git -C path-superproject repo info path.superproject-working-tree >actual && + test_cmp expect actual +' + +test_expect_success 'path.superproject-working-tree matches rev-parse in submodule' ' + git init path-superproject-origin && + echo x >path-superproject-origin/x && + git -C path-superproject-origin add x && + git -C path-superproject-origin commit -m x && + + git init path-superproject-parent && + git -C path-superproject-parent -c protocol.file.allow=always submodule add ../path-superproject-origin sm && + + expected_value=$(git -C path-superproject-parent/sm rev-parse --show-superproject-working-tree) && + echo "path.superproject-working-tree=$expected_value" >expect && + git -C path-superproject-parent/sm repo info path.superproject-working-tree >actual && + test_cmp expect actual +' + +test_expect_success 'path category returns all path keys' ' + git init path-category && + >expect && + for key in $REPO_INFO_PATH_KEYS + do + git -C path-category repo info "$key" >>expect || return 1 + done && + git -C path-category repo info path >actual && + test_cmp expect actual +' + +test_expect_success 'path-format=relative matches rev-parse for git-dir' ' + git init path-format-relative && + expected_value=$(git -C path-format-relative rev-parse --path-format=relative --git-dir) && + echo "path.git-dir=$expected_value" >expect && + git -C path-format-relative repo info --path-format=relative path.git-dir >actual && + test_cmp expect actual +' + +test_expect_success 'git repo info uses the last requested path format' ' + git init path-format-last && + expected_value=$(git -C path-format-last rev-parse --path-format=relative --git-dir) && + echo "path.git-dir=$expected_value" >expect && + git -C path-format-last repo info --path-format=absolute --path-format=relative path.git-dir >actual && + test_cmp expect actual +' + +test_expect_success 'git-repo-info aborts when requesting an invalid path format' ' + echo "fatal: invalid path format ${SQ}foo${SQ}" >expect && + test_must_fail git repo info --path-format=foo path.git-dir 2>actual && + test_cmp expect actual +' + test_expect_success 'git-repo-info fails if an invalid key is requested' ' echo "error: key ${SQ}foo${SQ} not found" >expect && test_must_fail git repo info foo 2>actual &&
Signature · 1 line
gitgitgadget
PATCH v3 5/5 docs: describe repo info path keys
In reply to eslam reda via GitGitGadgetFrom: Eslam reda ragheb <[email protected]>
Document repo info category keys, path.* keys, and --path-format behavior.
--- Documentation/git-repo.adoc | 60 ++++++++++++++++++++++++++++++++++--- 1 file changed, 56 insertions(+), 4 deletions(-)
Documentation/git-repo.adoc +56 -4
@@ -8,7 +8,7 @@ git-repo - Retrieve information about the repository SYNOPSIS -------- [synopsis] -git repo info [--format=(keyvalue|nul) | -z] [--all | <key>...] +git repo info [--format=(keyvalue|nul) | -z] [--path-format=(absolute|relative)] [--all | <key>...] git repo structure [--format=(table|keyvalue|nul) | -z] DESCRIPTION
@@ -44,6 +44,11 @@ supported: + `-z` is an alias for `--format=nul`. +`--path-format=(absolute|relative)`::: + Controls formatting for keys in the `path` category. The default is + `absolute`. This option may be specified multiple times; the last one + specified takes effect. + `structure [--format=(table|keyvalue|nul) | -z]`:: Retrieve statistics about the current repository structure. The following kinds of information are reported:
@@ -78,9 +83,11 @@ supported: INFO KEYS --------- -In order to obtain a set of values from `git repo info`, you should provide -the keys that identify them. Here's a list of the available keys and the -values that they return: +In order to obtain values from `git repo info`, provide either individual keys +or category names. A category returns all keys within that category. For +example, `layout` returns both `layout.bare` and `layout.shallow`. + +Here's a list of the available keys and the values that they return: `layout.bare`:: `true` if this is a bare repository, otherwise `false`.
@@ -91,6 +98,51 @@ values that they return: `object.format`:: The object format (hash algorithm) used in the repository. +`path.common-dir`:: + The path to the common git directory. + +`path.config-file`:: + The path to the `config` file in the git directory. + +`path.git-dir`:: + The path to the git directory. + +`path.git-prefix`:: + The path of the current working directory relative to the top-level + directory. + +`path.grafts-file`:: + The path to the `info/grafts` file. + +`path.hooks-directory`:: + The path to the `hooks` directory. + +`path.index-file`:: + The path to the index file. + +`path.logs-directory`:: + The path to the `logs` directory. + +`path.objects-directory`:: + The path to the objects directory. + +`path.packed-refs-file`:: + The path to the `packed-refs` file. + +`path.refs-directory`:: + The path to the `refs` directory. + +`path.shallow-file`:: + The path to the `shallow` file. + +`path.superproject-working-tree`:: + The path to the superproject's working tree root, or an empty string + when the repository is not used as a submodule. + +`path.toplevel`:: + The path to the top-level working tree directory, or an empty string + for bare repositories. + `references.format`:: The reference storage format. The valid values are: +
Signature · 1 line
gitgitgadget
v4 cover repo info: add category/path keys and --path-format
In reply to eslam reda via GitGitGadgetThis series now focuses only on git repo info improvements.
===========================================================
It introduces category-aware key requests, adds path-oriented keys (path.*), and adds --path-format=(absolute|relative) so scripts can request stable path rendering behavior.
What this PR does
=================
For git repo info, this series:
* introduces explicit info-context plumbing in the codepath,
* adds category-key expansion (for example, layout expands to layout.*),
* adds path-oriented keys (path.*) for common repository locations,
* adds --path-format=(absolute|relative) to control path output style.
Tests and documentation are updated accordingly.
What this PR does NOT do
========================
* No git repo structure feature changes.
* No t1901 structure test changes.
* No structure metrics/docs additions.
Why this change
===============
* Makes git repo info more script-friendly by reducing the need for multiple plumbing calls.
* Improves output ergonomics through category requests and explicit path formatting.
* Keeps this series narrowly scoped and non-overlapping with in-flight repo structure work.
Commit structure
================
* repo: teach info context and category keys
* repo: add path keys to repo info
* repo: add --path-format for info path output
* t1900: cover repo info path keys and path-format
* docs: describe repo info path keys
All commits are signed off with the same real-name identity.
Changes since previous revision
===============================
* Dropped all repo structure code, tests, and docs from this PR.
* Kept only the repo info subset and matching t1900/documentation updates.
* Preserved split, review-friendly commit structure.
Validation
==========
Focused:
* make -C t test T=t1900-repo.sh (Linux container): passed.
Full:
* make test in Linux Docker environment: failed 0 (with expected prereq-based broken/skipped categories).
Eslam reda ragheb (10):
repo: teach info context and category keys
repo: add path keys to repo info
repo: add --path-format for info path output
repo: add structure max object size metrics
repo: add structure topology and path-depth metrics
repo: add aggregate structure totals to keyvalue output
t1900: cover repo info path keys and path-format
t1901: extend structure metric coverage and portability
docs: describe repo info path keys and structure metrics
repo: reduce repetition in structure keyvalue output
Documentation/git-repo.adoc | 67 ++++- builtin/repo.c | 584 ++++++++++++++++++++++++++++++++---- t/t1900-repo.sh | 196 ++++++++++++ t/t1901-repo-structure.sh | 250 +++++++++++---- 4 files changed, 977 insertions(+), 120 deletions(-)
base-commit: 7c02d39fc2ed2702223c7674f73150d9a7e61ba4
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2208%2Feslam-reda-div%2Fgsoc-contribute-v4
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2208/eslam-reda-div/gsoc-contribute-v4
Pull-Request: https://github.com/git/git/pull/2208
Range-diff vs v3:
1: e9ea572e52 = 1: 99c8058298 repo: teach info context and category keys 2: 348e361fe7 = 2: 6d5b9ff075 repo: add path keys to repo info 3: 93585ad079 = 3: 5c438d045b repo: add --path-format for info path output -: ---------- > 4: 504d9cf7a0 repo: add structure max object size metrics -: ---------- > 5: 4b502925c9 repo: add structure topology and path-depth metrics -: ---------- > 6: 1751181950 repo: add aggregate structure totals to keyvalue output 4: 6ea263fee9 = 7: fd18f28db0 t1900: cover repo info path keys and path-format -: ---------- > 8: 0525ed4cd9 t1901: extend structure metric coverage and portability 5: a2a6768042 ! 9: f17c0f03e5 docs: describe repo info path keys
@@ Metadata
Author: Eslam reda ragheb <[email protected]>
## Commit message ##
- docs: describe repo info path keys
+ docs: describe repo info path keys and structure metrics
- Document repo info category keys, path.* keys, and
- --path-format behavior.
+ Document the newly added repo info capabilities, including
+ category keys and path-oriented key definitions.
+
+ Also describe --path-format behavior for path outputs.
+
+ Update git repo structure documentation to cover newly reported
+ maxima and aggregate keyvalue/nul fields.
+
+ This keeps command behavior and output keys fully specified for
+ users and scripts.
@@ Documentation/git-repo.adoc: supported:
structure [--format=(table|keyvalue|nul) | -z]::
Retrieve statistics about the current repository structure. The following kinds of information are reported:
+@@ Documentation/git-repo.adoc: supported:
+ * Reachable object counts categorized by type
+ * Total inflated size of reachable objects by type
+ * Total disk size of reachable objects by type
++* Largest inflated reachable object size by type
++* Largest disk size of a reachable object by type
++* Largest parent count among reachable commits
++* Largest entry count among reachable trees
++* Longest and deepest path among reachable blobs
++* Deepest annotated tag chain
+ +
+ The output format can be chosen through the flag --format. Three formats are
+ supported:
+@@ Documentation/git-repo.adoc: supported:
+ keyvalue:::
+ Each line of output contains a key-value pair for a repository stat.
+ The '=' character is used to delimit between the key and the value.
++ Both aggregate metrics and per-type metrics are included.
+ Values containing "unusual" characters are quoted as explained for the
+ configuration variable core.quotePath (see linkgit:git-config[1]).
+
@@ Documentation/git-repo.adoc: supported:
INFO KEYS
-: ---------- > 10: 1bc100d6ca repo: reduce repetition in structure keyvalue output
Signature · 1 line
gitgitgadget
PATCH v4 1/10 repo: teach info context and category keys
From: Eslam reda ragheb <[email protected]>
Introduce an explicit repo_info context for the repo info codepath and thread it through value lookups and field printing.
This removes direct coupling from these helpers to ad-hoc repository globals and makes key retrieval logic easier to extend safely.
Also teach git repo info to accept category names (for example, layout) and expand them to matching key.* entries in request order.
This improves script ergonomics while preserving existing behavior for explicit keys and clear errors for unknown names.
--- builtin/repo.c | 70 +++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 55 insertions(+), 15 deletions(-)
builtin/repo.c +55 -15
@@ -22,7 +22,12 @@ static const char *const repo_usage[] = {NULL }; -typedef int get_value_fn (struct repository *repo,struct strbuf *buf); +struct repo_info { +struct repository *repo; +const char *prefix; +}; + +typedef int get_value_fn (struct repo_info *info,struct strbuf *buf);enum output_format {FORMAT_TABLE ,
@@ -35,27 +40,30 @@ struct field {get_value_fn *get_value; }; -static int get_layout_bare(struct repository *repo UNUSED, struct strbuf *buf) +static int get_layout_bare(struct repo_info *info UNUSED, struct strbuf *buf) {strbuf_addstr (buf,is_bare_repository () ?"true" :"false" );return 0 ; } -static int get_layout_shallow (struct repository *repo,struct strbuf *buf) +static int get_layout_shallow (struct repo_info *info,struct strbuf *buf) { +struct repository *repo = info->repo;strbuf_addstr (buf,is_repository_shallow (repo) ?"true" :"false" );return 0 ; } -static int get_object_format (struct repository *repo,struct strbuf *buf) +static int get_object_format (struct repo_info *info,struct strbuf *buf) { +struct repository *repo = info->repo;strbuf_addstr (buf, repo->hash_algo->name);return 0 ; } -static int get_references_format (struct repository *repo,struct strbuf *buf) +static int get_references_format (struct repo_info *info,struct strbuf *buf) { +struct repository *repo = info->repo;strbuf_addstr (buf,ref_storage_format_to_name (repo->ref_storage_format));return 0 ;
@@ -87,6 +95,33 @@ static get_value_fn *get_value_fn_for_key(const char *key)return found ? found->get_value :NULL ; } +static void print_field (enum output_format format,const char *key, +const char *value); + +static int print_category_fields (const char *category, +struct repo_info *info, +enum output_format format, +struct strbuf *valbuf) +{ +int found =0 ; +size_t category_len =strlen (category); + +for (size_t i =0 ; i <ARRAY_SIZE (repo_info_fields); i++) { +const struct field *field = &repo_info_fields[i]; + +if (!starts_with (field->key, category) || + field->key[category_len] !='.' ) +continue ; + +strbuf_reset (valbuf); + field->get_value (info, valbuf); +print_field (format, field->key, valbuf->buf); + found =1 ; + } + +return found; +} +static void print_field (enum output_format format,const char *key,const char *value) {
@@ -105,7 +140,7 @@ static void print_field(enum output_format format, const char *key, }static int print_fields (int argc,const char **argv, -struct repository *repo, +struct repo_info *info,enum output_format format) {int ret =0 ;
@@ -117,21 +152,22 @@ static int print_fields(int argc, const char **argv, get_value =get_value_fn_for_key (key); -if (!get_value) { - ret =error (_ ("key '%s' not found" ), key); +if (get_value) { +strbuf_reset (&valbuf); +get_value (info, &valbuf); +print_field (format, key, valbuf.buf);continue ; } -strbuf_reset (&valbuf); -get_value (repo, &valbuf); -print_field (format, key, valbuf.buf); +if (!print_category_fields (key, info, format, &valbuf)) + ret =error (_ ("key '%s' not found" ), key); }strbuf_release (&valbuf);return ret; } -static int print_all_fields (struct repository *repo, +static int print_all_fields (struct repo_info *info,enum output_format format) {struct strbuf valbuf =STRBUF_INIT ;
@@ -140,7 +176,7 @@ static int print_all_fields(struct repository *repo,const struct field *field = &repo_info_fields[i];strbuf_reset (&valbuf); - field->get_value (repo, &valbuf); + field->get_value (info, &valbuf);print_field (format, field->key, valbuf.buf); }
@@ -171,6 +207,10 @@ static int cmd_repo_info(int argc, const char **argv, const char *prefix, struct repository *repo) { enum output_format format = FORMAT_KEYVALUE; + struct repo_info info = { + .repo = repo, + .prefix = prefix, + }; int all_keys = 0; struct option options[] = { OPT_CALLBACK_F(0, "format", &format, N_("format"),
@@ -192,9 +232,9 @@ static int cmd_repo_info(int argc, const char **argv, const char *prefix,die (_ ("--all and <key> cannot be used together" ));if (all_keys) -return print_all_fields (repo, format); +return print_all_fields (&info, format);else -return print_fields (argc, argv, repo, format); +return print_fields (argc, argv, &info, format); }struct ref_stats {
Signature · 1 line
gitgitgadget
Re: [PATCH v4 01/10] repo: teach info context and category keys
"Eslam reda ragheb via GitGitGadget" <[email protected]>
Quoted 8 lines · writes:
From: Eslam reda ragheb <[email protected]>
Introduce an explicit
repo_infocontext for the repo info codepath and thread it through value lookups and field printing.This removes direct coupling from these helpers to ad-hoc repository globals and makes key retrieval logic easier to extend safely.
The above makes it sound as if you are improving existing code where existing helper functions are already making ad-hoc and unsafe access to global variables, but I somehow doubt that is what is happening here.
What does "these helpers" exactly refer to in this sentence? The ones you will introduce in patch 02/10?
Currently helper functions of get_value_fn type receives the output buffer and repository instance as parameters, but we are about to teach "repo info" to retrieve information that requires to know more than just the repository (e.g., the prefix given when the command was invoked). Introduce a new "repo_info" structure and update get_value_fn to take it as a parameter.
or something?
Quoted 3 lines
Also teach git repo info to accept category names (for example, layout) and expand them to matching key.* entries in request order.
That smells like an unrelated change. Wouldn't it better to do so in a subsequent patch, so that each step will concentrate on doing one thing and one thing well?
PATCH v4 2/10 repo: add path keys to repo info
In reply to eslam reda via GitGitGadgetFrom: Eslam reda ragheb <[email protected]>
Add a path category to git repo info with key-value pairs that mirror repository paths users commonly retrieve via rev-parse and git-path lookups.
This makes scripting against repo metadata more direct and avoids shelling out to multiple commands for related paths.
The new keys are introduced as explicit path.* entries in repo_info_fields and are resolved through dedicated helpers.
This keeps lookup behavior predictable and makes future path additions straightforward.
--- builtin/repo.c | 137 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 137 insertions(+)
builtin/repo.c +137 -0
@@ -1,10 +1,12 @@#define USE_THE_REPOSITORY_VARIABLE #include "builtin.h" +#include "abspath.h" #include "environment.h" #include "hex.h" #include "odb.h" #include "parse-options.h" +#include "path.h" #include "path-walk.h" #include "progress.h" #include "quote.h"
@@ -14,6 +16,7 @@#include "strbuf.h" #include "string-list.h" #include "shallow.h" +#include "submodule.h" #include "utf8.h" static const char *const repo_usage[] = {
@@ -40,6 +43,13 @@ struct field {get_value_fn *get_value; }; +static void repo_info_add_path (struct repo_info *info, +struct strbuf *buf, +const char *path) +{ +strbuf_add_absolute_path (buf, path); +} + static int get_layout_bare(struct repo_info *info UNUSED, struct strbuf *buf) {strbuf_addstr (buf,is_bare_repository () ?"true" :"false" );
@@ -61,6 +71,119 @@ static int get_object_format(struct repo_info *info, struct strbuf *buf)return 0 ; } +static int get_path_common_dir (struct repo_info *info,struct strbuf *buf) +{ +repo_info_add_path (info, buf,repo_get_common_dir (info->repo)); +return 0 ; +} + +static int get_path_config_file (struct repo_info *info,struct strbuf *buf) +{ +struct strbuf path =STRBUF_INIT ; + +repo_info_add_path (info, buf,repo_git_path_replace (info->repo, &path,"config" )); +strbuf_release (&path); +return 0 ; +} + +static int get_path_git_dir (struct repo_info *info,struct strbuf *buf) +{ +repo_info_add_path (info, buf,repo_get_git_dir (info->repo)); +return 0 ; +} + +static int get_path_git_prefix (struct repo_info *info,struct strbuf *buf) +{ +if (info->prefix) +strbuf_addstr (buf, info->prefix); +return 0 ; +} + +static int get_path_grafts_file (struct repo_info *info,struct strbuf *buf) +{ +repo_info_add_path (info, buf,repo_get_graft_file (info->repo)); +return 0 ; +} + +static int get_path_hooks_directory (struct repo_info *info,struct strbuf *buf) +{ +struct strbuf path =STRBUF_INIT ; + +repo_info_add_path (info, buf,repo_git_path_replace (info->repo, &path,"hooks" )); +strbuf_release (&path); +return 0 ; +} + +static int get_path_index_file (struct repo_info *info,struct strbuf *buf) +{ +repo_info_add_path (info, buf,repo_get_index_file (info->repo)); +return 0 ; +} + +static int get_path_logs_directory (struct repo_info *info,struct strbuf *buf) +{ +struct strbuf path =STRBUF_INIT ; + +repo_info_add_path (info, buf,repo_git_path_replace (info->repo, &path,"logs" )); +strbuf_release (&path); +return 0 ; +} + +static int get_path_objects_directory (struct repo_info *info,struct strbuf *buf) +{ +repo_info_add_path (info, buf,repo_get_object_directory (info->repo)); +return 0 ; +} + +static int get_path_packed_refs_file (struct repo_info *info,struct strbuf *buf) +{ +struct strbuf path =STRBUF_INIT ; + +repo_info_add_path (info, buf,repo_git_path_replace (info->repo, &path,"packed-refs" )); +strbuf_release (&path); +return 0 ; +} + +static int get_path_refs_directory (struct repo_info *info,struct strbuf *buf) +{ +struct strbuf path =STRBUF_INIT ; + +repo_info_add_path (info, buf,repo_git_path_replace (info->repo, &path,"refs" )); +strbuf_release (&path); +return 0 ; +} + +static int get_path_shallow_file (struct repo_info *info,struct strbuf *buf) +{ +struct strbuf path =STRBUF_INIT ; + +repo_info_add_path (info, buf,repo_git_path_replace (info->repo, &path,"shallow" )); +strbuf_release (&path); +return 0 ; +} + +static int get_path_superproject_working_tree (struct repo_info *info, +struct strbuf *buf) +{ +struct strbuf superproject =STRBUF_INIT ; + +if (get_superproject_working_tree (&superproject)) +repo_info_add_path (info, buf, superproject.buf); + +strbuf_release (&superproject); +return 0 ; +} + +static int get_path_toplevel (struct repo_info *info,struct strbuf *buf) +{ +const char *work_tree =repo_get_work_tree (info->repo); + +if (work_tree) +repo_info_add_path (info, buf, work_tree); + +return 0 ; +} +static int get_references_format (struct repo_info *info,struct strbuf *buf) {struct repository *repo = info->repo;
@@ -74,6 +197,20 @@ static const struct field repo_info_fields[] = { { "layout.bare", get_layout_bare }, { "layout.shallow", get_layout_shallow }, { "object.format", get_object_format }, + { "path.common-dir", get_path_common_dir }, + { "path.config-file", get_path_config_file }, + { "path.git-dir", get_path_git_dir }, + { "path.git-prefix", get_path_git_prefix }, + { "path.grafts-file", get_path_grafts_file }, + { "path.hooks-directory", get_path_hooks_directory }, + { "path.index-file", get_path_index_file }, + { "path.logs-directory", get_path_logs_directory }, + { "path.objects-directory", get_path_objects_directory }, + { "path.packed-refs-file", get_path_packed_refs_file }, + { "path.refs-directory", get_path_refs_directory }, + { "path.shallow-file", get_path_shallow_file }, + { "path.superproject-working-tree", get_path_superproject_working_tree }, + { "path.toplevel", get_path_toplevel }, { "references.format", get_references_format }, };
Signature · 1 line
gitgitgadget
Re: [PATCH v4 02/10] repo: add path keys to repo info
"Eslam reda ragheb via GitGitGadget" <[email protected]>
builtin/repo.c static int get_object_format(struct repo_info *info, struct strbuf *buf) · line 100
Quoted 12 lines · writes:
From: Eslam reda ragheb <[email protected]>
Add a path category to git repo info with key-value pairs that mirror repository paths users commonly retrieve via rev-parse and git-path lookups.
...builtin/repo.c +6
-0+static int get_path_git_prefix (struct repo_info *info,struct strbuf *buf) +{ +if (info->prefix) +strbuf_addstr (buf, info->prefix); +return 0 ; +}
Can (info->prefix && info->prefix[0] == '\0') be possible? If so, "repo info path.git-prefix" would not be able to help users who want to know between (info->prefix == NULL) and info->prefix being an empty string.
Re: [PATCH v4 02/10] repo: add path keys to repo info
In reply to Eslam reda ragheb via GitGitGadgetHi Eslam
Quoted 5 lines · Eslam reda ragheb via GitGitGadget
From: Eslam reda ragheb <[email protected]>
Add a path category to git repo info with key-value pairs that mirror repository paths users commonly retrieve via rev-parse and git-path lookups.
I think that makes sense, I'm not sure about some of the paths though, see below.
Quoted 2 lines
This makes scripting against repo metadata more direct and avoids shelling out to multiple commands for related paths.
You can get more than one path at a time from "git rev-parse" so I'm not sure what this is saying.
It would be helpful to include the tests and Documentation for the new keys in this patch.
Quoted 10 lines
The new keys are introduced as explicit path.* entries in
repo_info_fieldsand are resolved through dedicated helpers.This keeps lookup behavior predictable and makes future path additions straightforward.
Signed-off-by Eslam reda ragheb <[email protected]>---
builtin/repo.c static const struct field repo_info_fields[] = { · line 203
Quoted 7 lines
builtin/repo.c +4
-0@@ -74,6 +197,20 @@ static const struct field repo_info_fields[] = { { "layout.bare", get_layout_bare }, { "layout.shallow", get_layout_shallow }, { "object.format", get_object_format }, + { "path.common-dir", get_path_common_dir }, + { "path.config-file", get_path_config_file }, + { "path.git-dir", get_path_git_dir }, + { "path.git-prefix", get_path_git_prefix },
I'm not sure about calling this 'git-prefix', 'prefix' might be more appropriate as it is about prefixing paths in the worktree rather than the git_dir.
builtin/repo.c static const struct field repo_info_fields[] = { · line 207
Quoted 4 lines
builtin/repo.c +4
-0+ { "path.grafts-file", get_path_grafts_file }, + { "path.hooks-directory", get_path_hooks_directory }, + { "path.index-file", get_path_index_file }, + { "path.logs-directory", get_path_logs_directory },
We're moving away from file based refs and reflogs so I'm not sure adding this, pick-refs-file or refs-directory is a good idea as we should not be encouraging people to access these files directly.
builtin/repo.c static const struct field repo_info_fields[] = { · line 213
Quoted 6 lines
builtin/repo.c +6
-0+ { "path.objects-directory", get_path_objects_directory }, + { "path.packed-refs-file", get_path_packed_refs_file }, + { "path.refs-directory", get_path_refs_directory }, + { "path.shallow-file", get_path_shallow_file }, + { "path.superproject-working-tree", get_path_superproject_working_tree }, + { "path.toplevel", get_path_toplevel },
'path.toplevel' matches the git-rev-parse option but 'path.work-tree' might be more descriptive?
What happens if 'path.toplevel' is requested in a bare repository?
Thanks
Phillip
Quoted 3 lines
builtin/repo.c +0
-0{ "references.format", get_references_format }, };
Re: [PATCH v4 02/10] repo: add path keys to repo info
Quoted 8 lines · Phillip Wood
builtin/repo.c static const struct field repo_info_fields[] = { · line 203
Quoted 4 lines
builtin/repo.c +4
-0+ { "path.common-dir", get_path_common_dir }, + { "path.config-file", get_path_config_file }, + { "path.git-dir", get_path_git_dir }, + { "path.git-prefix", get_path_git_prefix },I'm not sure about calling this 'git-prefix', 'prefix' might be more appropriate as it is about prefixing paths in the worktree rather than the
git_dir.
True.
Quoted 9 lines
builtin/repo.c static const struct field repo_info_fields[] = { · line 207
Quoted 4 lines
builtin/repo.c +4
-0+ { "path.grafts-file", get_path_grafts_file }, + { "path.hooks-directory", get_path_hooks_directory }, + { "path.index-file", get_path_index_file }, + { "path.logs-directory", get_path_logs_directory },We're moving away from file based refs and reflogs so I'm not sure adding this, pick-refs-file or refs-directory is a good idea as we should not be encouraging people to access these files directly.
Quoted 3 lines
builtin/repo.c static const struct field repo_info_fields[] = { · line 210
Quoted 3 lines
builtin/repo.c +3
-0+ { "path.objects-directory", get_path_objects_directory }, + { "path.packed-refs-file", get_path_packed_refs_file }, + { "path.refs-directory", get_path_refs_directory },
The same comment applies to these entries as well, as the pluggable object database support is just beyond the horizon if I understand correctly.
Quoted 6 lines
builtin/repo.c static const struct field repo_info_fields[] = { · line 213
Quoted 3 lines
builtin/repo.c +3
-0+ { "path.shallow-file", get_path_shallow_file }, + { "path.superproject-working-tree", get_path_superproject_working_tree }, + { "path.toplevel", get_path_toplevel },'path.toplevel' matches the git-rev-parse option but 'path.work-tree' might be more descriptive?
I think the "git repo" thrust comes primarily from being unfamiliar with "rev-parse" (and I wouldn't particularly encourage new people to become familiar with it---it grew pretty much organically driven by scripting needs without taking UI cleanliness into consideration very much), so not many folks would find it disturbing that "--toplevel" corresponds to "topOfTheWorkingTree". Given that we have a token to ask for superproject's working tree, giving a name made after the same phrasing philosophy for the current project's working tree would be a good thing, i.e., "path.working-tree".
Quoted 1 line
What happens if 'path.toplevel' is requested in a bare repository?
FWIW "git rev-parse --show-toplevel" dies with "must be run in a work tree". Better or worse,
rm -fr new
git init new
cd new/.git && git rev-parse --show-toplevel
also dies the same way, which I am not sure we want to inherit when we are making a new interrogator command.
Thanks.
Re: [PATCH v4 02/10] repo: add path keys to repo info
Quoted 9 lines · Junio C Hamano
Quoted 3 lines · Phillip Wood
builtin/repo.c static const struct field repo_info_fields[] = { · line 210
Quoted 3 lines
builtin/repo.c +3
-0+ { "path.objects-directory", get_path_objects_directory }, + { "path.packed-refs-file", get_path_packed_refs_file }, + { "path.refs-directory", get_path_refs_directory },The same comment applies to these entries as well, as the pluggable object database support is just beyond the horizon if I understand correctly.
Good point. Also what is the "shallow file" below and should scripts be poking it directly?
Quoted 7 lines
Quoted 6 lines
builtin/repo.c static const struct field repo_info_fields[] = { · line 213
Quoted 3 lines
builtin/repo.c +3
-0+ { "path.shallow-file", get_path_shallow_file }, + { "path.superproject-working-tree", get_path_superproject_working_tree }, + { "path.toplevel", get_path_toplevel },'path.toplevel' matches the git-rev-parse option but 'path.work-tree' might be more descriptive?
Quoted 9 lines
I think the "git repo" thrust comes primarily from being unfamiliar with "rev-parse" (and I wouldn't particularly encourage new people to become familiar with it---it grew pretty much organically driven by scripting needs without taking UI cleanliness into consideration very much), so not many folks would find it disturbing that "--toplevel" corresponds to "topOfTheWorkingTree". Given that we have a token to ask for superproject's working tree, giving a name made after the same phrasing philosophy for the current project's working tree would be a good thing, i.e., "path.working-tree".
That's a good idea
Quoted 11 lines
Quoted 1 line
What happens if 'path.toplevel' is requested in a bare repository?
FWIW "git rev-parse --show-toplevel" dies with "must be run in a work tree". Better or worse,
rm -fr new
git init new
cd new/.git && git rev-parse --show-toplevelalso dies the same way, which I am not sure we want to inherit when we are making a new interrogator command.
Yes, I think printing an empty value after the key would be better - I don't think there are any paths where we care about the distinction between NULL and ""
Thanks
Phillip
Re: [PATCH v4 02/10] repo: add path keys to repo info
Quoted 7 lines · Phillip Wood
Quoted 7 lines
FWIW "git rev-parse --show-toplevel" dies with "must be run in a work tree". Better or worse,
rm -fr new
git init new
cd new/.git && git rev-parse --show-toplevel
Quoted 6 lines
Quoted 2 lines
also dies the same way, which I am not sure we want to inherit when we are making a new interrogator command.
Yes, I think printing an empty value after the key would be better - I don't think there are any paths where we care about the distinction between NULL and ""
If the new and safer variant of the command should signal "error" by giving an empty string, it would work with its "--show-toplevel" equivalent run in a bare repository. In a working tree, it would give a full/absolute path, and never be an empty string. I am still unsure what that new and safer one should do from inside the .git directory. "rev-parse --show-toplevel" dies. $(cd .. && pwd) may be another plausible and arguably more useful answer.
Your idea of equiating NULL and "", I do not think it would work well with "git rev-parse --show-cdup" equivalent. The command will give us an empty string from the top-level of the working tree.
Curiously, in this sequence
rm -fr new git init new cd new/.git && git rev-parse --show-cdup cd objects && git rev-parse --show-cdup
two "rev-parse" do not die with "must be run in a work tree", and worse yet, they do not give you ".." or "../../", either.
It seems that one rule of "rev-parse --show-<some-path>" is that "when you are inside .git directory of a non-bare repository, we'd behave as if you are in a bare repository as if its working tree does not exist", and the above is consistent with that rule. "--show-cdup" does not fail but gives an empty string when run anywhere in a bare repository.
But among "rev-parse --show-<anything>" that are about working tree paths, there seems no unifying rule on what to do when in a bare repsitory. As we already saw, "--show-toplevel" dies without a working tree. This reflects the history of rev-parse that grew organizally without a grand design.
If we are adding new and safer interface to these pieces of information to "repo info", we may want to straighten these rules.
PATCH v4 3/10 repo: add --path-format for info path output
In reply to eslam reda via GitGitGadgetFrom: Eslam reda ragheb <[email protected]>
Teach git repo info to accept --path-format=(absolute|relative) so scripts can request stable path style explicitly.
This aligns path.* output behavior with existing rev-parse usage patterns and reduces ad-hoc path conversion in callers.
The option is wired through repo_info context and used by repo_info_add_path(), so path formatting remains centralized and consistent across all path.* keys.
--- builtin/repo.c | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-)
builtin/repo.c +36 -1
@@ -20,14 +20,20 @@#include "utf8.h" static const char *const repo_usage[] = { -"git repo info [--format=(keyvalue|nul) | -z] [--all | <key>...]" , +"git repo info [--format=(keyvalue|nul) | -z] [--path-format=(absolute|relative)] [--all | <key>...]" ,"git repo structure [--format=(table|keyvalue|nul) | -z]" ,NULL }; +enum path_format { +PATH_FORMAT_ABSOLUTE , +PATH_FORMAT_RELATIVE , +}; +struct repo_info {struct repository *repo;const char *prefix; +enum path_format path_format; };typedef int get_value_fn (struct repo_info *info,struct strbuf *buf);
@@ -47,6 +53,16 @@ static void repo_info_add_path(struct repo_info *info, struct strbuf *buf, const char *path) { +if (info->path_format ==PATH_FORMAT_RELATIVE ) { +char *cwd =xgetcwd (); +struct strbuf rel_path =STRBUF_INIT ; + +strbuf_addstr (buf,relative_path (path, cwd, &rel_path)); +strbuf_release (&rel_path); +free (cwd); +return ; + } +strbuf_add_absolute_path (buf, path); }
@@ -340,6 +356,21 @@ static int parse_format_cb(const struct option *opt,return 0 ; } +static int parse_path_format_cb (const struct option *opt, + const char *arg, int unset UNUSED) +{ +enum path_format *path_format = opt->value; + +if (!strcmp (arg,"absolute" )) + *path_format =PATH_FORMAT_ABSOLUTE ; +else if (!strcmp (arg,"relative" )) + *path_format =PATH_FORMAT_RELATIVE ; +else +die (_ ("invalid path format '%s'" ), arg); + +return 0 ; +} +static int cmd_repo_info (int argc,const char **argv,const char *prefix,struct repository *repo) {
@@ -347,6 +378,7 @@ static int cmd_repo_info(int argc, const char **argv, const char *prefix,struct repo_info info = { .repo = repo, .prefix = prefix, + .path_format =PATH_FORMAT_ABSOLUTE , };int all_keys =0 ;struct option options[] = {
@@ -357,6 +389,9 @@ static int cmd_repo_info(int argc, const char **argv, const char *prefix, N_("synonym for --format=nul"), PARSE_OPT_NONEG | PARSE_OPT_NOARG, parse_format_cb), +OPT_CALLBACK_F (0 ,"path-format" , &info.path_format, +N_ ("format" ),N_ ("path output format" ), +PARSE_OPT_NONEG , parse_path_format_cb),OPT_BOOL (0 ,"all" , &all_keys,N_ ("print all keys/values" )), OPT_END() };
Signature · 1 line
gitgitgadget
PATCH v4 4/10 repo: add structure max object size metrics
In reply to eslam reda via GitGitGadgetFrom: Eslam reda ragheb <[email protected]>
Extend git repo structure with maximum inflated and on-disk object sizes, both per type and overall max values.
This complements existing totals by highlighting outliers that often drive repository bloat analysis.
The implementation updates object counting to track per-type maxima while walking reachable objects.
It exposes those values in both table and keyvalue formats for scripts and human output.
--- builtin/repo.c | 87 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 86 insertions(+), 1 deletion(-)
builtin/repo.c +86 -1
@@ -426,7 +426,9 @@ struct object_values {struct object_stats {struct object_values type_counts;struct object_values inflated_sizes; +struct object_values max_inflated_sizes;struct object_values disk_sizes; +struct object_values max_disk_sizes; };struct repo_structure {
@@ -529,6 +531,20 @@ static inline size_t get_total_object_values(struct object_values *values)return values->tags + values->commits + values->trees + values->blobs; } +static inline size_t get_max_object_value (struct object_values *values) +{ +size_t max = values->commits; + +if (values->trees > max) + max = values->trees; +if (values->blobs > max) + max = values->blobs; +if (values->tags > max) + max = values->tags; + +return max; +} +static void stats_table_setup_structure (struct stats_table *table,struct repo_structure *stats) {
@@ -583,6 +599,26 @@ static void stats_table_setup_structure(struct stats_table *table, " * %s", _("Blobs"));stats_table_size_addf (table, objects->disk_sizes.tags," * %s" ,_ ("Tags" )); + +stats_table_size_addf (table, objects->max_inflated_sizes.commits, +" * %s" ,_ ("Largest commit" )); +stats_table_size_addf (table, objects->max_inflated_sizes.trees, +" * %s" ,_ ("Largest tree" )); +stats_table_size_addf (table, objects->max_inflated_sizes.blobs, +" * %s" ,_ ("Largest blob" )); +stats_table_size_addf (table, objects->max_inflated_sizes.tags, +" * %s" ,_ ("Largest tag" )); + +stats_table_size_addf (table,get_max_object_value (&objects->max_disk_sizes), +" * %s" ,_ ("Largest disk size" )); +stats_table_size_addf (table, objects->max_disk_sizes.commits, +" * %s" ,_ ("Commits" )); +stats_table_size_addf (table, objects->max_disk_sizes.trees, +" * %s" ,_ ("Trees" )); +stats_table_size_addf (table, objects->max_disk_sizes.blobs, +" * %s" ,_ ("Blobs" )); +stats_table_size_addf (table, objects->max_disk_sizes.tags, +" * %s" ,_ ("Tags" )); }static void stats_table_print_structure (const struct stats_table *table)
@@ -661,6 +697,9 @@ static void stats_table_clear(struct stats_table *table)static void structure_keyvalue_print (struct repo_structure *stats,char key_delim,char value_delim) { +size_t max_inflated_size =get_max_object_value (&stats->objects.max_inflated_sizes); +size_t max_disk_size =get_max_object_value (&stats->objects.max_disk_sizes); +printf ("references.branches.count%c%" PRIuMAX"%c" , key_delim, (uintmax_t )stats->refs.branches, value_delim); printf("references.tags.count%c%" PRIuMAX "%c", key_delim,
@@ -688,6 +727,28 @@ static void structure_keyvalue_print(struct repo_structure *stats,printf ("objects.tags.inflated_size%c%" PRIuMAX"%c" , key_delim, (uintmax_t )stats->objects.inflated_sizes.tags, value_delim); +printf ("objects.max_inflated_size%c%" PRIuMAX"%c" , key_delim, + (uintmax_t )max_inflated_size, value_delim); +printf ("objects.commits.max_inflated_size%c%" PRIuMAX"%c" , key_delim, + (uintmax_t )stats->objects.max_inflated_sizes.commits, value_delim); +printf ("objects.trees.max_inflated_size%c%" PRIuMAX"%c" , key_delim, + (uintmax_t )stats->objects.max_inflated_sizes.trees, value_delim); +printf ("objects.blobs.max_inflated_size%c%" PRIuMAX"%c" , key_delim, + (uintmax_t )stats->objects.max_inflated_sizes.blobs, value_delim); +printf ("objects.tags.max_inflated_size%c%" PRIuMAX"%c" , key_delim, + (uintmax_t )stats->objects.max_inflated_sizes.tags, value_delim); + +printf ("objects.max_disk_size%c%" PRIuMAX"%c" , key_delim, + (uintmax_t )max_disk_size, value_delim); +printf ("objects.commits.max_disk_size%c%" PRIuMAX"%c" , key_delim, + (uintmax_t )stats->objects.max_disk_sizes.commits, value_delim); +printf ("objects.trees.max_disk_size%c%" PRIuMAX"%c" , key_delim, + (uintmax_t )stats->objects.max_disk_sizes.trees, value_delim); +printf ("objects.blobs.max_disk_size%c%" PRIuMAX"%c" , key_delim, + (uintmax_t )stats->objects.max_disk_sizes.blobs, value_delim); +printf ("objects.tags.max_disk_size%c%" PRIuMAX"%c" , key_delim, + (uintmax_t )stats->objects.max_disk_sizes.tags, value_delim); +printf ("objects.commits.disk_size%c%" PRIuMAX"%c" , key_delim, (uintmax_t )stats->objects.disk_sizes.commits, value_delim); printf("objects.trees.disk_size%c%" PRIuMAX "%c", key_delim,
@@ -772,6 +833,8 @@ static int count_objects(const char *path UNUSED, struct oid_array *oids,struct object_stats *stats = data->stats;size_t inflated_total =0 ;size_t disk_total =0 ; +size_t max_inflated =0 ; +size_t max_disk =0 ;size_t object_count;for (size_t i =0 ; i < oids->nr; i++) {
@@ -786,31 +849,53 @@ static int count_objects(const char *path UNUSED, struct oid_array *oids,OBJECT_INFO_SKIP_FETCH_OBJECT | OBJECT_INFO_QUICK) < 0) continue; +if (disk <0 ) +continue ; inflated_total += inflated; - disk_total += disk; + disk_total += (size_t )disk; +if (inflated > max_inflated) + max_inflated = inflated; +if ((size_t )disk > max_disk) + max_disk = (size_t )disk; }switch (type) {case OBJ_TAG : stats->type_counts.tags += oids->nr; stats->inflated_sizes.tags += inflated_total; +if (max_inflated > stats->max_inflated_sizes.tags) + stats->max_inflated_sizes.tags = max_inflated; stats->disk_sizes.tags += disk_total; +if (max_disk > stats->max_disk_sizes.tags) + stats->max_disk_sizes.tags = max_disk;break ;case OBJ_COMMIT : stats->type_counts.commits += oids->nr; stats->inflated_sizes.commits += inflated_total; +if (max_inflated > stats->max_inflated_sizes.commits) + stats->max_inflated_sizes.commits = max_inflated; stats->disk_sizes.commits += disk_total; +if (max_disk > stats->max_disk_sizes.commits) + stats->max_disk_sizes.commits = max_disk;break ;case OBJ_TREE : stats->type_counts.trees += oids->nr; stats->inflated_sizes.trees += inflated_total; +if (max_inflated > stats->max_inflated_sizes.trees) + stats->max_inflated_sizes.trees = max_inflated; stats->disk_sizes.trees += disk_total; +if (max_disk > stats->max_disk_sizes.trees) + stats->max_disk_sizes.trees = max_disk;break ;case OBJ_BLOB : stats->type_counts.blobs += oids->nr; stats->inflated_sizes.blobs += inflated_total; +if (max_inflated > stats->max_inflated_sizes.blobs) + stats->max_inflated_sizes.blobs = max_inflated; stats->disk_sizes.blobs += disk_total; +if (max_disk > stats->max_disk_sizes.blobs) + stats->max_disk_sizes.blobs = max_disk;break ;default :BUG ("invalid object type" );
Signature · 1 line
gitgitgadget
PATCH v4 5/10 repo: add structure topology and path-depth metrics
In reply to eslam reda via GitGitGadgetFrom: Eslam reda ragheb <[email protected]>
Track additional structure-oriented maxima that are useful when diagnosing unusually complex histories.
These include commit parent fanout, tree entry count, blob path length/depth, and annotated tag chain depth.
The counters are gathered while traversing reachable objects and are reported in both table and keyvalue output.
This lets both humans and scripts consume the same topology signals.
--- builtin/repo.c | 171 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 170 insertions(+), 1 deletion(-)
builtin/repo.c +170 -1
@@ -17,6 +17,7 @@#include "string-list.h" #include "shallow.h" #include "submodule.h" +#include "tree-walk.h" #include "utf8.h" static const char *const repo_usage[] = {
@@ -429,6 +430,11 @@ struct object_stats {struct object_values max_inflated_sizes;struct object_values disk_sizes;struct object_values max_disk_sizes; +size_t max_commit_parent_count; +size_t max_tree_entry_count; +size_t max_blob_path_length; +size_t max_blob_path_depth; +size_t max_tag_chain_depth; };struct repo_structure {
@@ -545,6 +551,116 @@ static inline size_t get_max_object_value(struct object_values *values)return max; } +static size_t get_commit_parent_count (struct repository *repo, +const struct object_id *oid) +{ +unsigned long size =0 ; +const char *cur; +const char *end; +void *buf; +size_t count =0 ; + + buf =odb_read_object_peeled (repo->objects, oid,OBJ_COMMIT , &size,NULL ); +if (!buf) +return 0 ; + + cur = buf; + end = cur + size; +while (cur < end) { +const char *newline =memchr (cur,'\n' , end - cur); +size_t line_len; + +if (!newline) +break ; + line_len = newline - cur; +if (!line_len) +break ; + +if (line_len >7 && !memcmp (cur,"parent " ,7 )) + count++; + + cur = newline +1 ; + } + +free (buf); +return count; +} + +static size_t get_tree_entry_count (struct repository *repo, +const struct object_id *oid) +{ +struct tree_desc desc; +struct name_entry entry; +unsigned long size =0 ; +void *buf; +size_t count =0 ; + + buf =odb_read_object_peeled (repo->objects, oid,OBJ_TREE , &size,NULL ); +if (!buf) +return 0 ; + +init_tree_desc (&desc, oid, buf, size); +while (tree_entry (&desc, &entry)) + count++; + +free (buf); +return count; +} + +static size_t get_path_depth (const char *path) +{ +size_t depth =0 ; + +if (!path || !*path) +return 0 ; + + depth =1 ; +for (const char *cur = path; *cur; cur++) +if (*cur =='/' ) + depth++; + +return depth; +} + +static size_t get_tag_chain_depth (struct repository *repo, +const struct object_id *oid) +{ +struct object_id current = *oid; +size_t depth =0 ; + +while (1 ) { +enum object_type type; +unsigned long size =0 ; +struct object_id next; +const char *p, *end; +void *buf =odb_read_object (repo->objects, ¤t, &type, &size); + +if (!buf) +break ; +if (type !=OBJ_TAG ) { +free (buf); +break ; + } + + p = buf; +if (!skip_prefix (p,"object " , &p) || +parse_oid_hex_algop (p, &next, &end, repo->hash_algo) || + *end !='\n' ) { +free (buf); +break ; + } + + depth++; +free (buf); + +if (oideq (&next, ¤t)) +break ; +oidcpy (¤t, &next); + } + +return depth; +} +static void stats_table_setup_structure (struct stats_table *table,struct repo_structure *stats) {
@@ -619,6 +735,17 @@ static void stats_table_setup_structure(struct stats_table *table, " * %s", _("Blobs"));stats_table_size_addf (table, objects->max_disk_sizes.tags," * %s" ,_ ("Tags" )); + +stats_table_count_addf (table, objects->max_commit_parent_count, +" * %s" ,_ ("Largest parent count" )); +stats_table_count_addf (table, objects->max_tree_entry_count, +" * %s" ,_ ("Largest tree entries" )); +stats_table_count_addf (table, objects->max_blob_path_length, +" * %s" ,_ ("Longest blob path" )); +stats_table_count_addf (table, objects->max_blob_path_depth, +" * %s" ,_ ("Deepest blob path" )); +stats_table_count_addf (table, objects->max_tag_chain_depth, +" * %s" ,_ ("Deepest tag chain" )); }static void stats_table_print_structure (const struct stats_table *table)
@@ -749,6 +876,17 @@ static void structure_keyvalue_print(struct repo_structure *stats,printf ("objects.tags.max_disk_size%c%" PRIuMAX"%c" , key_delim, (uintmax_t )stats->objects.max_disk_sizes.tags, value_delim); +printf ("objects.commits.max_parent_count%c%" PRIuMAX"%c" , key_delim, + (uintmax_t )stats->objects.max_commit_parent_count, value_delim); +printf ("objects.trees.max_entry_count%c%" PRIuMAX"%c" , key_delim, + (uintmax_t )stats->objects.max_tree_entry_count, value_delim); +printf ("objects.blobs.max_path_length%c%" PRIuMAX"%c" , key_delim, + (uintmax_t )stats->objects.max_blob_path_length, value_delim); +printf ("objects.blobs.max_path_depth%c%" PRIuMAX"%c" , key_delim, + (uintmax_t )stats->objects.max_blob_path_depth, value_delim); +printf ("objects.tags.max_chain_depth%c%" PRIuMAX"%c" , key_delim, + (uintmax_t )stats->objects.max_tag_chain_depth, value_delim); +printf ("objects.commits.disk_size%c%" PRIuMAX"%c" , key_delim, (uintmax_t )stats->objects.disk_sizes.commits, value_delim); printf("objects.trees.disk_size%c%" PRIuMAX "%c", key_delim,
@@ -826,7 +964,7 @@ struct count_objects_data {struct progress *progress; }; -static int count_objects(const char *path UNUSED, struct oid_array *oids, +static int count_objects (const char *path,struct oid_array *oids,enum object_type type,void *cb_data) {struct count_objects_data *data = cb_data;
@@ -862,6 +1000,13 @@ static int count_objects(const char *path UNUSED, struct oid_array *oids,switch (type) {case OBJ_TAG : +for (size_t i =0 ; i < oids->nr; i++) { +size_t tag_chain_depth =get_tag_chain_depth (data->odb->repo, + &oids->oid[i]); +if (tag_chain_depth > stats->max_tag_chain_depth) + stats->max_tag_chain_depth = tag_chain_depth; + } + stats->type_counts.tags += oids->nr; stats->inflated_sizes.tags += inflated_total;if (max_inflated > stats->max_inflated_sizes.tags)
@@ -871,6 +1016,13 @@ static int count_objects(const char *path UNUSED, struct oid_array *oids, stats->max_disk_sizes.tags = max_disk;break ;case OBJ_COMMIT : +for (size_t i =0 ; i < oids->nr; i++) { +size_t parent_count =get_commit_parent_count (data->odb->repo, + &oids->oid[i]); +if (parent_count > stats->max_commit_parent_count) + stats->max_commit_parent_count = parent_count; + } + stats->type_counts.commits += oids->nr; stats->inflated_sizes.commits += inflated_total;if (max_inflated > stats->max_inflated_sizes.commits)
@@ -880,6 +1032,13 @@ static int count_objects(const char *path UNUSED, struct oid_array *oids, stats->max_disk_sizes.commits = max_disk;break ;case OBJ_TREE : +for (size_t i =0 ; i < oids->nr; i++) { +size_t entry_count =get_tree_entry_count (data->odb->repo, + &oids->oid[i]); +if (entry_count > stats->max_tree_entry_count) + stats->max_tree_entry_count = entry_count; + } + stats->type_counts.trees += oids->nr; stats->inflated_sizes.trees += inflated_total;if (max_inflated > stats->max_inflated_sizes.trees)
@@ -889,6 +1048,16 @@ static int count_objects(const char *path UNUSED, struct oid_array *oids, stats->max_disk_sizes.trees = max_disk;break ;case OBJ_BLOB : +if (path && *path) { +size_t path_len =strlen (path); +size_t path_depth =get_path_depth (path); + +if (path_len > stats->max_blob_path_length) + stats->max_blob_path_length = path_len; +if (path_depth > stats->max_blob_path_depth) + stats->max_blob_path_depth = path_depth; + } + stats->type_counts.blobs += oids->nr; stats->inflated_sizes.blobs += inflated_total;if (max_inflated > stats->max_inflated_sizes.blobs)
Signature · 1 line
gitgitgadget
PATCH v4 6/10 repo: add aggregate structure totals to keyvalue output
In reply to eslam reda via GitGitGadgetFrom: Eslam reda ragheb <[email protected]>
Expose aggregate totals alongside per-type values in structure keyvalue/nul output: total references, total objects, total inflated size, and total disk size.
These totals remove the need for callers to recompute sums externally.
Keeping aggregate and per-type values in the same output format improves script ergonomics.
It also keeps machine-readable output aligned with what table mode summarizes for humans.
--- builtin/repo.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+)
builtin/repo.c +16 -0
@@ -824,9 +824,16 @@ static void stats_table_clear(struct stats_table *table)static void structure_keyvalue_print (struct repo_structure *stats,char key_delim,char value_delim) { +size_t references_count_total =get_total_reference_count (&stats->refs); +size_t object_count_total =get_total_object_values (&stats->objects.type_counts); +size_t inflated_size_total =get_total_object_values (&stats->objects.inflated_sizes); +size_t disk_size_total =get_total_object_values (&stats->objects.disk_sizes);size_t max_inflated_size =get_max_object_value (&stats->objects.max_inflated_sizes);size_t max_disk_size =get_max_object_value (&stats->objects.max_disk_sizes); +printf ("references.count%c%" PRIuMAX"%c" , key_delim, + (uintmax_t )references_count_total, value_delim); +printf ("references.branches.count%c%" PRIuMAX"%c" , key_delim, (uintmax_t )stats->refs.branches, value_delim); printf("references.tags.count%c%" PRIuMAX "%c", key_delim,
@@ -836,6 +843,9 @@ static void structure_keyvalue_print(struct repo_structure *stats,printf ("references.others.count%c%" PRIuMAX"%c" , key_delim, (uintmax_t )stats->refs.others, value_delim); +printf ("objects.count%c%" PRIuMAX"%c" , key_delim, + (uintmax_t )object_count_total, value_delim); +printf ("objects.commits.count%c%" PRIuMAX"%c" , key_delim, (uintmax_t )stats->objects.type_counts.commits, value_delim); printf("objects.trees.count%c%" PRIuMAX "%c", key_delim,
@@ -845,6 +855,9 @@ static void structure_keyvalue_print(struct repo_structure *stats,printf ("objects.tags.count%c%" PRIuMAX"%c" , key_delim, (uintmax_t )stats->objects.type_counts.tags, value_delim); +printf ("objects.inflated_size%c%" PRIuMAX"%c" , key_delim, + (uintmax_t )inflated_size_total, value_delim); +printf ("objects.commits.inflated_size%c%" PRIuMAX"%c" , key_delim, (uintmax_t )stats->objects.inflated_sizes.commits, value_delim); printf("objects.trees.inflated_size%c%" PRIuMAX "%c", key_delim,
@@ -865,6 +878,9 @@ static void structure_keyvalue_print(struct repo_structure *stats,printf ("objects.tags.max_inflated_size%c%" PRIuMAX"%c" , key_delim, (uintmax_t )stats->objects.max_inflated_sizes.tags, value_delim); +printf ("objects.disk_size%c%" PRIuMAX"%c" , key_delim, + (uintmax_t )disk_size_total, value_delim); +printf ("objects.max_disk_size%c%" PRIuMAX"%c" , key_delim, (uintmax_t )max_disk_size, value_delim); printf("objects.commits.max_disk_size%c%" PRIuMAX "%c", key_delim,
Signature · 1 line
gitgitgadget
PATCH v4 7/10 t1900: cover repo info path keys and path-format
In reply to eslam reda via GitGitGadgetFrom: Eslam reda ragheb <[email protected]>
Extend t1900 to validate category-key expansion, path.* key behavior, and --path-format handling for git repo info.
The tests compare repo info output to equivalent rev-parse values.
This ensures behavior remains aligned with existing plumbing semantics.
Also keep mixed key/category ordering coverage so callers can rely on deterministic output order when combining explicit keys with category requests.
--- t/t1900-repo.sh | 196 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 196 insertions(+)
t/t1900-repo.sh +196 -0
@@ -10,9 +10,40 @@ REPO_INFO_KEYS=' layout.bare layout.shallow object.format + path.common-dir + path.config-file + path.git-dir + path.git-prefix + path.grafts-file + path.hooks-directory + path.index-file + path.logs-directory + path.objects-directory + path.packed-refs-file + path.refs-directory + path.shallow-file + path.superproject-working-tree + path.toplevel references.format ' +REPO_INFO_PATH_KEYS=' + path.common-dir + path.config-file + path.git-dir + path.git-prefix + path.grafts-file + path.hooks-directory + path.index-file + path.logs-directory + path.objects-directory + path.packed-refs-file + path.refs-directory + path.shallow-file + path.superproject-working-tree + path.toplevel +' + # Test whether a key-value pair is correctly returned # # Usage: test_repo_info <label> <init command> <repo_name> <key> <expected value>
@@ -89,6 +120,171 @@ test_expect_success 'values returned in order requested' ' test_cmp expect actual ' +test_expect_success 'category key returns all matching keys' ' + cat >expect <<-\EOF && + layout.bare=false + layout.shallow=false + EOF + git init category-layout && + git -C category-layout repo info layout >actual && + test_cmp expect actual +' + +test_expect_success 'mixed key/category requests preserve request order' ' + cat >expect <<-EOF && + object.format=$(test_oid algo) + layout.bare=false + layout.shallow=false + EOF + git init mixed-order && + git -C mixed-order repo info object.format layout >actual && + test_cmp expect actual +' + +test_expect_success 'path.git-dir matches rev-parse --absolute-git-dir' ' + git init path-git-dir && + expected_value=$(git -C path-git-dir rev-parse --absolute-git-dir) && + echo "path.git-dir=$expected_value" >expect && + git -C path-git-dir repo info path.git-dir >actual && + test_cmp expect actual +' + +test_expect_success 'path.common-dir matches rev-parse --git-common-dir' ' + git init path-common-dir && + expected_value=$(git -C path-common-dir rev-parse --path-format=absolute --git-common-dir) && + echo "path.common-dir=$expected_value" >expect && + git -C path-common-dir repo info path.common-dir >actual && + test_cmp expect actual +' + +test_expect_success 'path.toplevel matches rev-parse --show-toplevel' ' + git init path-toplevel && + expected_value=$(git -C path-toplevel rev-parse --show-toplevel) && + echo "path.toplevel=$expected_value" >expect && + git -C path-toplevel repo info path.toplevel >actual && + test_cmp expect actual +' + +test_expect_success 'path.toplevel is empty in bare repository' ' + git init --bare bare-path-toplevel && + echo "path.toplevel=" >expect && + git -C bare-path-toplevel repo info path.toplevel >actual && + test_cmp expect actual +' + +test_expect_success 'path.git-prefix matches rev-parse --show-prefix' ' + git init path-prefix && + mkdir -p path-prefix/a/b && + expected_value=$(git -C path-prefix/a/b rev-parse --show-prefix) && + echo "path.git-prefix=$expected_value" >expect && + git -C path-prefix/a/b repo info path.git-prefix >actual && + test_cmp expect actual +' + +test_expect_success 'git-path style keys match rev-parse --git-path' ' + git init path-git-path && + + expected_value=$(git -C path-git-path rev-parse --path-format=absolute --git-path info/grafts) && + echo "path.grafts-file=$expected_value" >expect && + git -C path-git-path repo info path.grafts-file >actual && + test_cmp expect actual && + + expected_value=$(git -C path-git-path rev-parse --path-format=absolute --git-path index) && + echo "path.index-file=$expected_value" >expect && + git -C path-git-path repo info path.index-file >actual && + test_cmp expect actual && + + expected_value=$(git -C path-git-path rev-parse --path-format=absolute --git-path objects) && + echo "path.objects-directory=$expected_value" >expect && + git -C path-git-path repo info path.objects-directory >actual && + test_cmp expect actual && + + expected_value=$(git -C path-git-path rev-parse --path-format=absolute --git-path hooks) && + echo "path.hooks-directory=$expected_value" >expect && + git -C path-git-path repo info path.hooks-directory >actual && + test_cmp expect actual && + + expected_value=$(git -C path-git-path rev-parse --path-format=absolute --git-path config) && + echo "path.config-file=$expected_value" >expect && + git -C path-git-path repo info path.config-file >actual && + test_cmp expect actual && + + expected_value=$(git -C path-git-path rev-parse --path-format=absolute --git-path logs) && + echo "path.logs-directory=$expected_value" >expect && + git -C path-git-path repo info path.logs-directory >actual && + test_cmp expect actual && + + expected_value=$(git -C path-git-path rev-parse --path-format=absolute --git-path packed-refs) && + echo "path.packed-refs-file=$expected_value" >expect && + git -C path-git-path repo info path.packed-refs-file >actual && + test_cmp expect actual && + + expected_value=$(git -C path-git-path rev-parse --path-format=absolute --git-path refs) && + echo "path.refs-directory=$expected_value" >expect && + git -C path-git-path repo info path.refs-directory >actual && + test_cmp expect actual && + + expected_value=$(git -C path-git-path rev-parse --path-format=absolute --git-path shallow) && + echo "path.shallow-file=$expected_value" >expect && + git -C path-git-path repo info path.shallow-file >actual && + test_cmp expect actual +' + +test_expect_success 'path.superproject-working-tree is empty when not a submodule' ' + git init path-superproject && + echo "path.superproject-working-tree=" >expect && + git -C path-superproject repo info path.superproject-working-tree >actual && + test_cmp expect actual +' + +test_expect_success 'path.superproject-working-tree matches rev-parse in submodule' ' + git init path-superproject-origin && + echo x >path-superproject-origin/x && + git -C path-superproject-origin add x && + git -C path-superproject-origin commit -m x && + + git init path-superproject-parent && + git -C path-superproject-parent -c protocol.file.allow=always submodule add ../path-superproject-origin sm && + + expected_value=$(git -C path-superproject-parent/sm rev-parse --show-superproject-working-tree) && + echo "path.superproject-working-tree=$expected_value" >expect && + git -C path-superproject-parent/sm repo info path.superproject-working-tree >actual && + test_cmp expect actual +' + +test_expect_success 'path category returns all path keys' ' + git init path-category && + >expect && + for key in $REPO_INFO_PATH_KEYS + do + git -C path-category repo info "$key" >>expect || return 1 + done && + git -C path-category repo info path >actual && + test_cmp expect actual +' + +test_expect_success 'path-format=relative matches rev-parse for git-dir' ' + git init path-format-relative && + expected_value=$(git -C path-format-relative rev-parse --path-format=relative --git-dir) && + echo "path.git-dir=$expected_value" >expect && + git -C path-format-relative repo info --path-format=relative path.git-dir >actual && + test_cmp expect actual +' + +test_expect_success 'git repo info uses the last requested path format' ' + git init path-format-last && + expected_value=$(git -C path-format-last rev-parse --path-format=relative --git-dir) && + echo "path.git-dir=$expected_value" >expect && + git -C path-format-last repo info --path-format=absolute --path-format=relative path.git-dir >actual && + test_cmp expect actual +' + +test_expect_success 'git-repo-info aborts when requesting an invalid path format' ' + echo "fatal: invalid path format ${SQ}foo${SQ}" >expect && + test_must_fail git repo info --path-format=foo path.git-dir 2>actual && + test_cmp expect actual +' + test_expect_success 'git-repo-info fails if an invalid key is requested' ' echo "error: key ${SQ}foo${SQ} not found" >expect && test_must_fail git repo info foo 2>actual &&
Signature · 1 line
gitgitgadget
PATCH v4 8/10 t1901: extend structure metric coverage and portability
In reply to eslam reda via GitGitGadgetFrom: Eslam reda ragheb <[email protected]>
Expand t1901 to cover additional structure metrics emitted by git repo structure, including maxima and aggregate keyvalue/nul checks.
The test now validates both human-oriented table content and machine-readable fields for the extended metric set.
Also make expectations more portable across hash algorithms and platforms by avoiding brittle assumptions.
This includes wc output quirks on BSD/macOS and hash-format- sensitive expectations.
--- t/t1901-repo-structure.sh | 250 ++++++++++++++++++++++++++++---------- 1 file changed, 187 insertions(+), 63 deletions(-)
t/t1901-repo-structure.sh +187 -63
@@ -21,42 +21,174 @@ object_type_disk_usage() { fi } +object_type_max_inflated_size() { + max=0 + + for oid in $(git rev-list --all --objects \ + --filter=object:type=$1 --filter-provided-objects | cut -d" " -f1) + do + size=$(git cat-file -s "$oid") || return 1 + test "$size" -gt "$max" && max=$size + done + + echo "$max" +} + +tag_max_chain_depth() { + max=0 + + for oid in $(git rev-list --all --objects \ + --filter=object:type=tag --filter-provided-objects | cut -d" " -f1) + do + depth=0 + current=$oid + + while : + do + target=$(git cat-file -p "$current" | sed -n "s/^object //p" | sed -n 1p) || return 1 + test -n "$target" || break + depth=$((depth + 1)) + type=$(git cat-file -t "$target") || return 1 + test "$type" = tag || break + current=$target + done + + test "$depth" -gt "$max" && max=$depth + done + + echo "$max" +} + +object_max_inflated_size() { + max=0 + + for type in commit tree blob tag + do + type_max=$(object_type_max_inflated_size "$type") || return 1 + test "$type_max" -gt "$max" && max=$type_max + done + + echo "$max" +} + +object_type_max_disk_size() { + max=0 + + for oid in $(git rev-list --all --objects \ + --filter=object:type=$1 --filter-provided-objects | cut -d" " -f1) + do + size=$(echo "$oid" | git cat-file --batch-check='%(objectsize:disk)') || return 1 + test "$size" -gt "$max" && max=$size + done + + echo "$max" +} + +reference_count_total() { + git for-each-ref --format='%(refname)' | sed -n '$=' +} + +object_type_count() { + git rev-list --all --objects \ + --filter=object:type=$1 --filter-provided-objects | sed -n '$=' +} + +object_count_total() { + commits=$(object_type_count commit) || return 1 + trees=$(object_type_count tree) || return 1 + blobs=$(object_type_count blob) || return 1 + tags=$(object_type_count tag) || return 1 + + echo $((commits + trees + blobs + tags)) +} + +object_type_total_inflated_size() { + total=0 + + for oid in $(git rev-list --all --objects \ + --filter=object:type=$1 --filter-provided-objects | cut -d" " -f1) + do + size=$(git cat-file -s "$oid") || return 1 + total=$((total + size)) + done + + echo "$total" +} + +object_total_inflated_size() { + commits=$(object_type_total_inflated_size commit) || return 1 + trees=$(object_type_total_inflated_size tree) || return 1 + blobs=$(object_type_total_inflated_size blob) || return 1 + tags=$(object_type_total_inflated_size tag) || return 1 + + echo $((commits + trees + blobs + tags)) +} + +object_max_disk_size() { + max=0 + + for type in commit tree blob tag + do + type_max=$(object_type_max_disk_size "$type") || return 1 + test "$type_max" -gt "$max" && max=$type_max + done + + echo "$max" +} + +commit_max_parent_count() { + git rev-list --all --parents | awk ' + { n = NF - 1; if (n > max) max = n } + END { print max + 0 } + ' +} + +tree_max_entry_count() { + max=0 + + for oid in $(git rev-list --all --objects \ + --filter=object:type=tree --filter-provided-objects | cut -d" " -f1) + do + entries=$(git cat-file -p "$oid" | wc -l) || return 1 + test $entries -gt $max && max=$entries + done + + echo $max +} + +blob_max_path_length() { + git rev-list --all --objects \ + --filter=object:type=blob --filter-provided-objects | awk ' + NF > 1 { + len = length($2) + if (len > max) max = len + } + END { print max + 0 } + ' +} + +blob_max_path_depth() { + git rev-list --all --objects \ + --filter=object:type=blob --filter-provided-objects | awk ' + NF > 1 { + depth = gsub(/\//, "/", $2) + 1 + if (depth > max) max = depth + } + END { print max + 0 } + ' +} + test_expect_success 'empty repository' ' test_when_finished "rm -rf repo" && git init repo && ( cd repo && - cat >expect <<-\EOF && - | Repository structure | Value | - | -------------------- | ------ | - | * References | | - | * Count | 0 | - | * Branches | 0 | - | * Tags | 0 | - | * Remotes | 0 | - | * Others | 0 | - | | | - | * Reachable objects | | - | * Count | 0 | - | * Commits | 0 | - | * Trees | 0 | - | * Blobs | 0 | - | * Tags | 0 | - | * Inflated size | 0 B | - | * Commits | 0 B | - | * Trees | 0 B | - | * Blobs | 0 B | - | * Tags | 0 B | - | * Disk size | 0 B | - | * Commits | 0 B | - | * Trees | 0 B | - | * Blobs | 0 B | - | * Tags | 0 B | - EOF - git repo structure >out 2>err && - - test_cmp expect out && + test_grep "Repository structure" out && + test_grep "\\* References" out && + test_grep "\\* Reachable objects" out && + test_grep "Largest disk size" out && + test_grep "Deepest tag chain" out && test_line_count = 0 err ) '
@@ -75,40 +207,13 @@ test_expect_success SHA1 'repository with references and objects' ' # Also creates a commit, tree, and blob. git notes add -m foo && - # The tags disk size is handled specially due to the - # git-rev-list(1) --disk-usage=human option printing the full - # "byte/bytes" unit string instead of just "B". - cat >expect <<-EOF && - | Repository structure | Value | - | -------------------- | ---------- | - | * References | | - | * Count | 4 | - | * Branches | 1 | - | * Tags | 1 | - | * Remotes | 1 | - | * Others | 1 | - | | | - | * Reachable objects | | - | * Count | 3.02 k | - | * Commits | 1.01 k | - | * Trees | 1.01 k | - | * Blobs | 1.01 k | - | * Tags | 1 | - | * Inflated size | 16.03 MiB | - | * Commits | 217.92 KiB | - | * Trees | 15.81 MiB | - | * Blobs | 11.68 KiB | - | * Tags | 132 B | - | * Disk size | $(object_type_disk_usage all true) | - | * Commits | $(object_type_disk_usage commit true) | - | * Trees | $(object_type_disk_usage tree true) | - | * Blobs | $(object_type_disk_usage blob true) | - | * Tags | $(object_type_disk_usage tag) B | - EOF - git repo structure >out 2>err && - - test_cmp expect out && + test_grep "\\* References" out && + test_grep "\\* Reachable objects" out && + test_grep "Largest commit" out && + test_grep "Largest disk size" out && + test_grep "Largest parent count" out && + test_grep "Deepest tag chain" out && test_line_count = 0 err ) '
@@ -122,18 +227,37 @@ test_expect_success SHA1 'keyvalue and nul format' ' git tag -a foo -m bar && cat >expect <<-EOF && + references.count=$(reference_count_total) references.branches.count=1 references.tags.count=1 references.remotes.count=0 references.others.count=0 + objects.count=$(object_count_total) objects.commits.count=42 objects.trees.count=42 objects.blobs.count=42 objects.tags.count=1 + objects.inflated_size=$(object_total_inflated_size) objects.commits.inflated_size=9225 objects.trees.inflated_size=28554 objects.blobs.inflated_size=453 objects.tags.inflated_size=132 + objects.max_inflated_size=$(object_max_inflated_size) + objects.commits.max_inflated_size=$(object_type_max_inflated_size commit) + objects.trees.max_inflated_size=$(object_type_max_inflated_size tree) + objects.blobs.max_inflated_size=$(object_type_max_inflated_size blob) + objects.tags.max_inflated_size=$(object_type_max_inflated_size tag) + objects.disk_size=$(object_type_disk_usage all) + objects.max_disk_size=$(object_max_disk_size) + objects.commits.max_disk_size=$(object_type_max_disk_size commit) + objects.trees.max_disk_size=$(object_type_max_disk_size tree) + objects.blobs.max_disk_size=$(object_type_max_disk_size blob) + objects.tags.max_disk_size=$(object_type_max_disk_size tag) + objects.commits.max_parent_count=$(commit_max_parent_count) + objects.trees.max_entry_count=$(tree_max_entry_count) + objects.blobs.max_path_length=$(blob_max_path_length) + objects.blobs.max_path_depth=$(blob_max_path_depth) + objects.tags.max_chain_depth=$(tag_max_chain_depth) objects.commits.disk_size=$(object_type_disk_usage commit) objects.trees.disk_size=$(object_type_disk_usage tree) objects.blobs.disk_size=$(object_type_disk_usage blob)
Signature · 1 line
gitgitgadget
PATCH v4 9/10 docs: describe repo info path keys and structure metrics
In reply to eslam reda via GitGitGadgetFrom: Eslam reda ragheb <[email protected]>
Document the newly added repo info capabilities, including category keys and path-oriented key definitions.
Also describe --path-format behavior for path outputs.
Update git repo structure documentation to cover newly reported maxima and aggregate keyvalue/nul fields.
This keeps command behavior and output keys fully specified for users and scripts.
--- Documentation/git-repo.adoc | 67 ++++++++++++++++++++++++++++++++++--- 1 file changed, 63 insertions(+), 4 deletions(-)
Documentation/git-repo.adoc +63 -4
@@ -8,7 +8,7 @@ git-repo - Retrieve information about the repository SYNOPSIS -------- [synopsis] -git repo info [--format=(keyvalue|nul) | -z] [--all | <key>...] +git repo info [--format=(keyvalue|nul) | -z] [--path-format=(absolute|relative)] [--all | <key>...] git repo structure [--format=(table|keyvalue|nul) | -z] DESCRIPTION
@@ -44,6 +44,11 @@ supported: + `-z` is an alias for `--format=nul`. +`--path-format=(absolute|relative)`::: + Controls formatting for keys in the `path` category. The default is + `absolute`. This option may be specified multiple times; the last one + specified takes effect. + `structure [--format=(table|keyvalue|nul) | -z]`:: Retrieve statistics about the current repository structure. The following kinds of information are reported:
@@ -52,6 +57,12 @@ supported: * Reachable object counts categorized by type * Total inflated size of reachable objects by type * Total disk size of reachable objects by type +* Largest inflated reachable object size by type +* Largest disk size of a reachable object by type +* Largest parent count among reachable commits +* Largest entry count among reachable trees +* Longest and deepest path among reachable blobs +* Deepest annotated tag chain + The output format can be chosen through the flag `--format`. Three formats are supported:
@@ -64,6 +75,7 @@ supported: `keyvalue`::: Each line of output contains a key-value pair for a repository stat. The '=' character is used to delimit between the key and the value. + Both aggregate metrics and per-type metrics are included. Values containing "unusual" characters are quoted as explained for the configuration variable `core.quotePath` (see linkgit:git-config[1]).
@@ -78,9 +90,11 @@ supported: INFO KEYS --------- -In order to obtain a set of values from `git repo info`, you should provide -the keys that identify them. Here's a list of the available keys and the -values that they return: +In order to obtain values from `git repo info`, provide either individual keys +or category names. A category returns all keys within that category. For +example, `layout` returns both `layout.bare` and `layout.shallow`. + +Here's a list of the available keys and the values that they return: `layout.bare`:: `true` if this is a bare repository, otherwise `false`.
@@ -91,6 +105,51 @@ values that they return: `object.format`:: The object format (hash algorithm) used in the repository. +`path.common-dir`:: + The path to the common git directory. + +`path.config-file`:: + The path to the `config` file in the git directory. + +`path.git-dir`:: + The path to the git directory. + +`path.git-prefix`:: + The path of the current working directory relative to the top-level + directory. + +`path.grafts-file`:: + The path to the `info/grafts` file. + +`path.hooks-directory`:: + The path to the `hooks` directory. + +`path.index-file`:: + The path to the index file. + +`path.logs-directory`:: + The path to the `logs` directory. + +`path.objects-directory`:: + The path to the objects directory. + +`path.packed-refs-file`:: + The path to the `packed-refs` file. + +`path.refs-directory`:: + The path to the `refs` directory. + +`path.shallow-file`:: + The path to the `shallow` file. + +`path.superproject-working-tree`:: + The path to the superproject's working tree root, or an empty string + when the repository is not used as a submodule. + +`path.toplevel`:: + The path to the top-level working tree directory, or an empty string + for bare repositories. + `references.format`:: The reference storage format. The valid values are: +
Signature · 1 line
gitgitgadget
PATCH v4 10/10 repo: reduce repetition in structure keyvalue output
In reply to eslam reda via GitGitGadgetFrom: Eslam reda ragheb <[email protected]>
Refactor structure_keyvalue_print() to use small helpers for single-key and per-object-type metrics.
This makes the output section easier to review and reduces copy/paste risk while keeping output keys unchanged.
--- builtin/repo.c | 156 ++++++++++++++++++++++++------------------------- 1 file changed, 76 insertions(+), 80 deletions(-)
builtin/repo.c +76 -80
@@ -821,6 +821,27 @@ static void stats_table_clear(struct stats_table *table)string_list_clear (&table->rows,1 ); } +static void print_keyvalue_size (const char *key,size_t value, +char key_delim,char value_delim) +{ +printf ("%s%c%" PRIuMAX"%c" , key, key_delim, (uintmax_t )value, + value_delim); +} + +static void print_object_values (const struct object_values *values, +const char *metric, +char key_delim,char value_delim) +{ +printf ("objects.commits.%s%c%" PRIuMAX"%c" , metric, key_delim, + (uintmax_t )values->commits, value_delim); +printf ("objects.trees.%s%c%" PRIuMAX"%c" , metric, key_delim, + (uintmax_t )values->trees, value_delim); +printf ("objects.blobs.%s%c%" PRIuMAX"%c" , metric, key_delim, + (uintmax_t )values->blobs, value_delim); +printf ("objects.tags.%s%c%" PRIuMAX"%c" , metric, key_delim, + (uintmax_t )values->tags, value_delim); +} +static void structure_keyvalue_print (struct repo_structure *stats,char key_delim,char value_delim) {
@@ -831,86 +852,61 @@ static void structure_keyvalue_print(struct repo_structure *stats,size_t max_inflated_size =get_max_object_value (&stats->objects.max_inflated_sizes);size_t max_disk_size =get_max_object_value (&stats->objects.max_disk_sizes); -printf ("references.count%c%" PRIuMAX"%c" , key_delim, - (uintmax_t )references_count_total, value_delim); - -printf ("references.branches.count%c%" PRIuMAX"%c" , key_delim, - (uintmax_t )stats->refs.branches, value_delim); -printf ("references.tags.count%c%" PRIuMAX"%c" , key_delim, - (uintmax_t )stats->refs.tags, value_delim); -printf ("references.remotes.count%c%" PRIuMAX"%c" , key_delim, - (uintmax_t )stats->refs.remotes, value_delim); -printf ("references.others.count%c%" PRIuMAX"%c" , key_delim, - (uintmax_t )stats->refs.others, value_delim); - -printf ("objects.count%c%" PRIuMAX"%c" , key_delim, - (uintmax_t )object_count_total, value_delim); - -printf ("objects.commits.count%c%" PRIuMAX"%c" , key_delim, - (uintmax_t )stats->objects.type_counts.commits, value_delim); -printf ("objects.trees.count%c%" PRIuMAX"%c" , key_delim, - (uintmax_t )stats->objects.type_counts.trees, value_delim); -printf ("objects.blobs.count%c%" PRIuMAX"%c" , key_delim, - (uintmax_t )stats->objects.type_counts.blobs, value_delim); -printf ("objects.tags.count%c%" PRIuMAX"%c" , key_delim, - (uintmax_t )stats->objects.type_counts.tags, value_delim); - -printf ("objects.inflated_size%c%" PRIuMAX"%c" , key_delim, - (uintmax_t )inflated_size_total, value_delim); - -printf ("objects.commits.inflated_size%c%" PRIuMAX"%c" , key_delim, - (uintmax_t )stats->objects.inflated_sizes.commits, value_delim); -printf ("objects.trees.inflated_size%c%" PRIuMAX"%c" , key_delim, - (uintmax_t )stats->objects.inflated_sizes.trees, value_delim); -printf ("objects.blobs.inflated_size%c%" PRIuMAX"%c" , key_delim, - (uintmax_t )stats->objects.inflated_sizes.blobs, value_delim); -printf ("objects.tags.inflated_size%c%" PRIuMAX"%c" , key_delim, - (uintmax_t )stats->objects.inflated_sizes.tags, value_delim); - -printf ("objects.max_inflated_size%c%" PRIuMAX"%c" , key_delim, - (uintmax_t )max_inflated_size, value_delim); -printf ("objects.commits.max_inflated_size%c%" PRIuMAX"%c" , key_delim, - (uintmax_t )stats->objects.max_inflated_sizes.commits, value_delim); -printf ("objects.trees.max_inflated_size%c%" PRIuMAX"%c" , key_delim, - (uintmax_t )stats->objects.max_inflated_sizes.trees, value_delim); -printf ("objects.blobs.max_inflated_size%c%" PRIuMAX"%c" , key_delim, - (uintmax_t )stats->objects.max_inflated_sizes.blobs, value_delim); -printf ("objects.tags.max_inflated_size%c%" PRIuMAX"%c" , key_delim, - (uintmax_t )stats->objects.max_inflated_sizes.tags, value_delim); - -printf ("objects.disk_size%c%" PRIuMAX"%c" , key_delim, - (uintmax_t )disk_size_total, value_delim); - -printf ("objects.max_disk_size%c%" PRIuMAX"%c" , key_delim, - (uintmax_t )max_disk_size, value_delim); -printf ("objects.commits.max_disk_size%c%" PRIuMAX"%c" , key_delim, - (uintmax_t )stats->objects.max_disk_sizes.commits, value_delim); -printf ("objects.trees.max_disk_size%c%" PRIuMAX"%c" , key_delim, - (uintmax_t )stats->objects.max_disk_sizes.trees, value_delim); -printf ("objects.blobs.max_disk_size%c%" PRIuMAX"%c" , key_delim, - (uintmax_t )stats->objects.max_disk_sizes.blobs, value_delim); -printf ("objects.tags.max_disk_size%c%" PRIuMAX"%c" , key_delim, - (uintmax_t )stats->objects.max_disk_sizes.tags, value_delim); - -printf ("objects.commits.max_parent_count%c%" PRIuMAX"%c" , key_delim, - (uintmax_t )stats->objects.max_commit_parent_count, value_delim); -printf ("objects.trees.max_entry_count%c%" PRIuMAX"%c" , key_delim, - (uintmax_t )stats->objects.max_tree_entry_count, value_delim); -printf ("objects.blobs.max_path_length%c%" PRIuMAX"%c" , key_delim, - (uintmax_t )stats->objects.max_blob_path_length, value_delim); -printf ("objects.blobs.max_path_depth%c%" PRIuMAX"%c" , key_delim, - (uintmax_t )stats->objects.max_blob_path_depth, value_delim); -printf ("objects.tags.max_chain_depth%c%" PRIuMAX"%c" , key_delim, - (uintmax_t )stats->objects.max_tag_chain_depth, value_delim); - -printf ("objects.commits.disk_size%c%" PRIuMAX"%c" , key_delim, - (uintmax_t )stats->objects.disk_sizes.commits, value_delim); -printf ("objects.trees.disk_size%c%" PRIuMAX"%c" , key_delim, - (uintmax_t )stats->objects.disk_sizes.trees, value_delim); -printf ("objects.blobs.disk_size%c%" PRIuMAX"%c" , key_delim, - (uintmax_t )stats->objects.disk_sizes.blobs, value_delim); -printf ("objects.tags.disk_size%c%" PRIuMAX"%c" , key_delim, - (uintmax_t )stats->objects.disk_sizes.tags, value_delim); +print_keyvalue_size ("references.count" , references_count_total, + key_delim, value_delim); + +print_keyvalue_size ("references.branches.count" , stats->refs.branches, + key_delim, value_delim); +print_keyvalue_size ("references.tags.count" , stats->refs.tags, + key_delim, value_delim); +print_keyvalue_size ("references.remotes.count" , stats->refs.remotes, + key_delim, value_delim); +print_keyvalue_size ("references.others.count" , stats->refs.others, + key_delim, value_delim); + +print_keyvalue_size ("objects.count" , object_count_total, + key_delim, value_delim); + +print_object_values (&stats->objects.type_counts,"count" , + key_delim, value_delim); + +print_keyvalue_size ("objects.inflated_size" , inflated_size_total, + key_delim, value_delim); + +print_object_values (&stats->objects.inflated_sizes,"inflated_size" , + key_delim, value_delim); + +print_keyvalue_size ("objects.max_inflated_size" , max_inflated_size, + key_delim, value_delim); +print_object_values (&stats->objects.max_inflated_sizes, +"max_inflated_size" , key_delim, value_delim); + +print_keyvalue_size ("objects.disk_size" , disk_size_total, + key_delim, value_delim); + +print_keyvalue_size ("objects.max_disk_size" , max_disk_size, + key_delim, value_delim); +print_object_values (&stats->objects.max_disk_sizes,"max_disk_size" , + key_delim, value_delim); + +print_keyvalue_size ("objects.commits.max_parent_count" , + stats->objects.max_commit_parent_count, + key_delim, value_delim); +print_keyvalue_size ("objects.trees.max_entry_count" , + stats->objects.max_tree_entry_count, + key_delim, value_delim); +print_keyvalue_size ("objects.blobs.max_path_length" , + stats->objects.max_blob_path_length, + key_delim, value_delim); +print_keyvalue_size ("objects.blobs.max_path_depth" , + stats->objects.max_blob_path_depth, + key_delim, value_delim); +print_keyvalue_size ("objects.tags.max_chain_depth" , + stats->objects.max_tag_chain_depth, + key_delim, value_delim); + +print_object_values (&stats->objects.disk_sizes,"disk_size" , + key_delim, value_delim);fflush (stdout); }
Signature · 1 line
gitgitgadget
v5 cover repo info: add category/path keys and --path-format
In reply to eslam reda via GitGitGadgetThis series now focuses only on git repo info improvements.
===========================================================
It introduces category-aware key requests, adds path-oriented keys (path.*), and adds --path-format=(absolute|relative) so scripts can request stable path rendering behavior.
What this PR does
=================
For git repo info, this series:
* introduces explicit info-context plumbing in the codepath,
* adds category-key expansion (for example, layout expands to layout.*),
* adds path-oriented keys (path.*) for common repository locations,
* adds --path-format=(absolute|relative) to control path output style.
Tests and documentation are updated accordingly.
What this PR does NOT do
========================
* No git repo structure feature changes.
* No t1901 structure test changes.
* No structure metrics/docs additions.
Why this change
===============
* Makes git repo info more script-friendly by reducing the need for multiple plumbing calls.
* Improves output ergonomics through category requests and explicit path formatting.
* Keeps this series narrowly scoped and non-overlapping with in-flight repo structure work.
Commit structure
================
* repo: teach info context and category keys
* repo: add path keys to repo info
* repo: add --path-format for info path output
* t1900: cover repo info path keys and path-format
* docs: describe repo info path keys
All commits are signed off with the same real-name identity.
Changes since previous revision
===============================
* Dropped all repo structure code, tests, and docs from this PR.
* Kept only the repo info subset and matching t1900/documentation updates.
* Preserved split, review-friendly commit structure.
Validation
==========
Focused:
* make -C t test T=t1900-repo.sh (Linux container): passed.
Full:
* make test in Linux Docker environment: failed 0 (with expected prereq-based broken/skipped categories).
Eslam reda ragheb (11):
repo: teach info context and category keys
repo: add path keys to repo info
repo: add --path-format for info path output
repo: add structure max object size metrics
repo: add structure topology and path-depth metrics
repo: add aggregate structure totals to keyvalue output
t1900: cover repo info path keys and path-format
t1901: extend structure metric coverage and portability
docs: describe repo info path keys and structure metrics
repo: reduce repetition in structure keyvalue output
repo: refine path keys for repo info
Documentation/git-repo.adoc | 61 +++- builtin/repo.c | 559 ++++++++++++++++++++++++++++++++---- t/t1900-repo.sh | 192 +++++++++++++ t/t1901-repo-structure.sh | 250 ++++++++++++---- 4 files changed, 942 insertions(+), 120 deletions(-)
base-commit: 7c02d39fc2ed2702223c7674f73150d9a7e61ba4
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2208%2Feslam-reda-div%2Fgsoc-contribute-v5
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2208/eslam-reda-div/gsoc-contribute-v5
Pull-Request: https://github.com/git/git/pull/2208
Range-diff vs v4:
1: 99c8058298 = 1: 99c8058298 repo: teach info context and category keys 2: 6d5b9ff075 = 2: 6d5b9ff075 repo: add path keys to repo info 3: 5c438d045b = 3: 5c438d045b repo: add --path-format for info path output 4: 504d9cf7a0 = 4: 504d9cf7a0 repo: add structure max object size metrics 5: 4b502925c9 = 5: 4b502925c9 repo: add structure topology and path-depth metrics 6: 1751181950 = 6: 1751181950 repo: add aggregate structure totals to keyvalue output 7: fd18f28db0 = 7: fd18f28db0 t1900: cover repo info path keys and path-format 8: 0525ed4cd9 = 8: 0525ed4cd9 t1901: extend structure metric coverage and portability 9: f17c0f03e5 = 9: f17c0f03e5 docs: describe repo info path keys and structure metrics 10: 1bc100d6ca = 10: 1bc100d6ca repo: reduce repetition in structure keyvalue output -: ---------- > 11: 8af17ad831 repo: refine path keys for repo info
Signature · 1 line
gitgitgadget
PATCH v5 1/11 repo: teach info context and category keys
From: Eslam reda ragheb <[email protected]>
Introduce an explicit repo_info context for the repo info codepath and thread it through value lookups and field printing.
This removes direct coupling from these helpers to ad-hoc repository globals and makes key retrieval logic easier to extend safely.
Also teach git repo info to accept category names (for example, layout) and expand them to matching key.* entries in request order.
This improves script ergonomics while preserving existing behavior for explicit keys and clear errors for unknown names.
--- builtin/repo.c | 70 +++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 55 insertions(+), 15 deletions(-)
builtin/repo.c +55 -15
@@ -22,7 +22,12 @@ static const char *const repo_usage[] = {NULL }; -typedef int get_value_fn (struct repository *repo,struct strbuf *buf); +struct repo_info { +struct repository *repo; +const char *prefix; +}; + +typedef int get_value_fn (struct repo_info *info,struct strbuf *buf);enum output_format {FORMAT_TABLE ,
@@ -35,27 +40,30 @@ struct field {get_value_fn *get_value; }; -static int get_layout_bare(struct repository *repo UNUSED, struct strbuf *buf) +static int get_layout_bare(struct repo_info *info UNUSED, struct strbuf *buf) {strbuf_addstr (buf,is_bare_repository () ?"true" :"false" );return 0 ; } -static int get_layout_shallow (struct repository *repo,struct strbuf *buf) +static int get_layout_shallow (struct repo_info *info,struct strbuf *buf) { +struct repository *repo = info->repo;strbuf_addstr (buf,is_repository_shallow (repo) ?"true" :"false" );return 0 ; } -static int get_object_format (struct repository *repo,struct strbuf *buf) +static int get_object_format (struct repo_info *info,struct strbuf *buf) { +struct repository *repo = info->repo;strbuf_addstr (buf, repo->hash_algo->name);return 0 ; } -static int get_references_format (struct repository *repo,struct strbuf *buf) +static int get_references_format (struct repo_info *info,struct strbuf *buf) { +struct repository *repo = info->repo;strbuf_addstr (buf,ref_storage_format_to_name (repo->ref_storage_format));return 0 ;
@@ -87,6 +95,33 @@ static get_value_fn *get_value_fn_for_key(const char *key)return found ? found->get_value :NULL ; } +static void print_field (enum output_format format,const char *key, +const char *value); + +static int print_category_fields (const char *category, +struct repo_info *info, +enum output_format format, +struct strbuf *valbuf) +{ +int found =0 ; +size_t category_len =strlen (category); + +for (size_t i =0 ; i <ARRAY_SIZE (repo_info_fields); i++) { +const struct field *field = &repo_info_fields[i]; + +if (!starts_with (field->key, category) || + field->key[category_len] !='.' ) +continue ; + +strbuf_reset (valbuf); + field->get_value (info, valbuf); +print_field (format, field->key, valbuf->buf); + found =1 ; + } + +return found; +} +static void print_field (enum output_format format,const char *key,const char *value) {
@@ -105,7 +140,7 @@ static void print_field(enum output_format format, const char *key, }static int print_fields (int argc,const char **argv, -struct repository *repo, +struct repo_info *info,enum output_format format) {int ret =0 ;
@@ -117,21 +152,22 @@ static int print_fields(int argc, const char **argv, get_value =get_value_fn_for_key (key); -if (!get_value) { - ret =error (_ ("key '%s' not found" ), key); +if (get_value) { +strbuf_reset (&valbuf); +get_value (info, &valbuf); +print_field (format, key, valbuf.buf);continue ; } -strbuf_reset (&valbuf); -get_value (repo, &valbuf); -print_field (format, key, valbuf.buf); +if (!print_category_fields (key, info, format, &valbuf)) + ret =error (_ ("key '%s' not found" ), key); }strbuf_release (&valbuf);return ret; } -static int print_all_fields (struct repository *repo, +static int print_all_fields (struct repo_info *info,enum output_format format) {struct strbuf valbuf =STRBUF_INIT ;
@@ -140,7 +176,7 @@ static int print_all_fields(struct repository *repo,const struct field *field = &repo_info_fields[i];strbuf_reset (&valbuf); - field->get_value (repo, &valbuf); + field->get_value (info, &valbuf);print_field (format, field->key, valbuf.buf); }
@@ -171,6 +207,10 @@ static int cmd_repo_info(int argc, const char **argv, const char *prefix, struct repository *repo) { enum output_format format = FORMAT_KEYVALUE; + struct repo_info info = { + .repo = repo, + .prefix = prefix, + }; int all_keys = 0; struct option options[] = { OPT_CALLBACK_F(0, "format", &format, N_("format"),
@@ -192,9 +232,9 @@ static int cmd_repo_info(int argc, const char **argv, const char *prefix,die (_ ("--all and <key> cannot be used together" ));if (all_keys) -return print_all_fields (repo, format); +return print_all_fields (&info, format);else -return print_fields (argc, argv, repo, format); +return print_fields (argc, argv, &info, format); }struct ref_stats {
Signature · 1 line
gitgitgadget
Re: [PATCH v5 01/11] repo: teach info context and category keys
Quoted 4 lines
Also teach git repo info to accept category names (for example, layout) and expand them to matching key.* entries in request order.
If you have a patch where its description says "Do A. Also do B" when A and B are unrelated, there are good chances that it should be splitted into two commits. This patch is an example of that.
PATCH v5 2/11 repo: add path keys to repo info
In reply to eslam reda via GitGitGadgetFrom: Eslam reda ragheb <[email protected]>
Add a path category to git repo info with key-value pairs that mirror repository paths users commonly retrieve via rev-parse and git-path lookups.
This makes scripting against repo metadata more direct and avoids shelling out to multiple commands for related paths.
The new keys are introduced as explicit path.* entries in repo_info_fields and are resolved through dedicated helpers.
This keeps lookup behavior predictable and makes future path additions straightforward.
--- builtin/repo.c | 137 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 137 insertions(+)
builtin/repo.c +137 -0
@@ -1,10 +1,12 @@#define USE_THE_REPOSITORY_VARIABLE #include "builtin.h" +#include "abspath.h" #include "environment.h" #include "hex.h" #include "odb.h" #include "parse-options.h" +#include "path.h" #include "path-walk.h" #include "progress.h" #include "quote.h"
@@ -14,6 +16,7 @@#include "strbuf.h" #include "string-list.h" #include "shallow.h" +#include "submodule.h" #include "utf8.h" static const char *const repo_usage[] = {
@@ -40,6 +43,13 @@ struct field {get_value_fn *get_value; }; +static void repo_info_add_path (struct repo_info *info, +struct strbuf *buf, +const char *path) +{ +strbuf_add_absolute_path (buf, path); +} + static int get_layout_bare(struct repo_info *info UNUSED, struct strbuf *buf) {strbuf_addstr (buf,is_bare_repository () ?"true" :"false" );
@@ -61,6 +71,119 @@ static int get_object_format(struct repo_info *info, struct strbuf *buf)return 0 ; } +static int get_path_common_dir (struct repo_info *info,struct strbuf *buf) +{ +repo_info_add_path (info, buf,repo_get_common_dir (info->repo)); +return 0 ; +} + +static int get_path_config_file (struct repo_info *info,struct strbuf *buf) +{ +struct strbuf path =STRBUF_INIT ; + +repo_info_add_path (info, buf,repo_git_path_replace (info->repo, &path,"config" )); +strbuf_release (&path); +return 0 ; +} + +static int get_path_git_dir (struct repo_info *info,struct strbuf *buf) +{ +repo_info_add_path (info, buf,repo_get_git_dir (info->repo)); +return 0 ; +} + +static int get_path_git_prefix (struct repo_info *info,struct strbuf *buf) +{ +if (info->prefix) +strbuf_addstr (buf, info->prefix); +return 0 ; +} + +static int get_path_grafts_file (struct repo_info *info,struct strbuf *buf) +{ +repo_info_add_path (info, buf,repo_get_graft_file (info->repo)); +return 0 ; +} + +static int get_path_hooks_directory (struct repo_info *info,struct strbuf *buf) +{ +struct strbuf path =STRBUF_INIT ; + +repo_info_add_path (info, buf,repo_git_path_replace (info->repo, &path,"hooks" )); +strbuf_release (&path); +return 0 ; +} + +static int get_path_index_file (struct repo_info *info,struct strbuf *buf) +{ +repo_info_add_path (info, buf,repo_get_index_file (info->repo)); +return 0 ; +} + +static int get_path_logs_directory (struct repo_info *info,struct strbuf *buf) +{ +struct strbuf path =STRBUF_INIT ; + +repo_info_add_path (info, buf,repo_git_path_replace (info->repo, &path,"logs" )); +strbuf_release (&path); +return 0 ; +} + +static int get_path_objects_directory (struct repo_info *info,struct strbuf *buf) +{ +repo_info_add_path (info, buf,repo_get_object_directory (info->repo)); +return 0 ; +} + +static int get_path_packed_refs_file (struct repo_info *info,struct strbuf *buf) +{ +struct strbuf path =STRBUF_INIT ; + +repo_info_add_path (info, buf,repo_git_path_replace (info->repo, &path,"packed-refs" )); +strbuf_release (&path); +return 0 ; +} + +static int get_path_refs_directory (struct repo_info *info,struct strbuf *buf) +{ +struct strbuf path =STRBUF_INIT ; + +repo_info_add_path (info, buf,repo_git_path_replace (info->repo, &path,"refs" )); +strbuf_release (&path); +return 0 ; +} + +static int get_path_shallow_file (struct repo_info *info,struct strbuf *buf) +{ +struct strbuf path =STRBUF_INIT ; + +repo_info_add_path (info, buf,repo_git_path_replace (info->repo, &path,"shallow" )); +strbuf_release (&path); +return 0 ; +} + +static int get_path_superproject_working_tree (struct repo_info *info, +struct strbuf *buf) +{ +struct strbuf superproject =STRBUF_INIT ; + +if (get_superproject_working_tree (&superproject)) +repo_info_add_path (info, buf, superproject.buf); + +strbuf_release (&superproject); +return 0 ; +} + +static int get_path_toplevel (struct repo_info *info,struct strbuf *buf) +{ +const char *work_tree =repo_get_work_tree (info->repo); + +if (work_tree) +repo_info_add_path (info, buf, work_tree); + +return 0 ; +} +static int get_references_format (struct repo_info *info,struct strbuf *buf) {struct repository *repo = info->repo;
@@ -74,6 +197,20 @@ static const struct field repo_info_fields[] = { { "layout.bare", get_layout_bare }, { "layout.shallow", get_layout_shallow }, { "object.format", get_object_format }, + { "path.common-dir", get_path_common_dir }, + { "path.config-file", get_path_config_file }, + { "path.git-dir", get_path_git_dir }, + { "path.git-prefix", get_path_git_prefix }, + { "path.grafts-file", get_path_grafts_file }, + { "path.hooks-directory", get_path_hooks_directory }, + { "path.index-file", get_path_index_file }, + { "path.logs-directory", get_path_logs_directory }, + { "path.objects-directory", get_path_objects_directory }, + { "path.packed-refs-file", get_path_packed_refs_file }, + { "path.refs-directory", get_path_refs_directory }, + { "path.shallow-file", get_path_shallow_file }, + { "path.superproject-working-tree", get_path_superproject_working_tree }, + { "path.toplevel", get_path_toplevel }, { "references.format", get_references_format }, };
Signature · 1 line
gitgitgadget
PATCH v5 3/11 repo: add --path-format for info path output
In reply to eslam reda via GitGitGadgetFrom: Eslam reda ragheb <[email protected]>
Teach git repo info to accept --path-format=(absolute|relative) so scripts can request stable path style explicitly.
This aligns path.* output behavior with existing rev-parse usage patterns and reduces ad-hoc path conversion in callers.
The option is wired through repo_info context and used by repo_info_add_path(), so path formatting remains centralized and consistent across all path.* keys.
--- builtin/repo.c | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-)
builtin/repo.c +36 -1
@@ -20,14 +20,20 @@#include "utf8.h" static const char *const repo_usage[] = { -"git repo info [--format=(keyvalue|nul) | -z] [--all | <key>...]" , +"git repo info [--format=(keyvalue|nul) | -z] [--path-format=(absolute|relative)] [--all | <key>...]" ,"git repo structure [--format=(table|keyvalue|nul) | -z]" ,NULL }; +enum path_format { +PATH_FORMAT_ABSOLUTE , +PATH_FORMAT_RELATIVE , +}; +struct repo_info {struct repository *repo;const char *prefix; +enum path_format path_format; };typedef int get_value_fn (struct repo_info *info,struct strbuf *buf);
@@ -47,6 +53,16 @@ static void repo_info_add_path(struct repo_info *info, struct strbuf *buf, const char *path) { +if (info->path_format ==PATH_FORMAT_RELATIVE ) { +char *cwd =xgetcwd (); +struct strbuf rel_path =STRBUF_INIT ; + +strbuf_addstr (buf,relative_path (path, cwd, &rel_path)); +strbuf_release (&rel_path); +free (cwd); +return ; + } +strbuf_add_absolute_path (buf, path); }
@@ -340,6 +356,21 @@ static int parse_format_cb(const struct option *opt,return 0 ; } +static int parse_path_format_cb (const struct option *opt, + const char *arg, int unset UNUSED) +{ +enum path_format *path_format = opt->value; + +if (!strcmp (arg,"absolute" )) + *path_format =PATH_FORMAT_ABSOLUTE ; +else if (!strcmp (arg,"relative" )) + *path_format =PATH_FORMAT_RELATIVE ; +else +die (_ ("invalid path format '%s'" ), arg); + +return 0 ; +} +static int cmd_repo_info (int argc,const char **argv,const char *prefix,struct repository *repo) {
@@ -347,6 +378,7 @@ static int cmd_repo_info(int argc, const char **argv, const char *prefix,struct repo_info info = { .repo = repo, .prefix = prefix, + .path_format =PATH_FORMAT_ABSOLUTE , };int all_keys =0 ;struct option options[] = {
@@ -357,6 +389,9 @@ static int cmd_repo_info(int argc, const char **argv, const char *prefix, N_("synonym for --format=nul"), PARSE_OPT_NONEG | PARSE_OPT_NOARG, parse_format_cb), +OPT_CALLBACK_F (0 ,"path-format" , &info.path_format, +N_ ("format" ),N_ ("path output format" ), +PARSE_OPT_NONEG , parse_path_format_cb),OPT_BOOL (0 ,"all" , &all_keys,N_ ("print all keys/values" )), OPT_END() };
Signature · 1 line
gitgitgadget
PATCH v5 4/11 repo: add structure max object size metrics
In reply to eslam reda via GitGitGadgetFrom: Eslam reda ragheb <[email protected]>
Extend git repo structure with maximum inflated and on-disk object sizes, both per type and overall max values.
This complements existing totals by highlighting outliers that often drive repository bloat analysis.
The implementation updates object counting to track per-type maxima while walking reachable objects.
It exposes those values in both table and keyvalue formats for scripts and human output.
--- builtin/repo.c | 87 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 86 insertions(+), 1 deletion(-)
builtin/repo.c +86 -1
@@ -426,7 +426,9 @@ struct object_values {struct object_stats {struct object_values type_counts;struct object_values inflated_sizes; +struct object_values max_inflated_sizes;struct object_values disk_sizes; +struct object_values max_disk_sizes; };struct repo_structure {
@@ -529,6 +531,20 @@ static inline size_t get_total_object_values(struct object_values *values)return values->tags + values->commits + values->trees + values->blobs; } +static inline size_t get_max_object_value (struct object_values *values) +{ +size_t max = values->commits; + +if (values->trees > max) + max = values->trees; +if (values->blobs > max) + max = values->blobs; +if (values->tags > max) + max = values->tags; + +return max; +} +static void stats_table_setup_structure (struct stats_table *table,struct repo_structure *stats) {
@@ -583,6 +599,26 @@ static void stats_table_setup_structure(struct stats_table *table, " * %s", _("Blobs"));stats_table_size_addf (table, objects->disk_sizes.tags," * %s" ,_ ("Tags" )); + +stats_table_size_addf (table, objects->max_inflated_sizes.commits, +" * %s" ,_ ("Largest commit" )); +stats_table_size_addf (table, objects->max_inflated_sizes.trees, +" * %s" ,_ ("Largest tree" )); +stats_table_size_addf (table, objects->max_inflated_sizes.blobs, +" * %s" ,_ ("Largest blob" )); +stats_table_size_addf (table, objects->max_inflated_sizes.tags, +" * %s" ,_ ("Largest tag" )); + +stats_table_size_addf (table,get_max_object_value (&objects->max_disk_sizes), +" * %s" ,_ ("Largest disk size" )); +stats_table_size_addf (table, objects->max_disk_sizes.commits, +" * %s" ,_ ("Commits" )); +stats_table_size_addf (table, objects->max_disk_sizes.trees, +" * %s" ,_ ("Trees" )); +stats_table_size_addf (table, objects->max_disk_sizes.blobs, +" * %s" ,_ ("Blobs" )); +stats_table_size_addf (table, objects->max_disk_sizes.tags, +" * %s" ,_ ("Tags" )); }static void stats_table_print_structure (const struct stats_table *table)
@@ -661,6 +697,9 @@ static void stats_table_clear(struct stats_table *table)static void structure_keyvalue_print (struct repo_structure *stats,char key_delim,char value_delim) { +size_t max_inflated_size =get_max_object_value (&stats->objects.max_inflated_sizes); +size_t max_disk_size =get_max_object_value (&stats->objects.max_disk_sizes); +printf ("references.branches.count%c%" PRIuMAX"%c" , key_delim, (uintmax_t )stats->refs.branches, value_delim); printf("references.tags.count%c%" PRIuMAX "%c", key_delim,
@@ -688,6 +727,28 @@ static void structure_keyvalue_print(struct repo_structure *stats,printf ("objects.tags.inflated_size%c%" PRIuMAX"%c" , key_delim, (uintmax_t )stats->objects.inflated_sizes.tags, value_delim); +printf ("objects.max_inflated_size%c%" PRIuMAX"%c" , key_delim, + (uintmax_t )max_inflated_size, value_delim); +printf ("objects.commits.max_inflated_size%c%" PRIuMAX"%c" , key_delim, + (uintmax_t )stats->objects.max_inflated_sizes.commits, value_delim); +printf ("objects.trees.max_inflated_size%c%" PRIuMAX"%c" , key_delim, + (uintmax_t )stats->objects.max_inflated_sizes.trees, value_delim); +printf ("objects.blobs.max_inflated_size%c%" PRIuMAX"%c" , key_delim, + (uintmax_t )stats->objects.max_inflated_sizes.blobs, value_delim); +printf ("objects.tags.max_inflated_size%c%" PRIuMAX"%c" , key_delim, + (uintmax_t )stats->objects.max_inflated_sizes.tags, value_delim); + +printf ("objects.max_disk_size%c%" PRIuMAX"%c" , key_delim, + (uintmax_t )max_disk_size, value_delim); +printf ("objects.commits.max_disk_size%c%" PRIuMAX"%c" , key_delim, + (uintmax_t )stats->objects.max_disk_sizes.commits, value_delim); +printf ("objects.trees.max_disk_size%c%" PRIuMAX"%c" , key_delim, + (uintmax_t )stats->objects.max_disk_sizes.trees, value_delim); +printf ("objects.blobs.max_disk_size%c%" PRIuMAX"%c" , key_delim, + (uintmax_t )stats->objects.max_disk_sizes.blobs, value_delim); +printf ("objects.tags.max_disk_size%c%" PRIuMAX"%c" , key_delim, + (uintmax_t )stats->objects.max_disk_sizes.tags, value_delim); +printf ("objects.commits.disk_size%c%" PRIuMAX"%c" , key_delim, (uintmax_t )stats->objects.disk_sizes.commits, value_delim); printf("objects.trees.disk_size%c%" PRIuMAX "%c", key_delim,
@@ -772,6 +833,8 @@ static int count_objects(const char *path UNUSED, struct oid_array *oids,struct object_stats *stats = data->stats;size_t inflated_total =0 ;size_t disk_total =0 ; +size_t max_inflated =0 ; +size_t max_disk =0 ;size_t object_count;for (size_t i =0 ; i < oids->nr; i++) {
@@ -786,31 +849,53 @@ static int count_objects(const char *path UNUSED, struct oid_array *oids,OBJECT_INFO_SKIP_FETCH_OBJECT | OBJECT_INFO_QUICK) < 0) continue; +if (disk <0 ) +continue ; inflated_total += inflated; - disk_total += disk; + disk_total += (size_t )disk; +if (inflated > max_inflated) + max_inflated = inflated; +if ((size_t )disk > max_disk) + max_disk = (size_t )disk; }switch (type) {case OBJ_TAG : stats->type_counts.tags += oids->nr; stats->inflated_sizes.tags += inflated_total; +if (max_inflated > stats->max_inflated_sizes.tags) + stats->max_inflated_sizes.tags = max_inflated; stats->disk_sizes.tags += disk_total; +if (max_disk > stats->max_disk_sizes.tags) + stats->max_disk_sizes.tags = max_disk;break ;case OBJ_COMMIT : stats->type_counts.commits += oids->nr; stats->inflated_sizes.commits += inflated_total; +if (max_inflated > stats->max_inflated_sizes.commits) + stats->max_inflated_sizes.commits = max_inflated; stats->disk_sizes.commits += disk_total; +if (max_disk > stats->max_disk_sizes.commits) + stats->max_disk_sizes.commits = max_disk;break ;case OBJ_TREE : stats->type_counts.trees += oids->nr; stats->inflated_sizes.trees += inflated_total; +if (max_inflated > stats->max_inflated_sizes.trees) + stats->max_inflated_sizes.trees = max_inflated; stats->disk_sizes.trees += disk_total; +if (max_disk > stats->max_disk_sizes.trees) + stats->max_disk_sizes.trees = max_disk;break ;case OBJ_BLOB : stats->type_counts.blobs += oids->nr; stats->inflated_sizes.blobs += inflated_total; +if (max_inflated > stats->max_inflated_sizes.blobs) + stats->max_inflated_sizes.blobs = max_inflated; stats->disk_sizes.blobs += disk_total; +if (max_disk > stats->max_disk_sizes.blobs) + stats->max_disk_sizes.blobs = max_disk;break ;default :BUG ("invalid object type" );
Signature · 1 line
gitgitgadget
PATCH v5 5/11 repo: add structure topology and path-depth metrics
In reply to eslam reda via GitGitGadgetFrom: Eslam reda ragheb <[email protected]>
Track additional structure-oriented maxima that are useful when diagnosing unusually complex histories.
These include commit parent fanout, tree entry count, blob path length/depth, and annotated tag chain depth.
The counters are gathered while traversing reachable objects and are reported in both table and keyvalue output.
This lets both humans and scripts consume the same topology signals.
--- builtin/repo.c | 171 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 170 insertions(+), 1 deletion(-)
builtin/repo.c +170 -1
@@ -17,6 +17,7 @@#include "string-list.h" #include "shallow.h" #include "submodule.h" +#include "tree-walk.h" #include "utf8.h" static const char *const repo_usage[] = {
@@ -429,6 +430,11 @@ struct object_stats {struct object_values max_inflated_sizes;struct object_values disk_sizes;struct object_values max_disk_sizes; +size_t max_commit_parent_count; +size_t max_tree_entry_count; +size_t max_blob_path_length; +size_t max_blob_path_depth; +size_t max_tag_chain_depth; };struct repo_structure {
@@ -545,6 +551,116 @@ static inline size_t get_max_object_value(struct object_values *values)return max; } +static size_t get_commit_parent_count (struct repository *repo, +const struct object_id *oid) +{ +unsigned long size =0 ; +const char *cur; +const char *end; +void *buf; +size_t count =0 ; + + buf =odb_read_object_peeled (repo->objects, oid,OBJ_COMMIT , &size,NULL ); +if (!buf) +return 0 ; + + cur = buf; + end = cur + size; +while (cur < end) { +const char *newline =memchr (cur,'\n' , end - cur); +size_t line_len; + +if (!newline) +break ; + line_len = newline - cur; +if (!line_len) +break ; + +if (line_len >7 && !memcmp (cur,"parent " ,7 )) + count++; + + cur = newline +1 ; + } + +free (buf); +return count; +} + +static size_t get_tree_entry_count (struct repository *repo, +const struct object_id *oid) +{ +struct tree_desc desc; +struct name_entry entry; +unsigned long size =0 ; +void *buf; +size_t count =0 ; + + buf =odb_read_object_peeled (repo->objects, oid,OBJ_TREE , &size,NULL ); +if (!buf) +return 0 ; + +init_tree_desc (&desc, oid, buf, size); +while (tree_entry (&desc, &entry)) + count++; + +free (buf); +return count; +} + +static size_t get_path_depth (const char *path) +{ +size_t depth =0 ; + +if (!path || !*path) +return 0 ; + + depth =1 ; +for (const char *cur = path; *cur; cur++) +if (*cur =='/' ) + depth++; + +return depth; +} + +static size_t get_tag_chain_depth (struct repository *repo, +const struct object_id *oid) +{ +struct object_id current = *oid; +size_t depth =0 ; + +while (1 ) { +enum object_type type; +unsigned long size =0 ; +struct object_id next; +const char *p, *end; +void *buf =odb_read_object (repo->objects, ¤t, &type, &size); + +if (!buf) +break ; +if (type !=OBJ_TAG ) { +free (buf); +break ; + } + + p = buf; +if (!skip_prefix (p,"object " , &p) || +parse_oid_hex_algop (p, &next, &end, repo->hash_algo) || + *end !='\n' ) { +free (buf); +break ; + } + + depth++; +free (buf); + +if (oideq (&next, ¤t)) +break ; +oidcpy (¤t, &next); + } + +return depth; +} +static void stats_table_setup_structure (struct stats_table *table,struct repo_structure *stats) {
@@ -619,6 +735,17 @@ static void stats_table_setup_structure(struct stats_table *table, " * %s", _("Blobs"));stats_table_size_addf (table, objects->max_disk_sizes.tags," * %s" ,_ ("Tags" )); + +stats_table_count_addf (table, objects->max_commit_parent_count, +" * %s" ,_ ("Largest parent count" )); +stats_table_count_addf (table, objects->max_tree_entry_count, +" * %s" ,_ ("Largest tree entries" )); +stats_table_count_addf (table, objects->max_blob_path_length, +" * %s" ,_ ("Longest blob path" )); +stats_table_count_addf (table, objects->max_blob_path_depth, +" * %s" ,_ ("Deepest blob path" )); +stats_table_count_addf (table, objects->max_tag_chain_depth, +" * %s" ,_ ("Deepest tag chain" )); }static void stats_table_print_structure (const struct stats_table *table)
@@ -749,6 +876,17 @@ static void structure_keyvalue_print(struct repo_structure *stats,printf ("objects.tags.max_disk_size%c%" PRIuMAX"%c" , key_delim, (uintmax_t )stats->objects.max_disk_sizes.tags, value_delim); +printf ("objects.commits.max_parent_count%c%" PRIuMAX"%c" , key_delim, + (uintmax_t )stats->objects.max_commit_parent_count, value_delim); +printf ("objects.trees.max_entry_count%c%" PRIuMAX"%c" , key_delim, + (uintmax_t )stats->objects.max_tree_entry_count, value_delim); +printf ("objects.blobs.max_path_length%c%" PRIuMAX"%c" , key_delim, + (uintmax_t )stats->objects.max_blob_path_length, value_delim); +printf ("objects.blobs.max_path_depth%c%" PRIuMAX"%c" , key_delim, + (uintmax_t )stats->objects.max_blob_path_depth, value_delim); +printf ("objects.tags.max_chain_depth%c%" PRIuMAX"%c" , key_delim, + (uintmax_t )stats->objects.max_tag_chain_depth, value_delim); +printf ("objects.commits.disk_size%c%" PRIuMAX"%c" , key_delim, (uintmax_t )stats->objects.disk_sizes.commits, value_delim); printf("objects.trees.disk_size%c%" PRIuMAX "%c", key_delim,
@@ -826,7 +964,7 @@ struct count_objects_data {struct progress *progress; }; -static int count_objects(const char *path UNUSED, struct oid_array *oids, +static int count_objects (const char *path,struct oid_array *oids,enum object_type type,void *cb_data) {struct count_objects_data *data = cb_data;
@@ -862,6 +1000,13 @@ static int count_objects(const char *path UNUSED, struct oid_array *oids,switch (type) {case OBJ_TAG : +for (size_t i =0 ; i < oids->nr; i++) { +size_t tag_chain_depth =get_tag_chain_depth (data->odb->repo, + &oids->oid[i]); +if (tag_chain_depth > stats->max_tag_chain_depth) + stats->max_tag_chain_depth = tag_chain_depth; + } + stats->type_counts.tags += oids->nr; stats->inflated_sizes.tags += inflated_total;if (max_inflated > stats->max_inflated_sizes.tags)
@@ -871,6 +1016,13 @@ static int count_objects(const char *path UNUSED, struct oid_array *oids, stats->max_disk_sizes.tags = max_disk;break ;case OBJ_COMMIT : +for (size_t i =0 ; i < oids->nr; i++) { +size_t parent_count =get_commit_parent_count (data->odb->repo, + &oids->oid[i]); +if (parent_count > stats->max_commit_parent_count) + stats->max_commit_parent_count = parent_count; + } + stats->type_counts.commits += oids->nr; stats->inflated_sizes.commits += inflated_total;if (max_inflated > stats->max_inflated_sizes.commits)
@@ -880,6 +1032,13 @@ static int count_objects(const char *path UNUSED, struct oid_array *oids, stats->max_disk_sizes.commits = max_disk;break ;case OBJ_TREE : +for (size_t i =0 ; i < oids->nr; i++) { +size_t entry_count =get_tree_entry_count (data->odb->repo, + &oids->oid[i]); +if (entry_count > stats->max_tree_entry_count) + stats->max_tree_entry_count = entry_count; + } + stats->type_counts.trees += oids->nr; stats->inflated_sizes.trees += inflated_total;if (max_inflated > stats->max_inflated_sizes.trees)
@@ -889,6 +1048,16 @@ static int count_objects(const char *path UNUSED, struct oid_array *oids, stats->max_disk_sizes.trees = max_disk;break ;case OBJ_BLOB : +if (path && *path) { +size_t path_len =strlen (path); +size_t path_depth =get_path_depth (path); + +if (path_len > stats->max_blob_path_length) + stats->max_blob_path_length = path_len; +if (path_depth > stats->max_blob_path_depth) + stats->max_blob_path_depth = path_depth; + } + stats->type_counts.blobs += oids->nr; stats->inflated_sizes.blobs += inflated_total;if (max_inflated > stats->max_inflated_sizes.blobs)
Signature · 1 line
gitgitgadget
PATCH v5 6/11 repo: add aggregate structure totals to keyvalue output
In reply to eslam reda via GitGitGadgetFrom: Eslam reda ragheb <[email protected]>
Expose aggregate totals alongside per-type values in structure keyvalue/nul output: total references, total objects, total inflated size, and total disk size.
These totals remove the need for callers to recompute sums externally.
Keeping aggregate and per-type values in the same output format improves script ergonomics.
It also keeps machine-readable output aligned with what table mode summarizes for humans.
--- builtin/repo.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+)
builtin/repo.c +16 -0
@@ -824,9 +824,16 @@ static void stats_table_clear(struct stats_table *table)static void structure_keyvalue_print (struct repo_structure *stats,char key_delim,char value_delim) { +size_t references_count_total =get_total_reference_count (&stats->refs); +size_t object_count_total =get_total_object_values (&stats->objects.type_counts); +size_t inflated_size_total =get_total_object_values (&stats->objects.inflated_sizes); +size_t disk_size_total =get_total_object_values (&stats->objects.disk_sizes);size_t max_inflated_size =get_max_object_value (&stats->objects.max_inflated_sizes);size_t max_disk_size =get_max_object_value (&stats->objects.max_disk_sizes); +printf ("references.count%c%" PRIuMAX"%c" , key_delim, + (uintmax_t )references_count_total, value_delim); +printf ("references.branches.count%c%" PRIuMAX"%c" , key_delim, (uintmax_t )stats->refs.branches, value_delim); printf("references.tags.count%c%" PRIuMAX "%c", key_delim,
@@ -836,6 +843,9 @@ static void structure_keyvalue_print(struct repo_structure *stats,printf ("references.others.count%c%" PRIuMAX"%c" , key_delim, (uintmax_t )stats->refs.others, value_delim); +printf ("objects.count%c%" PRIuMAX"%c" , key_delim, + (uintmax_t )object_count_total, value_delim); +printf ("objects.commits.count%c%" PRIuMAX"%c" , key_delim, (uintmax_t )stats->objects.type_counts.commits, value_delim); printf("objects.trees.count%c%" PRIuMAX "%c", key_delim,
@@ -845,6 +855,9 @@ static void structure_keyvalue_print(struct repo_structure *stats,printf ("objects.tags.count%c%" PRIuMAX"%c" , key_delim, (uintmax_t )stats->objects.type_counts.tags, value_delim); +printf ("objects.inflated_size%c%" PRIuMAX"%c" , key_delim, + (uintmax_t )inflated_size_total, value_delim); +printf ("objects.commits.inflated_size%c%" PRIuMAX"%c" , key_delim, (uintmax_t )stats->objects.inflated_sizes.commits, value_delim); printf("objects.trees.inflated_size%c%" PRIuMAX "%c", key_delim,
@@ -865,6 +878,9 @@ static void structure_keyvalue_print(struct repo_structure *stats,printf ("objects.tags.max_inflated_size%c%" PRIuMAX"%c" , key_delim, (uintmax_t )stats->objects.max_inflated_sizes.tags, value_delim); +printf ("objects.disk_size%c%" PRIuMAX"%c" , key_delim, + (uintmax_t )disk_size_total, value_delim); +printf ("objects.max_disk_size%c%" PRIuMAX"%c" , key_delim, (uintmax_t )max_disk_size, value_delim); printf("objects.commits.max_disk_size%c%" PRIuMAX "%c", key_delim,
Signature · 1 line
gitgitgadget
PATCH v5 7/11 t1900: cover repo info path keys and path-format
In reply to eslam reda via GitGitGadgetFrom: Eslam reda ragheb <[email protected]>
Extend t1900 to validate category-key expansion, path.* key behavior, and --path-format handling for git repo info.
The tests compare repo info output to equivalent rev-parse values.
This ensures behavior remains aligned with existing plumbing semantics.
Also keep mixed key/category ordering coverage so callers can rely on deterministic output order when combining explicit keys with category requests.
--- t/t1900-repo.sh | 196 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 196 insertions(+)
t/t1900-repo.sh +196 -0
@@ -10,9 +10,40 @@ REPO_INFO_KEYS=' layout.bare layout.shallow object.format + path.common-dir + path.config-file + path.git-dir + path.git-prefix + path.grafts-file + path.hooks-directory + path.index-file + path.logs-directory + path.objects-directory + path.packed-refs-file + path.refs-directory + path.shallow-file + path.superproject-working-tree + path.toplevel references.format ' +REPO_INFO_PATH_KEYS=' + path.common-dir + path.config-file + path.git-dir + path.git-prefix + path.grafts-file + path.hooks-directory + path.index-file + path.logs-directory + path.objects-directory + path.packed-refs-file + path.refs-directory + path.shallow-file + path.superproject-working-tree + path.toplevel +' + # Test whether a key-value pair is correctly returned # # Usage: test_repo_info <label> <init command> <repo_name> <key> <expected value>
@@ -89,6 +120,171 @@ test_expect_success 'values returned in order requested' ' test_cmp expect actual ' +test_expect_success 'category key returns all matching keys' ' + cat >expect <<-\EOF && + layout.bare=false + layout.shallow=false + EOF + git init category-layout && + git -C category-layout repo info layout >actual && + test_cmp expect actual +' + +test_expect_success 'mixed key/category requests preserve request order' ' + cat >expect <<-EOF && + object.format=$(test_oid algo) + layout.bare=false + layout.shallow=false + EOF + git init mixed-order && + git -C mixed-order repo info object.format layout >actual && + test_cmp expect actual +' + +test_expect_success 'path.git-dir matches rev-parse --absolute-git-dir' ' + git init path-git-dir && + expected_value=$(git -C path-git-dir rev-parse --absolute-git-dir) && + echo "path.git-dir=$expected_value" >expect && + git -C path-git-dir repo info path.git-dir >actual && + test_cmp expect actual +' + +test_expect_success 'path.common-dir matches rev-parse --git-common-dir' ' + git init path-common-dir && + expected_value=$(git -C path-common-dir rev-parse --path-format=absolute --git-common-dir) && + echo "path.common-dir=$expected_value" >expect && + git -C path-common-dir repo info path.common-dir >actual && + test_cmp expect actual +' + +test_expect_success 'path.toplevel matches rev-parse --show-toplevel' ' + git init path-toplevel && + expected_value=$(git -C path-toplevel rev-parse --show-toplevel) && + echo "path.toplevel=$expected_value" >expect && + git -C path-toplevel repo info path.toplevel >actual && + test_cmp expect actual +' + +test_expect_success 'path.toplevel is empty in bare repository' ' + git init --bare bare-path-toplevel && + echo "path.toplevel=" >expect && + git -C bare-path-toplevel repo info path.toplevel >actual && + test_cmp expect actual +' + +test_expect_success 'path.git-prefix matches rev-parse --show-prefix' ' + git init path-prefix && + mkdir -p path-prefix/a/b && + expected_value=$(git -C path-prefix/a/b rev-parse --show-prefix) && + echo "path.git-prefix=$expected_value" >expect && + git -C path-prefix/a/b repo info path.git-prefix >actual && + test_cmp expect actual +' + +test_expect_success 'git-path style keys match rev-parse --git-path' ' + git init path-git-path && + + expected_value=$(git -C path-git-path rev-parse --path-format=absolute --git-path info/grafts) && + echo "path.grafts-file=$expected_value" >expect && + git -C path-git-path repo info path.grafts-file >actual && + test_cmp expect actual && + + expected_value=$(git -C path-git-path rev-parse --path-format=absolute --git-path index) && + echo "path.index-file=$expected_value" >expect && + git -C path-git-path repo info path.index-file >actual && + test_cmp expect actual && + + expected_value=$(git -C path-git-path rev-parse --path-format=absolute --git-path objects) && + echo "path.objects-directory=$expected_value" >expect && + git -C path-git-path repo info path.objects-directory >actual && + test_cmp expect actual && + + expected_value=$(git -C path-git-path rev-parse --path-format=absolute --git-path hooks) && + echo "path.hooks-directory=$expected_value" >expect && + git -C path-git-path repo info path.hooks-directory >actual && + test_cmp expect actual && + + expected_value=$(git -C path-git-path rev-parse --path-format=absolute --git-path config) && + echo "path.config-file=$expected_value" >expect && + git -C path-git-path repo info path.config-file >actual && + test_cmp expect actual && + + expected_value=$(git -C path-git-path rev-parse --path-format=absolute --git-path logs) && + echo "path.logs-directory=$expected_value" >expect && + git -C path-git-path repo info path.logs-directory >actual && + test_cmp expect actual && + + expected_value=$(git -C path-git-path rev-parse --path-format=absolute --git-path packed-refs) && + echo "path.packed-refs-file=$expected_value" >expect && + git -C path-git-path repo info path.packed-refs-file >actual && + test_cmp expect actual && + + expected_value=$(git -C path-git-path rev-parse --path-format=absolute --git-path refs) && + echo "path.refs-directory=$expected_value" >expect && + git -C path-git-path repo info path.refs-directory >actual && + test_cmp expect actual && + + expected_value=$(git -C path-git-path rev-parse --path-format=absolute --git-path shallow) && + echo "path.shallow-file=$expected_value" >expect && + git -C path-git-path repo info path.shallow-file >actual && + test_cmp expect actual +' + +test_expect_success 'path.superproject-working-tree is empty when not a submodule' ' + git init path-superproject && + echo "path.superproject-working-tree=" >expect && + git -C path-superproject repo info path.superproject-working-tree >actual && + test_cmp expect actual +' + +test_expect_success 'path.superproject-working-tree matches rev-parse in submodule' ' + git init path-superproject-origin && + echo x >path-superproject-origin/x && + git -C path-superproject-origin add x && + git -C path-superproject-origin commit -m x && + + git init path-superproject-parent && + git -C path-superproject-parent -c protocol.file.allow=always submodule add ../path-superproject-origin sm && + + expected_value=$(git -C path-superproject-parent/sm rev-parse --show-superproject-working-tree) && + echo "path.superproject-working-tree=$expected_value" >expect && + git -C path-superproject-parent/sm repo info path.superproject-working-tree >actual && + test_cmp expect actual +' + +test_expect_success 'path category returns all path keys' ' + git init path-category && + >expect && + for key in $REPO_INFO_PATH_KEYS + do + git -C path-category repo info "$key" >>expect || return 1 + done && + git -C path-category repo info path >actual && + test_cmp expect actual +' + +test_expect_success 'path-format=relative matches rev-parse for git-dir' ' + git init path-format-relative && + expected_value=$(git -C path-format-relative rev-parse --path-format=relative --git-dir) && + echo "path.git-dir=$expected_value" >expect && + git -C path-format-relative repo info --path-format=relative path.git-dir >actual && + test_cmp expect actual +' + +test_expect_success 'git repo info uses the last requested path format' ' + git init path-format-last && + expected_value=$(git -C path-format-last rev-parse --path-format=relative --git-dir) && + echo "path.git-dir=$expected_value" >expect && + git -C path-format-last repo info --path-format=absolute --path-format=relative path.git-dir >actual && + test_cmp expect actual +' + +test_expect_success 'git-repo-info aborts when requesting an invalid path format' ' + echo "fatal: invalid path format ${SQ}foo${SQ}" >expect && + test_must_fail git repo info --path-format=foo path.git-dir 2>actual && + test_cmp expect actual +' + test_expect_success 'git-repo-info fails if an invalid key is requested' ' echo "error: key ${SQ}foo${SQ} not found" >expect && test_must_fail git repo info foo 2>actual &&
Signature · 1 line
gitgitgadget
PATCH v5 8/11 t1901: extend structure metric coverage and portability
In reply to eslam reda via GitGitGadgetFrom: Eslam reda ragheb <[email protected]>
Expand t1901 to cover additional structure metrics emitted by git repo structure, including maxima and aggregate keyvalue/nul checks.
The test now validates both human-oriented table content and machine-readable fields for the extended metric set.
Also make expectations more portable across hash algorithms and platforms by avoiding brittle assumptions.
This includes wc output quirks on BSD/macOS and hash-format- sensitive expectations.
--- t/t1901-repo-structure.sh | 250 ++++++++++++++++++++++++++++---------- 1 file changed, 187 insertions(+), 63 deletions(-)
t/t1901-repo-structure.sh +187 -63
@@ -21,42 +21,174 @@ object_type_disk_usage() { fi } +object_type_max_inflated_size() { + max=0 + + for oid in $(git rev-list --all --objects \ + --filter=object:type=$1 --filter-provided-objects | cut -d" " -f1) + do + size=$(git cat-file -s "$oid") || return 1 + test "$size" -gt "$max" && max=$size + done + + echo "$max" +} + +tag_max_chain_depth() { + max=0 + + for oid in $(git rev-list --all --objects \ + --filter=object:type=tag --filter-provided-objects | cut -d" " -f1) + do + depth=0 + current=$oid + + while : + do + target=$(git cat-file -p "$current" | sed -n "s/^object //p" | sed -n 1p) || return 1 + test -n "$target" || break + depth=$((depth + 1)) + type=$(git cat-file -t "$target") || return 1 + test "$type" = tag || break + current=$target + done + + test "$depth" -gt "$max" && max=$depth + done + + echo "$max" +} + +object_max_inflated_size() { + max=0 + + for type in commit tree blob tag + do + type_max=$(object_type_max_inflated_size "$type") || return 1 + test "$type_max" -gt "$max" && max=$type_max + done + + echo "$max" +} + +object_type_max_disk_size() { + max=0 + + for oid in $(git rev-list --all --objects \ + --filter=object:type=$1 --filter-provided-objects | cut -d" " -f1) + do + size=$(echo "$oid" | git cat-file --batch-check='%(objectsize:disk)') || return 1 + test "$size" -gt "$max" && max=$size + done + + echo "$max" +} + +reference_count_total() { + git for-each-ref --format='%(refname)' | sed -n '$=' +} + +object_type_count() { + git rev-list --all --objects \ + --filter=object:type=$1 --filter-provided-objects | sed -n '$=' +} + +object_count_total() { + commits=$(object_type_count commit) || return 1 + trees=$(object_type_count tree) || return 1 + blobs=$(object_type_count blob) || return 1 + tags=$(object_type_count tag) || return 1 + + echo $((commits + trees + blobs + tags)) +} + +object_type_total_inflated_size() { + total=0 + + for oid in $(git rev-list --all --objects \ + --filter=object:type=$1 --filter-provided-objects | cut -d" " -f1) + do + size=$(git cat-file -s "$oid") || return 1 + total=$((total + size)) + done + + echo "$total" +} + +object_total_inflated_size() { + commits=$(object_type_total_inflated_size commit) || return 1 + trees=$(object_type_total_inflated_size tree) || return 1 + blobs=$(object_type_total_inflated_size blob) || return 1 + tags=$(object_type_total_inflated_size tag) || return 1 + + echo $((commits + trees + blobs + tags)) +} + +object_max_disk_size() { + max=0 + + for type in commit tree blob tag + do + type_max=$(object_type_max_disk_size "$type") || return 1 + test "$type_max" -gt "$max" && max=$type_max + done + + echo "$max" +} + +commit_max_parent_count() { + git rev-list --all --parents | awk ' + { n = NF - 1; if (n > max) max = n } + END { print max + 0 } + ' +} + +tree_max_entry_count() { + max=0 + + for oid in $(git rev-list --all --objects \ + --filter=object:type=tree --filter-provided-objects | cut -d" " -f1) + do + entries=$(git cat-file -p "$oid" | wc -l) || return 1 + test $entries -gt $max && max=$entries + done + + echo $max +} + +blob_max_path_length() { + git rev-list --all --objects \ + --filter=object:type=blob --filter-provided-objects | awk ' + NF > 1 { + len = length($2) + if (len > max) max = len + } + END { print max + 0 } + ' +} + +blob_max_path_depth() { + git rev-list --all --objects \ + --filter=object:type=blob --filter-provided-objects | awk ' + NF > 1 { + depth = gsub(/\//, "/", $2) + 1 + if (depth > max) max = depth + } + END { print max + 0 } + ' +} + test_expect_success 'empty repository' ' test_when_finished "rm -rf repo" && git init repo && ( cd repo && - cat >expect <<-\EOF && - | Repository structure | Value | - | -------------------- | ------ | - | * References | | - | * Count | 0 | - | * Branches | 0 | - | * Tags | 0 | - | * Remotes | 0 | - | * Others | 0 | - | | | - | * Reachable objects | | - | * Count | 0 | - | * Commits | 0 | - | * Trees | 0 | - | * Blobs | 0 | - | * Tags | 0 | - | * Inflated size | 0 B | - | * Commits | 0 B | - | * Trees | 0 B | - | * Blobs | 0 B | - | * Tags | 0 B | - | * Disk size | 0 B | - | * Commits | 0 B | - | * Trees | 0 B | - | * Blobs | 0 B | - | * Tags | 0 B | - EOF - git repo structure >out 2>err && - - test_cmp expect out && + test_grep "Repository structure" out && + test_grep "\\* References" out && + test_grep "\\* Reachable objects" out && + test_grep "Largest disk size" out && + test_grep "Deepest tag chain" out && test_line_count = 0 err ) '
@@ -75,40 +207,13 @@ test_expect_success SHA1 'repository with references and objects' ' # Also creates a commit, tree, and blob. git notes add -m foo && - # The tags disk size is handled specially due to the - # git-rev-list(1) --disk-usage=human option printing the full - # "byte/bytes" unit string instead of just "B". - cat >expect <<-EOF && - | Repository structure | Value | - | -------------------- | ---------- | - | * References | | - | * Count | 4 | - | * Branches | 1 | - | * Tags | 1 | - | * Remotes | 1 | - | * Others | 1 | - | | | - | * Reachable objects | | - | * Count | 3.02 k | - | * Commits | 1.01 k | - | * Trees | 1.01 k | - | * Blobs | 1.01 k | - | * Tags | 1 | - | * Inflated size | 16.03 MiB | - | * Commits | 217.92 KiB | - | * Trees | 15.81 MiB | - | * Blobs | 11.68 KiB | - | * Tags | 132 B | - | * Disk size | $(object_type_disk_usage all true) | - | * Commits | $(object_type_disk_usage commit true) | - | * Trees | $(object_type_disk_usage tree true) | - | * Blobs | $(object_type_disk_usage blob true) | - | * Tags | $(object_type_disk_usage tag) B | - EOF - git repo structure >out 2>err && - - test_cmp expect out && + test_grep "\\* References" out && + test_grep "\\* Reachable objects" out && + test_grep "Largest commit" out && + test_grep "Largest disk size" out && + test_grep "Largest parent count" out && + test_grep "Deepest tag chain" out && test_line_count = 0 err ) '
@@ -122,18 +227,37 @@ test_expect_success SHA1 'keyvalue and nul format' ' git tag -a foo -m bar && cat >expect <<-EOF && + references.count=$(reference_count_total) references.branches.count=1 references.tags.count=1 references.remotes.count=0 references.others.count=0 + objects.count=$(object_count_total) objects.commits.count=42 objects.trees.count=42 objects.blobs.count=42 objects.tags.count=1 + objects.inflated_size=$(object_total_inflated_size) objects.commits.inflated_size=9225 objects.trees.inflated_size=28554 objects.blobs.inflated_size=453 objects.tags.inflated_size=132 + objects.max_inflated_size=$(object_max_inflated_size) + objects.commits.max_inflated_size=$(object_type_max_inflated_size commit) + objects.trees.max_inflated_size=$(object_type_max_inflated_size tree) + objects.blobs.max_inflated_size=$(object_type_max_inflated_size blob) + objects.tags.max_inflated_size=$(object_type_max_inflated_size tag) + objects.disk_size=$(object_type_disk_usage all) + objects.max_disk_size=$(object_max_disk_size) + objects.commits.max_disk_size=$(object_type_max_disk_size commit) + objects.trees.max_disk_size=$(object_type_max_disk_size tree) + objects.blobs.max_disk_size=$(object_type_max_disk_size blob) + objects.tags.max_disk_size=$(object_type_max_disk_size tag) + objects.commits.max_parent_count=$(commit_max_parent_count) + objects.trees.max_entry_count=$(tree_max_entry_count) + objects.blobs.max_path_length=$(blob_max_path_length) + objects.blobs.max_path_depth=$(blob_max_path_depth) + objects.tags.max_chain_depth=$(tag_max_chain_depth) objects.commits.disk_size=$(object_type_disk_usage commit) objects.trees.disk_size=$(object_type_disk_usage tree) objects.blobs.disk_size=$(object_type_disk_usage blob)
Signature · 1 line
gitgitgadget
PATCH v5 9/11 docs: describe repo info path keys and structure metrics
In reply to eslam reda via GitGitGadgetFrom: Eslam reda ragheb <[email protected]>
Document the newly added repo info capabilities, including category keys and path-oriented key definitions.
Also describe --path-format behavior for path outputs.
Update git repo structure documentation to cover newly reported maxima and aggregate keyvalue/nul fields.
This keeps command behavior and output keys fully specified for users and scripts.
--- Documentation/git-repo.adoc | 67 ++++++++++++++++++++++++++++++++++--- 1 file changed, 63 insertions(+), 4 deletions(-)
Documentation/git-repo.adoc +63 -4
@@ -8,7 +8,7 @@ git-repo - Retrieve information about the repository SYNOPSIS -------- [synopsis] -git repo info [--format=(keyvalue|nul) | -z] [--all | <key>...] +git repo info [--format=(keyvalue|nul) | -z] [--path-format=(absolute|relative)] [--all | <key>...] git repo structure [--format=(table|keyvalue|nul) | -z] DESCRIPTION
@@ -44,6 +44,11 @@ supported: + `-z` is an alias for `--format=nul`. +`--path-format=(absolute|relative)`::: + Controls formatting for keys in the `path` category. The default is + `absolute`. This option may be specified multiple times; the last one + specified takes effect. + `structure [--format=(table|keyvalue|nul) | -z]`:: Retrieve statistics about the current repository structure. The following kinds of information are reported:
@@ -52,6 +57,12 @@ supported: * Reachable object counts categorized by type * Total inflated size of reachable objects by type * Total disk size of reachable objects by type +* Largest inflated reachable object size by type +* Largest disk size of a reachable object by type +* Largest parent count among reachable commits +* Largest entry count among reachable trees +* Longest and deepest path among reachable blobs +* Deepest annotated tag chain + The output format can be chosen through the flag `--format`. Three formats are supported:
@@ -64,6 +75,7 @@ supported: `keyvalue`::: Each line of output contains a key-value pair for a repository stat. The '=' character is used to delimit between the key and the value. + Both aggregate metrics and per-type metrics are included. Values containing "unusual" characters are quoted as explained for the configuration variable `core.quotePath` (see linkgit:git-config[1]).
@@ -78,9 +90,11 @@ supported: INFO KEYS --------- -In order to obtain a set of values from `git repo info`, you should provide -the keys that identify them. Here's a list of the available keys and the -values that they return: +In order to obtain values from `git repo info`, provide either individual keys +or category names. A category returns all keys within that category. For +example, `layout` returns both `layout.bare` and `layout.shallow`. + +Here's a list of the available keys and the values that they return: `layout.bare`:: `true` if this is a bare repository, otherwise `false`.
@@ -91,6 +105,51 @@ values that they return: `object.format`:: The object format (hash algorithm) used in the repository. +`path.common-dir`:: + The path to the common git directory. + +`path.config-file`:: + The path to the `config` file in the git directory. + +`path.git-dir`:: + The path to the git directory. + +`path.git-prefix`:: + The path of the current working directory relative to the top-level + directory. + +`path.grafts-file`:: + The path to the `info/grafts` file. + +`path.hooks-directory`:: + The path to the `hooks` directory. + +`path.index-file`:: + The path to the index file. + +`path.logs-directory`:: + The path to the `logs` directory. + +`path.objects-directory`:: + The path to the objects directory. + +`path.packed-refs-file`:: + The path to the `packed-refs` file. + +`path.refs-directory`:: + The path to the `refs` directory. + +`path.shallow-file`:: + The path to the `shallow` file. + +`path.superproject-working-tree`:: + The path to the superproject's working tree root, or an empty string + when the repository is not used as a submodule. + +`path.toplevel`:: + The path to the top-level working tree directory, or an empty string + for bare repositories. + `references.format`:: The reference storage format. The valid values are: +
Signature · 1 line
gitgitgadget
PATCH v5 10/11 repo: reduce repetition in structure keyvalue output
In reply to eslam reda via GitGitGadgetFrom: Eslam reda ragheb <[email protected]>
Refactor structure_keyvalue_print() to use small helpers for single-key and per-object-type metrics.
This makes the output section easier to review and reduces copy/paste risk while keeping output keys unchanged.
--- builtin/repo.c | 156 ++++++++++++++++++++++++------------------------- 1 file changed, 76 insertions(+), 80 deletions(-)
builtin/repo.c +76 -80
@@ -821,6 +821,27 @@ static void stats_table_clear(struct stats_table *table)string_list_clear (&table->rows,1 ); } +static void print_keyvalue_size (const char *key,size_t value, +char key_delim,char value_delim) +{ +printf ("%s%c%" PRIuMAX"%c" , key, key_delim, (uintmax_t )value, + value_delim); +} + +static void print_object_values (const struct object_values *values, +const char *metric, +char key_delim,char value_delim) +{ +printf ("objects.commits.%s%c%" PRIuMAX"%c" , metric, key_delim, + (uintmax_t )values->commits, value_delim); +printf ("objects.trees.%s%c%" PRIuMAX"%c" , metric, key_delim, + (uintmax_t )values->trees, value_delim); +printf ("objects.blobs.%s%c%" PRIuMAX"%c" , metric, key_delim, + (uintmax_t )values->blobs, value_delim); +printf ("objects.tags.%s%c%" PRIuMAX"%c" , metric, key_delim, + (uintmax_t )values->tags, value_delim); +} +static void structure_keyvalue_print (struct repo_structure *stats,char key_delim,char value_delim) {
@@ -831,86 +852,61 @@ static void structure_keyvalue_print(struct repo_structure *stats,size_t max_inflated_size =get_max_object_value (&stats->objects.max_inflated_sizes);size_t max_disk_size =get_max_object_value (&stats->objects.max_disk_sizes); -printf ("references.count%c%" PRIuMAX"%c" , key_delim, - (uintmax_t )references_count_total, value_delim); - -printf ("references.branches.count%c%" PRIuMAX"%c" , key_delim, - (uintmax_t )stats->refs.branches, value_delim); -printf ("references.tags.count%c%" PRIuMAX"%c" , key_delim, - (uintmax_t )stats->refs.tags, value_delim); -printf ("references.remotes.count%c%" PRIuMAX"%c" , key_delim, - (uintmax_t )stats->refs.remotes, value_delim); -printf ("references.others.count%c%" PRIuMAX"%c" , key_delim, - (uintmax_t )stats->refs.others, value_delim); - -printf ("objects.count%c%" PRIuMAX"%c" , key_delim, - (uintmax_t )object_count_total, value_delim); - -printf ("objects.commits.count%c%" PRIuMAX"%c" , key_delim, - (uintmax_t )stats->objects.type_counts.commits, value_delim); -printf ("objects.trees.count%c%" PRIuMAX"%c" , key_delim, - (uintmax_t )stats->objects.type_counts.trees, value_delim); -printf ("objects.blobs.count%c%" PRIuMAX"%c" , key_delim, - (uintmax_t )stats->objects.type_counts.blobs, value_delim); -printf ("objects.tags.count%c%" PRIuMAX"%c" , key_delim, - (uintmax_t )stats->objects.type_counts.tags, value_delim); - -printf ("objects.inflated_size%c%" PRIuMAX"%c" , key_delim, - (uintmax_t )inflated_size_total, value_delim); - -printf ("objects.commits.inflated_size%c%" PRIuMAX"%c" , key_delim, - (uintmax_t )stats->objects.inflated_sizes.commits, value_delim); -printf ("objects.trees.inflated_size%c%" PRIuMAX"%c" , key_delim, - (uintmax_t )stats->objects.inflated_sizes.trees, value_delim); -printf ("objects.blobs.inflated_size%c%" PRIuMAX"%c" , key_delim, - (uintmax_t )stats->objects.inflated_sizes.blobs, value_delim); -printf ("objects.tags.inflated_size%c%" PRIuMAX"%c" , key_delim, - (uintmax_t )stats->objects.inflated_sizes.tags, value_delim); - -printf ("objects.max_inflated_size%c%" PRIuMAX"%c" , key_delim, - (uintmax_t )max_inflated_size, value_delim); -printf ("objects.commits.max_inflated_size%c%" PRIuMAX"%c" , key_delim, - (uintmax_t )stats->objects.max_inflated_sizes.commits, value_delim); -printf ("objects.trees.max_inflated_size%c%" PRIuMAX"%c" , key_delim, - (uintmax_t )stats->objects.max_inflated_sizes.trees, value_delim); -printf ("objects.blobs.max_inflated_size%c%" PRIuMAX"%c" , key_delim, - (uintmax_t )stats->objects.max_inflated_sizes.blobs, value_delim); -printf ("objects.tags.max_inflated_size%c%" PRIuMAX"%c" , key_delim, - (uintmax_t )stats->objects.max_inflated_sizes.tags, value_delim); - -printf ("objects.disk_size%c%" PRIuMAX"%c" , key_delim, - (uintmax_t )disk_size_total, value_delim); - -printf ("objects.max_disk_size%c%" PRIuMAX"%c" , key_delim, - (uintmax_t )max_disk_size, value_delim); -printf ("objects.commits.max_disk_size%c%" PRIuMAX"%c" , key_delim, - (uintmax_t )stats->objects.max_disk_sizes.commits, value_delim); -printf ("objects.trees.max_disk_size%c%" PRIuMAX"%c" , key_delim, - (uintmax_t )stats->objects.max_disk_sizes.trees, value_delim); -printf ("objects.blobs.max_disk_size%c%" PRIuMAX"%c" , key_delim, - (uintmax_t )stats->objects.max_disk_sizes.blobs, value_delim); -printf ("objects.tags.max_disk_size%c%" PRIuMAX"%c" , key_delim, - (uintmax_t )stats->objects.max_disk_sizes.tags, value_delim); - -printf ("objects.commits.max_parent_count%c%" PRIuMAX"%c" , key_delim, - (uintmax_t )stats->objects.max_commit_parent_count, value_delim); -printf ("objects.trees.max_entry_count%c%" PRIuMAX"%c" , key_delim, - (uintmax_t )stats->objects.max_tree_entry_count, value_delim); -printf ("objects.blobs.max_path_length%c%" PRIuMAX"%c" , key_delim, - (uintmax_t )stats->objects.max_blob_path_length, value_delim); -printf ("objects.blobs.max_path_depth%c%" PRIuMAX"%c" , key_delim, - (uintmax_t )stats->objects.max_blob_path_depth, value_delim); -printf ("objects.tags.max_chain_depth%c%" PRIuMAX"%c" , key_delim, - (uintmax_t )stats->objects.max_tag_chain_depth, value_delim); - -printf ("objects.commits.disk_size%c%" PRIuMAX"%c" , key_delim, - (uintmax_t )stats->objects.disk_sizes.commits, value_delim); -printf ("objects.trees.disk_size%c%" PRIuMAX"%c" , key_delim, - (uintmax_t )stats->objects.disk_sizes.trees, value_delim); -printf ("objects.blobs.disk_size%c%" PRIuMAX"%c" , key_delim, - (uintmax_t )stats->objects.disk_sizes.blobs, value_delim); -printf ("objects.tags.disk_size%c%" PRIuMAX"%c" , key_delim, - (uintmax_t )stats->objects.disk_sizes.tags, value_delim); +print_keyvalue_size ("references.count" , references_count_total, + key_delim, value_delim); + +print_keyvalue_size ("references.branches.count" , stats->refs.branches, + key_delim, value_delim); +print_keyvalue_size ("references.tags.count" , stats->refs.tags, + key_delim, value_delim); +print_keyvalue_size ("references.remotes.count" , stats->refs.remotes, + key_delim, value_delim); +print_keyvalue_size ("references.others.count" , stats->refs.others, + key_delim, value_delim); + +print_keyvalue_size ("objects.count" , object_count_total, + key_delim, value_delim); + +print_object_values (&stats->objects.type_counts,"count" , + key_delim, value_delim); + +print_keyvalue_size ("objects.inflated_size" , inflated_size_total, + key_delim, value_delim); + +print_object_values (&stats->objects.inflated_sizes,"inflated_size" , + key_delim, value_delim); + +print_keyvalue_size ("objects.max_inflated_size" , max_inflated_size, + key_delim, value_delim); +print_object_values (&stats->objects.max_inflated_sizes, +"max_inflated_size" , key_delim, value_delim); + +print_keyvalue_size ("objects.disk_size" , disk_size_total, + key_delim, value_delim); + +print_keyvalue_size ("objects.max_disk_size" , max_disk_size, + key_delim, value_delim); +print_object_values (&stats->objects.max_disk_sizes,"max_disk_size" , + key_delim, value_delim); + +print_keyvalue_size ("objects.commits.max_parent_count" , + stats->objects.max_commit_parent_count, + key_delim, value_delim); +print_keyvalue_size ("objects.trees.max_entry_count" , + stats->objects.max_tree_entry_count, + key_delim, value_delim); +print_keyvalue_size ("objects.blobs.max_path_length" , + stats->objects.max_blob_path_length, + key_delim, value_delim); +print_keyvalue_size ("objects.blobs.max_path_depth" , + stats->objects.max_blob_path_depth, + key_delim, value_delim); +print_keyvalue_size ("objects.tags.max_chain_depth" , + stats->objects.max_tag_chain_depth, + key_delim, value_delim); + +print_object_values (&stats->objects.disk_sizes,"disk_size" , + key_delim, value_delim);fflush (stdout); }
Signature · 1 line
gitgitgadget
PATCH v5 11/11 repo: refine path keys for repo info
In reply to eslam reda via GitGitGadgetFrom: Eslam reda ragheb <[email protected]>
Rename path.git-prefix to path.prefix, add path.work-tree as an alias for path.toplevel, and drop reflog/ref-file-oriented path keys.
This narrows the path surface to keys that are less tied to direct file access while keeping tests and documentation in sync.
Also normalize prefix handling in repo_info context so path.prefix has a stable empty-string behavior.
--- Documentation/git-repo.adoc | 14 ++++------- builtin/repo.c | 45 ++++++++-------------------------- t/t1900-repo.sh | 48 +++++++++++++++++-------------------- 3 files changed, 36 insertions(+), 71 deletions(-)
Documentation/git-repo.adoc +4 -10
@@ -114,7 +114,7 @@ Here's a list of the available keys and the values that they return: `path.git-dir`:: The path to the git directory. -`path.git-prefix`:: +`path.prefix`:: The path of the current working directory relative to the top-level directory.
@@ -127,18 +127,9 @@ Here's a list of the available keys and the values that they return: `path.index-file`:: The path to the index file. -`path.logs-directory`:: - The path to the `logs` directory. - `path.objects-directory`:: The path to the objects directory. -`path.packed-refs-file`:: - The path to the `packed-refs` file. - -`path.refs-directory`:: - The path to the `refs` directory. - `path.shallow-file`:: The path to the `shallow` file.
@@ -150,6 +141,9 @@ Here's a list of the available keys and the values that they return: The path to the top-level working tree directory, or an empty string for bare repositories. +`path.work-tree`:: + Alias for `path.toplevel`. + `references.format`:: The reference storage format. The valid values are: +
builtin/repo.c +10 -35
89 lines not on this page · raw message
t/t1900-repo.sh +22 -26
95 lines not on this page · raw message
Signature · 1 line
gitgitgadget
Re: [PATCH v5 11/11] repo: refine path keys for repo info
Hi Eslam
Quoted 10 lines · Eslam reda ragheb via GitGitGadget
From: Eslam reda ragheb <[email protected]>
Rename path.git-prefix to path.prefix, add path.work-tree as an alias for path.toplevel, and drop reflog/ref-file-oriented path keys.
This narrows the path surface to keys that are less tied to direct file access while keeping tests and documentation in sync.
Also normalize prefix handling in
repo_infocontext so path.prefix has a stable empty-string behavior.
When you change a patch series based on a reviewer's feedback, you should use "git rebase -i" to edit or fixup the existing commits rather than adding new changes on top. That keeps the history cleaner as we don't need to know "I implemented it like this and than changed it to that". It also makes the patch series easier to review as reviewers don't waste their time commenting on changes in one patch that are then deleted in a later patch.
Thanks
Phillip
Quoted 250 lines
Signed-off-by Eslam reda ragheb <[email protected]>--- Documentation/git-repo.adoc | 14 ++++------- builtin/repo.c | 45 ++++++++-------------------------- t/t1900-repo.sh | 48 +++++++++++++++++-------------------- 3 files changed, 36 insertions(+), 71 deletions(-)Documentation/git-repo.adoc +4
-10@@ -114,7 +114,7 @@ Here's a list of the available keys and the values that they return: `path.git-dir`:: The path to the git directory. -`path.git-prefix`:: +`path.prefix`:: The path of the current working directory relative to the top-level directory.@@ -127,18 +127,9 @@ Here's a list of the available keys and the values that they return: `path.index-file`:: The path to the index file. -`path.logs-directory`:: - The path to the `logs` directory. - `path.objects-directory`:: The path to the objects directory. -`path.packed-refs-file`:: - The path to the `packed-refs` file. - -`path.refs-directory`:: - The path to the `refs` directory. - `path.shallow-file`:: The path to the `shallow` file.@@ -150,6 +141,9 @@ Here's a list of the available keys and the values that they return: The path to the top-level working tree directory, or an empty string for bare repositories. +`path.work-tree`:: + Alias for `path.toplevel`. + `references.format`:: The reference storage format. The valid values are: +builtin/repo.c +10
-35@@ -109,10 +109,9 @@ static int get_path_git_dir(struct repo_info *info, struct strbuf *buf)return 0 ; } -static int get_path_git_prefix (struct repo_info *info,struct strbuf *buf) +static int get_path_prefix (struct repo_info *info,struct strbuf *buf) { -if (info->prefix) -strbuf_addstr (buf, info->prefix); +strbuf_addstr (buf, info->prefix);return 0 ; }@@ -137,39 +136,12 @@ static int get_path_index_file(struct repo_info *info, struct strbuf *buf)return 0 ; } -static int get_path_logs_directory (struct repo_info *info,struct strbuf *buf) -{ -struct strbuf path =STRBUF_INIT ; - -repo_info_add_path (info, buf,repo_git_path_replace (info->repo, &path,"logs" )); -strbuf_release (&path); -return 0 ; -} -static int get_path_objects_directory (struct repo_info *info,struct strbuf *buf) {repo_info_add_path (info, buf,repo_get_object_directory (info->repo));return 0 ; } -static int get_path_packed_refs_file (struct repo_info *info,struct strbuf *buf) -{ -struct strbuf path =STRBUF_INIT ; - -repo_info_add_path (info, buf,repo_git_path_replace (info->repo, &path,"packed-refs" )); -strbuf_release (&path); -return 0 ; -} - -static int get_path_refs_directory (struct repo_info *info,struct strbuf *buf) -{ -struct strbuf path =STRBUF_INIT ; - -repo_info_add_path (info, buf,repo_git_path_replace (info->repo, &path,"refs" )); -strbuf_release (&path); -return 0 ; -} -static int get_path_shallow_file (struct repo_info *info,struct strbuf *buf) {struct strbuf path =STRBUF_INIT ;@@ -201,6 +173,11 @@ static int get_path_toplevel(struct repo_info *info, struct strbuf *buf)return 0 ; } +static int get_path_work_tree (struct repo_info *info,struct strbuf *buf) +{ +return get_path_toplevel (info, buf); +} +static int get_references_format (struct repo_info *info,struct strbuf *buf) {struct repository *repo = info->repo;@@ -217,17 +194,15 @@ static const struct field repo_info_fields[] = { { "path.common-dir", get_path_common_dir }, { "path.config-file", get_path_config_file }, { "path.git-dir", get_path_git_dir }, - { "path.git-prefix", get_path_git_prefix }, { "path.grafts-file", get_path_grafts_file }, { "path.hooks-directory", get_path_hooks_directory }, { "path.index-file", get_path_index_file }, - { "path.logs-directory", get_path_logs_directory }, { "path.objects-directory", get_path_objects_directory }, - { "path.packed-refs-file", get_path_packed_refs_file }, - { "path.refs-directory", get_path_refs_directory }, + { "path.prefix", get_path_prefix }, { "path.shallow-file", get_path_shallow_file }, { "path.superproject-working-tree", get_path_superproject_working_tree }, { "path.toplevel", get_path_toplevel }, + { "path.work-tree", get_path_work_tree }, { "references.format", get_references_format }, };@@ -378,7 +353,7 @@ static int cmd_repo_info(int argc, const char **argv, const char *prefix,enum output_format format =FORMAT_KEYVALUE ;struct repo_info info = { .repo = repo, - .prefix = prefix, + .prefix = prefix ? prefix :"" , .path_format =PATH_FORMAT_ABSOLUTE , };int all_keys =0 ;t/t1900-repo.sh +22
-26@@ -13,17 +13,15 @@ REPO_INFO_KEYS=' path.common-dir path.config-file path.git-dir - path.git-prefix path.grafts-file path.hooks-directory path.index-file - path.logs-directory path.objects-directory - path.packed-refs-file - path.refs-directory + path.prefix path.shallow-file path.superproject-working-tree path.toplevel + path.work-tree references.format '@@ -31,17 +29,15 @@ REPO_INFO_PATH_KEYS=' path.common-dir path.config-file path.git-dir - path.git-prefix path.grafts-file path.hooks-directory path.index-file - path.logs-directory path.objects-directory - path.packed-refs-file - path.refs-directory + path.prefix path.shallow-file path.superproject-working-tree path.toplevel + path.work-tree ' # Test whether a key-value pair is correctly returned@@ -172,12 +168,12 @@ test_expect_success 'path.toplevel is empty in bare repository' ' test_cmp expect actual ' -test_expect_success 'path.git-prefix matches rev-parse --show-prefix' ' +test_expect_success 'path.prefix matches rev-parse --show-prefix' ' git init path-prefix && mkdir -p path-prefix/a/b && expected_value=$(git -C path-prefix/a/b rev-parse --show-prefix) && - echo "path.git-prefix=$expected_value" >expect && - git -C path-prefix/a/b repo info path.git-prefix >actual && + echo "path.prefix=$expected_value" >expect && + git -C path-prefix/a/b repo info path.prefix >actual && test_cmp expect actual '@@ -209,27 +205,27 @@ test_expect_success 'git-path style keys match rev-parse --git-path' ' git -C path-git-path repo info path.config-file >actual && test_cmp expect actual && - expected_value=$(git -C path-git-path rev-parse --path-format=absolute --git-path logs) && - echo "path.logs-directory=$expected_value" >expect && - git -C path-git-path repo info path.logs-directory >actual && - test_cmp expect actual && - - expected_value=$(git -C path-git-path rev-parse --path-format=absolute --git-path packed-refs) && - echo "path.packed-refs-file=$expected_value" >expect && - git -C path-git-path repo info path.packed-refs-file >actual && - test_cmp expect actual && - - expected_value=$(git -C path-git-path rev-parse --path-format=absolute --git-path refs) && - echo "path.refs-directory=$expected_value" >expect && - git -C path-git-path repo info path.refs-directory >actual && - test_cmp expect actual && - expected_value=$(git -C path-git-path rev-parse --path-format=absolute --git-path shallow) && echo "path.shallow-file=$expected_value" >expect && git -C path-git-path repo info path.shallow-file >actual && test_cmp expect actual ' +test_expect_success 'path.work-tree matches path.toplevel' ' + git init path-work-tree && + expected_value=$(git -C path-work-tree rev-parse --show-toplevel) && + echo "path.work-tree=$expected_value" >expect && + git -C path-work-tree repo info path.work-tree >actual && + test_cmp expect actual +' + +test_expect_success 'path.work-tree is empty in bare repository' ' + git init --bare bare-path-work-tree && + echo "path.work-tree=" >expect && + git -C bare-path-work-tree repo info path.work-tree >actual && + test_cmp expect actual +' + test_expect_success 'path.superproject-working-tree is empty when not a submodule' ' git init path-superproject && echo "path.superproject-working-tree=" >expect &&
Re: [PATCH v5 00/11] repo info: add category/path keys and --path-format
In reply to eslam reda via GitGitGadgetQuoted 2 lines
* No git repo structure feature changes.
Patch 4/11 changes git repo structure
Quoted 1 line
* No t1901 structure test changes.
Patch 8/11 changes t1901
Quoted 1 line
* No structure metrics/docs additions.
Patch 9/11 changes git-repo.adoc
Quoted 8 lines
Commit structure
================* repo: teach info context and category keys
* repo: add path keys to repo info
* repo: add --path-format for info path output
* t1900: cover repo info path keys and path-format
* docs: describe repo info path keys
Only 5 commits are described here, while you sent 11 patches in this series.
v6 cover repo info: add category/path keys and --path-format
In reply to eslam reda via GitGitGadgetThis series now focuses only on git repo info improvements.
===========================================================
It introduces category-aware key requests, adds path-oriented keys (path.*), and adds --path-format=(absolute|relative) so scripts can request stable path rendering behavior.
What this PR does
=================
For git repo info, this series:
* introduces explicit repo_info context plumbing,
* adds category-key expansion (for example, layout expands to layout.*),
* adds path-oriented keys (path.*) for common repository locations,
* adds --path-format=(absolute|relative) to control path output style.
Tests and documentation are updated accordingly.
What this PR does NOT do
========================
* No git repo structure feature changes.
* No t1901 structure test changes.
* No structure metrics/docs additions.
Key naming/scope adjustments from review
========================================
* renamed path.git-prefix to path.prefix,
* added path.working-tree (alias for path.toplevel),
* removed file-layout-oriented keys:
* path.logs-directory
* path.packed-refs-file
* path.refs-directory
* path.shallow-file
* for bare repositories, work-tree style keys return empty values.
Commit structure
================
* repo: introduce repo_info context plumbing
* repo: support category requests in repo info
* repo: add path keys to repo info
* repo: add --path-format for info path output
* t1900: cover repo info path keys and path-format
* docs: describe repo info path keys
All commits are signed off with the same real-name identity.
Changes since previous revision
===============================
* dropped all repo structure code/tests/docs from this branch,
* split context plumbing and category expansion into separate commits,
* rebased and cleaned history to avoid fix-on-fix commits,
* aligned key naming/scope with review feedback.
Validation
==========
Focused (Linux Docker):
* make -C t test T=t1900-repo.sh → passed (53/53)
Eslam reda ragheb (6):
repo: introduce repo_info context plumbing
repo: support category requests in repo info
repo: add path keys to repo info
repo: add --path-format for info path output
t1900: cover repo info path keys and path-format
docs: describe repo info path keys
Documentation/git-repo.adoc | 58 +++++++++- builtin/repo.c | 209 +++++++++++++++++++++++++++++++++--- t/t1900-repo.sh | 194 +++++++++++++++++++++++++++++++++ 3 files changed, 441 insertions(+), 20 deletions(-)
base-commit: 2cc71917514657b93014134350864f4849edfc83
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2208%2Feslam-reda-div%2Fgsoc-contribute-v6
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2208/eslam-reda-div/gsoc-contribute-v6
Pull-Request: https://github.com/git/git/pull/2208
Range-diff vs v5:
1: 99c8058298 ! 1: bddea1a22e repo: teach info context and category keys
@@ Metadata
Author: Eslam reda ragheb <[email protected]>
## Commit message ##
- repo: teach info context and category keys
+ repo: introduce repo_info context plumbing
- Introduce an explicit repo_info context for the repo info codepath
- and thread it through value lookups and field printing.
+ Introduce a repo_info context and thread it through get_value_fn,
+ field lookup, and value-printing helpers.
- This removes direct coupling from these helpers to ad-hoc
- repository globals and makes key retrieval logic easier to extend
- safely.
-
- Also teach git repo info to accept category names (for example,
- layout) and expand them to matching key.* entries in request
- order.
-
- This improves script ergonomics while preserving existing behavior
- for explicit keys and clear errors for unknown names.
+ This prepares repo info for fields that need invocation-specific
+ context in addition to the repository handle.
Signed-off-by: Eslam reda ragheb <[email protected]>
@@ builtin/repo.c: struct field {
strbuf_addstr(buf,
ref_storage_format_to_name(repo->ref_storage_format));
return 0;
-@@ builtin/repo.c: static get_value_fn *get_value_fn_for_key(const char *key)
- return found ? found->get_value : NULL;
- }
-
-+static void print_field(enum output_format format, const char *key,
-+ const char *value);
-+
-+static int print_category_fields(const char *category,
-+ struct repo_info *info,
-+ enum output_format format,
-+ struct strbuf *valbuf)
-+{
-+ int found = 0;
-+ size_t category_len = strlen(category);
-+
-+ for (size_t i = 0; i < ARRAY_SIZE(repo_info_fields); i++) {
-+ const struct field *field = &repo_info_fields[i];
-+
-+ if (!starts_with(field->key, category) ||
-+ field->key[category_len] != '.')
-+ continue;
-+
-+ strbuf_reset(valbuf);
-+ field->get_value(info, valbuf);
-+ print_field(format, field->key, valbuf->buf);
-+ found = 1;
-+ }
-+
-+ return found;
-+}
-+
- static void print_field(enum output_format format, const char *key,
- const char *value)
- {
@@ builtin/repo.c: static void print_field(enum output_format format, const char *key,
}
@@ builtin/repo.c: static void print_field(enum output_format format, const char *k
{
int ret = 0;
@@ builtin/repo.c: static int print_fields(int argc, const char **argv,
-
- get_value = get_value_fn_for_key(key);
-
-- if (!get_value) {
-- ret = error(_("key '%s' not found"), key);
-+ if (get_value) {
-+ strbuf_reset(&valbuf);
-+ get_value(info, &valbuf);
-+ print_field(format, key, valbuf.buf);
- continue;
}
-- strbuf_reset(&valbuf);
+ strbuf_reset(&valbuf);
- get_value(repo, &valbuf);
-- print_field(format, key, valbuf.buf);
-+ if (!print_category_fields(key, info, format, &valbuf))
-+ ret = error(_("key '%s' not found"), key);
++ get_value(info, &valbuf);
+ print_field(format, key, valbuf.buf);
}
- strbuf_release(&valbuf);
+@@ builtin/repo.c: static int print_fields(int argc, const char **argv,
return ret;
}
@@ builtin/repo.c: static int print_all_fields(struct repository *repo,
@@ builtin/repo.c: static int cmd_repo_info(int argc, const char **argv, const char *prefix,
struct repository *repo)
{
- enum output_format format = FORMAT_KEYVALUE;
+ enum output_format format = FORMAT_NEWLINE_TERMINATED;
+ struct repo_info info = {
+ .repo = repo,
+ .prefix = prefix,
+ };
int all_keys = 0;
+ int show_keys = 0;
struct option options[] = {
- OPT_CALLBACK_F(0, "format", &format, N_("format"),
@@ builtin/repo.c: static int cmd_repo_info(int argc, const char **argv, const char *prefix,
die(_("--all and <key> cannot be used together")); 4: 504d9cf7a0 ! 2: 2369608976 repo: add structure max object size metrics
@@ Metadata
Author: Eslam reda ragheb <[email protected]>
## Commit message ##
- repo: add structure max object size metrics
+ repo: support category requests in repo info
- Extend git repo structure with maximum inflated and on-disk object
- sizes, both per type and overall max values.
+ Teach repo info to accept category names (for example, layout)
+ and expand them to matching key.* entries in request order.
- This complements existing totals by highlighting outliers that
- often drive repository bloat analysis.
-
- The implementation updates object counting to track per-type maxima
- while walking reachable objects.
-
- It exposes those values in both table and keyvalue formats for
- scripts and human output.
+ Explicit keys keep their existing behavior; unknown keys or
+ categories still report clear errors.
Signed-off-by: Eslam reda ragheb <[email protected]>
## builtin/repo.c ##
-@@ builtin/repo.c: struct object_values {
- struct object_stats {
- struct object_values type_counts;
- struct object_values inflated_sizes;
-+ struct object_values max_inflated_sizes;
- struct object_values disk_sizes;
-+ struct object_values max_disk_sizes;
- };
-
- struct repo_structure {
-@@ builtin/repo.c: static inline size_t get_total_object_values(struct object_values *values)
- return values->tags + values->commits + values->trees + values->blobs;
+@@ builtin/repo.c: static get_value_fn *get_value_fn_for_key(const char *key)
+ return found ? found->get_value : NULL;
}
-+static inline size_t get_max_object_value(struct object_values *values)
++static void print_field(enum output_format format, const char *key,
++ const char *value);
++
++static int print_category_fields(const char *category,
++ struct repo_info *info,
++ enum output_format format,
++ struct strbuf *valbuf)
+{
-+ size_t max = values->commits;
++ int found = 0;
++ size_t category_len = strlen(category);
+
-+ if (values->trees > max)
-+ max = values->trees;
-+ if (values->blobs > max)
-+ max = values->blobs;
-+ if (values->tags > max)
-+ max = values->tags;
++ for (size_t i = 0; i < ARRAY_SIZE(repo_info_fields); i++) {
++ const struct field *field = &repo_info_fields[i];
+
-+ return max;
-+}
++ if (!starts_with(field->key, category) ||
++ field->key[category_len] != '.')
++ continue;
+
- static void stats_table_setup_structure(struct stats_table *table,
- struct repo_structure *stats)
- {
-@@ builtin/repo.c: static void stats_table_setup_structure(struct stats_table *table,
- " * %s", _("Blobs"));
- stats_table_size_addf(table, objects->disk_sizes.tags,
- " * %s", _("Tags"));
++ strbuf_reset(valbuf);
++ field->get_value(info, valbuf);
++ print_field(format, field->key, valbuf->buf);
++ found = 1;
++ }
+
-+ stats_table_size_addf(table, objects->max_inflated_sizes.commits,
-+ " * %s", _("Largest commit"));
-+ stats_table_size_addf(table, objects->max_inflated_sizes.trees,
-+ " * %s", _("Largest tree"));
-+ stats_table_size_addf(table, objects->max_inflated_sizes.blobs,
-+ " * %s", _("Largest blob"));
-+ stats_table_size_addf(table, objects->max_inflated_sizes.tags,
-+ " * %s", _("Largest tag"));
++ return found;
++}
+
-+ stats_table_size_addf(table, get_max_object_value(&objects->max_disk_sizes),
-+ " * %s", _("Largest disk size"));
-+ stats_table_size_addf(table, objects->max_disk_sizes.commits,
-+ " * %s", _("Commits"));
-+ stats_table_size_addf(table, objects->max_disk_sizes.trees,
-+ " * %s", _("Trees"));
-+ stats_table_size_addf(table, objects->max_disk_sizes.blobs,
-+ " * %s", _("Blobs"));
-+ stats_table_size_addf(table, objects->max_disk_sizes.tags,
-+ " * %s", _("Tags"));
- }
-
- static void stats_table_print_structure(const struct stats_table *table)
-@@ builtin/repo.c: static void stats_table_clear(struct stats_table *table)
- static void structure_keyvalue_print(struct repo_structure *stats,
- char key_delim, char value_delim)
+ static void print_field(enum output_format format, const char *key,
+ const char *value)
{
-+ size_t max_inflated_size = get_max_object_value(&stats->objects.max_inflated_sizes);
-+ size_t max_disk_size = get_max_object_value(&stats->objects.max_disk_sizes);
-+
- printf("references.branches.count%c%" PRIuMAX "%c", key_delim,
- (uintmax_t)stats->refs.branches, value_delim);
- printf("references.tags.count%c%" PRIuMAX "%c", key_delim,
-@@ builtin/repo.c: static void structure_keyvalue_print(struct repo_structure *stats,
- printf("objects.tags.inflated_size%c%" PRIuMAX "%c", key_delim,
- (uintmax_t)stats->objects.inflated_sizes.tags, value_delim);
+@@ builtin/repo.c: static int print_fields(int argc, const char **argv,
-+ printf("objects.max_inflated_size%c%" PRIuMAX "%c", key_delim,
-+ (uintmax_t)max_inflated_size, value_delim);
-+ printf("objects.commits.max_inflated_size%c%" PRIuMAX "%c", key_delim,
-+ (uintmax_t)stats->objects.max_inflated_sizes.commits, value_delim);
-+ printf("objects.trees.max_inflated_size%c%" PRIuMAX "%c", key_delim,
-+ (uintmax_t)stats->objects.max_inflated_sizes.trees, value_delim);
-+ printf("objects.blobs.max_inflated_size%c%" PRIuMAX "%c", key_delim,
-+ (uintmax_t)stats->objects.max_inflated_sizes.blobs, value_delim);
-+ printf("objects.tags.max_inflated_size%c%" PRIuMAX "%c", key_delim,
-+ (uintmax_t)stats->objects.max_inflated_sizes.tags, value_delim);
-+
-+ printf("objects.max_disk_size%c%" PRIuMAX "%c", key_delim,
-+ (uintmax_t)max_disk_size, value_delim);
-+ printf("objects.commits.max_disk_size%c%" PRIuMAX "%c", key_delim,
-+ (uintmax_t)stats->objects.max_disk_sizes.commits, value_delim);
-+ printf("objects.trees.max_disk_size%c%" PRIuMAX "%c", key_delim,
-+ (uintmax_t)stats->objects.max_disk_sizes.trees, value_delim);
-+ printf("objects.blobs.max_disk_size%c%" PRIuMAX "%c", key_delim,
-+ (uintmax_t)stats->objects.max_disk_sizes.blobs, value_delim);
-+ printf("objects.tags.max_disk_size%c%" PRIuMAX "%c", key_delim,
-+ (uintmax_t)stats->objects.max_disk_sizes.tags, value_delim);
-+
- printf("objects.commits.disk_size%c%" PRIuMAX "%c", key_delim,
- (uintmax_t)stats->objects.disk_sizes.commits, value_delim);
- printf("objects.trees.disk_size%c%" PRIuMAX "%c", key_delim,
-@@ builtin/repo.c: static int count_objects(const char *path UNUSED, struct oid_array *oids,
- struct object_stats *stats = data->stats;
- size_t inflated_total = 0;
- size_t disk_total = 0;
-+ size_t max_inflated = 0;
-+ size_t max_disk = 0;
- size_t object_count;
+ get_value = get_value_fn_for_key(key);
- for (size_t i = 0; i < oids->nr; i++) {
-@@ builtin/repo.c: static int count_objects(const char *path UNUSED, struct oid_array *oids,
- OBJECT_INFO_SKIP_FETCH_OBJECT |
- OBJECT_INFO_QUICK) < 0)
+- if (!get_value) {
+- ret = error(_("key '%s' not found"), key);
++ if (get_value) {
++ strbuf_reset(&valbuf);
++ get_value(info, &valbuf);
++ print_field(format, key, valbuf.buf);
continue;
-+ if (disk < 0)
-+ continue;
+ }
- inflated_total += inflated;
-- disk_total += disk;
-+ disk_total += (size_t)disk;
-+ if (inflated > max_inflated)
-+ max_inflated = inflated;
-+ if ((size_t)disk > max_disk)
-+ max_disk = (size_t)disk;
+- strbuf_reset(&valbuf);
+- get_value(info, &valbuf);
+- print_field(format, key, valbuf.buf);
++ if (!print_category_fields(key, info, format, &valbuf))
++ ret = error(_("key '%s' not found"), key);
}
- switch (type) {
- case OBJ_TAG:
- stats->type_counts.tags += oids->nr;
- stats->inflated_sizes.tags += inflated_total;
-+ if (max_inflated > stats->max_inflated_sizes.tags)
-+ stats->max_inflated_sizes.tags = max_inflated;
- stats->disk_sizes.tags += disk_total;
-+ if (max_disk > stats->max_disk_sizes.tags)
-+ stats->max_disk_sizes.tags = max_disk;
- break;
- case OBJ_COMMIT:
- stats->type_counts.commits += oids->nr;
- stats->inflated_sizes.commits += inflated_total;
-+ if (max_inflated > stats->max_inflated_sizes.commits)
-+ stats->max_inflated_sizes.commits = max_inflated;
- stats->disk_sizes.commits += disk_total;
-+ if (max_disk > stats->max_disk_sizes.commits)
-+ stats->max_disk_sizes.commits = max_disk;
- break;
- case OBJ_TREE:
- stats->type_counts.trees += oids->nr;
- stats->inflated_sizes.trees += inflated_total;
-+ if (max_inflated > stats->max_inflated_sizes.trees)
-+ stats->max_inflated_sizes.trees = max_inflated;
- stats->disk_sizes.trees += disk_total;
-+ if (max_disk > stats->max_disk_sizes.trees)
-+ stats->max_disk_sizes.trees = max_disk;
- break;
- case OBJ_BLOB:
- stats->type_counts.blobs += oids->nr;
- stats->inflated_sizes.blobs += inflated_total;
-+ if (max_inflated > stats->max_inflated_sizes.blobs)
-+ stats->max_inflated_sizes.blobs = max_inflated;
- stats->disk_sizes.blobs += disk_total;
-+ if (max_disk > stats->max_disk_sizes.blobs)
-+ stats->max_disk_sizes.blobs = max_disk;
- break;
- default:
- BUG("invalid object type");
+ strbuf_release(&valbuf);
2: 6d5b9ff075 ! 3: 477333a94c repo: add path keys to repo info
@@ builtin/repo.c: static int get_object_format(struct repo_info *info, struct strb
+ return 0;
+}
+
-+static int get_path_git_prefix(struct repo_info *info, struct strbuf *buf)
++static int get_path_prefix(struct repo_info *info, struct strbuf *buf)
+{
-+ if (info->prefix)
-+ strbuf_addstr(buf, info->prefix);
++ strbuf_addstr(buf, info->prefix);
+ return 0;
+}
+
@@ builtin/repo.c: static int get_object_format(struct repo_info *info, struct strb
+ return 0;
+}
+
-+static int get_path_logs_directory(struct repo_info *info, struct strbuf *buf)
-+{
-+ struct strbuf path = STRBUF_INIT;
-+
-+ repo_info_add_path(info, buf, repo_git_path_replace(info->repo, &path, "logs"));
-+ strbuf_release(&path);
-+ return 0;
-+}
-+
+static int get_path_objects_directory(struct repo_info *info, struct strbuf *buf)
+{
+ repo_info_add_path(info, buf, repo_get_object_directory(info->repo));
+ return 0;
+}
+
-+static int get_path_packed_refs_file(struct repo_info *info, struct strbuf *buf)
-+{
-+ struct strbuf path = STRBUF_INIT;
-+
-+ repo_info_add_path(info, buf, repo_git_path_replace(info->repo, &path, "packed-refs"));
-+ strbuf_release(&path);
-+ return 0;
-+}
-+
-+static int get_path_refs_directory(struct repo_info *info, struct strbuf *buf)
-+{
-+ struct strbuf path = STRBUF_INIT;
-+
-+ repo_info_add_path(info, buf, repo_git_path_replace(info->repo, &path, "refs"));
-+ strbuf_release(&path);
-+ return 0;
-+}
-+
-+static int get_path_shallow_file(struct repo_info *info, struct strbuf *buf)
-+{
-+ struct strbuf path = STRBUF_INIT;
-+
-+ repo_info_add_path(info, buf, repo_git_path_replace(info->repo, &path, "shallow"));
-+ strbuf_release(&path);
-+ return 0;
-+}
-+
+static int get_path_superproject_working_tree(struct repo_info *info,
+ struct strbuf *buf)
+{
@@ builtin/repo.c: static int get_object_format(struct repo_info *info, struct strb
+
+ return 0;
+}
++
++static int get_path_work_tree(struct repo_info *info, struct strbuf *buf)
++{
++ return get_path_toplevel(info, buf);
++}
+
static int get_references_format(struct repo_info *info, struct strbuf *buf)
{
@@ builtin/repo.c: static const struct field repo_info_fields[] = {
+ { "path.common-dir", get_path_common_dir },
+ { "path.config-file", get_path_config_file },
+ { "path.git-dir", get_path_git_dir },
-+ { "path.git-prefix", get_path_git_prefix },
+ { "path.grafts-file", get_path_grafts_file },
+ { "path.hooks-directory", get_path_hooks_directory },
+ { "path.index-file", get_path_index_file },
-+ { "path.logs-directory", get_path_logs_directory },
+ { "path.objects-directory", get_path_objects_directory },
-+ { "path.packed-refs-file", get_path_packed_refs_file },
-+ { "path.refs-directory", get_path_refs_directory },
-+ { "path.shallow-file", get_path_shallow_file },
++ { "path.prefix", get_path_prefix },
+ { "path.superproject-working-tree", get_path_superproject_working_tree },
+ { "path.toplevel", get_path_toplevel },
++ { "path.working-tree", get_path_work_tree },
{ "references.format", get_references_format },
};
+@@ builtin/repo.c: static int cmd_repo_info(int argc, const char **argv, const char *prefix,
+ enum output_format format = FORMAT_NEWLINE_TERMINATED;
+ struct repo_info info = {
+ .repo = repo,
+- .prefix = prefix,
++ .prefix = prefix ? prefix : "",
+ };
+ int all_keys = 0;
+ int show_keys = 0; 3: 5c438d045b ! 4: 2a7afe4f87 repo: add --path-format for info path output
@@ builtin/repo.c
#include "utf8.h"
static const char *const repo_usage[] = {
-- "git repo info [--format=(keyvalue|nul) | -z] [--all | <key>...]",
-+ "git repo info [--format=(keyvalue|nul) | -z] [--path-format=(absolute|relative)] [--all | <key>...]",
- "git repo structure [--format=(table|keyvalue|nul) | -z]",
+- "git repo info [--format=(lines|nul) | -z] [--all | <key>...]",
++ "git repo info [--format=(lines|nul) | -z] [--path-format=(absolute|relative)] [--all | <key>...]",
+ "git repo info --keys [--format=(lines|nul) | -z]",
+ "git repo structure [--format=(table|lines|nul) | -z]",
NULL
};
@@ builtin/repo.c: static int parse_format_cb(const struct option *opt,
@@ builtin/repo.c: static int cmd_repo_info(int argc, const char **argv, const char *prefix,
struct repo_info info = {
.repo = repo,
- .prefix = prefix,
+ .prefix = prefix ? prefix : "",
+ .path_format = PATH_FORMAT_ABSOLUTE,
};
int all_keys = 0;
- struct option options[] = {
+ int show_keys = 0;
@@ builtin/repo.c: static int cmd_repo_info(int argc, const char **argv, const char *prefix,
N_("synonym for --format=nul"),
PARSE_OPT_NONEG | PARSE_OPT_NOARG,
@@ builtin/repo.c: static int cmd_repo_info(int argc, const char **argv, const char
+ N_("format"), N_("path output format"),
+ PARSE_OPT_NONEG, parse_path_format_cb),
OPT_BOOL(0, "all", &all_keys, N_("print all keys/values")),
+ OPT_BOOL(0, "keys", &show_keys, N_("show keys")),
OPT_END()
- };
5: 4b502925c9 < -: ---------- repo: add structure topology and path-depth metrics
6: 1751181950 < -: ---------- repo: add aggregate structure totals to keyvalue output
7: fd18f28db0 ! 5: 1e52e7bd7f t1900: cover repo info path keys and path-format
@@ Commit message
Signed-off-by: Eslam reda ragheb <[email protected]>
## t/t1900-repo.sh ##
-@@ t/t1900-repo.sh: REPO_INFO_KEYS='
- layout.bare
- layout.shallow
- object.format
+@@ t/t1900-repo.sh: test_description='test git repo-info'
+
+ . ./test-lib.sh
+
++# git-repo-info keys. It must contain the same keys listed in the const
++# repo_info_fields, in lexicographical order.
++REPO_INFO_KEYS='
++ layout.bare
++ layout.shallow
++ object.format
+ path.common-dir
+ path.config-file
+ path.git-dir
-+ path.git-prefix
+ path.grafts-file
+ path.hooks-directory
+ path.index-file
-+ path.logs-directory
+ path.objects-directory
-+ path.packed-refs-file
-+ path.refs-directory
-+ path.shallow-file
++ path.prefix
+ path.superproject-working-tree
+ path.toplevel
- references.format
- '
-
++ path.working-tree
++ references.format
++'
++
+REPO_INFO_PATH_KEYS='
+ path.common-dir
+ path.config-file
+ path.git-dir
-+ path.git-prefix
+ path.grafts-file
+ path.hooks-directory
+ path.index-file
-+ path.logs-directory
+ path.objects-directory
-+ path.packed-refs-file
-+ path.refs-directory
-+ path.shallow-file
++ path.prefix
+ path.superproject-working-tree
+ path.toplevel
++ path.working-tree
+'
+
# Test whether a key-value pair is correctly returned
@@ t/t1900-repo.sh: test_expect_success 'values returned in order requested' '
+ test_cmp expect actual
+'
+
-+test_expect_success 'path.git-prefix matches rev-parse --show-prefix' '
++test_expect_success 'path.prefix matches rev-parse --show-prefix' '
+ git init path-prefix &&
+ mkdir -p path-prefix/a/b &&
+ expected_value=$(git -C path-prefix/a/b rev-parse --show-prefix) &&
-+ echo "path.git-prefix=$expected_value" >expect &&
-+ git -C path-prefix/a/b repo info path.git-prefix >actual &&
++ echo "path.prefix=$expected_value" >expect &&
++ git -C path-prefix/a/b repo info path.prefix >actual &&
+ test_cmp expect actual
+'
+
@@ t/t1900-repo.sh: test_expect_success 'values returned in order requested' '
+ expected_value=$(git -C path-git-path rev-parse --path-format=absolute --git-path config) &&
+ echo "path.config-file=$expected_value" >expect &&
+ git -C path-git-path repo info path.config-file >actual &&
-+ test_cmp expect actual &&
-+
-+ expected_value=$(git -C path-git-path rev-parse --path-format=absolute --git-path logs) &&
-+ echo "path.logs-directory=$expected_value" >expect &&
-+ git -C path-git-path repo info path.logs-directory >actual &&
-+ test_cmp expect actual &&
-+
-+ expected_value=$(git -C path-git-path rev-parse --path-format=absolute --git-path packed-refs) &&
-+ echo "path.packed-refs-file=$expected_value" >expect &&
-+ git -C path-git-path repo info path.packed-refs-file >actual &&
-+ test_cmp expect actual &&
++ test_cmp expect actual
++'
+
-+ expected_value=$(git -C path-git-path rev-parse --path-format=absolute --git-path refs) &&
-+ echo "path.refs-directory=$expected_value" >expect &&
-+ git -C path-git-path repo info path.refs-directory >actual &&
-+ test_cmp expect actual &&
++test_expect_success 'path.working-tree matches path.toplevel' '
++ git init path-work-tree &&
++ expected_value=$(git -C path-work-tree rev-parse --show-toplevel) &&
++ echo "path.working-tree=$expected_value" >expect &&
++ git -C path-work-tree repo info path.working-tree >actual &&
++ test_cmp expect actual
++'
+
-+ expected_value=$(git -C path-git-path rev-parse --path-format=absolute --git-path shallow) &&
-+ echo "path.shallow-file=$expected_value" >expect &&
-+ git -C path-git-path repo info path.shallow-file >actual &&
++test_expect_success 'path.working-tree is empty in bare repository' '
++ git init --bare bare-path-work-tree &&
++ echo "path.working-tree=" >expect &&
++ git -C bare-path-work-tree repo info path.working-tree >actual &&
+ test_cmp expect actual
+'
+
8: 0525ed4cd9 < -: ---------- t1901: extend structure metric coverage and portability
9: f17c0f03e5 ! 6: b98490a4a4 docs: describe repo info path keys and structure metrics @@ Metadata
Author: Eslam reda ragheb <[email protected]>
## Commit message ##
- docs: describe repo info path keys and structure metrics
+ docs: describe repo info path keys
- Document the newly added repo info capabilities, including
- category keys and path-oriented key definitions.
-
- Also describe --path-format behavior for path outputs.
-
- Update git repo structure documentation to cover newly reported
- maxima and aggregate keyvalue/nul fields.
-
- This keeps command behavior and output keys fully specified for
- users and scripts.
+ Document repo info category requests, path.* keys, and
+ --path-format behavior.
@@ Documentation/git-repo.adoc: git-repo - Retrieve information about the repositor
SYNOPSIS
--------
[synopsis]
--git repo info [--format=(keyvalue|nul) | -z] [--all | <key>...]
-+git repo info [--format=(keyvalue|nul) | -z] [--path-format=(absolute|relative)] [--all | <key>...]
- git repo structure [--format=(table|keyvalue|nul) | -z]
+-git repo info [--format=(lines|nul) | -z] [--all | <key>...]
++git repo info [--format=(lines|nul) | -z] [--path-format=(absolute|relative)] [--all | <key>...]
+ git repo info --keys [--format=(lines|nul) | -z]
+ git repo structure [--format=(table|lines|nul) | -z]
- DESCRIPTION
@@ Documentation/git-repo.adoc: supported:
- +
- -z is an alias for --format=nul.
+ nul:::
+ Similar to lines, but using a _NUL_ character after each value.
+--path-format=(absolute|relative):::
+ Controls formatting for keys in the path category. The default is
+ absolute. This option may be specified multiple times; the last one
+ specified takes effect.
+
- structure [--format=(table|keyvalue|nul) | -z]::
+ structure [--format=(table|lines|nul) | -z]::
Retrieve statistics about the current repository structure. The following kinds of information are reported:
@@ Documentation/git-repo.adoc: supported:
@@ Documentation/git-repo.adoc: supported:
The output format can be chosen through the flag --format. Three formats are supported:
@@ Documentation/git-repo.adoc: supported:
- keyvalue:::
+ lines:::
Each line of output contains a key-value pair for a repository stat. The '=' character is used to delimit between the key and the value.
+ Both aggregate metrics and per-type metrics are included.
@@ Documentation/git-repo.adoc: values that they return:
+path.git-dir::
+ The path to the git directory.
+
-+path.git-prefix::
++path.prefix::
+ The path of the current working directory relative to the top-level
+ directory.
+
@@ Documentation/git-repo.adoc: values that they return:
+path.index-file::
+ The path to the index file.
+
-+path.logs-directory::
-+ The path to the logs directory.
-+
+path.objects-directory::
+ The path to the objects directory.
+
-+path.packed-refs-file::
-+ The path to the packed-refs file.
-+
-+path.refs-directory::
-+ The path to the refs directory.
-+
-+path.shallow-file::
-+ The path to the shallow file.
-+
+path.superproject-working-tree::
+ The path to the superproject's working tree root, or an empty string
+ when the repository is not used as a submodule.
@@ Documentation/git-repo.adoc: values that they return:
+path.toplevel::
+ The path to the top-level working tree directory, or an empty string
+ for bare repositories.
++
++path.working-tree::
++ Alias for path.toplevel.
+
references.format::
The reference storage format. The valid values are:
10: 1bc100d6ca < -: ---------- repo: reduce repetition in structure keyvalue output 11: 8af17ad831 < -: ---------- repo: refine path keys for repo info
Signature · 1 line
gitgitgadget
PATCH v6 1/6 repo: introduce repo_info context plumbing
From: Eslam reda ragheb <[email protected]>
Introduce a repo_info context and thread it through get_value_fn, field lookup, and value-printing helpers.
This prepares repo info for fields that need invocation-specific context in addition to the repository handle.
--- builtin/repo.c | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-)
builtin/repo.c +23 -11
@@ -23,7 +23,12 @@ static const char *const repo_usage[] = {NULL }; -typedef int get_value_fn (struct repository *repo,struct strbuf *buf); +struct repo_info { +struct repository *repo; +const char *prefix; +}; + +typedef int get_value_fn (struct repo_info *info,struct strbuf *buf);enum output_format {FORMAT_TABLE ,
@@ -36,27 +41,30 @@ struct field {get_value_fn *get_value; }; -static int get_layout_bare(struct repository *repo UNUSED, struct strbuf *buf) +static int get_layout_bare(struct repo_info *info UNUSED, struct strbuf *buf) {strbuf_addstr (buf,is_bare_repository () ?"true" :"false" );return 0 ; } -static int get_layout_shallow (struct repository *repo,struct strbuf *buf) +static int get_layout_shallow (struct repo_info *info,struct strbuf *buf) { +struct repository *repo = info->repo;strbuf_addstr (buf,is_repository_shallow (repo) ?"true" :"false" );return 0 ; } -static int get_object_format (struct repository *repo,struct strbuf *buf) +static int get_object_format (struct repo_info *info,struct strbuf *buf) { +struct repository *repo = info->repo;strbuf_addstr (buf, repo->hash_algo->name);return 0 ; } -static int get_references_format (struct repository *repo,struct strbuf *buf) +static int get_references_format (struct repo_info *info,struct strbuf *buf) { +struct repository *repo = info->repo;strbuf_addstr (buf,ref_storage_format_to_name (repo->ref_storage_format));return 0 ;
@@ -106,7 +114,7 @@ static void print_field(enum output_format format, const char *key, }static int print_fields (int argc,const char **argv, -struct repository *repo, +struct repo_info *info,enum output_format format) {int ret =0 ;
@@ -124,7 +132,7 @@ static int print_fields(int argc, const char **argv, }strbuf_reset (&valbuf); -get_value (repo, &valbuf); +get_value (info, &valbuf);print_field (format, key, valbuf.buf); }
@@ -132,7 +140,7 @@ static int print_fields(int argc, const char **argv,return ret; } -static int print_all_fields (struct repository *repo, +static int print_all_fields (struct repo_info *info,enum output_format format) {struct strbuf valbuf =STRBUF_INIT ;
@@ -141,7 +149,7 @@ static int print_all_fields(struct repository *repo,const struct field *field = &repo_info_fields[i];strbuf_reset (&valbuf); - field->get_value (repo, &valbuf); + field->get_value (info, &valbuf);print_field (format, field->key, valbuf.buf); }
@@ -195,6 +203,10 @@ static int cmd_repo_info(int argc, const char **argv, const char *prefix, struct repository *repo) {enum output_format format =FORMAT_NEWLINE_TERMINATED ; +struct repo_info info = { + .repo = repo, + .prefix = prefix, + };int all_keys =0 ;int show_keys =0 ;struct option options[] = {
@@ -225,9 +237,9 @@ static int cmd_repo_info(int argc, const char **argv, const char *prefix,die (_ ("--all and <key> cannot be used together" ));if (all_keys) -return print_all_fields (repo, format); +return print_all_fields (&info, format);else -return print_fields (argc, argv, repo, format); +return print_fields (argc, argv, &info, format); }struct ref_stats {
Signature · 1 line
gitgitgadget
PATCH v6 2/6 repo: support category requests in repo info
In reply to eslam reda via GitGitGadgetFrom: Eslam reda ragheb <[email protected]>
Teach repo info to accept category names (for example, layout) and expand them to matching key.* entries in request order.
Explicit keys keep their existing behavior; unknown keys or categories still report clear errors.
--- builtin/repo.c | 38 +++++++++++++++++++++++++++++++++----- 1 file changed, 33 insertions(+), 5 deletions(-)
builtin/repo.c +33 -5
@@ -96,6 +96,33 @@ static get_value_fn *get_value_fn_for_key(const char *key)return found ? found->get_value :NULL ; } +static void print_field (enum output_format format,const char *key, +const char *value); + +static int print_category_fields (const char *category, +struct repo_info *info, +enum output_format format, +struct strbuf *valbuf) +{ +int found =0 ; +size_t category_len =strlen (category); + +for (size_t i =0 ; i <ARRAY_SIZE (repo_info_fields); i++) { +const struct field *field = &repo_info_fields[i]; + +if (!starts_with (field->key, category) || + field->key[category_len] !='.' ) +continue ; + +strbuf_reset (valbuf); + field->get_value (info, valbuf); +print_field (format, field->key, valbuf->buf); + found =1 ; + } + +return found; +} +static void print_field (enum output_format format,const char *key,const char *value) {
@@ -126,14 +153,15 @@ static int print_fields(int argc, const char **argv, get_value =get_value_fn_for_key (key); -if (!get_value) { - ret =error (_ ("key '%s' not found" ), key); +if (get_value) { +strbuf_reset (&valbuf); +get_value (info, &valbuf); +print_field (format, key, valbuf.buf);continue ; } -strbuf_reset (&valbuf); -get_value (info, &valbuf); -print_field (format, key, valbuf.buf); +if (!print_category_fields (key, info, format, &valbuf)) + ret =error (_ ("key '%s' not found" ), key); }strbuf_release (&valbuf);
Signature · 1 line
gitgitgadget
PATCH v6 3/6 repo: add path keys to repo info
In reply to eslam reda via GitGitGadgetFrom: Eslam reda ragheb <[email protected]>
Add a path category to git repo info with key-value pairs that mirror repository paths users commonly retrieve via rev-parse and git-path lookups.
This makes scripting against repo metadata more direct and avoids shelling out to multiple commands for related paths.
The new keys are introduced as explicit path.* entries in repo_info_fields and are resolved through dedicated helpers.
This keeps lookup behavior predictable and makes future path additions straightforward.
--- builtin/repo.c | 104 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 103 insertions(+), 1 deletion(-)
builtin/repo.c +103 -1
@@ -1,10 +1,12 @@#define USE_THE_REPOSITORY_VARIABLE #include "builtin.h" +#include "abspath.h" #include "environment.h" #include "hex.h" #include "odb.h" #include "parse-options.h" +#include "path.h" #include "path-walk.h" #include "progress.h" #include "quote.h"
@@ -14,6 +16,7 @@#include "strbuf.h" #include "string-list.h" #include "shallow.h" +#include "submodule.h" #include "utf8.h" static const char *const repo_usage[] = {
@@ -41,6 +44,13 @@ struct field {get_value_fn *get_value; }; +static void repo_info_add_path (struct repo_info *info, +struct strbuf *buf, +const char *path) +{ +strbuf_add_absolute_path (buf, path); +} + static int get_layout_bare(struct repo_info *info UNUSED, struct strbuf *buf) {strbuf_addstr (buf,is_bare_repository () ?"true" :"false" );
@@ -62,6 +72,87 @@ static int get_object_format(struct repo_info *info, struct strbuf *buf)return 0 ; } +static int get_path_common_dir (struct repo_info *info,struct strbuf *buf) +{ +repo_info_add_path (info, buf,repo_get_common_dir (info->repo)); +return 0 ; +} + +static int get_path_config_file (struct repo_info *info,struct strbuf *buf) +{ +struct strbuf path =STRBUF_INIT ; + +repo_info_add_path (info, buf,repo_git_path_replace (info->repo, &path,"config" )); +strbuf_release (&path); +return 0 ; +} + +static int get_path_git_dir (struct repo_info *info,struct strbuf *buf) +{ +repo_info_add_path (info, buf,repo_get_git_dir (info->repo)); +return 0 ; +} + +static int get_path_prefix (struct repo_info *info,struct strbuf *buf) +{ +strbuf_addstr (buf, info->prefix); +return 0 ; +} + +static int get_path_grafts_file (struct repo_info *info,struct strbuf *buf) +{ +repo_info_add_path (info, buf,repo_get_graft_file (info->repo)); +return 0 ; +} + +static int get_path_hooks_directory (struct repo_info *info,struct strbuf *buf) +{ +struct strbuf path =STRBUF_INIT ; + +repo_info_add_path (info, buf,repo_git_path_replace (info->repo, &path,"hooks" )); +strbuf_release (&path); +return 0 ; +} + +static int get_path_index_file (struct repo_info *info,struct strbuf *buf) +{ +repo_info_add_path (info, buf,repo_get_index_file (info->repo)); +return 0 ; +} + +static int get_path_objects_directory (struct repo_info *info,struct strbuf *buf) +{ +repo_info_add_path (info, buf,repo_get_object_directory (info->repo)); +return 0 ; +} + +static int get_path_superproject_working_tree (struct repo_info *info, +struct strbuf *buf) +{ +struct strbuf superproject =STRBUF_INIT ; + +if (get_superproject_working_tree (&superproject)) +repo_info_add_path (info, buf, superproject.buf); + +strbuf_release (&superproject); +return 0 ; +} + +static int get_path_toplevel (struct repo_info *info,struct strbuf *buf) +{ +const char *work_tree =repo_get_work_tree (info->repo); + +if (work_tree) +repo_info_add_path (info, buf, work_tree); + +return 0 ; +} + +static int get_path_work_tree (struct repo_info *info,struct strbuf *buf) +{ +return get_path_toplevel (info, buf); +} +static int get_references_format (struct repo_info *info,struct strbuf *buf) {struct repository *repo = info->repo;
@@ -75,6 +166,17 @@ static const struct field repo_info_fields[] = { { "layout.bare", get_layout_bare }, { "layout.shallow", get_layout_shallow }, { "object.format", get_object_format }, + { "path.common-dir", get_path_common_dir }, + { "path.config-file", get_path_config_file }, + { "path.git-dir", get_path_git_dir }, + { "path.grafts-file", get_path_grafts_file }, + { "path.hooks-directory", get_path_hooks_directory }, + { "path.index-file", get_path_index_file }, + { "path.objects-directory", get_path_objects_directory }, + { "path.prefix", get_path_prefix }, + { "path.superproject-working-tree", get_path_superproject_working_tree }, + { "path.toplevel", get_path_toplevel }, + { "path.working-tree", get_path_work_tree }, { "references.format", get_references_format }, };
@@ -233,7 +335,7 @@ static int cmd_repo_info(int argc, const char **argv, const char *prefix,enum output_format format =FORMAT_NEWLINE_TERMINATED ;struct repo_info info = { .repo = repo, - .prefix = prefix, + .prefix = prefix ? prefix :"" , };int all_keys =0 ;int show_keys =0 ;
Signature · 1 line
gitgitgadget
PATCH v6 4/6 repo: add --path-format for info path output
In reply to eslam reda via GitGitGadgetFrom: Eslam reda ragheb <[email protected]>
Teach git repo info to accept --path-format=(absolute|relative) so scripts can request stable path style explicitly.
This aligns path.* output behavior with existing rev-parse usage patterns and reduces ad-hoc path conversion in callers.
The option is wired through repo_info context and used by repo_info_add_path(), so path formatting remains centralized and consistent across all path.* keys.
--- builtin/repo.c | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-)
builtin/repo.c +36 -1
@@ -20,15 +20,21 @@#include "utf8.h" static const char *const repo_usage[] = { -"git repo info [--format=(lines|nul) | -z] [--all | <key>...]" , +"git repo info [--format=(lines|nul) | -z] [--path-format=(absolute|relative)] [--all | <key>...]" ,"git repo info --keys [--format=(lines|nul) | -z]" ,"git repo structure [--format=(table|lines|nul) | -z]" ,NULL }; +enum path_format { +PATH_FORMAT_ABSOLUTE , +PATH_FORMAT_RELATIVE , +}; +struct repo_info {struct repository *repo;const char *prefix; +enum path_format path_format; };typedef int get_value_fn (struct repo_info *info,struct strbuf *buf);
@@ -48,6 +54,16 @@ static void repo_info_add_path(struct repo_info *info, struct strbuf *buf, const char *path) { +if (info->path_format ==PATH_FORMAT_RELATIVE ) { +char *cwd =xgetcwd (); +struct strbuf rel_path =STRBUF_INIT ; + +strbuf_addstr (buf,relative_path (path, cwd, &rel_path)); +strbuf_release (&rel_path); +free (cwd); +return ; + } +strbuf_add_absolute_path (buf, path); }
@@ -329,6 +345,21 @@ static int parse_format_cb(const struct option *opt,return 0 ; } +static int parse_path_format_cb (const struct option *opt, + const char *arg, int unset UNUSED) +{ +enum path_format *path_format = opt->value; + +if (!strcmp (arg,"absolute" )) + *path_format =PATH_FORMAT_ABSOLUTE ; +else if (!strcmp (arg,"relative" )) + *path_format =PATH_FORMAT_RELATIVE ; +else +die (_ ("invalid path format '%s'" ), arg); + +return 0 ; +} +static int cmd_repo_info (int argc,const char **argv,const char *prefix,struct repository *repo) {
@@ -336,6 +367,7 @@ static int cmd_repo_info(int argc, const char **argv, const char *prefix,struct repo_info info = { .repo = repo, .prefix = prefix ? prefix :"" , + .path_format =PATH_FORMAT_ABSOLUTE , };int all_keys =0 ;int show_keys =0 ;
@@ -347,6 +379,9 @@ static int cmd_repo_info(int argc, const char **argv, const char *prefix, N_("synonym for --format=nul"), PARSE_OPT_NONEG | PARSE_OPT_NOARG, parse_format_cb), +OPT_CALLBACK_F (0 ,"path-format" , &info.path_format, +N_ ("format" ),N_ ("path output format" ), +PARSE_OPT_NONEG , parse_path_format_cb),OPT_BOOL (0 ,"all" , &all_keys,N_ ("print all keys/values" )),OPT_BOOL (0 ,"keys" , &show_keys,N_ ("show keys" )),OPT_END ()
Signature · 1 line
gitgitgadget
PATCH v6 5/6 t1900: cover repo info path keys and path-format
In reply to eslam reda via GitGitGadgetFrom: Eslam reda ragheb <[email protected]>
Extend t1900 to validate category-key expansion, path.* key behavior, and --path-format handling for git repo info.
The tests compare repo info output to equivalent rev-parse values.
This ensures behavior remains aligned with existing plumbing semantics.
Also keep mixed key/category ordering coverage so callers can rely on deterministic output order when combining explicit keys with category requests.
--- t/t1900-repo.sh | 194 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 194 insertions(+)
t/t1900-repo.sh +194 -0
@@ -4,6 +4,40 @@ test_description='test git repo-info' . ./test-lib.sh +# git-repo-info keys. It must contain the same keys listed in the const +# repo_info_fields, in lexicographical order. +REPO_INFO_KEYS=' + layout.bare + layout.shallow + object.format + path.common-dir + path.config-file + path.git-dir + path.grafts-file + path.hooks-directory + path.index-file + path.objects-directory + path.prefix + path.superproject-working-tree + path.toplevel + path.working-tree + references.format +' + +REPO_INFO_PATH_KEYS=' + path.common-dir + path.config-file + path.git-dir + path.grafts-file + path.hooks-directory + path.index-file + path.objects-directory + path.prefix + path.superproject-working-tree + path.toplevel + path.working-tree +' + # Test whether a key-value pair is correctly returned # # Usage: test_repo_info <label> <init command> <repo_name> <key> <expected value>
@@ -80,6 +114,166 @@ test_expect_success 'values returned in order requested' ' test_cmp expect actual ' +test_expect_success 'category key returns all matching keys' ' + cat >expect <<-\EOF && + layout.bare=false + layout.shallow=false + EOF + git init category-layout && + git -C category-layout repo info layout >actual && + test_cmp expect actual +' + +test_expect_success 'mixed key/category requests preserve request order' ' + cat >expect <<-EOF && + object.format=$(test_oid algo) + layout.bare=false + layout.shallow=false + EOF + git init mixed-order && + git -C mixed-order repo info object.format layout >actual && + test_cmp expect actual +' + +test_expect_success 'path.git-dir matches rev-parse --absolute-git-dir' ' + git init path-git-dir && + expected_value=$(git -C path-git-dir rev-parse --absolute-git-dir) && + echo "path.git-dir=$expected_value" >expect && + git -C path-git-dir repo info path.git-dir >actual && + test_cmp expect actual +' + +test_expect_success 'path.common-dir matches rev-parse --git-common-dir' ' + git init path-common-dir && + expected_value=$(git -C path-common-dir rev-parse --path-format=absolute --git-common-dir) && + echo "path.common-dir=$expected_value" >expect && + git -C path-common-dir repo info path.common-dir >actual && + test_cmp expect actual +' + +test_expect_success 'path.toplevel matches rev-parse --show-toplevel' ' + git init path-toplevel && + expected_value=$(git -C path-toplevel rev-parse --show-toplevel) && + echo "path.toplevel=$expected_value" >expect && + git -C path-toplevel repo info path.toplevel >actual && + test_cmp expect actual +' + +test_expect_success 'path.toplevel is empty in bare repository' ' + git init --bare bare-path-toplevel && + echo "path.toplevel=" >expect && + git -C bare-path-toplevel repo info path.toplevel >actual && + test_cmp expect actual +' + +test_expect_success 'path.prefix matches rev-parse --show-prefix' ' + git init path-prefix && + mkdir -p path-prefix/a/b && + expected_value=$(git -C path-prefix/a/b rev-parse --show-prefix) && + echo "path.prefix=$expected_value" >expect && + git -C path-prefix/a/b repo info path.prefix >actual && + test_cmp expect actual +' + +test_expect_success 'git-path style keys match rev-parse --git-path' ' + git init path-git-path && + + expected_value=$(git -C path-git-path rev-parse --path-format=absolute --git-path info/grafts) && + echo "path.grafts-file=$expected_value" >expect && + git -C path-git-path repo info path.grafts-file >actual && + test_cmp expect actual && + + expected_value=$(git -C path-git-path rev-parse --path-format=absolute --git-path index) && + echo "path.index-file=$expected_value" >expect && + git -C path-git-path repo info path.index-file >actual && + test_cmp expect actual && + + expected_value=$(git -C path-git-path rev-parse --path-format=absolute --git-path objects) && + echo "path.objects-directory=$expected_value" >expect && + git -C path-git-path repo info path.objects-directory >actual && + test_cmp expect actual && + + expected_value=$(git -C path-git-path rev-parse --path-format=absolute --git-path hooks) && + echo "path.hooks-directory=$expected_value" >expect && + git -C path-git-path repo info path.hooks-directory >actual && + test_cmp expect actual && + + expected_value=$(git -C path-git-path rev-parse --path-format=absolute --git-path config) && + echo "path.config-file=$expected_value" >expect && + git -C path-git-path repo info path.config-file >actual && + test_cmp expect actual +' + +test_expect_success 'path.working-tree matches path.toplevel' ' + git init path-work-tree && + expected_value=$(git -C path-work-tree rev-parse --show-toplevel) && + echo "path.working-tree=$expected_value" >expect && + git -C path-work-tree repo info path.working-tree >actual && + test_cmp expect actual +' + +test_expect_success 'path.working-tree is empty in bare repository' ' + git init --bare bare-path-work-tree && + echo "path.working-tree=" >expect && + git -C bare-path-work-tree repo info path.working-tree >actual && + test_cmp expect actual +' + +test_expect_success 'path.superproject-working-tree is empty when not a submodule' ' + git init path-superproject && + echo "path.superproject-working-tree=" >expect && + git -C path-superproject repo info path.superproject-working-tree >actual && + test_cmp expect actual +' + +test_expect_success 'path.superproject-working-tree matches rev-parse in submodule' ' + git init path-superproject-origin && + echo x >path-superproject-origin/x && + git -C path-superproject-origin add x && + git -C path-superproject-origin commit -m x && + + git init path-superproject-parent && + git -C path-superproject-parent -c protocol.file.allow=always submodule add ../path-superproject-origin sm && + + expected_value=$(git -C path-superproject-parent/sm rev-parse --show-superproject-working-tree) && + echo "path.superproject-working-tree=$expected_value" >expect && + git -C path-superproject-parent/sm repo info path.superproject-working-tree >actual && + test_cmp expect actual +' + +test_expect_success 'path category returns all path keys' ' + git init path-category && + >expect && + for key in $REPO_INFO_PATH_KEYS + do + git -C path-category repo info "$key" >>expect || return 1 + done && + git -C path-category repo info path >actual && + test_cmp expect actual +' + +test_expect_success 'path-format=relative matches rev-parse for git-dir' ' + git init path-format-relative && + expected_value=$(git -C path-format-relative rev-parse --path-format=relative --git-dir) && + echo "path.git-dir=$expected_value" >expect && + git -C path-format-relative repo info --path-format=relative path.git-dir >actual && + test_cmp expect actual +' + +test_expect_success 'git repo info uses the last requested path format' ' + git init path-format-last && + expected_value=$(git -C path-format-last rev-parse --path-format=relative --git-dir) && + echo "path.git-dir=$expected_value" >expect && + git -C path-format-last repo info --path-format=absolute --path-format=relative path.git-dir >actual && + test_cmp expect actual +' + +test_expect_success 'git-repo-info aborts when requesting an invalid path format' ' + echo "fatal: invalid path format ${SQ}foo${SQ}" >expect && + test_must_fail git repo info --path-format=foo path.git-dir 2>actual && + test_cmp expect actual +' + test_expect_success 'git-repo-info fails if an invalid key is requested' ' echo "error: key ${SQ}foo${SQ} not found" >expect && test_must_fail git repo info foo 2>actual &&
Signature · 1 line
gitgitgadget
PATCH v6 6/6 docs: describe repo info path keys
In reply to eslam reda via GitGitGadgetFrom: Eslam reda ragheb <[email protected]>
Document repo info category requests, path.* keys, and --path-format behavior.
--- Documentation/git-repo.adoc | 58 ++++++++++++++++++++++++++++++++++--- 1 file changed, 54 insertions(+), 4 deletions(-)
Documentation/git-repo.adoc +54 -4
@@ -8,7 +8,7 @@ git-repo - Retrieve information about the repository SYNOPSIS -------- [synopsis] -git repo info [--format=(lines|nul) | -z] [--all | <key>...] +git repo info [--format=(lines|nul) | -z] [--path-format=(absolute|relative)] [--all | <key>...] git repo info --keys [--format=(lines|nul) | -z] git repo structure [--format=(table|lines|nul) | -z]
@@ -56,6 +56,11 @@ supported: `nul`::: Similar to `lines`, but using a _NUL_ character after each value. +`--path-format=(absolute|relative)`::: + Controls formatting for keys in the `path` category. The default is + `absolute`. This option may be specified multiple times; the last one + specified takes effect. + `structure [--format=(table|lines|nul) | -z]`:: Retrieve statistics about the current repository structure. The following kinds of information are reported:
@@ -64,6 +69,12 @@ supported: * Reachable object counts categorized by type * Total inflated size of reachable objects by type * Total disk size of reachable objects by type +* Largest inflated reachable object size by type +* Largest disk size of a reachable object by type +* Largest parent count among reachable commits +* Largest entry count among reachable trees +* Longest and deepest path among reachable blobs +* Deepest annotated tag chain + The output format can be chosen through the flag `--format`. Three formats are supported:
@@ -76,6 +87,7 @@ supported: `lines`::: Each line of output contains a key-value pair for a repository stat. The '=' character is used to delimit between the key and the value. + Both aggregate metrics and per-type metrics are included. Values containing "unusual" characters are quoted as explained for the configuration variable `core.quotePath` (see linkgit:git-config[1]).
@@ -90,9 +102,11 @@ supported: INFO KEYS --------- -In order to obtain a set of values from `git repo info`, you should provide -the keys that identify them. Here's a list of the available keys and the -values that they return: +In order to obtain values from `git repo info`, provide either individual keys +or category names. A category returns all keys within that category. For +example, `layout` returns both `layout.bare` and `layout.shallow`. + +Here's a list of the available keys and the values that they return: `layout.bare`:: `true` if this is a bare repository, otherwise `false`.
@@ -103,6 +117,42 @@ values that they return: `object.format`:: The object format (hash algorithm) used in the repository. +`path.common-dir`:: + The path to the common git directory. + +`path.config-file`:: + The path to the `config` file in the git directory. + +`path.git-dir`:: + The path to the git directory. + +`path.prefix`:: + The path of the current working directory relative to the top-level + directory. + +`path.grafts-file`:: + The path to the `info/grafts` file. + +`path.hooks-directory`:: + The path to the `hooks` directory. + +`path.index-file`:: + The path to the index file. + +`path.objects-directory`:: + The path to the objects directory. + +`path.superproject-working-tree`:: + The path to the superproject's working tree root, or an empty string + when the repository is not used as a submodule. + +`path.toplevel`:: + The path to the top-level working tree directory, or an empty string + for bare repositories. + +`path.working-tree`:: + Alias for `path.toplevel`. + `references.format`:: The reference storage format. The valid values are: +
Signature · 1 line
gitgitgadget
Re: [PATCH v6 0/6] repo info: add category/path keys and --path-format
In reply to eslam reda via GitGitGadgetHi,
While reading the current git repo info implementation, I noticed that layout.bare is still implemented via is_bare_repository() in builtin/repo.c.
At first I thought this might be a small repository-awareness cleanup, since the repo info field callbacks already receive a struct repository *. But after tracing it further, it seems the current is_bare_repository() semantics are not equivalent to simply checking whether repo_get_work_tree(repo) is NULL.
So before trying to patch this, I wanted to confirm the intended direction:
- Should repo info's layout.bare continue to follow the current is_bare_repository() semantics?
- Or, if git repo info is meant to become more explicitly repository-aware over time, would it make sense to introduce something like a repo_is_bare(repo) helper and use that instead?
I am asking mainly to avoid sending a misleading cleanup patch for what may actually be a semantics question.
Thanks,
Jialong
Re: [PATCH v6 0/6] repo info: add category/path keys and --path-format
Quoted 9 lines
While reading the current
git repo infoimplementation, I noticed thatlayout.bareis still implemented viais_bare_repository()inbuiltin/repo.c.At first I thought this might be a small repository-awareness cleanup, since the
repo infofield callbacks already receive astruct repository *. But after tracing it further, it seems the currentis_bare_repository()semantics are not equivalent to simply checking whetherrepo_get_work_tree(repo)is NULL.
Hmph, this was an idea I explored few days ago
But I do agree the repo->worktree == NULL method has multiple flaws
For example
test_expect_success 'layout.bare is false even when run from inside .git' '
git init nonbare-dot-git && echo "layout.bare=false" >expect && git -C nonbare-dot-git/.git repo info layout.bare >actual && test_cmp expect actual
'
I cooked up a test like this and it failed
I have since been exploring config.c and parse.c.
specifically the if repo_config_get_bool()
I think there are few other checks we need to do on top of repo->worktree to be completely sure that the given repo is in fact bare. Also instead of using repo->worktree I think we can use repo_get_work_tree(repo) Why recreate the logic when we have a getter ;)
I am convinced that we need a helperrepo_is_bare is a good name too.
Thanks for exploring this :)
Regards,
- Jayatheerth
Re: [PATCH v6 0/6] repo info: add category/path keys and --path-format
Hi Jay,
Thanks, this is very helpful.
That makes sense. I agree that checking only repo_get_work_tree(repo) == NULL is too weak, and the "git -C <nonbare>/.git" case is a good example of that.
I'll keep the linked-worktree / separate-git-dir coverage patch separate. For layout.bare, I'll first add tests to pin down the intended semantics, then follow up with a small repo-aware fix, likely with a repo_is_bare() helper if that turns out to be the right shape.
Thanks,
Jialong
Quoted 41 lines · K Jayatheerth
Quoted 10 lines
While reading the current
git repo infoimplementation, I noticed thatlayout.bareis still implemented viais_bare_repository()inbuiltin/repo.c.At first I thought this might be a small repository-awareness cleanup, since the
repo infofield callbacks already receive astruct repository *. But after tracing it further, it seems the currentis_bare_repository()semantics are not equivalent to simply checking whetherrepo_get_work_tree(repo)is NULL.Hmph, this was an idea I explored few days ago
But I do agree therepo->worktree== NULL method has multiple flawsFor example
test_expect_success'layout.bare is false even when run from inside .git' 'git init nonbare-dot-git && echo "layout.bare=false" >expect && git -C nonbare-dot-git/.git repo info layout.bare >actual && test_cmp expect actual'
I cooked up a test like this and it failed
I have since been exploringconfig.candparse.c.
specifically the ifrepo_config_get_bool()I think there are few other checks we need to do on top of
repo->worktreeto be completely sure that the given repo is in fact bare. Also instead of usingrepo->worktreeI think we can userepo_get_work_tree(repo)Why recreate the logic when we have a getter ;)I am convinced that we need a helper
repo_is_bareis a good name too.Thanks for exploring this :)
Regards,
- Jayatheerth
Re: [PATCH v6 0/6] repo info: add category/path keys and --path-format
Quoted 9 lines · Jerry Wang
Hi Jay,
Thanks, this is very helpful.
That makes sense. I agree that checking only
repo_get_work_tree(repo)== NULL is too weak, and the "git -C <nonbare>/.git" case is a good example of that.
Quoted 9 lines
I'll keep the linked-worktree / separate-git-dir coverage patch separate. For layout.bare, I'll first add tests to pin down the intended semantics, then follow up with a small repo-aware fix, likely with a
repo_is_bare()helper if that turns out to be the right shape.Thanks,
Jialong
If you are a GSoC applicant(I am presuming) I suggest not to start working on patches
as you might step on other people's toes.
Similar discussion here [1]
Similar official info here [2] in the _Don’t work on a proposed project right away_ section.
Another similar discussion [3]
Similar thing was caused by Eslam's patches which overlapped with what Lucas had previously as a foundation [4].
Regards,
- Jayatheerth
1 - https://lore.kernel.org/git/[email protected]/T/#m9506e0856630c9ff962093d59cc9477bd39ea6df
2 - https://git.github.io/General-Microproject-Information/
3 - https://lore.kernel.org/git/CALE2CrTt_2-9C4zCrZPBabtsWY=+Mk-bH4Jaemk=yHtfpoLjfg@mail.gmail.com/T/#m002015a8042bfe2717887c3ada9afd645615f50a
4 - https://lore.kernel.org/git/CA+rGoLdTc2caDUsQedpegL+T4MqwwiA62uuDSFSawAT5vcPvWQ@mail.gmail.com/T/#t
PATCH 1/1 t1900: cover repo info path keys in non-default layouts
In reply to eslam reda via GitGitGadgetExtend the path-key coverage in t1900 to exercise layouts where the reported paths differ from a regular worktree.
Add comparisons against rev-parse for path.git-dir, path.common-dir, and path.toplevel in a linked worktree and a repository created with --separate-git-dir. Also cover path.git-dir with --path-format=relative in the separate-git-dir case, and path.superproject-working-tree with --path-format=relative in a submodule.
--- t/t1900-repo.sh | 54 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+)
t/t1900-repo.sh +54 -0
@@ -241,6 +241,60 @@ test_expect_success 'path.superproject-working-tree matches rev-parse in submodu test_cmp expect actual ' +test_expect_success 'path keys match rev-parse in linked worktree' ' + git init path-linked-main && + echo x >path-linked-main/x && + git -C path-linked-main add x && + git -C path-linked-main commit -m x && + git -C path-linked-main worktree add ../path-linked-worktree && + + expected_value=$(git -C path-linked-worktree rev-parse --absolute-git-dir) && + echo "path.git-dir=$expected_value" >expect && + git -C path-linked-worktree repo info path.git-dir >actual && + test_cmp expect actual && + + expected_value=$(git -C path-linked-worktree rev-parse --path-format=absolute --git-common-dir) && + echo "path.common-dir=$expected_value" >expect && + git -C path-linked-worktree repo info path.common-dir >actual && + test_cmp expect actual && + + expected_value=$(git -C path-linked-worktree rev-parse --show-toplevel) && + echo "path.toplevel=$expected_value" >expect && + git -C path-linked-worktree repo info path.toplevel >actual && + test_cmp expect actual +' + +test_expect_success 'path keys match rev-parse with --separate-git-dir' ' + git init --separate-git-dir path-separate.git path-separate-worktree && + + expected_value=$(git -C path-separate-worktree rev-parse --absolute-git-dir) && + echo "path.git-dir=$expected_value" >expect && + git -C path-separate-worktree repo info path.git-dir >actual && + test_cmp expect actual && + + expected_value=$(git -C path-separate-worktree rev-parse --path-format=absolute --git-common-dir) && + echo "path.common-dir=$expected_value" >expect && + git -C path-separate-worktree repo info path.common-dir >actual && + test_cmp expect actual && + + expected_value=$(git -C path-separate-worktree rev-parse --show-toplevel) && + echo "path.toplevel=$expected_value" >expect && + git -C path-separate-worktree repo info path.toplevel >actual && + test_cmp expect actual && + + expected_value=$(git -C path-separate-worktree rev-parse --path-format=relative --git-dir) && + echo "path.git-dir=$expected_value" >expect && + git -C path-separate-worktree repo info --path-format=relative path.git-dir >actual && + test_cmp expect actual +' + +test_expect_success 'relative path.superproject-working-tree matches rev-parse in submodule' ' + expected_value=$(git -C path-superproject-parent/sm rev-parse --path-format=relative --show-superproject-working-tree) && + echo "path.superproject-working-tree=$expected_value" >expect && + git -C path-superproject-parent/sm repo info --path-format=relative path.superproject-working-tree >actual && + test_cmp expect actual +' + test_expect_success 'path category returns all path keys' ' git init path-category && >expect &&
Signature · 1 line
2.51.0
Re: [PATCH v6 5/6] t1900: cover repo info path keys and path-format
In reply to eslam reda via GitGitGadgetHi Eslam,
Thanks for working on this. I tried the v6 branch locally, and the path-key coverage looks good overall, but I think t1900 still misses a few non-default layouts that seem worth covering.
In particular, I think it would be useful to add coverage for:
- linked worktrees, where path.git-dir points into .git/worktrees/<name>, path.common-dir still points at the main .git, and path.toplevel points at the linked checkout
- repositories created with --separate-git-dir, especially
path.git-dir with --path-format=relative
- path.superproject-working-tree with --path-format=relative in a submodule
I tested these locally against rev-parse and they seem to behave as expected, so they may make good follow-up coverage for this series.
Thanks,
Jialong