fetch-pack: prepare updated shallow file before fetching the pack

index-pack --strict looks up and follows parent commits. If shallow information is not ready by the time index-pack is run, index-pack may be led to non-existent objects. Make fetch-pack save shallow file to disk before invoking index-pack. git learns new global option --shallow-file to pass on the alternate shallow file path. Undocumented (and not even support --shallow-file= syntax) because it's unlikely to be used again elsewhere. Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Nguyễn Thái Ngọc Duy committed May 26, 2013 at 08:16 UTC 6035d6aad8ca11954c0d7821f6f3e7c047039c8f
5 files changed +93 -38
commit.h
+2
@@ -176,6 +176,8 @@ extern int for_each_commit_graft(each_commit_graft_fn, void *);
176 extern int is_repository_shallow(void);
177 extern struct commit_list *get_shallow_commits(struct object_array *heads,
178 int depth, int shallow_flag, int not_shallow_flag);
179 +extern void check_shallow_file_for_update(void);
180 +extern void set_alternate_shallow_file(const char *path);
181
182 int is_descendant_of(struct commit *, struct commit_list *);
183 int in_merge_bases(struct commit *, struct commit *);
fetch-pack.c
+37 -36
@@ -20,6 +20,8 @@ static int no_done;
20 static int fetch_fsck_objects = -1;
21 static int transfer_fsck_objects = -1;
22 static int agent_supported;
23 +static struct lock_file shallow_lock;
24 +static const char *alternate_shallow_file;
25
26 #define COMPLETE (1U << 0)
27 #define COMMON (1U << 1)
@@ -683,7 +685,7 @@ static int get_pack(struct fetch_pack_args *args,
685 int xd[2], char **pack_lockfile)
686 {
687 struct async demux;
686 - const char *argv[20];
688 + const char *argv[22];
689 char keep_arg[256];
690 char hdr_arg[256];
691 const char **av;
@@ -724,6 +726,11 @@ static int get_pack(struct fetch_pack_args *args,
726 do_keep = 1;
727 }
728
729 + if (alternate_shallow_file) {
730 + *av++ = "--shallow-file";
731 + *av++ = alternate_shallow_file;
732 + }
733 +
734 if (do_keep) {
735 if (pack_lockfile)
736 cmd.out = -1;
@@ -779,6 +786,27 @@ static int cmp_ref_by_name(const void *a_, const void *b_)
786 return strcmp(a->name, b->name);
787 }
788
789 +static void setup_alternate_shallow(void)
790 +{
791 + struct strbuf sb = STRBUF_INIT;
792 + int fd;
793 +
794 + check_shallow_file_for_update();
795 + fd = hold_lock_file_for_update(&shallow_lock, git_path("shallow"),
796 + LOCK_DIE_ON_ERROR);
797 + if (write_shallow_commits(&sb, 0)) {
798 + if (write_in_full(fd, sb.buf, sb.len) != sb.len)
799 + die_errno("failed to write to %s", shallow_lock.filename);
800 + alternate_shallow_file = shallow_lock.filename;
801 + } else
802 + /*
803 + * is_repository_shallow() sees empty string as "no
804 + * shallow file".
805 + */
806 + alternate_shallow_file = "";
807 + strbuf_release(&sb);
808 +}
809 +
810 static struct ref *do_fetch_pack(struct fetch_pack_args *args,
811 int fd[2],
812 const struct ref *orig_ref,
@@ -858,6 +886,8 @@ static struct ref *do_fetch_pack(struct fetch_pack_args *args,
886
887 if (args->stateless_rpc)
888 packet_flush(fd[1]);
889 + if (args->depth > 0)
890 + setup_alternate_shallow();
891 if (get_pack(args, fd, pack_lockfile))
892 die("git fetch-pack: fetch failed.");
893
@@ -936,15 +966,9 @@ struct ref *fetch_pack(struct fetch_pack_args *args,
966 struct ref **sought, int nr_sought,
967 char **pack_lockfile)
968 {
939 - struct stat st;
969 struct ref *ref_cpy;
970
971 fetch_pack_setup();
943 - if (args->depth > 0) {
944 - if (stat(git_path("shallow"), &st))
945 - st.st_mtime = 0;
946 - }
947 -
972 if (nr_sought)
973 nr_sought = remove_duplicates_in_refs(sought, nr_sought);
974
@@ -954,35 +978,12 @@ struct ref *fetch_pack(struct fetch_pack_args *args,
978 }
979 ref_cpy = do_fetch_pack(args, fd, ref, sought, nr_sought, pack_lockfile);
980
957 - if (args->depth > 0) {
958 - static struct lock_file lock;
959 - struct cache_time mtime;
960 - struct strbuf sb = STRBUF_INIT;
961 - char *shallow = git_path("shallow");
962 - int fd;
963 -
964 - mtime.sec = st.st_mtime;
965 - mtime.nsec = ST_MTIME_NSEC(st);
966 - if (stat(shallow, &st)) {
967 - if (mtime.sec)
968 - die("shallow file was removed during fetch");
969 - } else if (st.st_mtime != mtime.sec
970 -#ifdef USE_NSEC
971 - || ST_MTIME_NSEC(st) != mtime.nsec
972 -#endif
973 - )
974 - die("shallow file was changed during fetch");
975 -
976 - fd = hold_lock_file_for_update(&lock, shallow,
977 - LOCK_DIE_ON_ERROR);
978 - if (!write_shallow_commits(&sb, 0)
979 - || write_in_full(fd, sb.buf, sb.len) != sb.len) {
980 - unlink_or_warn(shallow);
981 - rollback_lock_file(&lock);
982 - } else {
983 - commit_lock_file(&lock);
984 - }
985 - strbuf_release(&sb);
981 + if (alternate_shallow_file) {
982 + if (*alternate_shallow_file == '\0') { /* --unshallow */
983 + unlink_or_warn(git_path("shallow"));
984 + rollback_lock_file(&shallow_lock);
985 + } else
986 + commit_lock_file(&shallow_lock);
987 }
988
989 reprepare_packed_git();
git.c
+7
@@ -4,6 +4,7 @@
4 #include "help.h"
5 #include "quote.h"
6 #include "run-command.h"
7 +#include "commit.h"
8
9 const char git_usage_string[] =
10 "git [--version] [--help] [-c name=value]\n"
@@ -146,6 +147,12 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)
147 setenv(GIT_LITERAL_PATHSPECS_ENVIRONMENT, "0", 1);
148 if (envchanged)
149 *envchanged = 1;
150 + } else if (!strcmp(cmd, "--shallow-file")) {
151 + (*argv)++;
152 + (*argc)--;
153 + set_alternate_shallow_file((*argv)[0]);
154 + if (envchanged)
155 + *envchanged = 1;
156 } else {
157 fprintf(stderr, "Unknown option: %s\n", cmd);
158 usage(git_usage_string);
shallow.c
+40 -2
@@ -3,6 +3,16 @@
3 #include "tag.h"
4
5 static int is_shallow = -1;
6 +static struct stat shallow_stat;
7 +static char *alternate_shallow_file;
8 +
9 +void set_alternate_shallow_file(const char *path)
10 +{
11 + if (is_shallow != -1)
12 + die("BUG: is_repository_shallow must not be called before set_alternate_shallow_file");
13 + free(alternate_shallow_file);
14 + alternate_shallow_file = path ? xstrdup(path) : NULL;
15 +}
16
17 int register_shallow(const unsigned char *sha1)
18 {
@@ -21,12 +31,21 @@ int is_repository_shallow(void)
31 {
32 FILE *fp;
33 char buf[1024];
34 + const char *path = alternate_shallow_file;
35
36 if (is_shallow >= 0)
37 return is_shallow;
38
28 - fp = fopen(git_path("shallow"), "r");
29 - if (!fp) {
39 + if (!path)
40 + path = git_path("shallow");
41 + /*
42 + * fetch-pack sets '--shallow-file ""' as an indicator that no
43 + * shallow file should be used. We could just open it and it
44 + * will likely fail. But let's do an explicit check instead.
45 + */
46 + if (!*path ||
47 + stat(path, &shallow_stat) ||
48 + (fp = fopen(path, "r")) == NULL) {
49 is_shallow = 0;
50 return is_shallow;
51 }
@@ -108,3 +127,22 @@ struct commit_list *get_shallow_commits(struct object_array *heads, int depth,
127
128 return result;
129 }
130 +
131 +void check_shallow_file_for_update(void)
132 +{
133 + struct stat st;
134 +
135 + if (!is_shallow)
136 + return;
137 + else if (is_shallow == -1)
138 + die("BUG: shallow must be initialized by now");
139 +
140 + if (stat(git_path("shallow"), &st))
141 + die("shallow file was removed during fetch");
142 + else if (st.st_mtime != shallow_stat.st_mtime
143 +#ifdef USE_NSEC
144 + || ST_MTIME_NSEC(st) != ST_MTIME_NSEC(shallow_stat)
145 +#endif
146 + )
147 + die("shallow file was changed during fetch");
148 +}
t/t5500-fetch-pack.sh
+7
@@ -135,6 +135,13 @@ test_expect_success 'clone shallow depth 1' '
135 test "`git --git-dir=shallow0/.git rev-list --count HEAD`" = 1
136 '
137
138 +test_expect_success 'clone shallow depth 1 with fsck' '
139 + git config --global fetch.fsckobjects true &&
140 + git clone --no-single-branch --depth 1 "file://$(pwd)/." shallow0fsck &&
141 + test "`git --git-dir=shallow0fsck/.git rev-list --count HEAD`" = 1 &&
142 + git config --global --unset fetch.fsckobjects
143 +'
144 +
145 test_expect_success 'clone shallow' '
146 git clone --no-single-branch --depth 2 "file://$(pwd)/." shallow
147 '