]>
Commit | Line | Data |
---|---|---|
7cb904ba ET |
1 | /* |
2 | * Copyright (C) the libgit2 contributors. All rights reserved. | |
3 | * | |
4 | * This file is part of libgit2, distributed under the GNU GPL v2 with | |
5 | * a Linking Exception. For full terms see the included COPYING file. | |
6 | */ | |
7 | ||
eae0bfdc PP |
8 | #include "apply.h" |
9 | ||
ac3d33df | 10 | #include "git2/apply.h" |
7cb904ba ET |
11 | #include "git2/patch.h" |
12 | #include "git2/filter.h" | |
ac3d33df JK |
13 | #include "git2/blob.h" |
14 | #include "git2/index.h" | |
15 | #include "git2/checkout.h" | |
16 | #include "git2/repository.h" | |
7cb904ba | 17 | #include "array.h" |
804d5fe9 | 18 | #include "patch.h" |
22a2d3d5 | 19 | #include "futils.h" |
3149ff6f ET |
20 | #include "delta.h" |
21 | #include "zstream.h" | |
ac3d33df JK |
22 | #include "reader.h" |
23 | #include "index.h" | |
7cb904ba | 24 | |
7cb904ba ET |
25 | typedef struct { |
26 | /* The lines that we allocate ourself are allocated out of the pool. | |
27 | * (Lines may have been allocated out of the diff.) | |
28 | */ | |
29 | git_pool pool; | |
30 | git_vector lines; | |
31 | } patch_image; | |
32 | ||
22a2d3d5 UG |
33 | static int apply_err(const char *fmt, ...) GIT_FORMAT_PRINTF(1, 2); |
34 | static int apply_err(const char *fmt, ...) | |
35 | { | |
36 | va_list ap; | |
37 | ||
38 | va_start(ap, fmt); | |
39 | git_error_vset(GIT_ERROR_PATCH, fmt, ap); | |
40 | va_end(ap); | |
41 | ||
42 | return GIT_EAPPLYFAIL; | |
43 | } | |
44 | ||
7cb904ba ET |
45 | static void patch_line_init( |
46 | git_diff_line *out, | |
47 | const char *in, | |
48 | size_t in_len, | |
49 | size_t in_offset) | |
50 | { | |
51 | out->content = in; | |
52 | out->content_len = in_len; | |
53 | out->content_offset = in_offset; | |
54 | } | |
55 | ||
274a727e | 56 | #define PATCH_IMAGE_INIT { GIT_POOL_INIT, GIT_VECTOR_INIT } |
7cb904ba ET |
57 | |
58 | static int patch_image_init_fromstr( | |
59 | patch_image *out, const char *in, size_t in_len) | |
60 | { | |
61 | git_diff_line *line; | |
62 | const char *start, *end; | |
63 | ||
64 | memset(out, 0x0, sizeof(patch_image)); | |
65 | ||
22a2d3d5 UG |
66 | if (git_pool_init(&out->pool, sizeof(git_diff_line)) < 0) |
67 | return -1; | |
68 | ||
69 | if (!in_len) | |
70 | return 0; | |
7cb904ba ET |
71 | |
72 | for (start = in; start < in + in_len; start = end) { | |
22a2d3d5 | 73 | end = memchr(start, '\n', in_len - (start - in)); |
7cb904ba | 74 | |
581a4d39 ET |
75 | if (end == NULL) |
76 | end = in + in_len; | |
77 | ||
78 | else if (end < in + in_len) | |
7cb904ba ET |
79 | end++; |
80 | ||
81 | line = git_pool_mallocz(&out->pool, 1); | |
ac3d33df | 82 | GIT_ERROR_CHECK_ALLOC(line); |
7cb904ba ET |
83 | |
84 | if (git_vector_insert(&out->lines, line) < 0) | |
85 | return -1; | |
86 | ||
87 | patch_line_init(line, start, (end - start), (start - in)); | |
88 | } | |
89 | ||
90 | return 0; | |
91 | } | |
92 | ||
93 | static void patch_image_free(patch_image *image) | |
94 | { | |
95 | if (image == NULL) | |
96 | return; | |
97 | ||
98 | git_pool_clear(&image->pool); | |
99 | git_vector_free(&image->lines); | |
100 | } | |
101 | ||
102 | static bool match_hunk( | |
103 | patch_image *image, | |
104 | patch_image *preimage, | |
105 | size_t linenum) | |
106 | { | |
107 | bool match = 0; | |
108 | size_t i; | |
109 | ||
110 | /* Ensure this hunk is within the image boundaries. */ | |
111 | if (git_vector_length(&preimage->lines) + linenum > | |
112 | git_vector_length(&image->lines)) | |
113 | return 0; | |
114 | ||
115 | match = 1; | |
116 | ||
117 | /* Check exact match. */ | |
118 | for (i = 0; i < git_vector_length(&preimage->lines); i++) { | |
119 | git_diff_line *preimage_line = git_vector_get(&preimage->lines, i); | |
120 | git_diff_line *image_line = git_vector_get(&image->lines, linenum + i); | |
121 | ||
531be3e8 | 122 | if (preimage_line->content_len != image_line->content_len || |
7cb904ba ET |
123 | memcmp(preimage_line->content, image_line->content, image_line->content_len) != 0) { |
124 | match = 0; | |
125 | break; | |
126 | } | |
127 | } | |
128 | ||
129 | return match; | |
130 | } | |
131 | ||
132 | static bool find_hunk_linenum( | |
133 | size_t *out, | |
134 | patch_image *image, | |
135 | patch_image *preimage, | |
136 | size_t linenum) | |
137 | { | |
138 | size_t max = git_vector_length(&image->lines); | |
139 | bool match; | |
140 | ||
141 | if (linenum > max) | |
142 | linenum = max; | |
143 | ||
144 | match = match_hunk(image, preimage, linenum); | |
145 | ||
146 | *out = linenum; | |
147 | return match; | |
148 | } | |
149 | ||
150 | static int update_hunk( | |
151 | patch_image *image, | |
ac3d33df | 152 | size_t linenum, |
7cb904ba ET |
153 | patch_image *preimage, |
154 | patch_image *postimage) | |
155 | { | |
156 | size_t postlen = git_vector_length(&postimage->lines); | |
157 | size_t prelen = git_vector_length(&preimage->lines); | |
158 | size_t i; | |
159 | int error = 0; | |
160 | ||
161 | if (postlen > prelen) | |
53571f2f | 162 | error = git_vector_insert_null( |
7cb904ba ET |
163 | &image->lines, linenum, (postlen - prelen)); |
164 | else if (prelen > postlen) | |
53571f2f | 165 | error = git_vector_remove_range( |
7cb904ba ET |
166 | &image->lines, linenum, (prelen - postlen)); |
167 | ||
168 | if (error) { | |
ac3d33df | 169 | git_error_set_oom(); |
7cb904ba ET |
170 | return -1; |
171 | } | |
172 | ||
173 | for (i = 0; i < git_vector_length(&postimage->lines); i++) { | |
174 | image->lines.contents[linenum + i] = | |
175 | git_vector_get(&postimage->lines, i); | |
176 | } | |
177 | ||
178 | return 0; | |
179 | } | |
180 | ||
ac3d33df JK |
181 | typedef struct { |
182 | git_apply_options opts; | |
183 | size_t skipped_new_lines; | |
184 | size_t skipped_old_lines; | |
185 | } apply_hunks_ctx; | |
186 | ||
7cb904ba ET |
187 | static int apply_hunk( |
188 | patch_image *image, | |
189 | git_patch *patch, | |
ac3d33df JK |
190 | git_patch_hunk *hunk, |
191 | apply_hunks_ctx *ctx) | |
7cb904ba | 192 | { |
0267c34c | 193 | patch_image preimage = PATCH_IMAGE_INIT, postimage = PATCH_IMAGE_INIT; |
7cb904ba ET |
194 | size_t line_num, i; |
195 | int error = 0; | |
196 | ||
ac3d33df JK |
197 | if (ctx->opts.hunk_cb) { |
198 | error = ctx->opts.hunk_cb(&hunk->hunk, ctx->opts.payload); | |
199 | ||
200 | if (error) { | |
201 | if (error > 0) { | |
202 | ctx->skipped_new_lines += hunk->hunk.new_lines; | |
203 | ctx->skipped_old_lines += hunk->hunk.old_lines; | |
204 | error = 0; | |
205 | } | |
206 | ||
207 | goto done; | |
208 | } | |
209 | } | |
210 | ||
7cb904ba ET |
211 | for (i = 0; i < hunk->line_count; i++) { |
212 | size_t linenum = hunk->line_start + i; | |
22a2d3d5 | 213 | git_diff_line *line = git_array_get(patch->lines, linenum), *prev; |
7cb904ba ET |
214 | |
215 | if (!line) { | |
909d5494 | 216 | error = apply_err("preimage does not contain line %"PRIuZ, linenum); |
7cb904ba ET |
217 | goto done; |
218 | } | |
219 | ||
22a2d3d5 UG |
220 | switch (line->origin) { |
221 | case GIT_DIFF_LINE_CONTEXT_EOFNL: | |
222 | case GIT_DIFF_LINE_DEL_EOFNL: | |
223 | case GIT_DIFF_LINE_ADD_EOFNL: | |
224 | prev = i ? git_array_get(patch->lines, linenum - 1) : NULL; | |
225 | if (prev && prev->content[prev->content_len - 1] == '\n') | |
226 | prev->content_len -= 1; | |
227 | break; | |
228 | case GIT_DIFF_LINE_CONTEXT: | |
229 | if ((error = git_vector_insert(&preimage.lines, line)) < 0 || | |
230 | (error = git_vector_insert(&postimage.lines, line)) < 0) | |
231 | goto done; | |
232 | break; | |
233 | case GIT_DIFF_LINE_DELETION: | |
234 | if ((error = git_vector_insert(&preimage.lines, line)) < 0) | |
235 | goto done; | |
236 | break; | |
237 | case GIT_DIFF_LINE_ADDITION: | |
238 | if ((error = git_vector_insert(&postimage.lines, line)) < 0) | |
239 | goto done; | |
240 | break; | |
7cb904ba ET |
241 | } |
242 | } | |
243 | ||
ac3d33df JK |
244 | if (hunk->hunk.new_start) { |
245 | line_num = hunk->hunk.new_start - | |
246 | ctx->skipped_new_lines + | |
247 | ctx->skipped_old_lines - | |
248 | 1; | |
249 | } else { | |
250 | line_num = 0; | |
251 | } | |
7cb904ba ET |
252 | |
253 | if (!find_hunk_linenum(&line_num, image, &preimage, line_num)) { | |
909d5494 | 254 | error = apply_err("hunk at line %d did not apply", |
7cb904ba ET |
255 | hunk->hunk.new_start); |
256 | goto done; | |
257 | } | |
258 | ||
259 | error = update_hunk(image, line_num, &preimage, &postimage); | |
260 | ||
261 | done: | |
262 | patch_image_free(&preimage); | |
263 | patch_image_free(&postimage); | |
264 | ||
265 | return error; | |
266 | } | |
267 | ||
268 | static int apply_hunks( | |
269 | git_buf *out, | |
270 | const char *source, | |
271 | size_t source_len, | |
ac3d33df JK |
272 | git_patch *patch, |
273 | apply_hunks_ctx *ctx) | |
7cb904ba | 274 | { |
804d5fe9 | 275 | git_patch_hunk *hunk; |
7cb904ba ET |
276 | git_diff_line *line; |
277 | patch_image image; | |
278 | size_t i; | |
279 | int error = 0; | |
280 | ||
281 | if ((error = patch_image_init_fromstr(&image, source, source_len)) < 0) | |
282 | goto done; | |
283 | ||
284 | git_array_foreach(patch->hunks, i, hunk) { | |
ac3d33df | 285 | if ((error = apply_hunk(&image, patch, hunk, ctx)) < 0) |
7cb904ba ET |
286 | goto done; |
287 | } | |
288 | ||
289 | git_vector_foreach(&image.lines, i, line) | |
290 | git_buf_put(out, line->content, line->content_len); | |
291 | ||
292 | done: | |
293 | patch_image_free(&image); | |
294 | ||
295 | return error; | |
296 | } | |
297 | ||
3149ff6f ET |
298 | static int apply_binary_delta( |
299 | git_buf *out, | |
300 | const char *source, | |
301 | size_t source_len, | |
302 | git_diff_binary_file *binary_file) | |
303 | { | |
304 | git_buf inflated = GIT_BUF_INIT; | |
305 | int error = 0; | |
306 | ||
307 | /* no diff means identical contents */ | |
308 | if (binary_file->datalen == 0) | |
309 | return git_buf_put(out, source, source_len); | |
310 | ||
311 | error = git_zstream_inflatebuf(&inflated, | |
312 | binary_file->data, binary_file->datalen); | |
313 | ||
314 | if (!error && inflated.size != binary_file->inflatedlen) { | |
315 | error = apply_err("inflated delta does not match expected length"); | |
ac3d33df | 316 | git_buf_dispose(out); |
3149ff6f ET |
317 | } |
318 | ||
319 | if (error < 0) | |
320 | goto done; | |
321 | ||
322 | if (binary_file->type == GIT_DIFF_BINARY_DELTA) { | |
323 | void *data; | |
324 | size_t data_len; | |
325 | ||
326 | error = git_delta_apply(&data, &data_len, (void *)source, source_len, | |
327 | (void *)inflated.ptr, inflated.size); | |
328 | ||
329 | out->ptr = data; | |
330 | out->size = data_len; | |
331 | out->asize = data_len; | |
332 | } | |
333 | else if (binary_file->type == GIT_DIFF_BINARY_LITERAL) { | |
334 | git_buf_swap(out, &inflated); | |
335 | } | |
336 | else { | |
337 | error = apply_err("unknown binary delta type"); | |
338 | goto done; | |
339 | } | |
340 | ||
341 | done: | |
ac3d33df | 342 | git_buf_dispose(&inflated); |
3149ff6f ET |
343 | return error; |
344 | } | |
345 | ||
346 | static int apply_binary( | |
347 | git_buf *out, | |
348 | const char *source, | |
349 | size_t source_len, | |
350 | git_patch *patch) | |
351 | { | |
352 | git_buf reverse = GIT_BUF_INIT; | |
adedac5a ET |
353 | int error = 0; |
354 | ||
355 | if (!patch->binary.contains_data) { | |
356 | error = apply_err("patch does not contain binary data"); | |
357 | goto done; | |
358 | } | |
359 | ||
360 | if (!patch->binary.old_file.datalen && !patch->binary.new_file.datalen) | |
361 | goto done; | |
3149ff6f ET |
362 | |
363 | /* first, apply the new_file delta to the given source */ | |
364 | if ((error = apply_binary_delta(out, source, source_len, | |
365 | &patch->binary.new_file)) < 0) | |
366 | goto done; | |
367 | ||
368 | /* second, apply the old_file delta to sanity check the result */ | |
369 | if ((error = apply_binary_delta(&reverse, out->ptr, out->size, | |
370 | &patch->binary.old_file)) < 0) | |
371 | goto done; | |
372 | ||
ac3d33df | 373 | /* Verify that the resulting file with the reverse patch applied matches the source file */ |
3149ff6f | 374 | if (source_len != reverse.size || |
ac3d33df | 375 | (source_len && memcmp(source, reverse.ptr, source_len) != 0)) { |
3149ff6f ET |
376 | error = apply_err("binary patch did not apply cleanly"); |
377 | goto done; | |
378 | } | |
379 | ||
380 | done: | |
381 | if (error < 0) | |
ac3d33df | 382 | git_buf_dispose(out); |
3149ff6f | 383 | |
ac3d33df | 384 | git_buf_dispose(&reverse); |
3149ff6f ET |
385 | return error; |
386 | } | |
387 | ||
7cb904ba ET |
388 | int git_apply__patch( |
389 | git_buf *contents_out, | |
390 | char **filename_out, | |
391 | unsigned int *mode_out, | |
392 | const char *source, | |
393 | size_t source_len, | |
ac3d33df JK |
394 | git_patch *patch, |
395 | const git_apply_options *given_opts) | |
7cb904ba | 396 | { |
ac3d33df | 397 | apply_hunks_ctx ctx = { GIT_APPLY_OPTIONS_INIT }; |
7cb904ba ET |
398 | char *filename = NULL; |
399 | unsigned int mode = 0; | |
400 | int error = 0; | |
401 | ||
402 | assert(contents_out && filename_out && mode_out && (source || !source_len) && patch); | |
403 | ||
ac3d33df JK |
404 | if (given_opts) |
405 | memcpy(&ctx.opts, given_opts, sizeof(git_apply_options)); | |
406 | ||
7cb904ba ET |
407 | *filename_out = NULL; |
408 | *mode_out = 0; | |
409 | ||
410 | if (patch->delta->status != GIT_DELTA_DELETED) { | |
b85bd8ce | 411 | const git_diff_file *newfile = &patch->delta->new_file; |
804d5fe9 ET |
412 | |
413 | filename = git__strdup(newfile->path); | |
414 | mode = newfile->mode ? | |
415 | newfile->mode : GIT_FILEMODE_BLOB; | |
7cb904ba ET |
416 | } |
417 | ||
3149ff6f ET |
418 | if (patch->delta->flags & GIT_DIFF_FLAG_BINARY) |
419 | error = apply_binary(contents_out, source, source_len, patch); | |
420 | else if (patch->hunks.size) | |
ac3d33df | 421 | error = apply_hunks(contents_out, source, source_len, patch, &ctx); |
3149ff6f ET |
422 | else |
423 | error = git_buf_put(contents_out, source, source_len); | |
424 | ||
425 | if (error) | |
7cb904ba ET |
426 | goto done; |
427 | ||
428 | if (patch->delta->status == GIT_DELTA_DELETED && | |
429 | git_buf_len(contents_out) > 0) { | |
430 | error = apply_err("removal patch leaves file contents"); | |
431 | goto done; | |
432 | } | |
433 | ||
434 | *filename_out = filename; | |
435 | *mode_out = mode; | |
436 | ||
437 | done: | |
438 | if (error < 0) | |
439 | git__free(filename); | |
440 | ||
441 | return error; | |
442 | } | |
ac3d33df JK |
443 | |
444 | static int apply_one( | |
445 | git_repository *repo, | |
446 | git_reader *preimage_reader, | |
447 | git_index *preimage, | |
448 | git_reader *postimage_reader, | |
449 | git_index *postimage, | |
450 | git_diff *diff, | |
451 | git_strmap *removed_paths, | |
452 | size_t i, | |
453 | const git_apply_options *opts) | |
454 | { | |
455 | git_patch *patch = NULL; | |
456 | git_buf pre_contents = GIT_BUF_INIT, post_contents = GIT_BUF_INIT; | |
457 | const git_diff_delta *delta; | |
458 | char *filename = NULL; | |
459 | unsigned int mode; | |
460 | git_oid pre_id, post_id; | |
461 | git_filemode_t pre_filemode; | |
462 | git_index_entry pre_entry, post_entry; | |
463 | bool skip_preimage = false; | |
ac3d33df JK |
464 | int error; |
465 | ||
466 | if ((error = git_patch_from_diff(&patch, diff, i)) < 0) | |
467 | goto done; | |
468 | ||
469 | delta = git_patch_get_delta(patch); | |
470 | ||
471 | if (opts->delta_cb) { | |
472 | error = opts->delta_cb(delta, opts->payload); | |
473 | ||
474 | if (error) { | |
475 | if (error > 0) | |
476 | error = 0; | |
477 | ||
478 | goto done; | |
479 | } | |
480 | } | |
481 | ||
482 | /* | |
483 | * Ensure that the file has not been deleted or renamed if we're | |
484 | * applying a modification delta. | |
485 | */ | |
486 | if (delta->status != GIT_DELTA_RENAMED && | |
487 | delta->status != GIT_DELTA_ADDED) { | |
22a2d3d5 | 488 | if (git_strmap_exists(removed_paths, delta->old_file.path)) { |
ac3d33df JK |
489 | error = apply_err("path '%s' has been renamed or deleted", delta->old_file.path); |
490 | goto done; | |
491 | } | |
492 | } | |
493 | ||
494 | /* | |
495 | * We may be applying a second delta to an already seen file. If so, | |
496 | * use the already modified data in the postimage instead of the | |
497 | * content from the index or working directory. (Don't do this in | |
498 | * the case of a rename, which must be specified before additional | |
499 | * deltas since we apply deltas to the target filename.) | |
500 | */ | |
501 | if (delta->status != GIT_DELTA_RENAMED) { | |
502 | if ((error = git_reader_read(&pre_contents, &pre_id, &pre_filemode, | |
503 | postimage_reader, delta->old_file.path)) == 0) { | |
504 | skip_preimage = true; | |
505 | } else if (error == GIT_ENOTFOUND) { | |
506 | git_error_clear(); | |
507 | error = 0; | |
508 | } else { | |
509 | goto done; | |
510 | } | |
511 | } | |
512 | ||
513 | if (!skip_preimage && delta->status != GIT_DELTA_ADDED) { | |
514 | error = git_reader_read(&pre_contents, &pre_id, &pre_filemode, | |
515 | preimage_reader, delta->old_file.path); | |
516 | ||
517 | /* ENOTFOUND means the preimage was not found; apply failed. */ | |
518 | if (error == GIT_ENOTFOUND) | |
519 | error = GIT_EAPPLYFAIL; | |
520 | ||
521 | /* When applying to BOTH, the index did not match the workdir. */ | |
522 | if (error == GIT_READER_MISMATCH) | |
523 | error = apply_err("%s: does not match index", delta->old_file.path); | |
524 | ||
525 | if (error < 0) | |
526 | goto done; | |
527 | ||
528 | /* | |
529 | * We need to populate the preimage data structure with the | |
530 | * contents that we are using as the preimage for this file. | |
531 | * This allows us to apply patches to files that have been | |
532 | * modified in the working directory. During checkout, | |
533 | * we will use this expected preimage as the baseline, and | |
534 | * limit checkout to only the paths affected by patch | |
535 | * application. (Without this, we would fail to write the | |
536 | * postimage contents to any file that had been modified | |
537 | * from HEAD on-disk, even if the patch application succeeded.) | |
538 | * Use the contents from the delta where available - some | |
539 | * fields may not be available, like the old file mode (eg in | |
540 | * an exact rename situation) so trust the patch parsing to | |
541 | * validate and use the preimage data in that case. | |
542 | */ | |
543 | if (preimage) { | |
544 | memset(&pre_entry, 0, sizeof(git_index_entry)); | |
545 | pre_entry.path = delta->old_file.path; | |
546 | pre_entry.mode = delta->old_file.mode ? delta->old_file.mode : pre_filemode; | |
547 | git_oid_cpy(&pre_entry.id, &pre_id); | |
548 | ||
549 | if ((error = git_index_add(preimage, &pre_entry)) < 0) | |
550 | goto done; | |
551 | } | |
552 | } | |
553 | ||
554 | if (delta->status != GIT_DELTA_DELETED) { | |
555 | if ((error = git_apply__patch(&post_contents, &filename, &mode, | |
556 | pre_contents.ptr, pre_contents.size, patch, opts)) < 0 || | |
22a2d3d5 | 557 | (error = git_blob_create_from_buffer(&post_id, repo, |
ac3d33df JK |
558 | post_contents.ptr, post_contents.size)) < 0) |
559 | goto done; | |
560 | ||
561 | memset(&post_entry, 0, sizeof(git_index_entry)); | |
562 | post_entry.path = filename; | |
563 | post_entry.mode = mode; | |
564 | git_oid_cpy(&post_entry.id, &post_id); | |
565 | ||
566 | if ((error = git_index_add(postimage, &post_entry)) < 0) | |
567 | goto done; | |
568 | } | |
569 | ||
570 | if (delta->status == GIT_DELTA_RENAMED || | |
571 | delta->status == GIT_DELTA_DELETED) | |
22a2d3d5 | 572 | error = git_strmap_set(removed_paths, delta->old_file.path, (char *) delta->old_file.path); |
ac3d33df JK |
573 | |
574 | if (delta->status == GIT_DELTA_RENAMED || | |
575 | delta->status == GIT_DELTA_ADDED) | |
576 | git_strmap_delete(removed_paths, delta->new_file.path); | |
577 | ||
578 | done: | |
579 | git_buf_dispose(&pre_contents); | |
580 | git_buf_dispose(&post_contents); | |
581 | git__free(filename); | |
582 | git_patch_free(patch); | |
583 | ||
584 | return error; | |
585 | } | |
586 | ||
587 | static int apply_deltas( | |
588 | git_repository *repo, | |
589 | git_reader *pre_reader, | |
590 | git_index *preimage, | |
591 | git_reader *post_reader, | |
592 | git_index *postimage, | |
593 | git_diff *diff, | |
594 | const git_apply_options *opts) | |
595 | { | |
596 | git_strmap *removed_paths; | |
597 | size_t i; | |
598 | int error = 0; | |
599 | ||
22a2d3d5 | 600 | if (git_strmap_new(&removed_paths) < 0) |
ac3d33df JK |
601 | return -1; |
602 | ||
603 | for (i = 0; i < git_diff_num_deltas(diff); i++) { | |
604 | if ((error = apply_one(repo, pre_reader, preimage, post_reader, postimage, diff, removed_paths, i, opts)) < 0) | |
605 | goto done; | |
606 | } | |
607 | ||
608 | done: | |
609 | git_strmap_free(removed_paths); | |
610 | return error; | |
611 | } | |
612 | ||
613 | int git_apply_to_tree( | |
614 | git_index **out, | |
615 | git_repository *repo, | |
616 | git_tree *preimage, | |
617 | git_diff *diff, | |
618 | const git_apply_options *given_opts) | |
619 | { | |
620 | git_index *postimage = NULL; | |
621 | git_reader *pre_reader = NULL, *post_reader = NULL; | |
622 | git_apply_options opts = GIT_APPLY_OPTIONS_INIT; | |
623 | const git_diff_delta *delta; | |
624 | size_t i; | |
625 | int error = 0; | |
626 | ||
627 | assert(out && repo && preimage && diff); | |
628 | ||
629 | *out = NULL; | |
630 | ||
631 | if (given_opts) | |
632 | memcpy(&opts, given_opts, sizeof(git_apply_options)); | |
633 | ||
634 | if ((error = git_reader_for_tree(&pre_reader, preimage)) < 0) | |
635 | goto done; | |
636 | ||
637 | /* | |
638 | * put the current tree into the postimage as-is - the diff will | |
639 | * replace any entries contained therein | |
640 | */ | |
641 | if ((error = git_index_new(&postimage)) < 0 || | |
642 | (error = git_index_read_tree(postimage, preimage)) < 0 || | |
643 | (error = git_reader_for_index(&post_reader, repo, postimage)) < 0) | |
644 | goto done; | |
645 | ||
646 | /* | |
647 | * Remove the old paths from the index before applying diffs - | |
648 | * we need to do a full pass to remove them before adding deltas, | |
649 | * in order to handle rename situations. | |
650 | */ | |
651 | for (i = 0; i < git_diff_num_deltas(diff); i++) { | |
652 | delta = git_diff_get_delta(diff, i); | |
653 | ||
22a2d3d5 UG |
654 | if (delta->status == GIT_DELTA_DELETED || |
655 | delta->status == GIT_DELTA_RENAMED) { | |
656 | if ((error = git_index_remove(postimage, | |
657 | delta->old_file.path, 0)) < 0) | |
658 | goto done; | |
659 | } | |
ac3d33df JK |
660 | } |
661 | ||
662 | if ((error = apply_deltas(repo, pre_reader, NULL, post_reader, postimage, diff, &opts)) < 0) | |
663 | goto done; | |
664 | ||
665 | *out = postimage; | |
666 | ||
667 | done: | |
668 | if (error < 0) | |
669 | git_index_free(postimage); | |
670 | ||
671 | git_reader_free(pre_reader); | |
672 | git_reader_free(post_reader); | |
673 | ||
674 | return error; | |
675 | } | |
676 | ||
677 | static int git_apply__to_workdir( | |
678 | git_repository *repo, | |
679 | git_diff *diff, | |
680 | git_index *preimage, | |
681 | git_index *postimage, | |
682 | git_apply_location_t location, | |
683 | git_apply_options *opts) | |
684 | { | |
685 | git_vector paths = GIT_VECTOR_INIT; | |
686 | git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT; | |
687 | const git_diff_delta *delta; | |
688 | size_t i; | |
689 | int error; | |
690 | ||
691 | GIT_UNUSED(opts); | |
692 | ||
693 | /* | |
694 | * Limit checkout to the paths affected by the diff; this ensures | |
695 | * that other modifications in the working directory are unaffected. | |
696 | */ | |
697 | if ((error = git_vector_init(&paths, git_diff_num_deltas(diff), NULL)) < 0) | |
698 | goto done; | |
699 | ||
700 | for (i = 0; i < git_diff_num_deltas(diff); i++) { | |
701 | delta = git_diff_get_delta(diff, i); | |
702 | ||
703 | if ((error = git_vector_insert(&paths, (void *)delta->old_file.path)) < 0) | |
704 | goto done; | |
705 | ||
706 | if (strcmp(delta->old_file.path, delta->new_file.path) && | |
707 | (error = git_vector_insert(&paths, (void *)delta->new_file.path)) < 0) | |
708 | goto done; | |
709 | } | |
710 | ||
711 | checkout_opts.checkout_strategy |= GIT_CHECKOUT_SAFE; | |
712 | checkout_opts.checkout_strategy |= GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH; | |
713 | checkout_opts.checkout_strategy |= GIT_CHECKOUT_DONT_WRITE_INDEX; | |
714 | ||
715 | if (location == GIT_APPLY_LOCATION_WORKDIR) | |
716 | checkout_opts.checkout_strategy |= GIT_CHECKOUT_DONT_UPDATE_INDEX; | |
717 | ||
718 | checkout_opts.paths.strings = (char **)paths.contents; | |
719 | checkout_opts.paths.count = paths.length; | |
720 | ||
721 | checkout_opts.baseline_index = preimage; | |
722 | ||
723 | error = git_checkout_index(repo, postimage, &checkout_opts); | |
724 | ||
725 | done: | |
726 | git_vector_free(&paths); | |
727 | return error; | |
728 | } | |
729 | ||
730 | static int git_apply__to_index( | |
731 | git_repository *repo, | |
732 | git_diff *diff, | |
733 | git_index *preimage, | |
734 | git_index *postimage, | |
735 | git_apply_options *opts) | |
736 | { | |
737 | git_index *index = NULL; | |
738 | const git_diff_delta *delta; | |
739 | const git_index_entry *entry; | |
740 | size_t i; | |
741 | int error; | |
742 | ||
743 | GIT_UNUSED(preimage); | |
744 | GIT_UNUSED(opts); | |
745 | ||
746 | if ((error = git_repository_index(&index, repo)) < 0) | |
747 | goto done; | |
748 | ||
749 | /* Remove deleted (or renamed) paths from the index. */ | |
750 | for (i = 0; i < git_diff_num_deltas(diff); i++) { | |
751 | delta = git_diff_get_delta(diff, i); | |
752 | ||
753 | if (delta->status == GIT_DELTA_DELETED || | |
754 | delta->status == GIT_DELTA_RENAMED) { | |
755 | if ((error = git_index_remove(index, delta->old_file.path, 0)) < 0) | |
756 | goto done; | |
757 | } | |
758 | } | |
759 | ||
760 | /* Then add the changes back to the index. */ | |
761 | for (i = 0; i < git_index_entrycount(postimage); i++) { | |
762 | entry = git_index_get_byindex(postimage, i); | |
763 | ||
764 | if ((error = git_index_add(index, entry)) < 0) | |
765 | goto done; | |
766 | } | |
767 | ||
768 | done: | |
769 | git_index_free(index); | |
770 | return error; | |
771 | } | |
772 | ||
22a2d3d5 UG |
773 | int git_apply_options_init(git_apply_options *opts, unsigned int version) |
774 | { | |
775 | GIT_INIT_STRUCTURE_FROM_TEMPLATE( | |
776 | opts, version, git_apply_options, GIT_APPLY_OPTIONS_INIT); | |
777 | return 0; | |
778 | } | |
779 | ||
ac3d33df JK |
780 | /* |
781 | * Handle the three application options ("locations"): | |
782 | * | |
783 | * GIT_APPLY_LOCATION_WORKDIR: the default, emulates `git apply`. | |
784 | * Applies the diff only to the workdir items and ignores the index | |
785 | * entirely. | |
786 | * | |
787 | * GIT_APPLY_LOCATION_INDEX: emulates `git apply --cached`. | |
788 | * Applies the diff only to the index items and ignores the workdir | |
789 | * completely. | |
790 | * | |
791 | * GIT_APPLY_LOCATION_BOTH: emulates `git apply --index`. | |
792 | * Applies the diff to both the index items and the working directory | |
793 | * items. | |
794 | */ | |
795 | ||
796 | int git_apply( | |
797 | git_repository *repo, | |
798 | git_diff *diff, | |
799 | git_apply_location_t location, | |
800 | const git_apply_options *given_opts) | |
801 | { | |
802 | git_indexwriter indexwriter = GIT_INDEXWRITER_INIT; | |
803 | git_index *index = NULL, *preimage = NULL, *postimage = NULL; | |
804 | git_reader *pre_reader = NULL, *post_reader = NULL; | |
805 | git_apply_options opts = GIT_APPLY_OPTIONS_INIT; | |
806 | int error = GIT_EINVALID; | |
807 | ||
808 | assert(repo && diff); | |
809 | ||
810 | GIT_ERROR_CHECK_VERSION( | |
811 | given_opts, GIT_APPLY_OPTIONS_VERSION, "git_apply_options"); | |
812 | ||
813 | if (given_opts) | |
814 | memcpy(&opts, given_opts, sizeof(git_apply_options)); | |
815 | ||
816 | /* | |
817 | * by default, we apply a patch directly to the working directory; | |
818 | * in `--cached` or `--index` mode, we apply to the contents already | |
819 | * in the index. | |
820 | */ | |
821 | switch (location) { | |
822 | case GIT_APPLY_LOCATION_BOTH: | |
823 | error = git_reader_for_workdir(&pre_reader, repo, true); | |
824 | break; | |
825 | case GIT_APPLY_LOCATION_INDEX: | |
826 | error = git_reader_for_index(&pre_reader, repo, NULL); | |
827 | break; | |
828 | case GIT_APPLY_LOCATION_WORKDIR: | |
829 | error = git_reader_for_workdir(&pre_reader, repo, false); | |
830 | break; | |
831 | default: | |
832 | assert(false); | |
833 | } | |
834 | ||
835 | if (error < 0) | |
836 | goto done; | |
837 | ||
838 | /* | |
839 | * Build the preimage and postimage (differences). Note that | |
840 | * this is not the complete preimage or postimage, it only | |
841 | * contains the files affected by the patch. We want to avoid | |
842 | * having the full repo index, so we will limit our checkout | |
843 | * to only write these files that were affected by the diff. | |
844 | */ | |
845 | if ((error = git_index_new(&preimage)) < 0 || | |
846 | (error = git_index_new(&postimage)) < 0 || | |
847 | (error = git_reader_for_index(&post_reader, repo, postimage)) < 0) | |
848 | goto done; | |
849 | ||
22a2d3d5 UG |
850 | if (!(opts.flags & GIT_APPLY_CHECK)) |
851 | if ((error = git_repository_index(&index, repo)) < 0 || | |
852 | (error = git_indexwriter_init(&indexwriter, index)) < 0) | |
853 | goto done; | |
ac3d33df JK |
854 | |
855 | if ((error = apply_deltas(repo, pre_reader, preimage, post_reader, postimage, diff, &opts)) < 0) | |
856 | goto done; | |
857 | ||
22a2d3d5 UG |
858 | if ((opts.flags & GIT_APPLY_CHECK)) |
859 | goto done; | |
860 | ||
ac3d33df JK |
861 | switch (location) { |
862 | case GIT_APPLY_LOCATION_BOTH: | |
863 | error = git_apply__to_workdir(repo, diff, preimage, postimage, location, &opts); | |
864 | break; | |
865 | case GIT_APPLY_LOCATION_INDEX: | |
866 | error = git_apply__to_index(repo, diff, preimage, postimage, &opts); | |
867 | break; | |
868 | case GIT_APPLY_LOCATION_WORKDIR: | |
869 | error = git_apply__to_workdir(repo, diff, preimage, postimage, location, &opts); | |
870 | break; | |
871 | default: | |
872 | assert(false); | |
873 | } | |
874 | ||
875 | if (error < 0) | |
876 | goto done; | |
877 | ||
878 | error = git_indexwriter_commit(&indexwriter); | |
879 | ||
880 | done: | |
881 | git_indexwriter_cleanup(&indexwriter); | |
882 | git_index_free(postimage); | |
883 | git_index_free(preimage); | |
884 | git_index_free(index); | |
885 | git_reader_free(pre_reader); | |
886 | git_reader_free(post_reader); | |
887 | ||
888 | return error; | |
889 | } |