From 9fd38038b9cf7d221bfa2d2ee95c8fad6d8b3d64 Mon Sep 17 00:00:00 2001 From: Victoria Dye Date: Mon, 2 Jun 2025 18:55:53 +0000 Subject: [PATCH 1/3] t1006: update 'run_tests' to test generic object specifiers Update the 'run_tests' test wrapper so that the first argument may refer to any specifier that uniquely identifies an object (e.g. a ref name, ':', '^{}', etc.), rather than only a full object ID. Also add tests that use non-OID identifiers, ensuring appropriate parsing in 'cat-file'. The identifiers used in some of the added tests include a space, which is incompatible with the '%(rest)' atom. To accommodate that without removing the test case, use 'test_expect_failure' when 'object_name' includes a space. Signed-off-by: Johannes Schindelin Signed-off-by: Victoria Dye Signed-off-by: Junio C Hamano --- t/t1006-cat-file.sh | 56 +++++++++++++++++++++++++++++---------------- 1 file changed, 36 insertions(+), 20 deletions(-) diff --git a/t/t1006-cat-file.sh b/t/t1006-cat-file.sh index 317da6869c..7c9512a6b4 100755 --- a/t/t1006-cat-file.sh +++ b/t/t1006-cat-file.sh @@ -113,53 +113,54 @@ strlen () { run_tests () { type=$1 - oid=$2 + object_name="$2" size=$3 content=$4 pretty_content=$5 + oid=${6:-"$object_name"} batch_output="$oid $type $size $content" test_expect_success "$type exists" ' - git cat-file -e $oid + git cat-file -e "$object_name" ' test_expect_success "Type of $type is correct" ' echo $type >expect && - git cat-file -t $oid >actual && + git cat-file -t "$object_name" >actual && test_cmp expect actual ' test_expect_success "Size of $type is correct" ' echo $size >expect && - git cat-file -s $oid >actual && + git cat-file -s "$object_name" >actual && test_cmp expect actual ' test -z "$content" || test_expect_success "Content of $type is correct" ' echo_without_newline "$content" >expect && - git cat-file $type $oid >actual && + git cat-file $type "$object_name" >actual && test_cmp expect actual ' test_expect_success "Pretty content of $type is correct" ' echo_without_newline "$pretty_content" >expect && - git cat-file -p $oid >actual && + git cat-file -p "$object_name" >actual && test_cmp expect actual ' test -z "$content" || test_expect_success "--batch output of $type is correct" ' echo "$batch_output" >expect && - echo $oid | git cat-file --batch >actual && + echo "$object_name" | git cat-file --batch >actual && test_cmp expect actual ' test_expect_success "--batch-check output of $type is correct" ' echo "$oid $type $size" >expect && - echo_without_newline $oid | git cat-file --batch-check >actual && + echo_without_newline "$object_name" | git cat-file --batch-check >actual && test_cmp expect actual ' @@ -168,13 +169,13 @@ $content" test -z "$content" || test_expect_success "--batch-command $opt output of $type content is correct" ' echo "$batch_output" >expect && - test_write_lines "contents $oid" | git cat-file --batch-command $opt >actual && + test_write_lines "contents $object_name" | git cat-file --batch-command $opt >actual && test_cmp expect actual ' test_expect_success "--batch-command $opt output of $type info is correct" ' echo "$oid $type $size" >expect && - test_write_lines "info $oid" | + test_write_lines "info $object_name" | git cat-file --batch-command $opt >actual && test_cmp expect actual ' @@ -182,19 +183,28 @@ $content" test_expect_success "custom --batch-check format" ' echo "$type $oid" >expect && - echo $oid | git cat-file --batch-check="%(objecttype) %(objectname)" >actual && + echo "$object_name" | git cat-file --batch-check="%(objecttype) %(objectname)" >actual && test_cmp expect actual ' test_expect_success "custom --batch-command format" ' echo "$type $oid" >expect && - echo "info $oid" | git cat-file --batch-command="%(objecttype) %(objectname)" >actual && + echo "info $object_name" | git cat-file --batch-command="%(objecttype) %(objectname)" >actual && test_cmp expect actual ' - test_expect_success '--batch-check with %(rest)' ' + # FIXME: %(rest) is incompatible with object names that include whitespace, + # e.g. HEAD:path/to/a/file with spaces. Use the resolved OID as input to + # test this instead of the raw object name. + if echo "$object_name" | grep " "; then + test_rest=test_expect_failure + else + test_rest=test_expect_success + fi + + $test_rest '--batch-check with %(rest)' ' echo "$type this is some extra content" >expect && - echo "$oid this is some extra content" | + echo "$object_name this is some extra content" | git cat-file --batch-check="%(objecttype) %(rest)" >actual && test_cmp expect actual ' @@ -205,7 +215,7 @@ $content" echo "$size" && echo "$content" } >expect && - echo $oid | git cat-file --batch="%(objectsize)" >actual && + echo "$object_name" | git cat-file --batch="%(objectsize)" >actual && test_cmp expect actual ' @@ -215,7 +225,7 @@ $content" echo "$type" && echo "$content" } >expect && - echo $oid | git cat-file --batch="%(objecttype)" >actual && + echo "$object_name" | git cat-file --batch="%(objecttype)" >actual && test_cmp expect actual ' } @@ -230,6 +240,8 @@ test_expect_success "setup" ' git config extensions.compatobjectformat $test_compat_hash_algo && echo_without_newline "$hello_content" > hello && git update-index --add hello && + echo_without_newline "$hello_content" > "path with spaces" && + git update-index --add --chmod=+x "path with spaces" && git commit -m "add hello file" ' @@ -269,13 +281,17 @@ test_expect_success '--batch-check without %(rest) considers whole line' ' tree_oid=$(git write-tree) tree_compat_oid=$(git rev-parse --output-object-format=$test_compat_hash_algo $tree_oid) -tree_size=$(($(test_oid rawsz) + 13)) -tree_compat_size=$(($(test_oid --hash=compat rawsz) + 13)) -tree_pretty_content="100644 blob $hello_oid hello${LF}" -tree_compat_pretty_content="100644 blob $hello_compat_oid hello${LF}" +tree_size=$((2 * $(test_oid rawsz) + 13 + 24)) +tree_compat_size=$((2 * $(test_oid --hash=compat rawsz) + 13 + 24)) +tree_pretty_content="100644 blob $hello_oid hello${LF}100755 blob $hello_oid path with spaces${LF}" +tree_compat_pretty_content="100644 blob $hello_compat_oid hello${LF}100755 blob $hello_compat_oid path with spaces${LF}" run_tests 'tree' $tree_oid $tree_size "" "$tree_pretty_content" run_tests 'tree' $tree_compat_oid $tree_compat_size "" "$tree_compat_pretty_content" +run_tests 'blob' "$tree_oid:hello" $hello_size "" "$hello_content" $hello_oid +run_tests 'blob' "$tree_compat_oid:hello" $hello_size "" "$hello_content" $hello_compat_oid +run_tests 'blob' "$tree_oid:path with spaces" $hello_size "" "$hello_content" $hello_oid +run_tests 'blob' "$tree_compat_oid:path with spaces" $hello_size "" "$hello_content" $hello_compat_oid commit_message="Initial commit" commit_oid=$(echo_without_newline "$commit_message" | git commit-tree $tree_oid) From aba14384354c6f722c127962bc756a542078f3e3 Mon Sep 17 00:00:00 2001 From: Victoria Dye Date: Mon, 2 Jun 2025 18:55:54 +0000 Subject: [PATCH 2/3] cat-file: add %(objectmode) atom Add a formatting atom, used with the --batch-check/--batch-command options, that prints the octal representation of the object mode if a given revision includes that information, e.g. one that follows the format :. If the mode information does not exist, an empty string is printed instead. Signed-off-by: Johannes Schindelin Signed-off-by: Victoria Dye Signed-off-by: Junio C Hamano --- Documentation/git-cat-file.adoc | 5 +++++ builtin/cat-file.c | 9 ++++++-- t/t1006-cat-file.sh | 38 +++++++++++++++++++-------------- 3 files changed, 34 insertions(+), 18 deletions(-) diff --git a/Documentation/git-cat-file.adoc b/Documentation/git-cat-file.adoc index cde79ad242..5c002c0499 100644 --- a/Documentation/git-cat-file.adoc +++ b/Documentation/git-cat-file.adoc @@ -307,6 +307,11 @@ newline. The available atoms are: `objecttype`:: The type of the object (the same as `cat-file -t` reports). +`objectmode`:: + If the specified object has mode information (such as a tree or + index entry), the mode expressed as an octal integer. Otherwise, + empty string. + `objectsize`:: The size, in bytes, of the object (the same as `cat-file -s` reports). diff --git a/builtin/cat-file.c b/builtin/cat-file.c index 67a5ff2b9e..b11576756b 100644 --- a/builtin/cat-file.c +++ b/builtin/cat-file.c @@ -275,6 +275,7 @@ struct expand_data { struct object_id oid; enum object_type type; unsigned long size; + unsigned short mode; off_t disk_size; const char *rest; struct object_id delta_base_oid; @@ -306,6 +307,7 @@ struct expand_data { */ unsigned skip_object_info : 1; }; +#define EXPAND_DATA_INIT { .mode = S_IFINVALID } static int is_atom(const char *atom, const char *s, int slen) { @@ -345,6 +347,9 @@ static int expand_atom(struct strbuf *sb, const char *atom, int len, else strbuf_addstr(sb, oid_to_hex(&data->delta_base_oid)); + } else if (is_atom("objectmode", atom, len)) { + if (!data->mark_query && !(S_IFINVALID == data->mode)) + strbuf_addf(sb, "%06o", data->mode); } else return 0; return 1; @@ -613,6 +618,7 @@ static void batch_one_object(const char *obj_name, goto out; } + data->mode = ctx.mode; batch_object_write(obj_name, scratch, opt, data, NULL, 0); out: @@ -866,7 +872,7 @@ static int batch_objects(struct batch_options *opt) { struct strbuf input = STRBUF_INIT; struct strbuf output = STRBUF_INIT; - struct expand_data data; + struct expand_data data = EXPAND_DATA_INIT; int save_warning; int retval = 0; @@ -875,7 +881,6 @@ static int batch_objects(struct batch_options *opt) * object_info to be handed to oid_object_info_extended for each * object. */ - memset(&data, 0, sizeof(data)); data.mark_query = 1; expand_format(&output, opt->format ? opt->format : DEFAULT_FORMAT, diff --git a/t/t1006-cat-file.sh b/t/t1006-cat-file.sh index 7c9512a6b4..97052b3f31 100755 --- a/t/t1006-cat-file.sh +++ b/t/t1006-cat-file.sh @@ -114,10 +114,11 @@ strlen () { run_tests () { type=$1 object_name="$2" - size=$3 - content=$4 - pretty_content=$5 - oid=${6:-"$object_name"} + mode=$3 + size=$4 + content=$5 + pretty_content=$6 + oid=${7:-"$object_name"} batch_output="$oid $type $size $content" @@ -209,6 +210,12 @@ $content" test_cmp expect actual ' + test_expect_success '--batch-check with %(objectmode)' ' + echo "$mode $oid" >expect && + echo $object_name | git cat-file --batch-check="%(objectmode) %(objectname)" >actual && + test_cmp expect actual + ' + test -z "$content" || test_expect_success "--batch without type ($type)" ' { @@ -247,8 +254,7 @@ test_expect_success "setup" ' run_blob_tests () { oid=$1 - - run_tests 'blob' $oid $hello_size "$hello_content" "$hello_content" + run_tests 'blob' $oid "" $hello_size "$hello_content" "$hello_content" test_expect_success '--batch-command --buffer with flush for blob info' ' echo "$oid blob $hello_size" >expect && @@ -286,12 +292,12 @@ tree_compat_size=$((2 * $(test_oid --hash=compat rawsz) + 13 + 24)) tree_pretty_content="100644 blob $hello_oid hello${LF}100755 blob $hello_oid path with spaces${LF}" tree_compat_pretty_content="100644 blob $hello_compat_oid hello${LF}100755 blob $hello_compat_oid path with spaces${LF}" -run_tests 'tree' $tree_oid $tree_size "" "$tree_pretty_content" -run_tests 'tree' $tree_compat_oid $tree_compat_size "" "$tree_compat_pretty_content" -run_tests 'blob' "$tree_oid:hello" $hello_size "" "$hello_content" $hello_oid -run_tests 'blob' "$tree_compat_oid:hello" $hello_size "" "$hello_content" $hello_compat_oid -run_tests 'blob' "$tree_oid:path with spaces" $hello_size "" "$hello_content" $hello_oid -run_tests 'blob' "$tree_compat_oid:path with spaces" $hello_size "" "$hello_content" $hello_compat_oid +run_tests 'tree' $tree_oid "" $tree_size "" "$tree_pretty_content" +run_tests 'tree' $tree_compat_oid "" $tree_compat_size "" "$tree_compat_pretty_content" +run_tests 'blob' "$tree_oid:hello" "100644" $hello_size "" "$hello_content" $hello_oid +run_tests 'blob' "$tree_compat_oid:hello" "100644" $hello_size "" "$hello_content" $hello_compat_oid +run_tests 'blob' "$tree_oid:path with spaces" "100755" $hello_size "" "$hello_content" $hello_oid +run_tests 'blob' "$tree_compat_oid:path with spaces" "100755" $hello_size "" "$hello_content" $hello_compat_oid commit_message="Initial commit" commit_oid=$(echo_without_newline "$commit_message" | git commit-tree $tree_oid) @@ -310,8 +316,8 @@ committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE $commit_message" -run_tests 'commit' $commit_oid $commit_size "$commit_content" "$commit_content" -run_tests 'commit' $commit_compat_oid $commit_compat_size "$commit_compat_content" "$commit_compat_content" +run_tests 'commit' $commit_oid "" $commit_size "$commit_content" "$commit_content" +run_tests 'commit' $commit_compat_oid "" $commit_compat_size "$commit_compat_content" "$commit_compat_content" tag_header_without_oid="type blob tag hellotag @@ -334,8 +340,8 @@ tag_size=$(strlen "$tag_content") tag_compat_oid=$(git rev-parse --output-object-format=$test_compat_hash_algo $tag_oid) tag_compat_size=$(strlen "$tag_compat_content") -run_tests 'tag' $tag_oid $tag_size "$tag_content" "$tag_content" -run_tests 'tag' $tag_compat_oid $tag_compat_size "$tag_compat_content" "$tag_compat_content" +run_tests 'tag' $tag_oid "" $tag_size "$tag_content" "$tag_content" +run_tests 'tag' $tag_compat_oid "" $tag_compat_size "$tag_compat_content" "$tag_compat_content" test_expect_success "Reach a blob from a tag pointing to it" ' echo_without_newline "$hello_content" >expect && From b0b910e052b2c5a8036343c0475dbbb01add6be8 Mon Sep 17 00:00:00 2001 From: Victoria Dye Date: Mon, 2 Jun 2025 18:55:55 +0000 Subject: [PATCH 3/3] cat-file.c: add batch handling for submodules When an object specification is passed to 'cat-file --batch[-check]' referring to a submodule (e.g. 'HEAD:path/to/my/submodule'), the current behavior of the command is to print the "missing" error message. However, it is often valuable for callers to distinguish between paths that are actually missing and "the submodule tree entry exists, but the object does not exist in the repository". To disambiguate without needing to invoke a separate Git process (e.g. 'ls-tree'), print the message " submodule" for such objects instead of " missing". In addition to the change from "missing" to "submodule", the new message differs from the old in that it always prints the resolved tree entry's OID, rather than the input object specification. Note that this implementation maintains a distinction between submodules where the commit OID is not present in the repo, and submodules where the commit OID *is* present; the former will now print " submodule", but the latter will still print the full object content. Signed-off-by: Victoria Dye Signed-off-by: Junio C Hamano --- Documentation/git-cat-file.adoc | 8 ++++++++ builtin/cat-file.c | 5 ++++- t/t1006-cat-file.sh | 25 +++++++++++++++++++++++++ 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/Documentation/git-cat-file.adoc b/Documentation/git-cat-file.adoc index 5c002c0499..180d1ad363 100644 --- a/Documentation/git-cat-file.adoc +++ b/Documentation/git-cat-file.adoc @@ -373,6 +373,14 @@ If a name is specified that might refer to more than one object (an ambiguous sh SP ambiguous LF ------------ +If a name is specified that refers to a submodule entry in a tree and the +target object does not exist in the repository, then `cat-file` will ignore +any custom format and print (with the object ID of the submodule): + +------------ + SP submodule LF +------------ + If `--follow-symlinks` is used, and a symlink in the repository points outside the repository, then `cat-file` will ignore any custom format and print: diff --git a/builtin/cat-file.c b/builtin/cat-file.c index b11576756b..4b23fcecbd 100644 --- a/builtin/cat-file.c +++ b/builtin/cat-file.c @@ -496,7 +496,10 @@ static void batch_object_write(const char *obj_name, &data->oid, &data->info, OBJECT_INFO_LOOKUP_REPLACE); if (ret < 0) { - report_object_status(opt, obj_name, &data->oid, "missing"); + if (data->mode == S_IFGITLINK) + report_object_status(opt, oid_to_hex(&data->oid), &data->oid, "submodule"); + else + report_object_status(opt, obj_name, &data->oid, "missing"); return; } diff --git a/t/t1006-cat-file.sh b/t/t1006-cat-file.sh index 97052b3f31..f123ef1e36 100755 --- a/t/t1006-cat-file.sh +++ b/t/t1006-cat-file.sh @@ -1220,6 +1220,31 @@ test_expect_success 'cat-file --batch-check respects replace objects' ' test_cmp expect actual ' +test_expect_success 'batch-check with a submodule' ' + # FIXME: this call to mktree is incompatible with compatObjectFormat + # because the submodule OID cannot be mapped to the compat hash algo. + test_unconfig extensions.compatobjectformat && + printf "160000 commit $(test_oid deadbeef)\tsub\n" >tree-with-sub && + tree=$(git mktree actual <<-EOF && + $tree:sub + EOF + printf "$(test_oid deadbeef) submodule\n" >expect && + test_cmp expect actual +' + +test_expect_success 'batch-check with a submodule, object exists' ' + printf "160000 commit $commit_oid\tsub\n" >tree-with-sub && + tree=$(git mktree actual <<-EOF && + $tree:sub + EOF + printf "$commit_oid commit $commit_size\n" >expect && + test_cmp expect actual +' + # Pull the entry for object with oid "$1" out of the output of # "cat-file --batch", including its object content (which requires # parsing and reading a set amount of bytes, hence perl).