cat-file: make --allow-unknown-type a noop

The cat-file command has some minor support for handling objects with "unknown" types. I.e., strings that are not "blob", "commit", "tree", or "tag". In theory this could be used for debugging or experimenting with extensions to Git. But in practice this support is not very useful: 1. You can get the type and size of such objects, but nothing else. Not even the contents! 2. Only loose objects are supported, since packfiles use numeric ids for the types, rather than strings. 3. Likewise you cannot ever transfer objects between repositories, because they cannot be represented in the packfiles used for the on-the-wire protocol. The support for these unknown types complicates the object-parsing code, and has led to bugs such as b748ddb7a4 (unpack_loose_header(): fix infinite loop on broken zlib input, 2025-02-25). So let's drop it. The first step is to remove the user-facing parts, which are accessible only via cat-file. This is technically backwards-incompatible, but given the limitations listed above, these objects couldn't possibly be useful in any workflow. However, we can't just rip out the option entirely. That would hurt a caller who ran: git cat-file -t --allow-unknown-object <oid> and fed it normal, well-formed objects. There --allow-unknown-type was doing nothing, but we wouldn't want to start bailing with an error. So to protect any such callers, we'll retain --allow-unknown-type as a noop. The code change is fairly small (but we'll able to clean up more code in follow-on patches). The test updates drop any use of the option. We still retain tests that feed the broken objects to cat-file without --allow-unknown-type, as we should continue to confirm that those objects are rejected. Note that in one spot we can drop a layer of loop, re-indenting the body; viewing the diff with "-w" helps there. Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Jeff King committed May 16, 2025 at 00:49 UTC f227fc7d43d9607edb286eaab0f7714a2f1e4659
3 files changed +56 -179
Documentation/git-cat-file.adoc
+1 -5
@@ -9,8 +9,7 @@ SYNOPSIS
9 --------
10 [verse]
11 'git cat-file' <type> <object>
12 -'git cat-file' (-e | -p) <object>
13 -'git cat-file' (-t | -s) [--allow-unknown-type] <object>
12 +'git cat-file' (-e | -p | -t | -s) <object>
13 'git cat-file' (--textconv | --filters)
14 [<rev>:<path|tree-ish> | --path=<path|tree-ish> <rev>]
15 'git cat-file' (--batch | --batch-check | --batch-command) [--batch-all-objects]
@@ -202,9 +201,6 @@ flush::
201 only once, even if it is stored multiple times in the
202 repository.
203
205 ---allow-unknown-type::
206 - Allow `-s` or `-t` to query broken/corrupt objects of unknown type.
207 -
204 --follow-symlinks::
205 With `--batch` or `--batch-check`, follow symlinks inside the
206 repository when requesting objects with extended SHA-1
builtin/cat-file.c
+5 -13
@@ -100,8 +100,7 @@ static int stream_blob(const struct object_id *oid)
100 return 0;
101 }
102
103 -static int cat_one_file(int opt, const char *exp_type, const char *obj_name,
104 - int unknown_type)
103 +static int cat_one_file(int opt, const char *exp_type, const char *obj_name)
104 {
105 int ret;
106 struct object_id oid;
@@ -121,9 +120,6 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name,
120 if (!path && opt_cw)
121 get_oid_flags |= GET_OID_REQUIRE_PATH;
122
124 - if (unknown_type)
125 - flags |= OBJECT_INFO_ALLOW_UNKNOWN_TYPE;
126 -
123 if (get_oid_with_context(the_repository, obj_name, get_oid_flags, &oid,
124 &obj_context))
125 die("Not a valid object name %s", obj_name);
@@ -1038,8 +1034,7 @@ int cmd_cat_file(int argc,
1034
1035 const char * const builtin_catfile_usage[] = {
1036 N_("git cat-file <type> <object>"),
1041 - N_("git cat-file (-e | -p) <object>"),
1042 - N_("git cat-file (-t | -s) [--allow-unknown-type] <object>"),
1037 + N_("git cat-file (-e | -p | -t | -s) <object>"),
1038 N_("git cat-file (--textconv | --filters)\n"
1039 " [<rev>:<path|tree-ish> | --path=<path|tree-ish> <rev>]"),
1040 N_("git cat-file (--batch | --batch-check | --batch-command) [--batch-all-objects]\n"
@@ -1057,8 +1052,8 @@ int cmd_cat_file(int argc,
1052 OPT_GROUP(N_("Emit [broken] object attributes")),
1053 OPT_CMDMODE('t', NULL, &opt, N_("show object type (one of 'blob', 'tree', 'commit', 'tag', ...)"), 't'),
1054 OPT_CMDMODE('s', NULL, &opt, N_("show object size"), 's'),
1060 - OPT_BOOL(0, "allow-unknown-type", &unknown_type,
1061 - N_("allow -s and -t to work with broken/corrupt objects")),
1055 + OPT_HIDDEN_BOOL(0, "allow-unknown-type", &unknown_type,
1056 + N_("historical option -- no-op")),
1057 OPT_BOOL(0, "use-mailmap", &use_mailmap, N_("use mail map file")),
1058 OPT_ALIAS(0, "mailmap", "use-mailmap"),
1059 /* Batch mode */
@@ -1209,10 +1204,7 @@ int cmd_cat_file(int argc,
1204 obj_name = argv[1];
1205 }
1206
1212 - if (unknown_type && opt != 't' && opt != 's')
1213 - die("git cat-file --allow-unknown-type: use with -s or -t");
1214 -
1215 - ret = cat_one_file(opt, exp_type, obj_name, unknown_type);
1207 + ret = cat_one_file(opt, exp_type, obj_name);
1208
1209 out:
1210 list_objects_filter_release(&batch.objects_filter);
t/t1006-cat-file.sh
+50 -161
@@ -136,18 +136,6 @@ $content"
136 test_cmp expect actual
137 '
138
139 - test_expect_success "Type of $type is correct using --allow-unknown-type" '
140 - echo $type >expect &&
141 - git cat-file -t --allow-unknown-type $oid >actual &&
142 - test_cmp expect actual
143 - '
144 -
145 - test_expect_success "Size of $type is correct using --allow-unknown-type" '
146 - echo $size >expect &&
147 - git cat-file -s --allow-unknown-type $oid >actual &&
148 - test_cmp expect actual
149 - '
150 -
139 test -z "$content" ||
140 test_expect_success "Content of $type is correct" '
141 echo_without_newline "$content" >expect &&
@@ -677,95 +665,67 @@ test_expect_success 'setup bogus data' '
665 bogus_long_oid=$(echo_without_newline "$bogus_long_content" | git hash-object -t $bogus_long_type --literally -w --stdin)
666 '
667
680 -for arg1 in '' --allow-unknown-type
668 +for arg1 in -s -t -p
669 do
682 - for arg2 in -s -t -p
683 - do
684 - if test "$arg1" = "--allow-unknown-type" && test "$arg2" = "-p"
685 - then
686 - continue
687 - fi
670 + test_expect_success "cat-file $arg1 error on bogus short OID" '
671 + cat >expect <<-\EOF &&
672 + fatal: invalid object type
673 + EOF
674
675 + test_must_fail git cat-file $arg1 $bogus_short_oid >out 2>actual &&
676 + test_must_be_empty out &&
677 + test_cmp expect actual
678 + '
679
690 - test_expect_success "cat-file $arg1 $arg2 error on bogus short OID" '
691 - cat >expect <<-\EOF &&
692 - fatal: invalid object type
680 + test_expect_success "cat-file $arg1 error on bogus full OID" '
681 + if test "$arg1" = "-p"
682 + then
683 + cat >expect <<-EOF
684 + error: header for $bogus_long_oid too long, exceeds 32 bytes
685 + fatal: Not a valid object name $bogus_long_oid
686 + EOF
687 + else
688 + cat >expect <<-EOF
689 + error: header for $bogus_long_oid too long, exceeds 32 bytes
690 + fatal: git cat-file: could not get object info
691 EOF
692 + fi &&
693
695 - if test "$arg1" = "--allow-unknown-type"
696 - then
697 - git cat-file $arg1 $arg2 $bogus_short_oid
698 - else
699 - test_must_fail git cat-file $arg1 $arg2 $bogus_short_oid >out 2>actual &&
700 - test_must_be_empty out &&
701 - test_cmp expect actual
702 - fi
703 - '
694 + test_must_fail git cat-file $arg1 $bogus_long_oid >out 2>actual &&
695 + test_must_be_empty out &&
696 + test_cmp expect actual
697 + '
698
705 - test_expect_success "cat-file $arg1 $arg2 error on bogus full OID" '
706 - if test "$arg2" = "-p"
707 - then
708 - cat >expect <<-EOF
709 - error: header for $bogus_long_oid too long, exceeds 32 bytes
710 - fatal: Not a valid object name $bogus_long_oid
711 - EOF
712 - else
713 - cat >expect <<-EOF
714 - error: header for $bogus_long_oid too long, exceeds 32 bytes
715 - fatal: git cat-file: could not get object info
716 - EOF
717 - fi &&
718 -
719 - if test "$arg1" = "--allow-unknown-type"
720 - then
721 - git cat-file $arg1 $arg2 $bogus_short_oid
722 - else
723 - test_must_fail git cat-file $arg1 $arg2 $bogus_long_oid >out 2>actual &&
724 - test_must_be_empty out &&
725 - test_cmp expect actual
726 - fi
727 - '
699 + test_expect_success "cat-file $arg1 error on missing short OID" '
700 + cat >expect.err <<-EOF &&
701 + fatal: Not a valid object name $(test_oid deadbeef_short)
702 + EOF
703 + test_must_fail git cat-file $arg1 $(test_oid deadbeef_short) >out 2>err.actual &&
704 + test_must_be_empty out &&
705 + test_cmp expect.err err.actual
706 + '
707
729 - test_expect_success "cat-file $arg1 $arg2 error on missing short OID" '
730 - cat >expect.err <<-EOF &&
731 - fatal: Not a valid object name $(test_oid deadbeef_short)
708 + test_expect_success "cat-file $arg1 error on missing full OID" '
709 + if test "$arg1" = "-p"
710 + then
711 + cat >expect.err <<-EOF
712 + fatal: Not a valid object name $(test_oid deadbeef)
713 EOF
733 - test_must_fail git cat-file $arg1 $arg2 $(test_oid deadbeef_short) >out 2>err.actual &&
734 - test_must_be_empty out &&
735 - test_cmp expect.err err.actual
736 - '
737 -
738 - test_expect_success "cat-file $arg1 $arg2 error on missing full OID" '
739 - if test "$arg2" = "-p"
740 - then
741 - cat >expect.err <<-EOF
742 - fatal: Not a valid object name $(test_oid deadbeef)
743 - EOF
744 - else
745 - cat >expect.err <<-\EOF
746 - fatal: git cat-file: could not get object info
747 - EOF
748 - fi &&
749 - test_must_fail git cat-file $arg1 $arg2 $(test_oid deadbeef) >out 2>err.actual &&
750 - test_must_be_empty out &&
751 - test_cmp expect.err err.actual
752 - '
753 - done
714 + else
715 + cat >expect.err <<-\EOF
716 + fatal: git cat-file: could not get object info
717 + EOF
718 + fi &&
719 + test_must_fail git cat-file $arg1 $(test_oid deadbeef) >out 2>err.actual &&
720 + test_must_be_empty out &&
721 + test_cmp expect.err err.actual
722 + '
723 done
724
756 -test_expect_success '-e is OK with a broken object without --allow-unknown-type' '
725 +test_expect_success '-e is OK with a broken object' '
726 git cat-file -e $bogus_short_oid
727 '
728
760 -test_expect_success '-e can not be combined with --allow-unknown-type' '
761 - test_expect_code 128 git cat-file -e --allow-unknown-type $bogus_short_oid
762 -'
763 -
764 -test_expect_success '-p cannot print a broken object even with --allow-unknown-type' '
765 - test_must_fail git cat-file -p $bogus_short_oid &&
766 - test_expect_code 128 git cat-file -p --allow-unknown-type $bogus_short_oid
767 -'
768 -
729 test_expect_success '<type> <hash> does not work with objects of broken types' '
730 cat >err.expect <<-\EOF &&
731 fatal: invalid object type "bogus"
@@ -788,60 +748,8 @@ test_expect_success 'broken types combined with --batch and --batch-check' '
748 test_cmp err.expect err.actual
749 '
750
791 -test_expect_success 'the --batch and --batch-check options do not combine with --allow-unknown-type' '
792 - test_expect_code 128 git cat-file --batch --allow-unknown-type <bogus-oid &&
793 - test_expect_code 128 git cat-file --batch-check --allow-unknown-type <bogus-oid
794 -'
795 -
796 -test_expect_success 'the --allow-unknown-type option does not consider replacement refs' '
797 - cat >expect <<-EOF &&
798 - $bogus_short_type
799 - EOF
800 - git cat-file -t --allow-unknown-type $bogus_short_oid >actual &&
801 - test_cmp expect actual &&
802 -
803 - # Create it manually, as "git replace" will die on bogus
804 - # types.
805 - head=$(git rev-parse --verify HEAD) &&
806 - test_when_finished "test-tool ref-store main delete-refs 0 msg refs/replace/$bogus_short_oid" &&
807 - test-tool ref-store main update-ref msg "refs/replace/$bogus_short_oid" $head $ZERO_OID REF_SKIP_OID_VERIFICATION &&
808 -
809 - cat >expect <<-EOF &&
810 - commit
811 - EOF
812 - git cat-file -t --allow-unknown-type $bogus_short_oid >actual &&
813 - test_cmp expect actual
814 -'
815 -
816 -test_expect_success "Type of broken object is correct" '
817 - echo $bogus_short_type >expect &&
818 - git cat-file -t --allow-unknown-type $bogus_short_oid >actual &&
819 - test_cmp expect actual
820 -'
821 -
822 -test_expect_success "Size of broken object is correct" '
823 - echo $bogus_short_size >expect &&
824 - git cat-file -s --allow-unknown-type $bogus_short_oid >actual &&
825 - test_cmp expect actual
826 -'
827 -
828 -test_expect_success 'clean up broken object' '
829 - rm .git/objects/$(test_oid_to_path $bogus_short_oid)
830 -'
831 -
832 -test_expect_success "Type of broken object is correct when type is large" '
833 - echo $bogus_long_type >expect &&
834 - git cat-file -t --allow-unknown-type $bogus_long_oid >actual &&
835 - test_cmp expect actual
836 -'
837 -
838 -test_expect_success "Size of large broken object is correct when type is large" '
839 - echo $bogus_long_size >expect &&
840 - git cat-file -s --allow-unknown-type $bogus_long_oid >actual &&
841 - test_cmp expect actual
842 -'
843 -
844 -test_expect_success 'clean up broken object' '
751 +test_expect_success 'clean up broken objects' '
752 + rm .git/objects/$(test_oid_to_path $bogus_short_oid) &&
753 rm .git/objects/$(test_oid_to_path $bogus_long_oid)
754 '
755
@@ -903,25 +811,6 @@ test_expect_success 'cat-file -t and -s on corrupt loose object' '
811 )
812 '
813
906 -test_expect_success 'truncated object with --allow-unknown-type' - <<\EOT
907 - objtype='a really long type name that exceeds the 32-byte limit' &&
908 - blob=$(git hash-object -w --literally -t "$objtype" /dev/null) &&
909 - objpath=.git/objects/$(test_oid_to_path "$blob") &&
910 -
911 - # We want to truncate the object far enough in that we don't hit the
912 - # end while inflating the first 32 bytes (since we want to have to dig
913 - # for the trailing NUL of the header). But we don't want to go too far,
914 - # since our header isn't very big. And of course we are counting
915 - # deflated zlib bytes in the on-disk file, so it's a bit of a guess.
916 - # Empirically 50 seems to work.
917 - mv "$objpath" obj.bak &&
918 - test_when_finished 'mv obj.bak "$objpath"' &&
919 - test_copy_bytes 50 <obj.bak >"$objpath" &&
920 -
921 - test_must_fail git cat-file --allow-unknown-type -t $blob 2>err &&
922 - test_grep "unable to unpack $blob header" err
923 -EOT
924 -
814 test_expect_success 'object reading handles zlib dictionary' - <<\EOT
815 echo 'content that will be recompressed' >file &&
816 blob=$(git hash-object -w file) &&