]> git.proxmox.com Git - libgit2.git/blob - src/apply.c
b0be2d8e6637380edc2941895bbf234d1eebd959
[libgit2.git] / src / apply.c
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
8 #include "apply.h"
9
10 #include "git2/apply.h"
11 #include "git2/patch.h"
12 #include "git2/filter.h"
13 #include "git2/blob.h"
14 #include "git2/index.h"
15 #include "git2/checkout.h"
16 #include "git2/repository.h"
17 #include "array.h"
18 #include "patch.h"
19 #include "futils.h"
20 #include "delta.h"
21 #include "zstream.h"
22 #include "reader.h"
23 #include "index.h"
24
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
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
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
56 #define PATCH_IMAGE_INIT { GIT_POOL_INIT, GIT_VECTOR_INIT }
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
66 if (git_pool_init(&out->pool, sizeof(git_diff_line)) < 0)
67 return -1;
68
69 if (!in_len)
70 return 0;
71
72 for (start = in; start < in + in_len; start = end) {
73 end = memchr(start, '\n', in_len - (start - in));
74
75 if (end == NULL)
76 end = in + in_len;
77
78 else if (end < in + in_len)
79 end++;
80
81 line = git_pool_mallocz(&out->pool, 1);
82 GIT_ERROR_CHECK_ALLOC(line);
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
122 if (preimage_line->content_len != image_line->content_len ||
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,
152 size_t linenum,
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)
162 error = git_vector_insert_null(
163 &image->lines, linenum, (postlen - prelen));
164 else if (prelen > postlen)
165 error = git_vector_remove_range(
166 &image->lines, linenum, (prelen - postlen));
167
168 if (error) {
169 git_error_set_oom();
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
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
187 static int apply_hunk(
188 patch_image *image,
189 git_patch *patch,
190 git_patch_hunk *hunk,
191 apply_hunks_ctx *ctx)
192 {
193 patch_image preimage = PATCH_IMAGE_INIT, postimage = PATCH_IMAGE_INIT;
194 size_t line_num, i;
195 int error = 0;
196
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
211 for (i = 0; i < hunk->line_count; i++) {
212 size_t linenum = hunk->line_start + i;
213 git_diff_line *line = git_array_get(patch->lines, linenum), *prev;
214
215 if (!line) {
216 error = apply_err("preimage does not contain line %"PRIuZ, linenum);
217 goto done;
218 }
219
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;
241 }
242 }
243
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 }
252
253 if (!find_hunk_linenum(&line_num, image, &preimage, line_num)) {
254 error = apply_err("hunk at line %d did not apply",
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,
272 git_patch *patch,
273 apply_hunks_ctx *ctx)
274 {
275 git_patch_hunk *hunk;
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) {
285 if ((error = apply_hunk(&image, patch, hunk, ctx)) < 0)
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
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");
316 git_buf_dispose(out);
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:
342 git_buf_dispose(&inflated);
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;
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;
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
373 /* Verify that the resulting file with the reverse patch applied matches the source file */
374 if (source_len != reverse.size ||
375 (source_len && memcmp(source, reverse.ptr, source_len) != 0)) {
376 error = apply_err("binary patch did not apply cleanly");
377 goto done;
378 }
379
380 done:
381 if (error < 0)
382 git_buf_dispose(out);
383
384 git_buf_dispose(&reverse);
385 return error;
386 }
387
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,
394 git_patch *patch,
395 const git_apply_options *given_opts)
396 {
397 apply_hunks_ctx ctx = { GIT_APPLY_OPTIONS_INIT };
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
404 if (given_opts)
405 memcpy(&ctx.opts, given_opts, sizeof(git_apply_options));
406
407 *filename_out = NULL;
408 *mode_out = 0;
409
410 if (patch->delta->status != GIT_DELTA_DELETED) {
411 const git_diff_file *newfile = &patch->delta->new_file;
412
413 filename = git__strdup(newfile->path);
414 mode = newfile->mode ?
415 newfile->mode : GIT_FILEMODE_BLOB;
416 }
417
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)
421 error = apply_hunks(contents_out, source, source_len, patch, &ctx);
422 else
423 error = git_buf_put(contents_out, source, source_len);
424
425 if (error)
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 }
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;
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) {
488 if (git_strmap_exists(removed_paths, delta->old_file.path)) {
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 ||
557 (error = git_blob_create_from_buffer(&post_id, repo,
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)
572 error = git_strmap_set(removed_paths, delta->old_file.path, (char *) delta->old_file.path);
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
600 if (git_strmap_new(&removed_paths) < 0)
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
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 }
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
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
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
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;
854
855 if ((error = apply_deltas(repo, pre_reader, preimage, post_reader, postimage, diff, &opts)) < 0)
856 goto done;
857
858 if ((opts.flags & GIT_APPLY_CHECK))
859 goto done;
860
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 }