[PATCH] Re-organize "git-rev-list --objects" logic
[git.git] / http-fetch.c
1 #include "cache.h"
2 #include "commit.h"
3
4 #include "fetch.h"
5
6 #include <curl/curl.h>
7 #include <curl/easy.h>
8
9 #if LIBCURL_VERSION_NUM < 0x070704
10 #define curl_global_cleanup() do { /* nothing */ } while(0)
11 #endif
12 #if LIBCURL_VERSION_NUM < 0x070800
13 #define curl_global_init(a) do { /* nothing */ } while(0)
14 #endif
15
16 static CURL *curl;
17 static struct curl_slist *no_pragma_header;
18
19 static char *base;
20
21 static SHA_CTX c;
22 static z_stream stream;
23
24 static int local;
25 static int zret;
26
27 static int curl_ssl_verify;
28
29 struct buffer
30 {
31         size_t posn;
32         size_t size;
33         void *buffer;
34 };
35
36 static size_t fwrite_buffer(void *ptr, size_t eltsize, size_t nmemb,
37                             struct buffer *buffer)
38 {
39         size_t size = eltsize * nmemb;
40         if (size > buffer->size - buffer->posn)
41                 size = buffer->size - buffer->posn;
42         memcpy(buffer->buffer + buffer->posn, ptr, size);
43         buffer->posn += size;
44         return size;
45 }
46
47 static size_t fwrite_sha1_file(void *ptr, size_t eltsize, size_t nmemb,
48                                void *data)
49 {
50         unsigned char expn[4096];
51         size_t size = eltsize * nmemb;
52         int posn = 0;
53         do {
54                 ssize_t retval = write(local, ptr + posn, size - posn);
55                 if (retval < 0)
56                         return posn;
57                 posn += retval;
58         } while (posn < size);
59
60         stream.avail_in = size;
61         stream.next_in = ptr;
62         do {
63                 stream.next_out = expn;
64                 stream.avail_out = sizeof(expn);
65                 zret = inflate(&stream, Z_SYNC_FLUSH);
66                 SHA1_Update(&c, expn, sizeof(expn) - stream.avail_out);
67         } while (stream.avail_in && zret == Z_OK);
68         return size;
69 }
70
71 void prefetch(unsigned char *sha1)
72 {
73 }
74
75 static int got_indices = 0;
76
77 static struct packed_git *packs = NULL;
78
79 static int fetch_index(unsigned char *sha1)
80 {
81         char *filename;
82         char *url;
83
84         FILE *indexfile;
85
86         if (has_pack_index(sha1))
87                 return 0;
88
89         if (get_verbosely)
90                 fprintf(stderr, "Getting index for pack %s\n",
91                         sha1_to_hex(sha1));
92         
93         url = xmalloc(strlen(base) + 64);
94         sprintf(url, "%s/objects/pack/pack-%s.idx",
95                 base, sha1_to_hex(sha1));
96         
97         filename = sha1_pack_index_name(sha1);
98         indexfile = fopen(filename, "w");
99         if (!indexfile)
100                 return error("Unable to open local file %s for pack index",
101                              filename);
102
103         curl_easy_setopt(curl, CURLOPT_FILE, indexfile);
104         curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite);
105         curl_easy_setopt(curl, CURLOPT_URL, url);
106         curl_easy_setopt(curl, CURLOPT_HTTPHEADER, no_pragma_header);
107         
108         if (curl_easy_perform(curl)) {
109                 fclose(indexfile);
110                 return error("Unable to get pack index %s", url);
111         }
112
113         fclose(indexfile);
114         return 0;
115 }
116
117 static int setup_index(unsigned char *sha1)
118 {
119         struct packed_git *new_pack;
120         if (has_pack_file(sha1))
121                 return 0; // don't list this as something we can get
122
123         if (fetch_index(sha1))
124                 return -1;
125
126         new_pack = parse_pack_index(sha1);
127         new_pack->next = packs;
128         packs = new_pack;
129         return 0;
130 }
131
132 static int fetch_indices(void)
133 {
134         unsigned char sha1[20];
135         char *url;
136         struct buffer buffer;
137         char *data;
138         int i = 0;
139
140         if (got_indices)
141                 return 0;
142
143         data = xmalloc(4096);
144         buffer.size = 4096;
145         buffer.posn = 0;
146         buffer.buffer = data;
147
148         if (get_verbosely)
149                 fprintf(stderr, "Getting pack list\n");
150         
151         url = xmalloc(strlen(base) + 21);
152         sprintf(url, "%s/objects/info/packs", base);
153
154         curl_easy_setopt(curl, CURLOPT_FILE, &buffer);
155         curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
156         curl_easy_setopt(curl, CURLOPT_URL, url);
157         curl_easy_setopt(curl, CURLOPT_HTTPHEADER, NULL);
158         
159         if (curl_easy_perform(curl)) {
160                 return error("Unable to get pack index %s", url);
161         }
162
163         do {
164                 switch (data[i]) {
165                 case 'P':
166                         i++;
167                         if (i + 52 < buffer.posn &&
168                             !strncmp(data + i, " pack-", 6) &&
169                             !strncmp(data + i + 46, ".pack\n", 6)) {
170                                 get_sha1_hex(data + i + 6, sha1);
171                                 setup_index(sha1);
172                                 i += 51;
173                                 break;
174                         }
175                 default:
176                         while (data[i] != '\n')
177                                 i++;
178                 }
179                 i++;
180         } while (i < buffer.posn);
181
182         got_indices = 1;
183         return 0;
184 }
185
186 static int fetch_pack(unsigned char *sha1)
187 {
188         char *url;
189         struct packed_git *target;
190         struct packed_git **lst;
191         FILE *packfile;
192         char *filename;
193
194         if (fetch_indices())
195                 return -1;
196         target = find_sha1_pack(sha1, packs);
197         if (!target)
198                 return error("Couldn't get %s: not separate or in any pack",
199                              sha1_to_hex(sha1));
200
201         if (get_verbosely) {
202                 fprintf(stderr, "Getting pack %s\n",
203                         sha1_to_hex(target->sha1));
204                 fprintf(stderr, " which contains %s\n",
205                         sha1_to_hex(sha1));
206         }
207
208         url = xmalloc(strlen(base) + 65);
209         sprintf(url, "%s/objects/pack/pack-%s.pack",
210                 base, sha1_to_hex(target->sha1));
211
212         filename = sha1_pack_name(target->sha1);
213         packfile = fopen(filename, "w");
214         if (!packfile)
215                 return error("Unable to open local file %s for pack",
216                              filename);
217
218         curl_easy_setopt(curl, CURLOPT_FILE, packfile);
219         curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite);
220         curl_easy_setopt(curl, CURLOPT_URL, url);
221         curl_easy_setopt(curl, CURLOPT_HTTPHEADER, no_pragma_header);
222         
223         if (curl_easy_perform(curl)) {
224                 fclose(packfile);
225                 return error("Unable to get pack file %s", url);
226         }
227
228         fclose(packfile);
229
230         lst = &packs;
231         while (*lst != target)
232                 lst = &((*lst)->next);
233         *lst = (*lst)->next;
234
235         install_packed_git(target);
236
237         return 0;
238 }
239
240 int fetch(unsigned char *sha1)
241 {
242         char *hex = sha1_to_hex(sha1);
243         char *filename = sha1_file_name(sha1);
244         unsigned char real_sha1[20];
245         char *url;
246         char *posn;
247
248         local = open(filename, O_WRONLY | O_CREAT | O_EXCL, 0666);
249
250         if (local < 0)
251                 return error("Couldn't open local object %s\n", filename);
252
253         memset(&stream, 0, sizeof(stream));
254
255         inflateInit(&stream);
256
257         SHA1_Init(&c);
258
259         curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1);
260         curl_easy_setopt(curl, CURLOPT_FILE, NULL);
261         curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite_sha1_file);
262         curl_easy_setopt(curl, CURLOPT_HTTPHEADER, no_pragma_header);
263
264         url = xmalloc(strlen(base) + 50);
265         strcpy(url, base);
266         posn = url + strlen(base);
267         strcpy(posn, "objects/");
268         posn += 8;
269         memcpy(posn, hex, 2);
270         posn += 2;
271         *(posn++) = '/';
272         strcpy(posn, hex + 2);
273
274         curl_easy_setopt(curl, CURLOPT_URL, url);
275
276         if (curl_easy_perform(curl)) {
277                 unlink(filename);
278                 if (fetch_pack(sha1))
279                         return error("Tried %s", url);
280                 return 0;
281         }
282
283         close(local);
284         inflateEnd(&stream);
285         SHA1_Final(real_sha1, &c);
286         if (zret != Z_STREAM_END) {
287                 unlink(filename);
288                 return error("File %s (%s) corrupt\n", hex, url);
289         }
290         if (memcmp(sha1, real_sha1, 20)) {
291                 unlink(filename);
292                 return error("File %s has bad hash\n", hex);
293         }
294         
295         pull_say("got %s\n", hex);
296         return 0;
297 }
298
299 int fetch_ref(char *ref, unsigned char *sha1)
300 {
301         char *url, *posn;
302         char hex[42];
303         struct buffer buffer;
304         buffer.size = 41;
305         buffer.posn = 0;
306         buffer.buffer = hex;
307         hex[41] = '\0';
308         
309         curl_easy_setopt(curl, CURLOPT_FILE, &buffer);
310         curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
311         curl_easy_setopt(curl, CURLOPT_HTTPHEADER, NULL);
312
313         url = xmalloc(strlen(base) + 6 + strlen(ref));
314         strcpy(url, base);
315         posn = url + strlen(base);
316         strcpy(posn, "refs/");
317         posn += 5;
318         strcpy(posn, ref);
319
320         curl_easy_setopt(curl, CURLOPT_URL, url);
321
322         if (curl_easy_perform(curl))
323                 return error("Couldn't get %s for %s\n", url, ref);
324
325         hex[40] = '\0';
326         get_sha1_hex(hex, sha1);
327         return 0;
328 }
329
330 int main(int argc, char **argv)
331 {
332         char *commit_id;
333         char *url;
334         int arg = 1;
335
336         while (arg < argc && argv[arg][0] == '-') {
337                 if (argv[arg][1] == 't') {
338                         get_tree = 1;
339                 } else if (argv[arg][1] == 'c') {
340                         get_history = 1;
341                 } else if (argv[arg][1] == 'a') {
342                         get_all = 1;
343                         get_tree = 1;
344                         get_history = 1;
345                 } else if (argv[arg][1] == 'v') {
346                         get_verbosely = 1;
347                 } else if (argv[arg][1] == 'w') {
348                         write_ref = argv[arg + 1];
349                         arg++;
350                 }
351                 arg++;
352         }
353         if (argc < arg + 2) {
354                 usage("git-http-fetch [-c] [-t] [-a] [-d] [-v] [--recover] [-w ref] commit-id url");
355                 return 1;
356         }
357         commit_id = argv[arg];
358         url = argv[arg + 1];
359
360         curl_global_init(CURL_GLOBAL_ALL);
361
362         curl = curl_easy_init();
363         no_pragma_header = curl_slist_append(no_pragma_header, "Pragma:");
364
365         curl_ssl_verify = getenv("GIT_SSL_NO_VERIFY") ? 0 : 1;
366         curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, curl_ssl_verify);
367 #if LIBCURL_VERSION_NUM >= 0x070907
368         curl_easy_setopt(curl, CURLOPT_NETRC, CURL_NETRC_OPTIONAL);
369 #endif
370
371         base = url;
372
373         if (pull(commit_id))
374                 return 1;
375
376         curl_slist_free_all(no_pragma_header);
377         curl_global_cleanup();
378         return 0;
379 }