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