fetch-pack: print and use dangling .gitmodules
Teach index-pack to print dangling .gitmodules links after its "keep" or "pack" line instead of declaring an error, and teach fetch-pack to check such lines printed. This allows the tree side of the .gitmodules link to be in one packfile and the blob side to be in another without failing the fsck check, because it is now fetch-pack which checks such objects after all packfiles have been downloaded and indexed (and not index-pack on an individual packfile, as it is before this commit). Signed-off-by: Jonathan Tan <jonathantanmy@google.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Jonathan Tan committed
Feb 22, 2021 at 11:20 UTC
5476e1efded571e374cd97c7d69f17962ba1c44f
9 files changed
+165
-22
Documentation/git-index-pack.txt
+6
-1
@@ -78,7 +78,12 @@ OPTIONS
78
Die if the pack contains broken links. For internal use only.
79
80
--fsck-objects::
81
- Die if the pack contains broken objects. For internal use only.
81
+ For internal use only.
82
++
83
+Die if the pack contains broken objects. If the pack contains a tree
84
+pointing to a .gitmodules blob that does not exist, prints the hash of
85
+that blob (for the caller to check) after the hash that goes into the
86
+name of the pack/idx file (see "Notes").
87
88
--threads=<n>::
89
Specifies the number of threads to spawn when resolving
builtin/index-pack.c
+23
-2
@@ -1693,6 +1693,22 @@ static void show_pack_info(int stat_only)
1693
}
1694
}
1695
1696
+static int print_dangling_gitmodules(struct fsck_options *o,
1697
+ const struct object_id *oid,
1698
+ enum object_type object_type,
1699
+ int msg_type, const char *message)
1700
+{
1701
+ /*
1702
+ * NEEDSWORK: Plumb the MSG_ID (from fsck.c) here and use it
1703
+ * instead of relying on this string check.
1704
+ */
1705
+ if (starts_with(message, "gitmodulesMissing")) {
1706
+ printf("%s\n", oid_to_hex(oid));
1707
+ return 0;
1708
+ }
1709
+ return fsck_error_function(o, oid, object_type, msg_type, message);
1710
+}
1711
+
1712
int cmd_index_pack(int argc, const char **argv, const char *prefix)
1713
{
1714
int i, fix_thin_pack = 0, verify = 0, stat_only = 0;
@@ -1888,8 +1904,13 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)
1904
else
1905
close(input_fd);
1906
1891
- if (do_fsck_object && fsck_finish(&fsck_options))
1892
- die(_("fsck error in pack objects"));
1907
+ if (do_fsck_object) {
1908
+ struct fsck_options fo = fsck_options;
1909
+
1910
+ fo.error_func = print_dangling_gitmodules;
1911
+ if (fsck_finish(&fo))
1912
+ die(_("fsck error in pack objects"));
1913
+ }
1914
1915
free(objects);
1916
strbuf_release(&index_name_buf);
builtin/receive-pack.c
+1
-1
@@ -2275,7 +2275,7 @@ static const char *unpack(int err_fd, struct shallow_info *si)
2275
status = start_command(&child);
2276
if (status)
2277
return "index-pack fork failed";
2278
- pack_lockfile = index_pack_lockfile(child.out);
2278
+ pack_lockfile = index_pack_lockfile(child.out, NULL);
2279
close(child.out);
2280
status = finish_command(&child);
2281
if (status)
fetch-pack.c
+66
-12
@@ -796,6 +796,26 @@ static void write_promisor_file(const char *keep_name,
796
strbuf_release(&promisor_name);
797
}
798
799
+static void parse_gitmodules_oids(int fd, struct oidset *gitmodules_oids)
800
+{
801
+ int len = the_hash_algo->hexsz + 1; /* hash + NL */
802
+
803
+ do {
804
+ char hex_hash[GIT_MAX_HEXSZ + 1];
805
+ int read_len = read_in_full(fd, hex_hash, len);
806
+ struct object_id oid;
807
+ const char *end;
808
+
809
+ if (!read_len)
810
+ return;
811
+ if (read_len != len)
812
+ die("invalid length read %d", read_len);
813
+ if (parse_oid_hex(hex_hash, &oid, &end) || *end != '\n')
814
+ die("invalid hash");
815
+ oidset_insert(gitmodules_oids, &oid);
816
+ } while (1);
817
+}
818
+
819
/*
820
* If packfile URIs were provided, pass a non-NULL pointer to index_pack_args.
821
* The strings to pass as the --index-pack-arg arguments to http-fetch will be
@@ -804,7 +824,8 @@ static void write_promisor_file(const char *keep_name,
824
static int get_pack(struct fetch_pack_args *args,
825
int xd[2], struct string_list *pack_lockfiles,
826
struct strvec *index_pack_args,
807
- struct ref **sought, int nr_sought)
827
+ struct ref **sought, int nr_sought,
828
+ struct oidset *gitmodules_oids)
829
{
830
struct async demux;
831
int do_keep = args->keep_pack;
@@ -812,6 +833,7 @@ static int get_pack(struct fetch_pack_args *args,
833
struct pack_header header;
834
int pass_header = 0;
835
struct child_process cmd = CHILD_PROCESS_INIT;
836
+ int fsck_objects = 0;
837
int ret;
838
839
memset(&demux, 0, sizeof(demux));
@@ -846,8 +868,15 @@ static int get_pack(struct fetch_pack_args *args,
868
strvec_push(&cmd.args, alternate_shallow_file);
869
}
870
849
- if (do_keep || args->from_promisor || index_pack_args) {
850
- if (pack_lockfiles)
871
+ if (fetch_fsck_objects >= 0
872
+ ? fetch_fsck_objects
873
+ : transfer_fsck_objects >= 0
874
+ ? transfer_fsck_objects
875
+ : 0)
876
+ fsck_objects = 1;
877
+
878
+ if (do_keep || args->from_promisor || index_pack_args || fsck_objects) {
879
+ if (pack_lockfiles || fsck_objects)
880
cmd.out = -1;
881
cmd_name = "index-pack";
882
strvec_push(&cmd.args, cmd_name);
@@ -897,11 +926,7 @@ static int get_pack(struct fetch_pack_args *args,
926
strvec_pushf(&cmd.args, "--pack_header=%"PRIu32",%"PRIu32,
927
ntohl(header.hdr_version),
928
ntohl(header.hdr_entries));
900
- if (fetch_fsck_objects >= 0
901
- ? fetch_fsck_objects
902
- : transfer_fsck_objects >= 0
903
- ? transfer_fsck_objects
904
- : 0) {
929
+ if (fsck_objects) {
930
if (args->from_promisor || index_pack_args)
931
/*
932
* We cannot use --strict in index-pack because it
@@ -925,10 +950,15 @@ static int get_pack(struct fetch_pack_args *args,
950
cmd.git_cmd = 1;
951
if (start_command(&cmd))
952
die(_("fetch-pack: unable to fork off %s"), cmd_name);
928
- if (do_keep && pack_lockfiles) {
929
- char *pack_lockfile = index_pack_lockfile(cmd.out);
953
+ if (do_keep && (pack_lockfiles || fsck_objects)) {
954
+ int is_well_formed;
955
+ char *pack_lockfile = index_pack_lockfile(cmd.out, &is_well_formed);
956
+
957
+ if (!is_well_formed)
958
+ die(_("fetch-pack: invalid index-pack output"));
959
if (pack_lockfile)
960
string_list_append_nodup(pack_lockfiles, pack_lockfile);
961
+ parse_gitmodules_oids(cmd.out, gitmodules_oids);
962
close(cmd.out);
963
}
964
@@ -963,6 +993,22 @@ static int cmp_ref_by_name(const void *a_, const void *b_)
993
return strcmp(a->name, b->name);
994
}
995
996
+static void fsck_gitmodules_oids(struct oidset *gitmodules_oids)
997
+{
998
+ struct oidset_iter iter;
999
+ const struct object_id *oid;
1000
+ struct fsck_options fo = FSCK_OPTIONS_STRICT;
1001
+
1002
+ if (!oidset_size(gitmodules_oids))
1003
+ return;
1004
+
1005
+ oidset_iter_init(gitmodules_oids, &iter);
1006
+ while ((oid = oidset_iter_next(&iter)))
1007
+ register_found_gitmodules(oid);
1008
+ if (fsck_finish(&fo))
1009
+ die("fsck failed");
1010
+}
1011
+
1012
static struct ref *do_fetch_pack(struct fetch_pack_args *args,
1013
int fd[2],
1014
const struct ref *orig_ref,
@@ -977,6 +1023,7 @@ static struct ref *do_fetch_pack(struct fetch_pack_args *args,
1023
int agent_len;
1024
struct fetch_negotiator negotiator_alloc;
1025
struct fetch_negotiator *negotiator;
1026
+ struct oidset gitmodules_oids = OIDSET_INIT;
1027
1028
negotiator = &negotiator_alloc;
1029
fetch_negotiator_init(r, negotiator);
@@ -1092,8 +1139,10 @@ static struct ref *do_fetch_pack(struct fetch_pack_args *args,
1139
alternate_shallow_file = setup_temporary_shallow(si->shallow);
1140
else
1141
alternate_shallow_file = NULL;
1095
- if (get_pack(args, fd, pack_lockfiles, NULL, sought, nr_sought))
1142
+ if (get_pack(args, fd, pack_lockfiles, NULL, sought, nr_sought,
1143
+ &gitmodules_oids))
1144
die(_("git fetch-pack: fetch failed."));
1145
+ fsck_gitmodules_oids(&gitmodules_oids);
1146
1147
all_done:
1148
if (negotiator)
@@ -1544,6 +1593,7 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
1593
struct string_list packfile_uris = STRING_LIST_INIT_DUP;
1594
int i;
1595
struct strvec index_pack_args = STRVEC_INIT;
1596
+ struct oidset gitmodules_oids = OIDSET_INIT;
1597
1598
negotiator = &negotiator_alloc;
1599
fetch_negotiator_init(r, negotiator);
@@ -1634,7 +1684,7 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
1684
process_section_header(&reader, "packfile", 0);
1685
if (get_pack(args, fd, pack_lockfiles,
1686
packfile_uris.nr ? &index_pack_args : NULL,
1637
- sought, nr_sought))
1687
+ sought, nr_sought, &gitmodules_oids))
1688
die(_("git fetch-pack: fetch failed."));
1689
do_check_stateless_delimiter(args, &reader);
1690
@@ -1677,6 +1727,8 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
1727
1728
packname[the_hash_algo->hexsz] = '\0';
1729
1730
+ parse_gitmodules_oids(cmd.out, &gitmodules_oids);
1731
+
1732
close(cmd.out);
1733
1734
if (finish_command(&cmd))
@@ -1696,6 +1748,8 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
1748
string_list_clear(&packfile_uris, 0);
1749
strvec_clear(&index_pack_args);
1750
1751
+ fsck_gitmodules_oids(&gitmodules_oids);
1752
+
1753
if (negotiator)
1754
negotiator->release(negotiator);
1755
fsck.c
+5
@@ -1243,6 +1243,11 @@ int fsck_error_function(struct fsck_options *o,
1243
return 1;
1244
}
1245
1246
+void register_found_gitmodules(const struct object_id *oid)
1247
+{
1248
+ oidset_insert(&gitmodules_found, oid);
1249
+}
1250
+
1251
int fsck_finish(struct fsck_options *options)
1252
{
1253
int ret = 0;
fsck.h
+2
@@ -62,6 +62,8 @@ int fsck_walk(struct object *obj, void *data, struct fsck_options *options);
62
int fsck_object(struct object *obj, void *data, unsigned long size,
63
struct fsck_options *options);
64
65
+void register_found_gitmodules(const struct object_id *oid);
66
+
67
/*
68
* Some fsck checks are context-dependent, and may end up queued; run this
69
* after completing all fsck_object() calls in order to resolve any remaining
pack-write.c
+7
-1
@@ -272,7 +272,7 @@ void fixup_pack_header_footer(int pack_fd,
272
fsync_or_die(pack_fd, pack_name);
273
}
274
275
-char *index_pack_lockfile(int ip_out)
275
+char *index_pack_lockfile(int ip_out, int *is_well_formed)
276
{
277
char packname[GIT_MAX_HEXSZ + 6];
278
const int len = the_hash_algo->hexsz + 6;
@@ -286,11 +286,17 @@ char *index_pack_lockfile(int ip_out)
286
*/
287
if (read_in_full(ip_out, packname, len) == len && packname[len-1] == '\n') {
288
const char *name;
289
+
290
+ if (is_well_formed)
291
+ *is_well_formed = 1;
292
packname[len-1] = 0;
293
if (skip_prefix(packname, "keep\t", &name))
294
return xstrfmt("%s/pack/pack-%s.keep",
295
get_object_directory(), name);
296
+ return NULL;
297
}
298
+ if (is_well_formed)
299
+ *is_well_formed = 0;
300
return NULL;
301
}
302
pack.h
+1
-1
@@ -85,7 +85,7 @@ int verify_pack_index(struct packed_git *);
85
int verify_pack(struct repository *, struct packed_git *, verify_fn fn, struct progress *, uint32_t);
86
off_t write_pack_header(struct hashfile *f, uint32_t);
87
void fixup_pack_header_footer(int, unsigned char *, const char *, uint32_t, unsigned char *, off_t);
88
-char *index_pack_lockfile(int fd);
88
+char *index_pack_lockfile(int fd, int *is_well_formed);
89
90
/*
91
* The "hdr" output buffer should be at least this big, which will handle sizes
t/t5702-protocol-v2.sh
+54
-4
@@ -847,8 +847,9 @@ test_expect_success 'part of packfile response provided as URI' '
847
test -f hfound &&
848
test -f h2found &&
849
850
- # Ensure that there are exactly 6 files (3 .pack and 3 .idx).
851
- ls http_child/.git/objects/pack/* >filelist &&
850
+ # Ensure that there are exactly 3 packfiles with associated .idx
851
+ ls http_child/.git/objects/pack/*.pack \
852
+ http_child/.git/objects/pack/*.idx >filelist &&
853
test_line_count = 6 filelist
854
'
855
@@ -901,8 +902,9 @@ test_expect_success 'packfile-uri with transfer.fsckobjects' '
902
-c fetch.uriprotocols=http,https \
903
clone "$HTTPD_URL/smart/http_parent" http_child &&
904
904
- # Ensure that there are exactly 4 files (2 .pack and 2 .idx).
905
- ls http_child/.git/objects/pack/* >filelist &&
905
+ # Ensure that there are exactly 2 packfiles with associated .idx
906
+ ls http_child/.git/objects/pack/*.pack \
907
+ http_child/.git/objects/pack/*.idx >filelist &&
908
test_line_count = 4 filelist
909
'
910
@@ -936,6 +938,54 @@ test_expect_success 'packfile-uri with transfer.fsckobjects fails on bad object'
938
test_i18ngrep "invalid author/committer line - missing email" error
939
'
940
941
+test_expect_success 'packfile-uri with transfer.fsckobjects succeeds when .gitmodules is separate from tree' '
942
+ P="$HTTPD_DOCUMENT_ROOT_PATH/http_parent" &&
943
+ rm -rf "$P" http_child &&
944
+
945
+ git init "$P" &&
946
+ git -C "$P" config "uploadpack.allowsidebandall" "true" &&
947
+
948
+ echo "[submodule libfoo]" >"$P/.gitmodules" &&
949
+ echo "path = include/foo" >>"$P/.gitmodules" &&
950
+ echo "url = git://example.com/git/lib.git" >>"$P/.gitmodules" &&
951
+ git -C "$P" add .gitmodules &&
952
+ git -C "$P" commit -m x &&
953
+
954
+ configure_exclusion "$P" .gitmodules >h &&
955
+
956
+ sane_unset GIT_TEST_SIDEBAND_ALL &&
957
+ git -c protocol.version=2 -c transfer.fsckobjects=1 \
958
+ -c fetch.uriprotocols=http,https \
959
+ clone "$HTTPD_URL/smart/http_parent" http_child &&
960
+
961
+ # Ensure that there are exactly 2 packfiles with associated .idx
962
+ ls http_child/.git/objects/pack/*.pack \
963
+ http_child/.git/objects/pack/*.idx >filelist &&
964
+ test_line_count = 4 filelist
965
+'
966
+
967
+test_expect_success 'packfile-uri with transfer.fsckobjects fails when .gitmodules separate from tree is invalid' '
968
+ P="$HTTPD_DOCUMENT_ROOT_PATH/http_parent" &&
969
+ rm -rf "$P" http_child err &&
970
+
971
+ git init "$P" &&
972
+ git -C "$P" config "uploadpack.allowsidebandall" "true" &&
973
+
974
+ echo "[submodule \"..\"]" >"$P/.gitmodules" &&
975
+ echo "path = include/foo" >>"$P/.gitmodules" &&
976
+ echo "url = git://example.com/git/lib.git" >>"$P/.gitmodules" &&
977
+ git -C "$P" add .gitmodules &&
978
+ git -C "$P" commit -m x &&
979
+
980
+ configure_exclusion "$P" .gitmodules >h &&
981
+
982
+ sane_unset GIT_TEST_SIDEBAND_ALL &&
983
+ test_must_fail git -c protocol.version=2 -c transfer.fsckobjects=1 \
984
+ -c fetch.uriprotocols=http,https \
985
+ clone "$HTTPD_URL/smart/http_parent" http_child 2>err &&
986
+ test_i18ngrep "disallowed submodule name" err
987
+'
988
+
989
# DO NOT add non-httpd-specific tests here, because the last part of this
990
# test script is only executed when httpd is available and enabled.
991