]> git.proxmox.com Git - libgit2.git/blob - src/patch_generate.c
New upstream version 1.4.3+dfsg.1
[libgit2.git] / src / patch_generate.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 "patch_generate.h"
9
10 #include "git2/blob.h"
11 #include "diff.h"
12 #include "diff_generate.h"
13 #include "diff_file.h"
14 #include "diff_driver.h"
15 #include "diff_xdiff.h"
16 #include "delta.h"
17 #include "zstream.h"
18 #include "futils.h"
19
20 static void diff_output_init(
21 git_patch_generated_output *, const git_diff_options *, git_diff_file_cb,
22 git_diff_binary_cb, git_diff_hunk_cb, git_diff_line_cb, void*);
23
24 static void diff_output_to_patch(
25 git_patch_generated_output *, git_patch_generated *);
26
27 static void patch_generated_free(git_patch *p)
28 {
29 git_patch_generated *patch = (git_patch_generated *)p;
30
31 git_array_clear(patch->base.lines);
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
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
48 if (patch->flags & GIT_PATCH_GENERATED_ALLOCATED)
49 git__free(patch);
50 }
51
52 static void patch_generated_update_binary(git_patch_generated *patch)
53 {
54 if ((patch->base.delta->flags & DIFF_FLAGS_KNOWN_BINARY) != 0)
55 return;
56
57 if ((patch->ofile.file->flags & GIT_DIFF_FLAG_BINARY) != 0 ||
58 (patch->nfile.file->flags & GIT_DIFF_FLAG_BINARY) != 0)
59 patch->base.delta->flags |= GIT_DIFF_FLAG_BINARY;
60
61 else if (patch->ofile.file->size > GIT_XDIFF_MAX_SIZE ||
62 patch->nfile.file->size > GIT_XDIFF_MAX_SIZE)
63 patch->base.delta->flags |= GIT_DIFF_FLAG_BINARY;
64
65 else if ((patch->ofile.file->flags & DIFF_FLAGS_NOT_BINARY) != 0 &&
66 (patch->nfile.file->flags & DIFF_FLAGS_NOT_BINARY) != 0)
67 patch->base.delta->flags |= GIT_DIFF_FLAG_NOT_BINARY;
68 }
69
70 static void patch_generated_init_common(git_patch_generated *patch)
71 {
72 patch->base.free_fn = patch_generated_free;
73
74 patch_generated_update_binary(patch);
75
76 patch->flags |= GIT_PATCH_GENERATED_INITIALIZED;
77
78 if (patch->diff)
79 git_diff_addref(patch->diff);
80 }
81
82 static int patch_generated_normalize_options(
83 git_diff_options *out,
84 const git_diff_options *opts)
85 {
86 if (opts) {
87 GIT_ERROR_CHECK_VERSION(opts, GIT_DIFF_OPTIONS_VERSION, "git_diff_options");
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
102 GIT_ERROR_CHECK_ALLOC(out->old_prefix);
103 GIT_ERROR_CHECK_ALLOC(out->new_prefix);
104
105 return 0;
106 }
107
108 static int patch_generated_init(
109 git_patch_generated *patch, git_diff *diff, size_t delta_index)
110 {
111 int error = 0;
112
113 memset(patch, 0, sizeof(*patch));
114
115 patch->diff = diff;
116 patch->base.repo = diff->repo;
117 patch->base.delta = git_vector_get(&diff->deltas, delta_index);
118 patch->delta_index = delta_index;
119
120 if ((error = patch_generated_normalize_options(
121 &patch->base.diff_opts, &diff->opts)) < 0 ||
122 (error = git_diff_file_content__init_from_diff(
123 &patch->ofile, diff, patch->base.delta, true)) < 0 ||
124 (error = git_diff_file_content__init_from_diff(
125 &patch->nfile, diff, patch->base.delta, false)) < 0)
126 return error;
127
128 patch_generated_init_common(patch);
129
130 return 0;
131 }
132
133 static int patch_generated_alloc_from_diff(
134 git_patch_generated **out, git_diff *diff, size_t delta_index)
135 {
136 int error;
137 git_patch_generated *patch = git__calloc(1, sizeof(git_patch_generated));
138 GIT_ERROR_CHECK_ALLOC(patch);
139
140 if (!(error = patch_generated_init(patch, diff, delta_index))) {
141 patch->flags |= GIT_PATCH_GENERATED_ALLOCATED;
142 GIT_REFCOUNT_INC(&patch->base);
143 } else {
144 git__free(patch);
145 patch = NULL;
146 }
147
148 *out = patch;
149 return error;
150 }
151
152 GIT_INLINE(bool) should_skip_binary(git_patch_generated *patch, git_diff_file *file)
153 {
154 if ((patch->base.diff_opts.flags & GIT_DIFF_SHOW_BINARY) != 0)
155 return false;
156
157 return (file->flags & GIT_DIFF_FLAG_BINARY) != 0;
158 }
159
160 static bool patch_generated_diffable(git_patch_generated *patch)
161 {
162 size_t olen, nlen;
163
164 if (patch->base.delta->status == GIT_DELTA_UNMODIFIED)
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 */
171 if ((patch->base.delta->flags & GIT_DIFF_FLAG_BINARY) != 0 &&
172 (patch->base.diff_opts.flags & GIT_DIFF_SHOW_BINARY) == 0) {
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
189 static int patch_generated_load(git_patch_generated *patch, git_patch_generated_output *output)
190 {
191 int error = 0;
192 bool incomplete_data;
193
194 if ((patch->flags & GIT_PATCH_GENERATED_LOADED) != 0)
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 */
200 if ((patch->ofile.opts_flags & GIT_DIFF_SKIP_BINARY_CHECK) != 0 &&
201 output && !output->binary_cb && !output->hunk_cb && !output->data_cb)
202 return 0;
203
204 incomplete_data =
205 (((patch->ofile.flags & GIT_DIFF_FLAG__NO_DATA) != 0 ||
206 (patch->ofile.file->flags & GIT_DIFF_FLAG_VALID_ID) != 0) &&
207 ((patch->nfile.flags & GIT_DIFF_FLAG__NO_DATA) != 0 ||
208 (patch->nfile.file->flags & GIT_DIFF_FLAG_VALID_ID) != 0));
209
210 if ((error = git_diff_file_content__load(
211 &patch->ofile, &patch->base.diff_opts)) < 0 ||
212 (error = git_diff_file_content__load(
213 &patch->nfile, &patch->base.diff_opts)) < 0 ||
214 should_skip_binary(patch, patch->nfile.file))
215 goto cleanup;
216
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 */
220 if (incomplete_data &&
221 patch->ofile.file->mode == patch->nfile.file->mode &&
222 patch->ofile.file->mode != GIT_FILEMODE_COMMIT &&
223 git_oid_equal(&patch->ofile.file->id, &patch->nfile.file->id) &&
224 patch->base.delta->status == GIT_DELTA_MODIFIED) /* not RENAMED/COPIED! */
225 patch->base.delta->status = GIT_DELTA_UNMODIFIED;
226
227 cleanup:
228 patch_generated_update_binary(patch);
229
230 if (!error) {
231 if (patch_generated_diffable(patch))
232 patch->flags |= GIT_PATCH_GENERATED_DIFFABLE;
233
234 patch->flags |= GIT_PATCH_GENERATED_LOADED;
235 }
236
237 return error;
238 }
239
240 static int patch_generated_invoke_file_callback(
241 git_patch_generated *patch, git_patch_generated_output *output)
242 {
243 float progress = patch->diff ?
244 ((float)patch->delta_index / patch->diff->deltas.length) : 1.0f;
245
246 if (!output->file_cb)
247 return 0;
248
249 return git_error_set_after_callback_function(
250 output->file_cb(patch->base.delta, progress, output->payload),
251 "git_patch");
252 }
253
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 {
264 git_str deflate = GIT_STR_INIT, delta = GIT_STR_INIT;
265 size_t delta_data_len = 0;
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) {
282 void *delta_data;
283
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) {
290 error = git_zstream_deflatebuf(
291 &delta, delta_data, delta_data_len);
292
293 git__free(delta_data);
294 } else if (error == GIT_EBUFS) {
295 error = 0;
296 }
297
298 if (error < 0)
299 goto done;
300 }
301
302 if (delta.size && delta.size < deflate.size) {
303 *out_type = GIT_DIFF_BINARY_DELTA;
304 *out_datalen = delta.size;
305 *out_data = git_str_detach(&delta);
306 *out_inflatedlen = delta_data_len;
307 } else {
308 *out_type = GIT_DIFF_BINARY_LITERAL;
309 *out_datalen = deflate.size;
310 *out_data = git_str_detach(&deflate);
311 *out_inflatedlen = b_datalen;
312 }
313
314 done:
315 git_str_dispose(&deflate);
316 git_str_dispose(&delta);
317
318 return error;
319 }
320
321 static int diff_binary(git_patch_generated_output *output, git_patch_generated *patch)
322 {
323 git_diff_binary binary = {0};
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
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) {
333 binary.contains_data = 1;
334
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 }
350
351 error = git_error_set_after_callback_function(
352 output->binary_cb(patch->base.delta, &binary, output->payload),
353 "git_patch");
354
355 git__free((char *) binary.old_file.data);
356 git__free((char *) binary.new_file.data);
357
358 return error;
359 }
360
361 static int patch_generated_create(
362 git_patch_generated *patch,
363 git_patch_generated_output *output)
364 {
365 int error = 0;
366
367 if ((patch->flags & GIT_PATCH_GENERATED_DIFFED) != 0)
368 return 0;
369
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)
372 return 0;
373
374 if ((patch->flags & GIT_PATCH_GENERATED_LOADED) == 0 &&
375 (error = patch_generated_load(patch, output)) < 0)
376 return error;
377
378 if ((patch->flags & GIT_PATCH_GENERATED_DIFFABLE) == 0)
379 return 0;
380
381 if ((patch->base.delta->flags & GIT_DIFF_FLAG_BINARY) != 0) {
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 }
389
390 patch->flags |= GIT_PATCH_GENERATED_DIFFED;
391 return error;
392 }
393
394 static int diff_required(git_diff *diff, const char *action)
395 {
396 if (diff)
397 return 0;
398 git_error_set(GIT_ERROR_INVALID, "must provide valid diff to %s", action);
399 return -1;
400 }
401
402 typedef struct {
403 git_patch_generated patch;
404 git_diff_delta delta;
405 char paths[GIT_FLEX_ARRAY];
406 } patch_generated_with_delta;
407
408 static int diff_single_generate(patch_generated_with_delta *pd, git_xdiff_output *xo)
409 {
410 int error = 0;
411 git_patch_generated *patch = &pd->patch;
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);
414
415 pd->delta.status = has_new ?
416 (has_old ? GIT_DELTA_MODIFIED : GIT_DELTA_ADDED) :
417 (has_old ? GIT_DELTA_DELETED : GIT_DELTA_UNTRACKED);
418
419 if (git_oid_equal(&patch->nfile.file->id, &patch->ofile.file->id))
420 pd->delta.status = GIT_DELTA_UNMODIFIED;
421
422 patch->base.delta = &pd->delta;
423
424 patch_generated_init_common(patch);
425
426 if (pd->delta.status == GIT_DELTA_UNMODIFIED &&
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
436 return error;
437 }
438
439 error = patch_generated_invoke_file_callback(patch, (git_patch_generated_output *)xo);
440
441 if (!error)
442 error = patch_generated_create(patch, (git_patch_generated_output *)xo);
443
444 return error;
445 }
446
447 static int patch_generated_from_sources(
448 patch_generated_with_delta *pd,
449 git_xdiff_output *xo,
450 git_diff_file_content_src *oldsrc,
451 git_diff_file_content_src *newsrc,
452 const git_diff_options *opts)
453 {
454 int error = 0;
455 git_repository *repo =
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;
460
461 if ((error = patch_generated_normalize_options(&pd->patch.base.diff_opts, opts)) < 0)
462 return error;
463
464 if (opts && (opts->flags & GIT_DIFF_REVERSE) != 0) {
465 void *tmp = lfile; lfile = rfile; rfile = tmp;
466 tmp = ldata; ldata = rdata; rdata = tmp;
467 }
468
469 pd->patch.base.delta = &pd->delta;
470
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;
482
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)
487 return error;
488
489 return diff_single_generate(pd, xo);
490 }
491
492 static int patch_generated_with_delta_alloc(
493 patch_generated_with_delta **out,
494 const char **old_path,
495 const char **new_path)
496 {
497 patch_generated_with_delta *pd;
498 size_t old_len = *old_path ? strlen(*old_path) : 0;
499 size_t new_len = *new_path ? strlen(*new_path) : 0;
500 size_t alloc_len;
501
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);
505
506 *out = pd = git__calloc(1, alloc_len);
507 GIT_ERROR_CHECK_ALLOC(pd);
508
509 pd->patch.flags = GIT_PATCH_GENERATED_ALLOCATED;
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
526 static int diff_from_sources(
527 git_diff_file_content_src *oldsrc,
528 git_diff_file_content_src *newsrc,
529 const git_diff_options *opts,
530 git_diff_file_cb file_cb,
531 git_diff_binary_cb binary_cb,
532 git_diff_hunk_cb hunk_cb,
533 git_diff_line_cb data_cb,
534 void *payload)
535 {
536 int error = 0;
537 patch_generated_with_delta pd;
538 git_xdiff_output xo;
539
540 memset(&xo, 0, sizeof(xo));
541 diff_output_init(
542 &xo.output, opts, file_cb, binary_cb, hunk_cb, data_cb, payload);
543 git_xdiff_init(&xo, opts);
544
545 memset(&pd, 0, sizeof(pd));
546
547 error = patch_generated_from_sources(&pd, &xo, oldsrc, newsrc, opts);
548
549 git_patch_free(&pd.patch.base);
550
551 return error;
552 }
553
554 static int patch_from_sources(
555 git_patch **out,
556 git_diff_file_content_src *oldsrc,
557 git_diff_file_content_src *newsrc,
558 const git_diff_options *opts)
559 {
560 int error = 0;
561 patch_generated_with_delta *pd;
562 git_xdiff_output xo;
563
564 GIT_ASSERT_ARG(out);
565 *out = NULL;
566
567 if ((error = patch_generated_with_delta_alloc(
568 &pd, &oldsrc->as_path, &newsrc->as_path)) < 0)
569 return error;
570
571 memset(&xo, 0, sizeof(xo));
572 diff_output_to_patch(&xo.output, &pd->patch);
573 git_xdiff_init(&xo, opts);
574
575 if (!(error = patch_generated_from_sources(pd, &xo, oldsrc, newsrc, opts)))
576 *out = (git_patch *)pd;
577 else
578 git_patch_free((git_patch *)pd);
579
580 return error;
581 }
582
583 int git_diff_blobs(
584 const git_blob *old_blob,
585 const char *old_path,
586 const git_blob *new_blob,
587 const char *new_path,
588 const git_diff_options *opts,
589 git_diff_file_cb file_cb,
590 git_diff_binary_cb binary_cb,
591 git_diff_hunk_cb hunk_cb,
592 git_diff_line_cb data_cb,
593 void *payload)
594 {
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(
600 &osrc, &nsrc, opts, file_cb, binary_cb, hunk_cb, data_cb, payload);
601 }
602
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);
616 }
617
618 int git_diff_blob_to_buffer(
619 const git_blob *old_blob,
620 const char *old_path,
621 const char *buf,
622 size_t buflen,
623 const char *buf_path,
624 const git_diff_options *opts,
625 git_diff_file_cb file_cb,
626 git_diff_binary_cb binary_cb,
627 git_diff_hunk_cb hunk_cb,
628 git_diff_line_cb data_cb,
629 void *payload)
630 {
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(
636 &osrc, &nsrc, opts, file_cb, binary_cb, hunk_cb, data_cb, payload);
637 }
638
639 int git_patch_from_blob_and_buffer(
640 git_patch **out,
641 const git_blob *old_blob,
642 const char *old_path,
643 const void *buf,
644 size_t buflen,
645 const char *buf_path,
646 const git_diff_options *opts)
647 {
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 }
654
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,
664 git_diff_binary_cb binary_cb,
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(
674 &osrc, &nsrc, opts, file_cb, binary_cb, hunk_cb, data_cb, payload);
675 }
676
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,
682 const void *new_buf,
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);
692 }
693
694 int git_patch_generated_from_diff(
695 git_patch **patch_ptr, git_diff *diff, size_t idx)
696 {
697 int error = 0;
698 git_xdiff_output xo;
699 git_diff_delta *delta = NULL;
700 git_patch_generated *patch = NULL;
701
702 if (patch_ptr) *patch_ptr = NULL;
703
704 if (diff_required(diff, "git_patch_from_diff") < 0)
705 return -1;
706
707 delta = git_vector_get(&diff->deltas, idx);
708 if (!delta) {
709 git_error_set(GIT_ERROR_INVALID, "index out of range for delta in diff");
710 return GIT_ENOTFOUND;
711 }
712
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
722 if ((error = patch_generated_alloc_from_diff(&patch, diff, idx)) < 0)
723 return error;
724
725 memset(&xo, 0, sizeof(xo));
726 diff_output_to_patch(&xo.output, patch);
727 git_xdiff_init(&xo, &diff->opts);
728
729 error = patch_generated_invoke_file_callback(patch, &xo.output);
730
731 if (!error)
732 error = patch_generated_create(patch, &xo.output);
733
734 if (!error) {
735 /* TODO: if cumulative diff size is < 0.5 total size, flatten patch */
736 /* TODO: and unload the file content */
737 }
738
739 if (error || !patch_ptr)
740 git_patch_free(&patch->base);
741 else
742 *patch_ptr = &patch->base;
743
744 return error;
745 }
746
747 git_diff_driver *git_patch_generated_driver(git_patch_generated *patch)
748 {
749 /* ofile driver is representative for whole patch */
750 return patch->ofile.driver;
751 }
752
753 int git_patch_generated_old_data(
754 char **ptr, long *len, git_patch_generated *patch)
755 {
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
762 *ptr = patch->ofile.map.data;
763 *len = (long)patch->ofile.map.len;
764
765 return 0;
766 }
767
768 int git_patch_generated_new_data(
769 char **ptr, long *len, git_patch_generated *patch)
770 {
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
777 *ptr = patch->nfile.map.data;
778 *len = (long)patch->nfile.map.len;
779
780 return 0;
781 }
782
783 static int patch_generated_file_cb(
784 const git_diff_delta *delta,
785 float progress,
786 void *payload)
787 {
788 GIT_UNUSED(delta); GIT_UNUSED(progress); GIT_UNUSED(payload);
789 return 0;
790 }
791
792 static int patch_generated_binary_cb(
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);
805 GIT_ERROR_CHECK_ALLOC(patch->binary.old_file.data);
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);
813 GIT_ERROR_CHECK_ALLOC(patch->binary.new_file.data);
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
822 static int git_patch_hunk_cb(
823 const git_diff_delta *delta,
824 const git_diff_hunk *hunk_,
825 void *payload)
826 {
827 git_patch_generated *patch = payload;
828 git_patch_hunk *hunk;
829
830 GIT_UNUSED(delta);
831
832 hunk = git_array_alloc(patch->base.hunks);
833 GIT_ERROR_CHECK_ALLOC(hunk);
834
835 memcpy(&hunk->hunk, hunk_, sizeof(hunk->hunk));
836
837 patch->base.header_size += hunk_->header_len;
838
839 hunk->line_start = git_array_size(patch->base.lines);
840 hunk->line_count = 0;
841
842 return 0;
843 }
844
845 static int patch_generated_line_cb(
846 const git_diff_delta *delta,
847 const git_diff_hunk *hunk_,
848 const git_diff_line *line_,
849 void *payload)
850 {
851 git_patch_generated *patch = payload;
852 git_patch_hunk *hunk;
853 git_diff_line *line;
854
855 GIT_UNUSED(delta);
856 GIT_UNUSED(hunk_);
857
858 hunk = git_array_last(patch->base.hunks);
859 GIT_ASSERT(hunk); /* programmer error if no hunk is available */
860
861 line = git_array_alloc(patch->base.lines);
862 GIT_ERROR_CHECK_ALLOC(line);
863
864 memcpy(line, line_, sizeof(*line));
865
866 /* do some bookkeeping so we can provide old/new line numbers */
867
868 patch->base.content_size += line->content_len;
869
870 if (line->origin == GIT_DIFF_LINE_ADDITION ||
871 line->origin == GIT_DIFF_LINE_DELETION)
872 patch->base.content_size += 1;
873 else if (line->origin == GIT_DIFF_LINE_CONTEXT) {
874 patch->base.content_size += 1;
875 patch->base.context_size += line->content_len + 1;
876 } else if (line->origin == GIT_DIFF_LINE_CONTEXT_EOFNL)
877 patch->base.context_size += line->content_len;
878
879 hunk->line_count++;
880
881 return 0;
882 }
883
884 static void diff_output_init(
885 git_patch_generated_output *out,
886 const git_diff_options *opts,
887 git_diff_file_cb file_cb,
888 git_diff_binary_cb binary_cb,
889 git_diff_hunk_cb hunk_cb,
890 git_diff_line_cb data_cb,
891 void *payload)
892 {
893 GIT_UNUSED(opts);
894
895 memset(out, 0, sizeof(*out));
896
897 out->file_cb = file_cb;
898 out->binary_cb = binary_cb;
899 out->hunk_cb = hunk_cb;
900 out->data_cb = data_cb;
901 out->payload = payload;
902 }
903
904 static void diff_output_to_patch(
905 git_patch_generated_output *out, git_patch_generated *patch)
906 {
907 diff_output_init(
908 out,
909 NULL,
910 patch_generated_file_cb,
911 patch_generated_binary_cb,
912 git_patch_hunk_cb,
913 patch_generated_line_cb,
914 patch);
915 }