12

I'd like to attach a note to a tree object. However, in order to do so I first need to know the tree object's hash. For a given directory name that's part of my repository, how do I get its belonging tree object's hash in order to attach a note to it?

From reading this answer I understand I could use

git cat-file -p master^{tree}

to list the root tree's contents, but I'd still have to grep the output for the directory name, and follow nested tree objects recursively to get the tree object's hash for a directory deeper in the hierarchy.

Basically, I'm looking for the implementation of a fictional get-tree-hash.sh script. If called like

get-tree-hash.sh path/to/directory/in/my/git/repo

it should output

The hash for the "repo" tree inside "path/to/directory/in/my/git" is:
92a68a2f5560fa7080393b633e2afd1d5271deef
Community
  • 1
  • 1
sschuberth
  • 28,386
  • 6
  • 101
  • 146

3 Answers3

16

You can do:

git rev-parse HEAD:path/to/directory/in/my/git

To print only the hash. So you don't need cut or awk to extract it.

matvore
  • 792
  • 6
  • 10
  • That's nicer indeed. Details about this syntax are documented [here](https://git-scm.com/docs/git-rev-parse#git-rev-parse-emltrevgtltpathgtemegemHEADREADMEememREADMEememmasterREADMEem). – sschuberth Oct 05 '18 at 06:55
6

Just figured it out myself,

git ls-tree HEAD -- path/to/directory/in/my/git | cut -d' ' -f3 | cut -f1

does what I want.

sschuberth
  • 28,386
  • 6
  • 101
  • 146
0

Instead of

git ls-tree HEAD -- path/to/directory/in/my/git | cut -d' ' -f3 | cut -f1

You can use the new format option (Git 2.36+ only, Q2 2022):

git ls-tree <tree-ish> --format='%x09'

With Git 2.36 (Q2 2022), "git ls-tree"(man) learns --oid-only option, similar to "--name-only", and more generalized "--format" option.

See commit 22184af (23 Mar 2022) by Johannes Schindelin (dscho).
See commit 9c4d58f, commit 0f88783, commit 455923e, commit e815171, commit 132ceda, commit 26f6d4d, commit 82e69b0, commit 4e4566f, commit a53343e (23 Mar 2022) by Ævar Arnfjörð Bjarmason (avar).
See commit cab851c, commit 315f22c, commit f6b224d, commit 87af0dd, commit 889f783 (23 Mar 2022) by Teng Long (dyrone).
(Merged by Junio C Hamano -- gitster -- in commit 1041d58, 04 Apr 2022)

ls-tree: introduce "--format" option

Signed-off-by: Ævar Arnfjörð Bjarmason
Signed-off-by: Teng Long

Add a --format option to ls-tree.
It has an existing default output, and then --long and --name-only options to emit the default output along with the objectsize and, or to only emit object paths.

Rather than add --type-only, --object-only etc.
we can just support a --format using a strbuf_expand() similar to "for-each-ref --format".
We might still add such options in the future for convenience.

The --format implementation is slower than the existing code, but this change does not cause any performance regressions.
We'll leave the existing show_tree() unchanged, and only run show_tree_fmt() in if a --format different than the hardcoded built-in ones corresponding to the existing modes is provided.

I.e.
something like the "--long" output would be much slower with this, mainly due to how we need to allocate various things to do with quote.c instead of spewing the output directly to stdout.

The new option of '--format' comes from Ævar Arnfjörð Bjarmasonn's idea and suggestion, this commit makes modifications in terms of the original discussion on community.

In that thread, there was a "GIT_TEST_LS_TREE_FORMAT_BACKEND" variable to ensure that we had test coverage for passing tests that would otherwise use show_tree() through show_tree_fmt(), and thus that the formatting mechanism could handle all the same cases as the non-formatting options.

Somewhere in subsequent re-rolls of that we seem to have drifted away from what the goal of these tests should be.
We're trying to ensure correctness of show_tree_fmt().
We can't tell if we "hit [the] fast-path" here, and instead of having an explicit test for that, we can just add it to something our "test_ls_tree_format" tests for.

Here is the statistics about performance tests:

Default format (hitten the builtin formats):

"git ls-tree <tree-ish>" vs "--format='%(mode) %(type) %(object)%x09%(file)'"

$hyperfine --warmup=10 "/opt/git/master/bin/git ls-tree -r HEAD"
Benchmark 1: /opt/git/master/bin/git ls-tree -r HEAD
Time (mean ± σ):     105.2 ms ±   3.3 ms    [User: 84.3 ms, System: 20.8 ms]
Range (min … max):    99.2 ms … 113.2 ms    28 runs

$hyperfine --warmup=10 "/opt/git/ls-tree-oid-only/bin/git ls-tree -r --format='%(mode) %(type) %(object)%x09%(file)'  HEAD"
Benchmark 1: /opt/git/ls-tree-oid-only/bin/git ls-tree -r --format='%(mode) %(type) %(object)%x09%(file)'  HEAD
Time (mean ± σ):     106.4 ms ±   2.7 ms    [User: 86.1 ms, System: 20.2 ms]
Range (min … max):   100.2 ms … 110.5 ms    29 runs

Default format includes object size (hitten the builtin formats):

"git ls-tree -l <tree-ish>" vs "--format='%(mode) %(type) %(object) %(size:padded)%x09%(file)'"

$hyperfine --warmup=10 "/opt/git/master/bin/git ls-tree -r -l HEAD"
Benchmark 1: /opt/git/master/bin/git ls-tree -r -l HEAD
Time (mean ± σ):     335.1 ms ±   6.5 ms    [User: 304.6 ms, System: 30.4 ms]
Range (min … max):   327.5 ms … 348.4 ms    10 runs

$hyperfine --warmup=10 "/opt/git/ls-tree-oid-only/bin/git ls-tree -r --format='%(mode) %(type) %(object) %(size:padded)%x09%(file)'  HEAD"
Benchmark 1: /opt/git/ls-tree-oid-only/bin/git ls-tree -r --format='%(mode) %(type) %(object) %(size:padded)%x09%(file)'  HEAD
Time (mean ± σ):     337.2 ms ±   8.2 ms    [User: 309.2 ms, System: 27.9 ms]
Range (min … max):   328.8 ms … 349.4 ms    10 runs

git ls-tree now includes in its man page:

--format=<format>

A string that interpolates %(fieldname) from the result being shown.

It also interpolates %% to %, and %xx where xx are hex digits interpolates to character with hex code xx; for example %00 interpolates to \0 (NUL), %09 to \t (TAB) and %0a to \n (LF).

When specified, --format cannot be combined with other format-altering options, including --long, --name-only and --object-only.

git ls-tree now includes in its man page:

The output format of ls-tree is determined by either the --format option, or other format-altering options such as --name-only etc. (see --format above).

The use of certain --format directives is equivalent to using those options, but invoking the full formatting machinery can be slower than using an appropriate formatting option.

In cases where the --format would exactly map to an existing option ls-tree will use the appropriate faster path. Thus the default format is equivalent to:

%(objectmode) %(objecttype) %(objectname)%x09%(path)

git ls-tree now includes in its man page:

Customized format:

It is possible to print in a custom format by using the --format option, which is able to interpolate different fields using a %(fieldname) notation. For example, if you only care about the "objectname" and "path" fields, you can execute with a specific "--format" like

git ls-tree --format='%(objectname) %(path)' <tree-ish>

FIELD NAMES

Various values from structured fields can be used to interpolate into the resulting output. For each outputing line, the following names can be used:

objectmode

The mode of the object.

objecttype

The type of the object (blob or tree).

objectname

The name of the object.

objectsize[:padded]

The size of the object ("-" if it's a tree). It also supports a padded format of size with "%(size:padded)".

path

The pathname of the object.


Warning: "git ls-tree --format=%(path) %(path)' $tree $path"(man)" showed the path three times, which has been corrected with Git 2.40 (Q1 2023).

See commit c388fcd, commit 16fb5c5 (14 Jan 2023) by René Scharfe (rscharfe).
(Merged by Junio C Hamano -- gitster -- in commit 8a40af9, 23 Jan 2023)

ls-tree: fix expansion of repeated %(path)

Signed-off-by: René Scharfe

expand_show_tree() borrows the base strbuf given to us by read_tree() to build the full path of the current entry when handling %(path).
Only its indirect caller, show_tree_fmt(), removes the added entry name.
That works fine as long as %(path) is only included once in the format string, but accumulates duplicates if it's repeated:

$ git ls-tree --format='%(path) %(path) %(path)' HEAD M*
Makefile MakefileMakefile MakefileMakefileMakefile

Reset the length after each use to get the same expansion every time; here's the behavior with this patch:

$ ./git ls-tree --format='%(path) %(path) %(path)' HEAD M*
Makefile Makefile Makefile

Git 2.42 (Q3 2023) update the documentation for the %x format placeholder.

See commit 3744ffc (15 Jun 2023) by René Scharfe (rscharfe).
(Merged by Junio C Hamano -- gitster -- in commit e0e8a2d, 22 Jun 2023)

ls-tree: fix documentation of %x format placeholder

Signed-off-by: René Scharfe

ls-tree --format(man) expands %x followed by two hexadecimal digits to the character indicated by that hexadecimal number, e.g.:

$ git ls-tree --format=%x41 HEAD | head -1
A

It rejects % followed by a hexadecimal digit, e.g.:

$ git ls-tree --format=%41 HEAD | head -1
fatal: bad ls-tree format: element '41' does not start with '('

This functionality is provided by strbuf_expand_literal_cb(), which has not been changed since it was factored out by fd2015b ("strbuf: separate callback for strbuf_expand:ing literals", 2019-01-28, Git v2.22.0-rc0 -- merge listed in batch #2).

Adjust the documentation accordingly.

git ls-tree now includes in its man page:

%xNN where NN are hex digits interpolates to character with hex code NN; for example %x00 interpolates to \0 (NUL), %x09 to \t (TAB) and %x0a to \n (LF).

VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
  • Note: [using Git 2.37 is preferable](https://github.com/git/git/commit/1d232d38bde689fb04161824d1e494617b3f8082). – VonC Jun 15 '22 at 18:24