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