]>
Commit | Line | Data |
---|---|---|
2e0afafe JS |
1 | #include "cache.h" |
2 | #include "object.h" | |
3 | #include "commit.h" | |
4 | #include "diff.h" | |
5 | #include "revision.h" | |
6 | #include "list-objects.h" | |
7 | #include "exec_cmd.h" | |
8 | ||
9 | /* | |
10 | * Basic handler for bundle files to connect repositories via sneakernet. | |
11 | * Invocation must include action. | |
12 | * This function can create a bundle or provide information on an existing | |
13 | * bundle supporting git-fetch, git-pull, and git-ls-remote | |
14 | */ | |
15 | ||
fa257b05 | 16 | static const char *bundle_usage="git-bundle (create <bundle> <git-rev-list args> | verify <bundle> | list-heads <bundle> [refname]... | unbundle <bundle> [refname]... )"; |
2e0afafe JS |
17 | |
18 | static const char bundle_signature[] = "# v2 git bundle\n"; | |
19 | ||
20 | struct ref_list { | |
21 | unsigned int nr, alloc; | |
fb9a5415 | 22 | struct ref_list_entry { |
2e0afafe JS |
23 | unsigned char sha1[20]; |
24 | char *name; | |
25 | } *list; | |
26 | }; | |
27 | ||
28 | static void add_to_ref_list(const unsigned char *sha1, const char *name, | |
29 | struct ref_list *list) | |
30 | { | |
31 | if (list->nr + 1 >= list->alloc) { | |
32 | list->alloc = alloc_nr(list->nr + 1); | |
33 | list->list = xrealloc(list->list, | |
34 | list->alloc * sizeof(list->list[0])); | |
35 | } | |
36 | memcpy(list->list[list->nr].sha1, sha1, 20); | |
37 | list->list[list->nr].name = xstrdup(name); | |
38 | list->nr++; | |
39 | } | |
40 | ||
41 | struct bundle_header { | |
fa257b05 JS |
42 | struct ref_list prerequisites; |
43 | struct ref_list references; | |
2e0afafe JS |
44 | }; |
45 | ||
46 | /* this function returns the length of the string */ | |
47 | static int read_string(int fd, char *buffer, int size) | |
48 | { | |
49 | int i; | |
50 | for (i = 0; i < size - 1; i++) { | |
fa257b05 | 51 | int count = xread(fd, buffer + i, 1); |
2e0afafe JS |
52 | if (count < 0) |
53 | return error("Read error: %s", strerror(errno)); | |
54 | if (count == 0) { | |
55 | i--; | |
56 | break; | |
57 | } | |
58 | if (buffer[i] == '\n') | |
59 | break; | |
60 | } | |
61 | buffer[i + 1] = '\0'; | |
62 | return i + 1; | |
63 | } | |
64 | ||
65 | /* returns an fd */ | |
66 | static int read_header(const char *path, struct bundle_header *header) { | |
67 | char buffer[1024]; | |
68 | int fd = open(path, O_RDONLY); | |
69 | ||
70 | if (fd < 0) | |
71 | return error("could not open '%s'", path); | |
72 | if (read_string(fd, buffer, sizeof(buffer)) < 0 || | |
73 | strcmp(buffer, bundle_signature)) { | |
74 | close(fd); | |
75 | return error("'%s' does not look like a v2 bundle file", path); | |
76 | } | |
77 | while (read_string(fd, buffer, sizeof(buffer)) > 0 | |
78 | && buffer[0] != '\n') { | |
fa257b05 JS |
79 | int is_prereq = buffer[0] == '-'; |
80 | int offset = is_prereq ? 1 : 0; | |
2e0afafe JS |
81 | int len = strlen(buffer); |
82 | unsigned char sha1[20]; | |
fa257b05 | 83 | struct ref_list *list = is_prereq ? &header->prerequisites |
2e0afafe | 84 | : &header->references; |
fa257b05 JS |
85 | char delim; |
86 | ||
2e0afafe JS |
87 | if (buffer[len - 1] == '\n') |
88 | buffer[len - 1] = '\0'; | |
fa257b05 JS |
89 | if (get_sha1_hex(buffer + offset, sha1)) { |
90 | warn("unrecognized header: %s", buffer); | |
91 | continue; | |
92 | } | |
93 | delim = buffer[40 + offset]; | |
94 | if (!isspace(delim) && (delim != '\0' || !is_prereq)) | |
95 | die ("invalid header: %s", buffer); | |
96 | add_to_ref_list(sha1, isspace(delim) ? | |
97 | buffer + 41 + offset : "", list); | |
2e0afafe JS |
98 | } |
99 | return fd; | |
100 | } | |
101 | ||
102 | /* if in && *in >= 0, take that as input file descriptor instead */ | |
103 | static int fork_with_pipe(const char **argv, int *in, int *out) | |
104 | { | |
105 | int needs_in, needs_out; | |
106 | int fdin[2], fdout[2], pid; | |
107 | ||
108 | needs_in = in && *in < 0; | |
109 | if (needs_in) { | |
110 | if (pipe(fdin) < 0) | |
111 | return error("could not setup pipe"); | |
112 | *in = fdin[1]; | |
113 | } | |
114 | ||
115 | needs_out = out && *out < 0; | |
116 | if (needs_out) { | |
117 | if (pipe(fdout) < 0) | |
118 | return error("could not setup pipe"); | |
119 | *out = fdout[0]; | |
120 | } | |
121 | ||
122 | if ((pid = fork()) < 0) { | |
123 | if (needs_in) { | |
124 | close(fdin[0]); | |
125 | close(fdin[1]); | |
126 | } | |
127 | if (needs_out) { | |
128 | close(fdout[0]); | |
129 | close(fdout[1]); | |
130 | } | |
131 | return error("could not fork"); | |
132 | } | |
133 | if (!pid) { | |
134 | if (needs_in) { | |
135 | dup2(fdin[0], 0); | |
136 | close(fdin[0]); | |
137 | close(fdin[1]); | |
fa257b05 | 138 | } else if (in) { |
2e0afafe | 139 | dup2(*in, 0); |
fa257b05 JS |
140 | close(*in); |
141 | } | |
2e0afafe JS |
142 | if (needs_out) { |
143 | dup2(fdout[1], 1); | |
144 | close(fdout[0]); | |
145 | close(fdout[1]); | |
fa257b05 | 146 | } else if (out) { |
2e0afafe | 147 | dup2(*out, 1); |
fa257b05 JS |
148 | close(*out); |
149 | } | |
2e0afafe JS |
150 | exit(execv_git_cmd(argv)); |
151 | } | |
152 | if (needs_in) | |
153 | close(fdin[0]); | |
fa257b05 JS |
154 | else if (in) |
155 | close(*in); | |
2e0afafe JS |
156 | if (needs_out) |
157 | close(fdout[1]); | |
fa257b05 JS |
158 | else if (out) |
159 | close(*out); | |
2e0afafe JS |
160 | return pid; |
161 | } | |
162 | ||
163 | static int verify_bundle(struct bundle_header *header) | |
164 | { | |
165 | /* | |
166 | * Do fast check, then if any prereqs are missing then go line by line | |
167 | * to be verbose about the errors | |
168 | */ | |
169 | struct ref_list *p = &header->prerequisites; | |
fb9a5415 JS |
170 | struct rev_info revs; |
171 | const char *argv[] = {NULL, "--all"}; | |
172 | struct object_array refs; | |
173 | struct commit *commit; | |
174 | int i, ret = 0, req_nr; | |
64d99e9c | 175 | const char *message = "Repository lacks these prerequisite commits:"; |
fb9a5415 JS |
176 | |
177 | init_revisions(&revs, NULL); | |
178 | for (i = 0; i < p->nr; i++) { | |
179 | struct ref_list_entry *e = p->list + i; | |
180 | struct object *o = parse_object(e->sha1); | |
181 | if (o) { | |
182 | o->flags |= BOUNDARY_SHOW; | |
183 | add_pending_object(&revs, o, e->name); | |
184 | continue; | |
185 | } | |
186 | if (++ret == 1) | |
187 | error(message); | |
188 | error("%s %s", sha1_to_hex(e->sha1), e->name); | |
189 | } | |
190 | if (revs.pending.nr == 0) | |
191 | return ret; | |
192 | req_nr = revs.pending.nr; | |
193 | setup_revisions(2, argv, &revs, NULL); | |
194 | ||
195 | memset(&refs, 0, sizeof(struct object_array)); | |
196 | for (i = 0; i < revs.pending.nr; i++) { | |
197 | struct object_array_entry *e = revs.pending.objects + i; | |
198 | add_object_array(e->item, e->name, &refs); | |
199 | } | |
200 | ||
201 | prepare_revision_walk(&revs); | |
202 | ||
203 | i = req_nr; | |
204 | while (i && (commit = get_revision(&revs))) | |
205 | if (commit->object.flags & BOUNDARY_SHOW) | |
206 | i--; | |
207 | ||
208 | for (i = 0; i < req_nr; i++) | |
209 | if (!(refs.objects[i].item->flags & SHOWN)) { | |
210 | if (++ret == 1) | |
211 | error(message); | |
212 | error("%s %s", sha1_to_hex(refs.objects[i].item->sha1), | |
213 | refs.objects[i].name); | |
214 | } | |
2e0afafe | 215 | |
fb9a5415 JS |
216 | for (i = 0; i < refs.nr; i++) |
217 | clear_commit_marks((struct commit *)refs.objects[i].item, -1); | |
2e0afafe JS |
218 | |
219 | return ret; | |
220 | } | |
221 | ||
222 | static int list_heads(struct bundle_header *header, int argc, const char **argv) | |
223 | { | |
224 | int i; | |
225 | struct ref_list *r = &header->references; | |
226 | ||
227 | for (i = 0; i < r->nr; i++) { | |
228 | if (argc > 1) { | |
229 | int j; | |
230 | for (j = 1; j < argc; j++) | |
231 | if (!strcmp(r->list[i].name, argv[j])) | |
232 | break; | |
233 | if (j == argc) | |
234 | continue; | |
235 | } | |
236 | printf("%s %s\n", sha1_to_hex(r->list[i].sha1), | |
237 | r->list[i].name); | |
238 | } | |
239 | return 0; | |
240 | } | |
241 | ||
242 | static void show_commit(struct commit *commit) | |
243 | { | |
fa257b05 JS |
244 | write_or_die(1, sha1_to_hex(commit->object.sha1), 40); |
245 | write_or_die(1, "\n", 1); | |
2e0afafe JS |
246 | if (commit->parents) { |
247 | free_commit_list(commit->parents); | |
248 | commit->parents = NULL; | |
249 | } | |
250 | } | |
251 | ||
252 | static void show_object(struct object_array_entry *p) | |
253 | { | |
254 | /* An object with name "foo\n0000000..." can be used to | |
fa257b05 JS |
255 | * confuse downstream git-pack-objects very badly. |
256 | */ | |
2e0afafe JS |
257 | const char *ep = strchr(p->name, '\n'); |
258 | int len = ep ? ep - p->name : strlen(p->name); | |
fa257b05 JS |
259 | write_or_die(1, sha1_to_hex(p->item->sha1), 40); |
260 | write_or_die(1, " ", 1); | |
2e0afafe | 261 | if (len) |
fa257b05 JS |
262 | write_or_die(1, p->name, len); |
263 | write_or_die(1, "\n", 1); | |
2e0afafe JS |
264 | } |
265 | ||
266 | static int create_bundle(struct bundle_header *header, const char *path, | |
267 | int argc, const char **argv) | |
268 | { | |
269 | int bundle_fd = -1; | |
23929677 | 270 | const char **argv_boundary = xmalloc((argc + 4) * sizeof(const char *)); |
2e0afafe JS |
271 | const char **argv_pack = xmalloc(4 * sizeof(const char *)); |
272 | int pid, in, out, i, status; | |
273 | char buffer[1024]; | |
274 | struct rev_info revs; | |
275 | ||
276 | bundle_fd = (!strcmp(path, "-") ? 1 : | |
277 | open(path, O_CREAT | O_WRONLY, 0666)); | |
278 | if (bundle_fd < 0) | |
279 | return error("Could not write to '%s'", path); | |
280 | ||
281 | /* write signature */ | |
fa257b05 | 282 | write_or_die(bundle_fd, bundle_signature, strlen(bundle_signature)); |
2e0afafe JS |
283 | |
284 | /* write prerequisites */ | |
23929677 | 285 | memcpy(argv_boundary + 3, argv + 1, argc * sizeof(const char *)); |
2e0afafe JS |
286 | argv_boundary[0] = "rev-list"; |
287 | argv_boundary[1] = "--boundary"; | |
23929677 JS |
288 | argv_boundary[2] = "--pretty=oneline"; |
289 | argv_boundary[argc + 2] = NULL; | |
2e0afafe JS |
290 | out = -1; |
291 | pid = fork_with_pipe(argv_boundary, NULL, &out); | |
292 | if (pid < 0) | |
293 | return -1; | |
294 | while ((i = read_string(out, buffer, sizeof(buffer))) > 0) | |
295 | if (buffer[0] == '-') | |
fa257b05 | 296 | write_or_die(bundle_fd, buffer, i); |
2e0afafe JS |
297 | while ((i = waitpid(pid, &status, 0)) < 0) |
298 | if (errno != EINTR) | |
299 | return error("rev-list died"); | |
300 | if (!WIFEXITED(status) || WEXITSTATUS(status)) | |
301 | return error("rev-list died %d", WEXITSTATUS(status)); | |
302 | ||
303 | /* write references */ | |
304 | save_commit_buffer = 0; | |
305 | init_revisions(&revs, NULL); | |
306 | revs.tag_objects = 1; | |
307 | revs.tree_objects = 1; | |
308 | revs.blob_objects = 1; | |
309 | argc = setup_revisions(argc, argv, &revs, NULL); | |
310 | if (argc > 1) | |
311 | return error("unrecognized argument: %s'", argv[1]); | |
312 | for (i = 0; i < revs.pending.nr; i++) { | |
313 | struct object_array_entry *e = revs.pending.objects + i; | |
314 | if (!(e->item->flags & UNINTERESTING)) { | |
315 | unsigned char sha1[20]; | |
316 | char *ref; | |
317 | if (dwim_ref(e->name, strlen(e->name), sha1, &ref) != 1) | |
318 | continue; | |
fa257b05 JS |
319 | write_or_die(bundle_fd, sha1_to_hex(e->item->sha1), 40); |
320 | write_or_die(bundle_fd, " ", 1); | |
321 | write_or_die(bundle_fd, ref, strlen(ref)); | |
322 | write_or_die(bundle_fd, "\n", 1); | |
2e0afafe JS |
323 | free(ref); |
324 | } | |
325 | } | |
326 | ||
327 | /* end header */ | |
fa257b05 | 328 | write_or_die(bundle_fd, "\n", 1); |
2e0afafe JS |
329 | |
330 | /* write pack */ | |
331 | argv_pack[0] = "pack-objects"; | |
332 | argv_pack[1] = "--all-progress"; | |
333 | argv_pack[2] = "--stdout"; | |
334 | argv_pack[3] = NULL; | |
335 | in = -1; | |
336 | out = bundle_fd; | |
337 | pid = fork_with_pipe(argv_pack, &in, &out); | |
338 | if (pid < 0) | |
339 | return error("Could not spawn pack-objects"); | |
340 | close(1); | |
2e0afafe JS |
341 | dup2(in, 1); |
342 | close(in); | |
343 | prepare_revision_walk(&revs); | |
344 | traverse_commit_list(&revs, show_commit, show_object); | |
345 | close(1); | |
346 | while (waitpid(pid, &status, 0) < 0) | |
347 | if (errno != EINTR) | |
348 | return -1; | |
349 | if (!WIFEXITED(status) || WEXITSTATUS(status)) | |
350 | return error ("pack-objects died"); | |
351 | return 0; | |
352 | } | |
353 | ||
354 | static int unbundle(struct bundle_header *header, int bundle_fd, | |
355 | int argc, const char **argv) | |
356 | { | |
357 | const char *argv_index_pack[] = {"index-pack", "--stdin", NULL}; | |
358 | int pid, status, dev_null; | |
359 | ||
360 | if (verify_bundle(header)) | |
361 | return -1; | |
362 | dev_null = open("/dev/null", O_WRONLY); | |
363 | pid = fork_with_pipe(argv_index_pack, &bundle_fd, &dev_null); | |
364 | if (pid < 0) | |
365 | return error("Could not spawn index-pack"); | |
2e0afafe JS |
366 | while (waitpid(pid, &status, 0) < 0) |
367 | if (errno != EINTR) | |
368 | return error("index-pack died"); | |
369 | if (!WIFEXITED(status) || WEXITSTATUS(status)) | |
370 | return error("index-pack exited with status %d", | |
371 | WEXITSTATUS(status)); | |
372 | return list_heads(header, argc, argv); | |
373 | } | |
374 | ||
375 | int cmd_bundle(int argc, const char **argv, const char *prefix) | |
376 | { | |
377 | struct bundle_header header; | |
378 | int nongit = 0; | |
379 | const char *cmd, *bundle_file; | |
380 | int bundle_fd = -1; | |
381 | char buffer[PATH_MAX]; | |
382 | ||
383 | if (argc < 3) | |
384 | usage(bundle_usage); | |
385 | ||
386 | cmd = argv[1]; | |
387 | bundle_file = argv[2]; | |
388 | argc -= 2; | |
389 | argv += 2; | |
390 | ||
391 | prefix = setup_git_directory_gently(&nongit); | |
392 | if (prefix && bundle_file[0] != '/') { | |
393 | snprintf(buffer, sizeof(buffer), "%s/%s", prefix, bundle_file); | |
394 | bundle_file = buffer; | |
395 | } | |
396 | ||
397 | memset(&header, 0, sizeof(header)); | |
398 | if (strcmp(cmd, "create") && | |
399 | !(bundle_fd = read_header(bundle_file, &header))) | |
400 | return 1; | |
401 | ||
402 | if (!strcmp(cmd, "verify")) { | |
403 | close(bundle_fd); | |
404 | if (verify_bundle(&header)) | |
405 | return 1; | |
406 | fprintf(stderr, "%s is okay\n", bundle_file); | |
407 | return 0; | |
408 | } | |
409 | if (!strcmp(cmd, "list-heads")) { | |
410 | close(bundle_fd); | |
411 | return !!list_heads(&header, argc, argv); | |
412 | } | |
413 | if (!strcmp(cmd, "create")) { | |
414 | if (nongit) | |
415 | die("Need a repository to create a bundle."); | |
416 | return !!create_bundle(&header, bundle_file, argc, argv); | |
417 | } else if (!strcmp(cmd, "unbundle")) { | |
418 | if (nongit) | |
419 | die("Need a repository to unbundle."); | |
420 | return !!unbundle(&header, bundle_fd, argc, argv); | |
421 | } else | |
422 | usage(bundle_usage); | |
423 | } | |
424 |