Make git-fetch-pack and git-upload-pack negotiate needs/haves fully
[git.git] / upload-pack.c
1 #include "cache.h"
2 #include "refs.h"
3 #include "pkt-line.h"
4
5 static const char upload_pack_usage[] = "git-upload-pack <dir>";
6
7 #define MAX_HAS (16)
8 #define MAX_NEEDS (16)
9 static int nr_has = 0, nr_needs = 0;
10 static unsigned char has_sha1[MAX_HAS][20];
11 static unsigned char needs_sha1[MAX_NEEDS][20];
12
13 static void create_pack_file(void)
14 {
15         /*
16          * Here, we should do
17          *
18          *      git-rev-list --objects needs_sha1 --not has_sha1 |
19          *              git-pack-objects --stdout
20          *
21          * but we don't.
22          */
23 }
24
25 static int got_sha1(char *hex, unsigned char *sha1)
26 {
27         int nr;
28         if (get_sha1_hex(hex, sha1))
29                 die("git-upload-pack: expected SHA1 object, got '%s'", hex);
30         if (!has_sha1_file(sha1))
31                 return 0;
32         nr = nr_has;
33         if (nr < MAX_HAS) {
34                 memcpy(has_sha1[nr], sha1, 20);
35                 nr_has = nr+1;
36         }
37         return 1;
38 }
39
40 static int get_common_commits(void)
41 {
42         static char line[1000];
43         unsigned char sha1[20];
44         int len;
45
46         for(;;) {
47                 len = packet_read_line(0, line, sizeof(line));
48
49                 if (!len) {
50                         packet_write(1, "NAK\n");
51                         continue;
52                 }
53                 if (line[len-1] == '\n')
54                         line[--len] = 0;
55                 if (!strncmp(line, "have ", 5)) {
56                         if (got_sha1(line+5, sha1)) {
57                                 packet_write(1, "ACK %s\n", sha1_to_hex(sha1));
58                                 break;
59                         }
60                         continue;
61                 }
62                 if (!strcmp(line, "done")) {
63                         packet_write(1, "NAK\n");
64                         return -1;
65                 }
66                 die("git-upload-pack: expected SHA1 list, got '%s'", line);
67         }
68
69         for (;;) {
70                 len = packet_read_line(0, line, sizeof(line));
71                 if (!len)
72                         break;
73                 if (!strncmp(line, "have ", 5)) {
74                         got_sha1(line+5, sha1);
75                         continue;
76                 }
77                 if (!strcmp(line, "done"))
78                         break;
79                 die("git-upload-pack: expected SHA1 list, got '%s'", line);
80         }
81         return 0;
82 }
83
84 static int receive_needs(void)
85 {
86         static char line[1000];
87         int len, needs;
88
89         needs = 0;
90         for (;;) {
91                 len = packet_read_line(0, line, sizeof(line));
92                 if (!len)
93                         return needs;
94
95                 /*
96                  * This is purely theoretical right now: git-fetch-pack only
97                  * ever asks for a single HEAD
98                  */
99                 if (needs >= MAX_NEEDS)
100                         die("I'm only doing a max of %d requests", MAX_NEEDS);
101                 if (strncmp("want ", line, 5) || get_sha1_hex(line+5, needs_sha1[needs]))
102                         die("git-upload-pack: protocol error, expected to get sha, not '%s'", line);
103                 needs++;
104         }
105 }
106
107 static int send_ref(const char *refname, const unsigned char *sha1)
108 {
109         packet_write(1, "%s %s\n", sha1_to_hex(sha1), refname);
110         return 0;
111 }
112
113 static int upload_pack(void)
114 {
115         for_each_ref(send_ref);
116         packet_flush(1);
117         nr_needs = receive_needs();
118         if (!nr_needs)
119                 return 0;
120         get_common_commits();
121         create_pack_file();
122         return 0;
123 }
124
125 int main(int argc, char **argv)
126 {
127         const char *dir;
128         if (argc != 2)
129                 usage(upload_pack_usage);
130         dir = argv[1];
131         if (chdir(dir))
132                 die("git-upload-pack unable to chdir to %s", dir);
133         chdir(".git");
134         if (access("objects", X_OK) || access("refs", X_OK))
135                 die("git-upload-pack: %s doesn't seem to be a git archive", dir);
136         setenv("GIT_DIR", ".", 1);
137         upload_pack();
138         return 0;
139 }