fetch: support fetching from a shallow repository

This patch just put together pieces from the 8 steps patch. We stop at step 7 and reject refs that require new shallow commits. Note that, by rejecting refs that require new shallow commits, we leave dangling objects in the repo, which become "object islands" by the next "git fetch" of the same source. If the first fetch our "ours" set is zero and we do practically nothing at step 7, "ours" is full at the next fetch and we may need to walk through commits for reachability test. Room for improvement. 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 Dec 5, 2013 at 20:02 UTC 4820a33baa963c4559736d7a1c4c35f8dcb37293
5 files changed +176 -5
builtin/fetch.c
+9
@@ -405,6 +405,8 @@ static int iterate_ref_map(void *cb_data, unsigned char sha1[20])
405 struct ref **rm = cb_data;
406 struct ref *ref = *rm;
407
408 + while (ref && ref->status == REF_STATUS_REJECT_SHALLOW)
409 + ref = ref->next;
410 if (!ref)
411 return -1; /* end of the list */
412 *rm = ref->next;
@@ -451,6 +453,13 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
453 struct ref *ref = NULL;
454 const char *merge_status_marker = "";
455
456 + if (rm->status == REF_STATUS_REJECT_SHALLOW) {
457 + if (want_status == FETCH_HEAD_MERGE)
458 + warning(_("reject %s because shallow roots are not allowed to be updated"),
459 + rm->peer_ref ? rm->peer_ref->name : rm->name);
460 + continue;
461 + }
462 +
463 commit = lookup_commit_reference_gently(rm->old_sha1, 1);
464 if (!commit)
465 rm->fetch_head_status = FETCH_HEAD_NOT_FOR_MERGE;
fetch-pack.c
+30 -2
@@ -854,7 +854,7 @@ static struct ref *do_fetch_pack(struct fetch_pack_args *args,
854 if (args->depth > 0)
855 setup_alternate_shallow(&shallow_lock, &alternate_shallow_file,
856 NULL);
857 - else if (args->cloning && si->shallow && si->shallow->nr)
857 + else if (si->nr_ours || si->nr_theirs)
858 alternate_shallow_file = setup_temporary_shallow(si->shallow);
859 else
860 alternate_shallow_file = NULL;
@@ -930,8 +930,11 @@ static int remove_duplicates_in_refs(struct ref **ref, int nr)
930 }
931
932 static void update_shallow(struct fetch_pack_args *args,
933 + struct ref **sought, int nr_sought,
934 struct shallow_info *si)
935 {
936 + struct sha1_array ref = SHA1_ARRAY_INIT;
937 + int *status;
938 int i;
939
940 if (args->depth > 0 && alternate_shallow_file) {
@@ -978,6 +981,31 @@ static void update_shallow(struct fetch_pack_args *args,
981 sha1_array_clear(&extra);
982 return;
983 }
984 +
985 + if (!si->nr_ours && !si->nr_theirs)
986 + return;
987 +
988 + remove_nonexistent_theirs_shallow(si);
989 + /* XXX remove_nonexistent_ours_in_pack() */
990 + if (!si->nr_ours && !si->nr_theirs)
991 + return;
992 + for (i = 0; i < nr_sought; i++)
993 + sha1_array_append(&ref, sought[i]->old_sha1);
994 + si->ref = &ref;
995 +
996 + /*
997 + * remote is also shallow, check what ref is safe to update
998 + * without updating .git/shallow
999 + */
1000 + status = xcalloc(nr_sought, sizeof(*status));
1001 + assign_shallow_commits_to_refs(si, NULL, status);
1002 + if (si->nr_ours || si->nr_theirs) {
1003 + for (i = 0; i < nr_sought; i++)
1004 + if (status[i])
1005 + sought[i]->status = REF_STATUS_REJECT_SHALLOW;
1006 + }
1007 + free(status);
1008 + sha1_array_clear(&ref);
1009 }
1010
1011 struct ref *fetch_pack(struct fetch_pack_args *args,
@@ -1003,7 +1031,7 @@ struct ref *fetch_pack(struct fetch_pack_args *args,
1031 ref_cpy = do_fetch_pack(args, fd, ref, sought, nr_sought,
1032 &si, pack_lockfile);
1033 reprepare_packed_git();
1006 - update_shallow(args, &si);
1034 + update_shallow(args, sought, nr_sought, &si);
1035 clear_shallow_info(&si);
1036 return ref_cpy;
1037 }
remote.h
+1
@@ -109,6 +109,7 @@ struct ref {
109 REF_STATUS_REJECT_FETCH_FIRST,
110 REF_STATUS_REJECT_NEEDS_FORCE,
111 REF_STATUS_REJECT_STALE,
112 + REF_STATUS_REJECT_SHALLOW,
113 REF_STATUS_UPTODATE,
114 REF_STATUS_REMOTE_REJECT,
115 REF_STATUS_EXPECTING_REPORT
t/t5537-fetch-shallow.sh new
+128
@@ -0,0 +1,128 @@
1 +#!/bin/sh
2 +
3 +test_description='fetch/clone from a shallow clone'
4 +
5 +. ./test-lib.sh
6 +
7 +commit() {
8 + echo "$1" >tracked &&
9 + git add tracked &&
10 + git commit -m "$1"
11 +}
12 +
13 +test_expect_success 'setup' '
14 + commit 1 &&
15 + commit 2 &&
16 + commit 3 &&
17 + commit 4 &&
18 + git config --global transfer.fsckObjects true
19 +'
20 +
21 +test_expect_success 'setup shallow clone' '
22 + git clone --no-local --depth=2 .git shallow &&
23 + git --git-dir=shallow/.git log --format=%s >actual &&
24 + cat <<EOF >expect &&
25 +4
26 +3
27 +EOF
28 + test_cmp expect actual
29 +'
30 +
31 +test_expect_success 'clone from shallow clone' '
32 + git clone --no-local shallow shallow2 &&
33 + (
34 + cd shallow2 &&
35 + git fsck &&
36 + git log --format=%s >actual &&
37 + cat <<EOF >expect &&
38 +4
39 +3
40 +EOF
41 + test_cmp expect actual
42 + )
43 +'
44 +
45 +test_expect_success 'fetch from shallow clone' '
46 + (
47 + cd shallow &&
48 + commit 5
49 + ) &&
50 + (
51 + cd shallow2 &&
52 + git fetch &&
53 + git fsck &&
54 + git log --format=%s origin/master >actual &&
55 + cat <<EOF >expect &&
56 +5
57 +4
58 +3
59 +EOF
60 + test_cmp expect actual
61 + )
62 +'
63 +
64 +test_expect_success 'fetch --depth from shallow clone' '
65 + (
66 + cd shallow &&
67 + commit 6
68 + ) &&
69 + (
70 + cd shallow2 &&
71 + git fetch --depth=2 &&
72 + git fsck &&
73 + git log --format=%s origin/master >actual &&
74 + cat <<EOF >expect &&
75 +6
76 +5
77 +EOF
78 + test_cmp expect actual
79 + )
80 +'
81 +
82 +test_expect_success 'fetch something upstream has but hidden by clients shallow boundaries' '
83 + # the blob "1" is available in .git but hidden by the
84 + # shallow2/.git/shallow and it should be resent
85 + ! git --git-dir=shallow2/.git cat-file blob `echo 1|git hash-object --stdin` >/dev/null &&
86 + echo 1 >1.t &&
87 + git add 1.t &&
88 + git commit -m add-1-back &&
89 + (
90 + cd shallow2 &&
91 + git fetch ../.git +refs/heads/master:refs/remotes/top/master &&
92 + git fsck &&
93 + git log --format=%s top/master >actual &&
94 + cat <<EOF >expect &&
95 +add-1-back
96 +4
97 +3
98 +EOF
99 + test_cmp expect actual
100 + ) &&
101 + git --git-dir=shallow2/.git cat-file blob `echo 1|git hash-object --stdin` >/dev/null
102 +
103 +'
104 +
105 +test_expect_success 'fetch that requires changes in .git/shallow is filtered' '
106 + (
107 + cd shallow &&
108 + git checkout --orphan no-shallow &&
109 + commit no-shallow
110 + ) &&
111 + git init notshallow &&
112 + (
113 + cd notshallow &&
114 + git fetch ../shallow/.git refs/heads/*:refs/remotes/shallow/*&&
115 + git for-each-ref --format="%(refname)" >actual.refs &&
116 + cat <<EOF >expect.refs &&
117 +refs/remotes/shallow/no-shallow
118 +EOF
119 + test_cmp expect.refs actual.refs &&
120 + git log --format=%s shallow/no-shallow >actual &&
121 + cat <<EOF >expect &&
122 +no-shallow
123 +EOF
124 + test_cmp expect actual
125 + )
126 +'
127 +
128 +test_done
transport.c
+8 -3
@@ -515,7 +515,7 @@ static struct ref *get_refs_via_connect(struct transport *transport, int for_pus
515 get_remote_heads(data->fd[0], NULL, 0, &refs,
516 for_push ? REF_NORMAL : 0,
517 &data->extra_have,
518 - transport->cloning ? &data->shallow : NULL);
518 + &data->shallow);
519 data->got_remote_heads = 1;
520
521 return refs;
@@ -547,8 +547,7 @@ static int fetch_refs_via_pack(struct transport *transport,
547 if (!data->got_remote_heads) {
548 connect_setup(transport, 0, 0);
549 get_remote_heads(data->fd[0], NULL, 0, &refs_tmp, 0,
550 - NULL,
551 - transport->cloning ? &data->shallow : NULL);
550 + NULL, &data->shallow);
551 data->got_remote_heads = 1;
552 }
553
@@ -720,6 +719,10 @@ static int print_one_push_status(struct ref *ref, const char *dest, int count, i
719 print_ref_status('!', "[rejected]", ref, ref->peer_ref,
720 "stale info", porcelain);
721 break;
722 + case REF_STATUS_REJECT_SHALLOW:
723 + print_ref_status('!', "[rejected]", ref, ref->peer_ref,
724 + "new shallow roots not allowed", porcelain);
725 + break;
726 case REF_STATUS_REMOTE_REJECT:
727 print_ref_status('!', "[remote rejected]", ref,
728 ref->deletion ? NULL : ref->peer_ref,
@@ -815,6 +818,8 @@ static int git_transport_push(struct transport *transport, struct ref *remote_re
818 get_remote_heads(data->fd[0], NULL, 0, &tmp_refs, REF_NORMAL, NULL, NULL);
819 data->got_remote_heads = 1;
820 }
821 + if (data->shallow.nr)
822 + die("pushing to a shallow repository is not supported");
823
824 memset(&args, 0, sizeof(args));
825 args.send_mirror = !!(flags & TRANSPORT_PUSH_MIRROR);