]>
Commit | Line | Data |
---|---|---|
114f5a6c RB |
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 | */ | |
eae0bfdc PP |
7 | |
8 | #include "patch_generate.h" | |
9 | ||
6789b7a7 | 10 | #include "git2/blob.h" |
114f5a6c | 11 | #include "diff.h" |
9be638ec | 12 | #include "diff_generate.h" |
114f5a6c RB |
13 | #include "diff_file.h" |
14 | #include "diff_driver.h" | |
114f5a6c | 15 | #include "diff_xdiff.h" |
8147b1af ET |
16 | #include "delta.h" |
17 | #include "zstream.h" | |
22a2d3d5 | 18 | #include "futils.h" |
114f5a6c | 19 | |
3b5f7954 | 20 | static void diff_output_init( |
8d44f8b7 | 21 | git_patch_generated_output *, const git_diff_options *, git_diff_file_cb, |
8147b1af | 22 | git_diff_binary_cb, git_diff_hunk_cb, git_diff_line_cb, void*); |
114f5a6c | 23 | |
8d44f8b7 ET |
24 | static void diff_output_to_patch( |
25 | git_patch_generated_output *, git_patch_generated *); | |
114f5a6c | 26 | |
8d44f8b7 | 27 | static void patch_generated_free(git_patch *p) |
804d5fe9 | 28 | { |
8d44f8b7 | 29 | git_patch_generated *patch = (git_patch_generated *)p; |
804d5fe9 | 30 | |
a03952f0 | 31 | git_array_clear(patch->base.lines); |
4117a235 ET |
32 | git_array_clear(patch->base.hunks); |
33 | ||
34 | git__free((char *)patch->base.binary.old_file.data); | |
35 | git__free((char *)patch->base.binary.new_file.data); | |
36 | ||
804d5fe9 ET |
37 | git_diff_file_content__clear(&patch->ofile); |
38 | git_diff_file_content__clear(&patch->nfile); | |
39 | ||
40 | git_diff_free(patch->diff); /* decrements refcount */ | |
41 | patch->diff = NULL; | |
42 | ||
43 | git_pool_clear(&patch->flattened); | |
44 | ||
45 | git__free((char *)patch->base.diff_opts.old_prefix); | |
46 | git__free((char *)patch->base.diff_opts.new_prefix); | |
47 | ||
8d44f8b7 | 48 | if (patch->flags & GIT_PATCH_GENERATED_ALLOCATED) |
804d5fe9 ET |
49 | git__free(patch); |
50 | } | |
51 | ||
8d44f8b7 | 52 | static void patch_generated_update_binary(git_patch_generated *patch) |
804d5fe9 ET |
53 | { |
54 | if ((patch->base.delta->flags & DIFF_FLAGS_KNOWN_BINARY) != 0) | |
114f5a6c RB |
55 | return; |
56 | ||
74ded024 RB |
57 | if ((patch->ofile.file->flags & GIT_DIFF_FLAG_BINARY) != 0 || |
58 | (patch->nfile.file->flags & GIT_DIFF_FLAG_BINARY) != 0) | |
804d5fe9 | 59 | patch->base.delta->flags |= GIT_DIFF_FLAG_BINARY; |
114f5a6c | 60 | |
6c014bcc | 61 | else if (patch->ofile.file->size > GIT_XDIFF_MAX_SIZE || |
804d5fe9 ET |
62 | patch->nfile.file->size > GIT_XDIFF_MAX_SIZE) |
63 | patch->base.delta->flags |= GIT_DIFF_FLAG_BINARY; | |
6c014bcc | 64 | |
74ded024 | 65 | else if ((patch->ofile.file->flags & DIFF_FLAGS_NOT_BINARY) != 0 && |
804d5fe9 ET |
66 | (patch->nfile.file->flags & DIFF_FLAGS_NOT_BINARY) != 0) |
67 | patch->base.delta->flags |= GIT_DIFF_FLAG_NOT_BINARY; | |
114f5a6c RB |
68 | } |
69 | ||
8d44f8b7 | 70 | static void patch_generated_init_common(git_patch_generated *patch) |
114f5a6c | 71 | { |
8d44f8b7 | 72 | patch->base.free_fn = patch_generated_free; |
114f5a6c | 73 | |
8d44f8b7 | 74 | patch_generated_update_binary(patch); |
804d5fe9 | 75 | |
8d44f8b7 | 76 | patch->flags |= GIT_PATCH_GENERATED_INITIALIZED; |
114f5a6c RB |
77 | |
78 | if (patch->diff) | |
3ff1d123 | 79 | git_diff_addref(patch->diff); |
114f5a6c RB |
80 | } |
81 | ||
8d44f8b7 | 82 | static int patch_generated_normalize_options( |
8147b1af ET |
83 | git_diff_options *out, |
84 | const git_diff_options *opts) | |
85 | { | |
86 | if (opts) { | |
ac3d33df | 87 | GIT_ERROR_CHECK_VERSION(opts, GIT_DIFF_OPTIONS_VERSION, "git_diff_options"); |
8147b1af ET |
88 | memcpy(out, opts, sizeof(git_diff_options)); |
89 | } else { | |
90 | git_diff_options default_opts = GIT_DIFF_OPTIONS_INIT; | |
91 | memcpy(out, &default_opts, sizeof(git_diff_options)); | |
92 | } | |
93 | ||
94 | out->old_prefix = opts && opts->old_prefix ? | |
95 | git__strdup(opts->old_prefix) : | |
96 | git__strdup(DIFF_OLD_PREFIX_DEFAULT); | |
97 | ||
98 | out->new_prefix = opts && opts->new_prefix ? | |
99 | git__strdup(opts->new_prefix) : | |
100 | git__strdup(DIFF_NEW_PREFIX_DEFAULT); | |
101 | ||
ac3d33df JK |
102 | GIT_ERROR_CHECK_ALLOC(out->old_prefix); |
103 | GIT_ERROR_CHECK_ALLOC(out->new_prefix); | |
8147b1af ET |
104 | |
105 | return 0; | |
106 | } | |
107 | ||
8d44f8b7 ET |
108 | static int patch_generated_init( |
109 | git_patch_generated *patch, git_diff *diff, size_t delta_index) | |
114f5a6c RB |
110 | { |
111 | int error = 0; | |
112 | ||
113 | memset(patch, 0, sizeof(*patch)); | |
804d5fe9 ET |
114 | |
115 | patch->diff = diff; | |
116 | patch->base.repo = diff->repo; | |
117 | patch->base.delta = git_vector_get(&diff->deltas, delta_index); | |
114f5a6c RB |
118 | patch->delta_index = delta_index; |
119 | ||
8d44f8b7 | 120 | if ((error = patch_generated_normalize_options( |
804d5fe9 | 121 | &patch->base.diff_opts, &diff->opts)) < 0 || |
360f42f4 | 122 | (error = git_diff_file_content__init_from_diff( |
804d5fe9 | 123 | &patch->ofile, diff, patch->base.delta, true)) < 0 || |
8147b1af | 124 | (error = git_diff_file_content__init_from_diff( |
804d5fe9 | 125 | &patch->nfile, diff, patch->base.delta, false)) < 0) |
114f5a6c RB |
126 | return error; |
127 | ||
8d44f8b7 | 128 | patch_generated_init_common(patch); |
114f5a6c RB |
129 | |
130 | return 0; | |
131 | } | |
132 | ||
8d44f8b7 ET |
133 | static int patch_generated_alloc_from_diff( |
134 | git_patch_generated **out, git_diff *diff, size_t delta_index) | |
114f5a6c RB |
135 | { |
136 | int error; | |
8d44f8b7 | 137 | git_patch_generated *patch = git__calloc(1, sizeof(git_patch_generated)); |
ac3d33df | 138 | GIT_ERROR_CHECK_ALLOC(patch); |
114f5a6c | 139 | |
8d44f8b7 ET |
140 | if (!(error = patch_generated_init(patch, diff, delta_index))) { |
141 | patch->flags |= GIT_PATCH_GENERATED_ALLOCATED; | |
eae0bfdc | 142 | GIT_REFCOUNT_INC(&patch->base); |
114f5a6c RB |
143 | } else { |
144 | git__free(patch); | |
145 | patch = NULL; | |
146 | } | |
147 | ||
148 | *out = patch; | |
149 | return error; | |
150 | } | |
151 | ||
8d44f8b7 | 152 | GIT_INLINE(bool) should_skip_binary(git_patch_generated *patch, git_diff_file *file) |
8147b1af | 153 | { |
804d5fe9 | 154 | if ((patch->base.diff_opts.flags & GIT_DIFF_SHOW_BINARY) != 0) |
8147b1af ET |
155 | return false; |
156 | ||
157 | return (file->flags & GIT_DIFF_FLAG_BINARY) != 0; | |
158 | } | |
159 | ||
8d44f8b7 | 160 | static bool patch_generated_diffable(git_patch_generated *patch) |
54077091 ET |
161 | { |
162 | size_t olen, nlen; | |
163 | ||
804d5fe9 | 164 | if (patch->base.delta->status == GIT_DELTA_UNMODIFIED) |
54077091 ET |
165 | return false; |
166 | ||
167 | /* if we've determined this to be binary (and we are not showing binary | |
168 | * data) then we have skipped loading the map data. instead, query the | |
169 | * file data itself. | |
170 | */ | |
804d5fe9 ET |
171 | if ((patch->base.delta->flags & GIT_DIFF_FLAG_BINARY) != 0 && |
172 | (patch->base.diff_opts.flags & GIT_DIFF_SHOW_BINARY) == 0) { | |
54077091 ET |
173 | olen = (size_t)patch->ofile.file->size; |
174 | nlen = (size_t)patch->nfile.file->size; | |
175 | } else { | |
176 | olen = patch->ofile.map.len; | |
177 | nlen = patch->nfile.map.len; | |
178 | } | |
179 | ||
180 | /* if both sides are empty, files are identical */ | |
181 | if (!olen && !nlen) | |
182 | return false; | |
183 | ||
184 | /* otherwise, check the file sizes and the oid */ | |
185 | return (olen != nlen || | |
186 | !git_oid_equal(&patch->ofile.file->id, &patch->nfile.file->id)); | |
187 | } | |
188 | ||
8d44f8b7 | 189 | static int patch_generated_load(git_patch_generated *patch, git_patch_generated_output *output) |
114f5a6c RB |
190 | { |
191 | int error = 0; | |
192 | bool incomplete_data; | |
193 | ||
8d44f8b7 | 194 | if ((patch->flags & GIT_PATCH_GENERATED_LOADED) != 0) |
114f5a6c RB |
195 | return 0; |
196 | ||
197 | /* if no hunk and data callbacks and user doesn't care if data looks | |
198 | * binary, then there is no need to actually load the data | |
199 | */ | |
5dc98298 | 200 | if ((patch->ofile.opts_flags & GIT_DIFF_SKIP_BINARY_CHECK) != 0 && |
8147b1af | 201 | output && !output->binary_cb && !output->hunk_cb && !output->data_cb) |
114f5a6c RB |
202 | return 0; |
203 | ||
114f5a6c | 204 | incomplete_data = |
74ded024 | 205 | (((patch->ofile.flags & GIT_DIFF_FLAG__NO_DATA) != 0 || |
9950bb4e | 206 | (patch->ofile.file->flags & GIT_DIFF_FLAG_VALID_ID) != 0) && |
74ded024 | 207 | ((patch->nfile.flags & GIT_DIFF_FLAG__NO_DATA) != 0 || |
9950bb4e | 208 | (patch->nfile.file->flags & GIT_DIFF_FLAG_VALID_ID) != 0)); |
114f5a6c | 209 | |
41019152 PS |
210 | if ((error = git_diff_file_content__load( |
211 | &patch->ofile, &patch->base.diff_opts)) < 0 || | |
22a2d3d5 | 212 | (error = git_diff_file_content__load( |
41019152 PS |
213 | &patch->nfile, &patch->base.diff_opts)) < 0 || |
214 | should_skip_binary(patch, patch->nfile.file)) | |
215 | goto cleanup; | |
114f5a6c | 216 | |
c67ff958 RB |
217 | /* if previously missing an oid, and now that we have it the two sides |
218 | * are the same (and not submodules), update MODIFIED -> UNMODIFIED | |
219 | */ | |
114f5a6c | 220 | if (incomplete_data && |
74ded024 | 221 | patch->ofile.file->mode == patch->nfile.file->mode && |
c67ff958 | 222 | patch->ofile.file->mode != GIT_FILEMODE_COMMIT && |
9950bb4e | 223 | git_oid_equal(&patch->ofile.file->id, &patch->nfile.file->id) && |
804d5fe9 ET |
224 | patch->base.delta->status == GIT_DELTA_MODIFIED) /* not RENAMED/COPIED! */ |
225 | patch->base.delta->status = GIT_DELTA_UNMODIFIED; | |
114f5a6c RB |
226 | |
227 | cleanup: | |
8d44f8b7 | 228 | patch_generated_update_binary(patch); |
114f5a6c RB |
229 | |
230 | if (!error) { | |
8d44f8b7 ET |
231 | if (patch_generated_diffable(patch)) |
232 | patch->flags |= GIT_PATCH_GENERATED_DIFFABLE; | |
114f5a6c | 233 | |
8d44f8b7 | 234 | patch->flags |= GIT_PATCH_GENERATED_LOADED; |
114f5a6c RB |
235 | } |
236 | ||
237 | return error; | |
238 | } | |
239 | ||
8d44f8b7 ET |
240 | static int patch_generated_invoke_file_callback( |
241 | git_patch_generated *patch, git_patch_generated_output *output) | |
114f5a6c | 242 | { |
96869a4e | 243 | float progress = patch->diff ? |
114f5a6c RB |
244 | ((float)patch->delta_index / patch->diff->deltas.length) : 1.0f; |
245 | ||
25e0b157 RB |
246 | if (!output->file_cb) |
247 | return 0; | |
114f5a6c | 248 | |
ac3d33df | 249 | return git_error_set_after_callback_function( |
804d5fe9 | 250 | output->file_cb(patch->base.delta, progress, output->payload), |
25e0b157 | 251 | "git_patch"); |
114f5a6c RB |
252 | } |
253 | ||
8147b1af ET |
254 | static int create_binary( |
255 | git_diff_binary_t *out_type, | |
256 | char **out_data, | |
257 | size_t *out_datalen, | |
258 | size_t *out_inflatedlen, | |
259 | const char *a_data, | |
260 | size_t a_datalen, | |
261 | const char *b_data, | |
262 | size_t b_datalen) | |
263 | { | |
e579e0f7 | 264 | git_str deflate = GIT_STR_INIT, delta = GIT_STR_INIT; |
34b32053 | 265 | size_t delta_data_len = 0; |
8147b1af ET |
266 | int error; |
267 | ||
268 | /* The git_delta function accepts unsigned long only */ | |
269 | if (!git__is_ulong(a_datalen) || !git__is_ulong(b_datalen)) | |
270 | return GIT_EBUFS; | |
271 | ||
272 | if ((error = git_zstream_deflatebuf(&deflate, b_data, b_datalen)) < 0) | |
273 | goto done; | |
274 | ||
275 | /* The git_delta function accepts unsigned long only */ | |
276 | if (!git__is_ulong(deflate.size)) { | |
277 | error = GIT_EBUFS; | |
278 | goto done; | |
279 | } | |
280 | ||
281 | if (a_datalen && b_datalen) { | |
1cd65991 | 282 | void *delta_data; |
8147b1af | 283 | |
1cd65991 ET |
284 | error = git_delta(&delta_data, &delta_data_len, |
285 | a_data, a_datalen, | |
286 | b_data, b_datalen, | |
287 | deflate.size); | |
288 | ||
289 | if (error == 0) { | |
8147b1af | 290 | error = git_zstream_deflatebuf( |
60e15ecd | 291 | &delta, delta_data, delta_data_len); |
8147b1af ET |
292 | |
293 | git__free(delta_data); | |
1cd65991 ET |
294 | } else if (error == GIT_EBUFS) { |
295 | error = 0; | |
8147b1af | 296 | } |
1cd65991 ET |
297 | |
298 | if (error < 0) | |
299 | goto done; | |
8147b1af ET |
300 | } |
301 | ||
302 | if (delta.size && delta.size < deflate.size) { | |
303 | *out_type = GIT_DIFF_BINARY_DELTA; | |
304 | *out_datalen = delta.size; | |
e579e0f7 | 305 | *out_data = git_str_detach(&delta); |
8147b1af ET |
306 | *out_inflatedlen = delta_data_len; |
307 | } else { | |
308 | *out_type = GIT_DIFF_BINARY_LITERAL; | |
309 | *out_datalen = deflate.size; | |
e579e0f7 | 310 | *out_data = git_str_detach(&deflate); |
8147b1af ET |
311 | *out_inflatedlen = b_datalen; |
312 | } | |
313 | ||
314 | done: | |
e579e0f7 MB |
315 | git_str_dispose(&deflate); |
316 | git_str_dispose(&delta); | |
8147b1af ET |
317 | |
318 | return error; | |
319 | } | |
320 | ||
8d44f8b7 | 321 | static int diff_binary(git_patch_generated_output *output, git_patch_generated *patch) |
8147b1af | 322 | { |
adedac5a | 323 | git_diff_binary binary = {0}; |
8147b1af ET |
324 | const char *old_data = patch->ofile.map.data; |
325 | const char *new_data = patch->nfile.map.data; | |
326 | size_t old_len = patch->ofile.map.len, | |
327 | new_len = patch->nfile.map.len; | |
328 | int error; | |
329 | ||
4b34f687 PS |
330 | /* Only load contents if the user actually wants to diff |
331 | * binary files. */ | |
332 | if (patch->base.diff_opts.flags & GIT_DIFF_SHOW_BINARY) { | |
adedac5a ET |
333 | binary.contains_data = 1; |
334 | ||
4b34f687 PS |
335 | /* Create the old->new delta (as the "new" side of the patch), |
336 | * and the new->old delta (as the "old" side) | |
337 | */ | |
338 | if ((error = create_binary(&binary.old_file.type, | |
339 | (char **)&binary.old_file.data, | |
340 | &binary.old_file.datalen, | |
341 | &binary.old_file.inflatedlen, | |
342 | new_data, new_len, old_data, old_len)) < 0 || | |
343 | (error = create_binary(&binary.new_file.type, | |
344 | (char **)&binary.new_file.data, | |
345 | &binary.new_file.datalen, | |
346 | &binary.new_file.inflatedlen, | |
347 | old_data, old_len, new_data, new_len)) < 0) | |
348 | return error; | |
349 | } | |
8147b1af | 350 | |
ac3d33df | 351 | error = git_error_set_after_callback_function( |
804d5fe9 | 352 | output->binary_cb(patch->base.delta, &binary, output->payload), |
8147b1af | 353 | "git_patch"); |
9568660f CMN |
354 | |
355 | git__free((char *) binary.old_file.data); | |
356 | git__free((char *) binary.new_file.data); | |
357 | ||
358 | return error; | |
8147b1af ET |
359 | } |
360 | ||
8d44f8b7 ET |
361 | static int patch_generated_create( |
362 | git_patch_generated *patch, | |
363 | git_patch_generated_output *output) | |
114f5a6c RB |
364 | { |
365 | int error = 0; | |
366 | ||
8d44f8b7 | 367 | if ((patch->flags & GIT_PATCH_GENERATED_DIFFED) != 0) |
114f5a6c RB |
368 | return 0; |
369 | ||
8147b1af ET |
370 | /* if we are not looking at the binary or text data, don't do the diff */ |
371 | if (!output->binary_cb && !output->hunk_cb && !output->data_cb) | |
69c66b55 RB |
372 | return 0; |
373 | ||
8d44f8b7 ET |
374 | if ((patch->flags & GIT_PATCH_GENERATED_LOADED) == 0 && |
375 | (error = patch_generated_load(patch, output)) < 0) | |
114f5a6c RB |
376 | return error; |
377 | ||
8d44f8b7 | 378 | if ((patch->flags & GIT_PATCH_GENERATED_DIFFABLE) == 0) |
114f5a6c RB |
379 | return 0; |
380 | ||
804d5fe9 | 381 | if ((patch->base.delta->flags & GIT_DIFF_FLAG_BINARY) != 0) { |
8147b1af ET |
382 | if (output->binary_cb) |
383 | error = diff_binary(output, patch); | |
384 | } | |
385 | else { | |
386 | if (output->diff_cb) | |
387 | error = output->diff_cb(output, patch); | |
388 | } | |
114f5a6c | 389 | |
8d44f8b7 | 390 | patch->flags |= GIT_PATCH_GENERATED_DIFFED; |
114f5a6c RB |
391 | return error; |
392 | } | |
393 | ||
3ff1d123 | 394 | static int diff_required(git_diff *diff, const char *action) |
114f5a6c RB |
395 | { |
396 | if (diff) | |
397 | return 0; | |
ac3d33df | 398 | git_error_set(GIT_ERROR_INVALID, "must provide valid diff to %s", action); |
114f5a6c RB |
399 | return -1; |
400 | } | |
401 | ||
114f5a6c | 402 | typedef struct { |
8d44f8b7 | 403 | git_patch_generated patch; |
114f5a6c | 404 | git_diff_delta delta; |
74ded024 | 405 | char paths[GIT_FLEX_ARRAY]; |
8d44f8b7 | 406 | } patch_generated_with_delta; |
114f5a6c | 407 | |
8d44f8b7 | 408 | static int diff_single_generate(patch_generated_with_delta *pd, git_xdiff_output *xo) |
114f5a6c RB |
409 | { |
410 | int error = 0; | |
8d44f8b7 | 411 | git_patch_generated *patch = &pd->patch; |
74ded024 RB |
412 | bool has_old = ((patch->ofile.flags & GIT_DIFF_FLAG__NO_DATA) == 0); |
413 | bool has_new = ((patch->nfile.flags & GIT_DIFF_FLAG__NO_DATA) == 0); | |
114f5a6c | 414 | |
f9c824c5 | 415 | pd->delta.status = has_new ? |
114f5a6c RB |
416 | (has_old ? GIT_DELTA_MODIFIED : GIT_DELTA_ADDED) : |
417 | (has_old ? GIT_DELTA_DELETED : GIT_DELTA_UNTRACKED); | |
418 | ||
9950bb4e | 419 | if (git_oid_equal(&patch->nfile.file->id, &patch->ofile.file->id)) |
f9c824c5 | 420 | pd->delta.status = GIT_DELTA_UNMODIFIED; |
114f5a6c | 421 | |
804d5fe9 | 422 | patch->base.delta = &pd->delta; |
114f5a6c | 423 | |
8d44f8b7 | 424 | patch_generated_init_common(patch); |
114f5a6c | 425 | |
74ded024 | 426 | if (pd->delta.status == GIT_DELTA_UNMODIFIED && |
adedac5a ET |
427 | !(patch->ofile.opts_flags & GIT_DIFF_INCLUDE_UNMODIFIED)) { |
428 | ||
429 | /* Even empty patches are flagged as binary, and even though | |
430 | * there's no difference, we flag this as "containing data" | |
431 | * (the data is known to be empty, as opposed to wholly unknown). | |
432 | */ | |
433 | if (patch->base.diff_opts.flags & GIT_DIFF_SHOW_BINARY) | |
434 | patch->base.binary.contains_data = 1; | |
435 | ||
74ded024 | 436 | return error; |
adedac5a | 437 | } |
74ded024 | 438 | |
8d44f8b7 | 439 | error = patch_generated_invoke_file_callback(patch, (git_patch_generated_output *)xo); |
114f5a6c RB |
440 | |
441 | if (!error) | |
8d44f8b7 | 442 | error = patch_generated_create(patch, (git_patch_generated_output *)xo); |
114f5a6c | 443 | |
114f5a6c RB |
444 | return error; |
445 | } | |
446 | ||
8d44f8b7 ET |
447 | static int patch_generated_from_sources( |
448 | patch_generated_with_delta *pd, | |
f9c824c5 | 449 | git_xdiff_output *xo, |
6789b7a7 RB |
450 | git_diff_file_content_src *oldsrc, |
451 | git_diff_file_content_src *newsrc, | |
f9c824c5 | 452 | const git_diff_options *opts) |
114f5a6c RB |
453 | { |
454 | int error = 0; | |
114f5a6c | 455 | git_repository *repo = |
6789b7a7 RB |
456 | oldsrc->blob ? git_blob_owner(oldsrc->blob) : |
457 | newsrc->blob ? git_blob_owner(newsrc->blob) : NULL; | |
458 | git_diff_file *lfile = &pd->delta.old_file, *rfile = &pd->delta.new_file; | |
459 | git_diff_file_content *ldata = &pd->patch.ofile, *rdata = &pd->patch.nfile; | |
114f5a6c | 460 | |
8d44f8b7 | 461 | if ((error = patch_generated_normalize_options(&pd->patch.base.diff_opts, opts)) < 0) |
3208df37 | 462 | return error; |
114f5a6c | 463 | |
114f5a6c | 464 | if (opts && (opts->flags & GIT_DIFF_REVERSE) != 0) { |
6789b7a7 RB |
465 | void *tmp = lfile; lfile = rfile; rfile = tmp; |
466 | tmp = ldata; ldata = rdata; rdata = tmp; | |
114f5a6c RB |
467 | } |
468 | ||
804d5fe9 | 469 | pd->patch.base.delta = &pd->delta; |
74ded024 | 470 | |
6789b7a7 RB |
471 | if (!oldsrc->as_path) { |
472 | if (newsrc->as_path) | |
473 | oldsrc->as_path = newsrc->as_path; | |
474 | else | |
475 | oldsrc->as_path = newsrc->as_path = "file"; | |
476 | } | |
477 | else if (!newsrc->as_path) | |
478 | newsrc->as_path = oldsrc->as_path; | |
479 | ||
480 | lfile->path = oldsrc->as_path; | |
481 | rfile->path = newsrc->as_path; | |
74ded024 | 482 | |
6789b7a7 RB |
483 | if ((error = git_diff_file_content__init_from_src( |
484 | ldata, repo, opts, oldsrc, lfile)) < 0 || | |
485 | (error = git_diff_file_content__init_from_src( | |
486 | rdata, repo, opts, newsrc, rfile)) < 0) | |
f9c824c5 RB |
487 | return error; |
488 | ||
489 | return diff_single_generate(pd, xo); | |
490 | } | |
491 | ||
8d44f8b7 ET |
492 | static int patch_generated_with_delta_alloc( |
493 | patch_generated_with_delta **out, | |
74ded024 RB |
494 | const char **old_path, |
495 | const char **new_path) | |
496 | { | |
8d44f8b7 | 497 | patch_generated_with_delta *pd; |
74ded024 RB |
498 | size_t old_len = *old_path ? strlen(*old_path) : 0; |
499 | size_t new_len = *new_path ? strlen(*new_path) : 0; | |
f1453c59 | 500 | size_t alloc_len; |
74ded024 | 501 | |
ac3d33df JK |
502 | GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, sizeof(*pd), old_len); |
503 | GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, new_len); | |
504 | GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, 2); | |
392702ee ET |
505 | |
506 | *out = pd = git__calloc(1, alloc_len); | |
ac3d33df | 507 | GIT_ERROR_CHECK_ALLOC(pd); |
74ded024 | 508 | |
8d44f8b7 | 509 | pd->patch.flags = GIT_PATCH_GENERATED_ALLOCATED; |
74ded024 RB |
510 | |
511 | if (*old_path) { | |
512 | memcpy(&pd->paths[0], *old_path, old_len); | |
513 | *old_path = &pd->paths[0]; | |
514 | } else if (*new_path) | |
515 | *old_path = &pd->paths[old_len + 1]; | |
516 | ||
517 | if (*new_path) { | |
518 | memcpy(&pd->paths[old_len + 1], *new_path, new_len); | |
519 | *new_path = &pd->paths[old_len + 1]; | |
520 | } else if (*old_path) | |
521 | *new_path = &pd->paths[0]; | |
522 | ||
523 | return 0; | |
524 | } | |
525 | ||
6789b7a7 RB |
526 | static int diff_from_sources( |
527 | git_diff_file_content_src *oldsrc, | |
528 | git_diff_file_content_src *newsrc, | |
f9c824c5 RB |
529 | const git_diff_options *opts, |
530 | git_diff_file_cb file_cb, | |
8147b1af | 531 | git_diff_binary_cb binary_cb, |
f9c824c5 | 532 | git_diff_hunk_cb hunk_cb, |
3ff1d123 | 533 | git_diff_line_cb data_cb, |
f9c824c5 RB |
534 | void *payload) |
535 | { | |
536 | int error = 0; | |
8d44f8b7 | 537 | patch_generated_with_delta pd; |
f9c824c5 RB |
538 | git_xdiff_output xo; |
539 | ||
f9c824c5 | 540 | memset(&xo, 0, sizeof(xo)); |
f9c824c5 | 541 | diff_output_init( |
8147b1af | 542 | &xo.output, opts, file_cb, binary_cb, hunk_cb, data_cb, payload); |
f9c824c5 | 543 | git_xdiff_init(&xo, opts); |
114f5a6c | 544 | |
96869a4e | 545 | memset(&pd, 0, sizeof(pd)); |
6789b7a7 | 546 | |
8d44f8b7 | 547 | error = patch_generated_from_sources(&pd, &xo, oldsrc, newsrc, opts); |
114f5a6c | 548 | |
804d5fe9 | 549 | git_patch_free(&pd.patch.base); |
114f5a6c RB |
550 | |
551 | return error; | |
552 | } | |
553 | ||
6789b7a7 | 554 | static int patch_from_sources( |
3ff1d123 | 555 | git_patch **out, |
6789b7a7 RB |
556 | git_diff_file_content_src *oldsrc, |
557 | git_diff_file_content_src *newsrc, | |
f9c824c5 RB |
558 | const git_diff_options *opts) |
559 | { | |
560 | int error = 0; | |
8d44f8b7 | 561 | patch_generated_with_delta *pd; |
f9c824c5 RB |
562 | git_xdiff_output xo; |
563 | ||
c25aa7cd | 564 | GIT_ASSERT_ARG(out); |
f9c824c5 RB |
565 | *out = NULL; |
566 | ||
8d44f8b7 | 567 | if ((error = patch_generated_with_delta_alloc( |
6789b7a7 RB |
568 | &pd, &oldsrc->as_path, &newsrc->as_path)) < 0) |
569 | return error; | |
f9c824c5 RB |
570 | |
571 | memset(&xo, 0, sizeof(xo)); | |
8dd8aa48 | 572 | diff_output_to_patch(&xo.output, &pd->patch); |
f9c824c5 RB |
573 | git_xdiff_init(&xo, opts); |
574 | ||
8d44f8b7 | 575 | if (!(error = patch_generated_from_sources(pd, &xo, oldsrc, newsrc, opts))) |
3ff1d123 | 576 | *out = (git_patch *)pd; |
f9c824c5 | 577 | else |
3ff1d123 | 578 | git_patch_free((git_patch *)pd); |
f9c824c5 RB |
579 | |
580 | return error; | |
581 | } | |
582 | ||
6789b7a7 | 583 | int git_diff_blobs( |
114f5a6c | 584 | const git_blob *old_blob, |
74ded024 | 585 | const char *old_path, |
6789b7a7 RB |
586 | const git_blob *new_blob, |
587 | const char *new_path, | |
588 | const git_diff_options *opts, | |
589 | git_diff_file_cb file_cb, | |
8147b1af | 590 | git_diff_binary_cb binary_cb, |
6789b7a7 RB |
591 | git_diff_hunk_cb hunk_cb, |
592 | git_diff_line_cb data_cb, | |
593 | void *payload) | |
114f5a6c | 594 | { |
6789b7a7 RB |
595 | git_diff_file_content_src osrc = |
596 | GIT_DIFF_FILE_CONTENT_SRC__BLOB(old_blob, old_path); | |
597 | git_diff_file_content_src nsrc = | |
598 | GIT_DIFF_FILE_CONTENT_SRC__BLOB(new_blob, new_path); | |
599 | return diff_from_sources( | |
8147b1af | 600 | &osrc, &nsrc, opts, file_cb, binary_cb, hunk_cb, data_cb, payload); |
6789b7a7 | 601 | } |
74ded024 | 602 | |
6789b7a7 RB |
603 | int git_patch_from_blobs( |
604 | git_patch **out, | |
605 | const git_blob *old_blob, | |
606 | const char *old_path, | |
607 | const git_blob *new_blob, | |
608 | const char *new_path, | |
609 | const git_diff_options *opts) | |
610 | { | |
611 | git_diff_file_content_src osrc = | |
612 | GIT_DIFF_FILE_CONTENT_SRC__BLOB(old_blob, old_path); | |
613 | git_diff_file_content_src nsrc = | |
614 | GIT_DIFF_FILE_CONTENT_SRC__BLOB(new_blob, new_path); | |
615 | return patch_from_sources(out, &osrc, &nsrc, opts); | |
f9c824c5 RB |
616 | } |
617 | ||
618 | int git_diff_blob_to_buffer( | |
619 | const git_blob *old_blob, | |
74ded024 | 620 | const char *old_path, |
f9c824c5 RB |
621 | const char *buf, |
622 | size_t buflen, | |
74ded024 | 623 | const char *buf_path, |
f9c824c5 RB |
624 | const git_diff_options *opts, |
625 | git_diff_file_cb file_cb, | |
8147b1af | 626 | git_diff_binary_cb binary_cb, |
f9c824c5 | 627 | git_diff_hunk_cb hunk_cb, |
3ff1d123 | 628 | git_diff_line_cb data_cb, |
f9c824c5 RB |
629 | void *payload) |
630 | { | |
6789b7a7 RB |
631 | git_diff_file_content_src osrc = |
632 | GIT_DIFF_FILE_CONTENT_SRC__BLOB(old_blob, old_path); | |
633 | git_diff_file_content_src nsrc = | |
634 | GIT_DIFF_FILE_CONTENT_SRC__BUF(buf, buflen, buf_path); | |
635 | return diff_from_sources( | |
8147b1af | 636 | &osrc, &nsrc, opts, file_cb, binary_cb, hunk_cb, data_cb, payload); |
f9c824c5 RB |
637 | } |
638 | ||
3ff1d123 RB |
639 | int git_patch_from_blob_and_buffer( |
640 | git_patch **out, | |
f9c824c5 | 641 | const git_blob *old_blob, |
74ded024 | 642 | const char *old_path, |
eae0bfdc | 643 | const void *buf, |
f9c824c5 | 644 | size_t buflen, |
74ded024 | 645 | const char *buf_path, |
f9c824c5 RB |
646 | const git_diff_options *opts) |
647 | { | |
6789b7a7 RB |
648 | git_diff_file_content_src osrc = |
649 | GIT_DIFF_FILE_CONTENT_SRC__BLOB(old_blob, old_path); | |
650 | git_diff_file_content_src nsrc = | |
651 | GIT_DIFF_FILE_CONTENT_SRC__BUF(buf, buflen, buf_path); | |
652 | return patch_from_sources(out, &osrc, &nsrc, opts); | |
653 | } | |
74ded024 | 654 | |
6789b7a7 RB |
655 | int git_diff_buffers( |
656 | const void *old_buf, | |
657 | size_t old_len, | |
658 | const char *old_path, | |
659 | const void *new_buf, | |
660 | size_t new_len, | |
661 | const char *new_path, | |
662 | const git_diff_options *opts, | |
663 | git_diff_file_cb file_cb, | |
8147b1af | 664 | git_diff_binary_cb binary_cb, |
6789b7a7 RB |
665 | git_diff_hunk_cb hunk_cb, |
666 | git_diff_line_cb data_cb, | |
667 | void *payload) | |
668 | { | |
669 | git_diff_file_content_src osrc = | |
670 | GIT_DIFF_FILE_CONTENT_SRC__BUF(old_buf, old_len, old_path); | |
671 | git_diff_file_content_src nsrc = | |
672 | GIT_DIFF_FILE_CONTENT_SRC__BUF(new_buf, new_len, new_path); | |
673 | return diff_from_sources( | |
8147b1af | 674 | &osrc, &nsrc, opts, file_cb, binary_cb, hunk_cb, data_cb, payload); |
6789b7a7 | 675 | } |
114f5a6c | 676 | |
6789b7a7 RB |
677 | int git_patch_from_buffers( |
678 | git_patch **out, | |
679 | const void *old_buf, | |
680 | size_t old_len, | |
681 | const char *old_path, | |
eae0bfdc | 682 | const void *new_buf, |
6789b7a7 RB |
683 | size_t new_len, |
684 | const char *new_path, | |
685 | const git_diff_options *opts) | |
686 | { | |
687 | git_diff_file_content_src osrc = | |
688 | GIT_DIFF_FILE_CONTENT_SRC__BUF(old_buf, old_len, old_path); | |
689 | git_diff_file_content_src nsrc = | |
690 | GIT_DIFF_FILE_CONTENT_SRC__BUF(new_buf, new_len, new_path); | |
691 | return patch_from_sources(out, &osrc, &nsrc, opts); | |
114f5a6c RB |
692 | } |
693 | ||
b859faa6 | 694 | int git_patch_generated_from_diff( |
10672e3e | 695 | git_patch **patch_ptr, git_diff *diff, size_t idx) |
114f5a6c RB |
696 | { |
697 | int error = 0; | |
698 | git_xdiff_output xo; | |
699 | git_diff_delta *delta = NULL; | |
8d44f8b7 | 700 | git_patch_generated *patch = NULL; |
114f5a6c RB |
701 | |
702 | if (patch_ptr) *patch_ptr = NULL; | |
114f5a6c | 703 | |
3ff1d123 | 704 | if (diff_required(diff, "git_patch_from_diff") < 0) |
114f5a6c RB |
705 | return -1; |
706 | ||
707 | delta = git_vector_get(&diff->deltas, idx); | |
708 | if (!delta) { | |
ac3d33df | 709 | git_error_set(GIT_ERROR_INVALID, "index out of range for delta in diff"); |
114f5a6c RB |
710 | return GIT_ENOTFOUND; |
711 | } | |
712 | ||
114f5a6c RB |
713 | if (git_diff_delta__should_skip(&diff->opts, delta)) |
714 | return 0; | |
715 | ||
716 | /* don't load the patch data unless we need it for binary check */ | |
717 | if (!patch_ptr && | |
718 | ((delta->flags & DIFF_FLAGS_KNOWN_BINARY) != 0 || | |
719 | (diff->opts.flags & GIT_DIFF_SKIP_BINARY_CHECK) != 0)) | |
720 | return 0; | |
721 | ||
8d44f8b7 | 722 | if ((error = patch_generated_alloc_from_diff(&patch, diff, idx)) < 0) |
114f5a6c RB |
723 | return error; |
724 | ||
96869a4e | 725 | memset(&xo, 0, sizeof(xo)); |
8dd8aa48 | 726 | diff_output_to_patch(&xo.output, patch); |
114f5a6c RB |
727 | git_xdiff_init(&xo, &diff->opts); |
728 | ||
8d44f8b7 | 729 | error = patch_generated_invoke_file_callback(patch, &xo.output); |
114f5a6c RB |
730 | |
731 | if (!error) | |
8d44f8b7 | 732 | error = patch_generated_create(patch, &xo.output); |
114f5a6c RB |
733 | |
734 | if (!error) { | |
25e0b157 RB |
735 | /* TODO: if cumulative diff size is < 0.5 total size, flatten patch */ |
736 | /* TODO: and unload the file content */ | |
114f5a6c RB |
737 | } |
738 | ||
739 | if (error || !patch_ptr) | |
804d5fe9 | 740 | git_patch_free(&patch->base); |
114f5a6c | 741 | else |
804d5fe9 | 742 | *patch_ptr = &patch->base; |
114f5a6c | 743 | |
114f5a6c RB |
744 | return error; |
745 | } | |
746 | ||
8d44f8b7 | 747 | git_diff_driver *git_patch_generated_driver(git_patch_generated *patch) |
360f42f4 RB |
748 | { |
749 | /* ofile driver is representative for whole patch */ | |
750 | return patch->ofile.driver; | |
751 | } | |
752 | ||
e579e0f7 MB |
753 | int git_patch_generated_old_data( |
754 | char **ptr, long *len, git_patch_generated *patch) | |
360f42f4 | 755 | { |
e579e0f7 MB |
756 | if (patch->ofile.map.len > LONG_MAX || |
757 | patch->ofile.map.len > GIT_XDIFF_MAX_SIZE) { | |
758 | git_error_set(GIT_ERROR_INVALID, "files too large for diff"); | |
759 | return -1; | |
760 | } | |
761 | ||
360f42f4 | 762 | *ptr = patch->ofile.map.data; |
e579e0f7 MB |
763 | *len = (long)patch->ofile.map.len; |
764 | ||
765 | return 0; | |
360f42f4 RB |
766 | } |
767 | ||
e579e0f7 MB |
768 | int git_patch_generated_new_data( |
769 | char **ptr, long *len, git_patch_generated *patch) | |
360f42f4 | 770 | { |
e579e0f7 MB |
771 | if (patch->ofile.map.len > LONG_MAX || |
772 | patch->ofile.map.len > GIT_XDIFF_MAX_SIZE) { | |
773 | git_error_set(GIT_ERROR_INVALID, "files too large for diff"); | |
774 | return -1; | |
775 | } | |
776 | ||
360f42f4 | 777 | *ptr = patch->nfile.map.data; |
e579e0f7 MB |
778 | *len = (long)patch->nfile.map.len; |
779 | ||
780 | return 0; | |
360f42f4 RB |
781 | } |
782 | ||
8d44f8b7 | 783 | static int patch_generated_file_cb( |
114f5a6c RB |
784 | const git_diff_delta *delta, |
785 | float progress, | |
786 | void *payload) | |
787 | { | |
f9c824c5 | 788 | GIT_UNUSED(delta); GIT_UNUSED(progress); GIT_UNUSED(payload); |
114f5a6c RB |
789 | return 0; |
790 | } | |
791 | ||
8d44f8b7 | 792 | static int patch_generated_binary_cb( |
8147b1af ET |
793 | const git_diff_delta *delta, |
794 | const git_diff_binary *binary, | |
795 | void *payload) | |
796 | { | |
797 | git_patch *patch = payload; | |
798 | ||
799 | GIT_UNUSED(delta); | |
800 | ||
801 | memcpy(&patch->binary, binary, sizeof(git_diff_binary)); | |
802 | ||
803 | if (binary->old_file.data) { | |
804 | patch->binary.old_file.data = git__malloc(binary->old_file.datalen); | |
ac3d33df | 805 | GIT_ERROR_CHECK_ALLOC(patch->binary.old_file.data); |
8147b1af ET |
806 | |
807 | memcpy((char *)patch->binary.old_file.data, | |
808 | binary->old_file.data, binary->old_file.datalen); | |
809 | } | |
810 | ||
811 | if (binary->new_file.data) { | |
812 | patch->binary.new_file.data = git__malloc(binary->new_file.datalen); | |
ac3d33df | 813 | GIT_ERROR_CHECK_ALLOC(patch->binary.new_file.data); |
8147b1af ET |
814 | |
815 | memcpy((char *)patch->binary.new_file.data, | |
816 | binary->new_file.data, binary->new_file.datalen); | |
817 | } | |
818 | ||
819 | return 0; | |
820 | } | |
821 | ||
804d5fe9 | 822 | static int git_patch_hunk_cb( |
114f5a6c | 823 | const git_diff_delta *delta, |
3ff1d123 | 824 | const git_diff_hunk *hunk_, |
114f5a6c RB |
825 | void *payload) |
826 | { | |
8d44f8b7 | 827 | git_patch_generated *patch = payload; |
804d5fe9 | 828 | git_patch_hunk *hunk; |
114f5a6c RB |
829 | |
830 | GIT_UNUSED(delta); | |
831 | ||
804d5fe9 | 832 | hunk = git_array_alloc(patch->base.hunks); |
ac3d33df | 833 | GIT_ERROR_CHECK_ALLOC(hunk); |
114f5a6c | 834 | |
3ff1d123 | 835 | memcpy(&hunk->hunk, hunk_, sizeof(hunk->hunk)); |
114f5a6c | 836 | |
804d5fe9 | 837 | patch->base.header_size += hunk_->header_len; |
197b8966 | 838 | |
804d5fe9 | 839 | hunk->line_start = git_array_size(patch->base.lines); |
114f5a6c RB |
840 | hunk->line_count = 0; |
841 | ||
114f5a6c RB |
842 | return 0; |
843 | } | |
844 | ||
8d44f8b7 | 845 | static int patch_generated_line_cb( |
114f5a6c | 846 | const git_diff_delta *delta, |
3ff1d123 | 847 | const git_diff_hunk *hunk_, |
3b5f7954 | 848 | const git_diff_line *line_, |
114f5a6c RB |
849 | void *payload) |
850 | { | |
8d44f8b7 | 851 | git_patch_generated *patch = payload; |
804d5fe9 | 852 | git_patch_hunk *hunk; |
22a2d3d5 | 853 | git_diff_line *line; |
114f5a6c RB |
854 | |
855 | GIT_UNUSED(delta); | |
3ff1d123 | 856 | GIT_UNUSED(hunk_); |
114f5a6c | 857 | |
804d5fe9 | 858 | hunk = git_array_last(patch->base.hunks); |
c25aa7cd | 859 | GIT_ASSERT(hunk); /* programmer error if no hunk is available */ |
114f5a6c | 860 | |
804d5fe9 | 861 | line = git_array_alloc(patch->base.lines); |
ac3d33df | 862 | GIT_ERROR_CHECK_ALLOC(line); |
114f5a6c | 863 | |
3b5f7954 | 864 | memcpy(line, line_, sizeof(*line)); |
114f5a6c | 865 | |
114f5a6c RB |
866 | /* do some bookkeeping so we can provide old/new line numbers */ |
867 | ||
804d5fe9 | 868 | patch->base.content_size += line->content_len; |
197b8966 | 869 | |
3b5f7954 RB |
870 | if (line->origin == GIT_DIFF_LINE_ADDITION || |
871 | line->origin == GIT_DIFF_LINE_DELETION) | |
804d5fe9 | 872 | patch->base.content_size += 1; |
3b5f7954 | 873 | else if (line->origin == GIT_DIFF_LINE_CONTEXT) { |
804d5fe9 ET |
874 | patch->base.content_size += 1; |
875 | patch->base.context_size += line->content_len + 1; | |
3b5f7954 | 876 | } else if (line->origin == GIT_DIFF_LINE_CONTEXT_EOFNL) |
804d5fe9 | 877 | patch->base.context_size += line->content_len; |
114f5a6c RB |
878 | |
879 | hunk->line_count++; | |
880 | ||
881 | return 0; | |
882 | } | |
883 | ||
884 | static void diff_output_init( | |
8d44f8b7 | 885 | git_patch_generated_output *out, |
114f5a6c RB |
886 | const git_diff_options *opts, |
887 | git_diff_file_cb file_cb, | |
8147b1af | 888 | git_diff_binary_cb binary_cb, |
114f5a6c | 889 | git_diff_hunk_cb hunk_cb, |
3ff1d123 | 890 | git_diff_line_cb data_cb, |
114f5a6c RB |
891 | void *payload) |
892 | { | |
893 | GIT_UNUSED(opts); | |
894 | ||
895 | memset(out, 0, sizeof(*out)); | |
896 | ||
897 | out->file_cb = file_cb; | |
8147b1af | 898 | out->binary_cb = binary_cb; |
114f5a6c RB |
899 | out->hunk_cb = hunk_cb; |
900 | out->data_cb = data_cb; | |
901 | out->payload = payload; | |
902 | } | |
903 | ||
8d44f8b7 ET |
904 | static void diff_output_to_patch( |
905 | git_patch_generated_output *out, git_patch_generated *patch) | |
114f5a6c RB |
906 | { |
907 | diff_output_init( | |
8147b1af ET |
908 | out, |
909 | NULL, | |
8d44f8b7 ET |
910 | patch_generated_file_cb, |
911 | patch_generated_binary_cb, | |
804d5fe9 | 912 | git_patch_hunk_cb, |
8d44f8b7 | 913 | patch_generated_line_cb, |
8147b1af | 914 | patch); |
114f5a6c | 915 | } |