]> git.proxmox.com Git - libgit2.git/blob - src/patch_generate.c
Add BD on ca-certificates
[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 "fileops.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 should_skip_binary(patch, patch->ofile.file))
213 goto cleanup;
214 if ((error = git_diff_file_content__load(
215 &patch->nfile, &patch->base.diff_opts)) < 0 ||
216 should_skip_binary(patch, patch->nfile.file))
217 goto cleanup;
218
219 /* if previously missing an oid, and now that we have it the two sides
220 * are the same (and not submodules), update MODIFIED -> UNMODIFIED
221 */
222 if (incomplete_data &&
223 patch->ofile.file->mode == patch->nfile.file->mode &&
224 patch->ofile.file->mode != GIT_FILEMODE_COMMIT &&
225 git_oid_equal(&patch->ofile.file->id, &patch->nfile.file->id) &&
226 patch->base.delta->status == GIT_DELTA_MODIFIED) /* not RENAMED/COPIED! */
227 patch->base.delta->status = GIT_DELTA_UNMODIFIED;
228
229 cleanup:
230 patch_generated_update_binary(patch);
231
232 if (!error) {
233 if (patch_generated_diffable(patch))
234 patch->flags |= GIT_PATCH_GENERATED_DIFFABLE;
235
236 patch->flags |= GIT_PATCH_GENERATED_LOADED;
237 }
238
239 return error;
240 }
241
242 static int patch_generated_invoke_file_callback(
243 git_patch_generated *patch, git_patch_generated_output *output)
244 {
245 float progress = patch->diff ?
246 ((float)patch->delta_index / patch->diff->deltas.length) : 1.0f;
247
248 if (!output->file_cb)
249 return 0;
250
251 return git_error_set_after_callback_function(
252 output->file_cb(patch->base.delta, progress, output->payload),
253 "git_patch");
254 }
255
256 static int create_binary(
257 git_diff_binary_t *out_type,
258 char **out_data,
259 size_t *out_datalen,
260 size_t *out_inflatedlen,
261 const char *a_data,
262 size_t a_datalen,
263 const char *b_data,
264 size_t b_datalen)
265 {
266 git_buf deflate = GIT_BUF_INIT, delta = GIT_BUF_INIT;
267 size_t delta_data_len = 0;
268 int error;
269
270 /* The git_delta function accepts unsigned long only */
271 if (!git__is_ulong(a_datalen) || !git__is_ulong(b_datalen))
272 return GIT_EBUFS;
273
274 if ((error = git_zstream_deflatebuf(&deflate, b_data, b_datalen)) < 0)
275 goto done;
276
277 /* The git_delta function accepts unsigned long only */
278 if (!git__is_ulong(deflate.size)) {
279 error = GIT_EBUFS;
280 goto done;
281 }
282
283 if (a_datalen && b_datalen) {
284 void *delta_data;
285
286 error = git_delta(&delta_data, &delta_data_len,
287 a_data, a_datalen,
288 b_data, b_datalen,
289 deflate.size);
290
291 if (error == 0) {
292 error = git_zstream_deflatebuf(
293 &delta, delta_data, delta_data_len);
294
295 git__free(delta_data);
296 } else if (error == GIT_EBUFS) {
297 error = 0;
298 }
299
300 if (error < 0)
301 goto done;
302 }
303
304 if (delta.size && delta.size < deflate.size) {
305 *out_type = GIT_DIFF_BINARY_DELTA;
306 *out_datalen = delta.size;
307 *out_data = git_buf_detach(&delta);
308 *out_inflatedlen = delta_data_len;
309 } else {
310 *out_type = GIT_DIFF_BINARY_LITERAL;
311 *out_datalen = deflate.size;
312 *out_data = git_buf_detach(&deflate);
313 *out_inflatedlen = b_datalen;
314 }
315
316 done:
317 git_buf_dispose(&deflate);
318 git_buf_dispose(&delta);
319
320 return error;
321 }
322
323 static int diff_binary(git_patch_generated_output *output, git_patch_generated *patch)
324 {
325 git_diff_binary binary = {0};
326 const char *old_data = patch->ofile.map.data;
327 const char *new_data = patch->nfile.map.data;
328 size_t old_len = patch->ofile.map.len,
329 new_len = patch->nfile.map.len;
330 int error;
331
332 /* Only load contents if the user actually wants to diff
333 * binary files. */
334 if (patch->base.diff_opts.flags & GIT_DIFF_SHOW_BINARY) {
335 binary.contains_data = 1;
336
337 /* Create the old->new delta (as the "new" side of the patch),
338 * and the new->old delta (as the "old" side)
339 */
340 if ((error = create_binary(&binary.old_file.type,
341 (char **)&binary.old_file.data,
342 &binary.old_file.datalen,
343 &binary.old_file.inflatedlen,
344 new_data, new_len, old_data, old_len)) < 0 ||
345 (error = create_binary(&binary.new_file.type,
346 (char **)&binary.new_file.data,
347 &binary.new_file.datalen,
348 &binary.new_file.inflatedlen,
349 old_data, old_len, new_data, new_len)) < 0)
350 return error;
351 }
352
353 error = git_error_set_after_callback_function(
354 output->binary_cb(patch->base.delta, &binary, output->payload),
355 "git_patch");
356
357 git__free((char *) binary.old_file.data);
358 git__free((char *) binary.new_file.data);
359
360 return error;
361 }
362
363 static int patch_generated_create(
364 git_patch_generated *patch,
365 git_patch_generated_output *output)
366 {
367 int error = 0;
368
369 if ((patch->flags & GIT_PATCH_GENERATED_DIFFED) != 0)
370 return 0;
371
372 /* if we are not looking at the binary or text data, don't do the diff */
373 if (!output->binary_cb && !output->hunk_cb && !output->data_cb)
374 return 0;
375
376 if ((patch->flags & GIT_PATCH_GENERATED_LOADED) == 0 &&
377 (error = patch_generated_load(patch, output)) < 0)
378 return error;
379
380 if ((patch->flags & GIT_PATCH_GENERATED_DIFFABLE) == 0)
381 return 0;
382
383 if ((patch->base.delta->flags & GIT_DIFF_FLAG_BINARY) != 0) {
384 if (output->binary_cb)
385 error = diff_binary(output, patch);
386 }
387 else {
388 if (output->diff_cb)
389 error = output->diff_cb(output, patch);
390 }
391
392 patch->flags |= GIT_PATCH_GENERATED_DIFFED;
393 return error;
394 }
395
396 static int diff_required(git_diff *diff, const char *action)
397 {
398 if (diff)
399 return 0;
400 git_error_set(GIT_ERROR_INVALID, "must provide valid diff to %s", action);
401 return -1;
402 }
403
404 typedef struct {
405 git_patch_generated patch;
406 git_diff_delta delta;
407 char paths[GIT_FLEX_ARRAY];
408 } patch_generated_with_delta;
409
410 static int diff_single_generate(patch_generated_with_delta *pd, git_xdiff_output *xo)
411 {
412 int error = 0;
413 git_patch_generated *patch = &pd->patch;
414 bool has_old = ((patch->ofile.flags & GIT_DIFF_FLAG__NO_DATA) == 0);
415 bool has_new = ((patch->nfile.flags & GIT_DIFF_FLAG__NO_DATA) == 0);
416
417 pd->delta.status = has_new ?
418 (has_old ? GIT_DELTA_MODIFIED : GIT_DELTA_ADDED) :
419 (has_old ? GIT_DELTA_DELETED : GIT_DELTA_UNTRACKED);
420
421 if (git_oid_equal(&patch->nfile.file->id, &patch->ofile.file->id))
422 pd->delta.status = GIT_DELTA_UNMODIFIED;
423
424 patch->base.delta = &pd->delta;
425
426 patch_generated_init_common(patch);
427
428 if (pd->delta.status == GIT_DELTA_UNMODIFIED &&
429 !(patch->ofile.opts_flags & GIT_DIFF_INCLUDE_UNMODIFIED)) {
430
431 /* Even empty patches are flagged as binary, and even though
432 * there's no difference, we flag this as "containing data"
433 * (the data is known to be empty, as opposed to wholly unknown).
434 */
435 if (patch->base.diff_opts.flags & GIT_DIFF_SHOW_BINARY)
436 patch->base.binary.contains_data = 1;
437
438 return error;
439 }
440
441 error = patch_generated_invoke_file_callback(patch, (git_patch_generated_output *)xo);
442
443 if (!error)
444 error = patch_generated_create(patch, (git_patch_generated_output *)xo);
445
446 return error;
447 }
448
449 static int patch_generated_from_sources(
450 patch_generated_with_delta *pd,
451 git_xdiff_output *xo,
452 git_diff_file_content_src *oldsrc,
453 git_diff_file_content_src *newsrc,
454 const git_diff_options *opts)
455 {
456 int error = 0;
457 git_repository *repo =
458 oldsrc->blob ? git_blob_owner(oldsrc->blob) :
459 newsrc->blob ? git_blob_owner(newsrc->blob) : NULL;
460 git_diff_file *lfile = &pd->delta.old_file, *rfile = &pd->delta.new_file;
461 git_diff_file_content *ldata = &pd->patch.ofile, *rdata = &pd->patch.nfile;
462
463 if ((error = patch_generated_normalize_options(&pd->patch.base.diff_opts, opts)) < 0)
464 return error;
465
466 if (opts && (opts->flags & GIT_DIFF_REVERSE) != 0) {
467 void *tmp = lfile; lfile = rfile; rfile = tmp;
468 tmp = ldata; ldata = rdata; rdata = tmp;
469 }
470
471 pd->patch.base.delta = &pd->delta;
472
473 if (!oldsrc->as_path) {
474 if (newsrc->as_path)
475 oldsrc->as_path = newsrc->as_path;
476 else
477 oldsrc->as_path = newsrc->as_path = "file";
478 }
479 else if (!newsrc->as_path)
480 newsrc->as_path = oldsrc->as_path;
481
482 lfile->path = oldsrc->as_path;
483 rfile->path = newsrc->as_path;
484
485 if ((error = git_diff_file_content__init_from_src(
486 ldata, repo, opts, oldsrc, lfile)) < 0 ||
487 (error = git_diff_file_content__init_from_src(
488 rdata, repo, opts, newsrc, rfile)) < 0)
489 return error;
490
491 return diff_single_generate(pd, xo);
492 }
493
494 static int patch_generated_with_delta_alloc(
495 patch_generated_with_delta **out,
496 const char **old_path,
497 const char **new_path)
498 {
499 patch_generated_with_delta *pd;
500 size_t old_len = *old_path ? strlen(*old_path) : 0;
501 size_t new_len = *new_path ? strlen(*new_path) : 0;
502 size_t alloc_len;
503
504 GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, sizeof(*pd), old_len);
505 GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, new_len);
506 GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, 2);
507
508 *out = pd = git__calloc(1, alloc_len);
509 GIT_ERROR_CHECK_ALLOC(pd);
510
511 pd->patch.flags = GIT_PATCH_GENERATED_ALLOCATED;
512
513 if (*old_path) {
514 memcpy(&pd->paths[0], *old_path, old_len);
515 *old_path = &pd->paths[0];
516 } else if (*new_path)
517 *old_path = &pd->paths[old_len + 1];
518
519 if (*new_path) {
520 memcpy(&pd->paths[old_len + 1], *new_path, new_len);
521 *new_path = &pd->paths[old_len + 1];
522 } else if (*old_path)
523 *new_path = &pd->paths[0];
524
525 return 0;
526 }
527
528 static int diff_from_sources(
529 git_diff_file_content_src *oldsrc,
530 git_diff_file_content_src *newsrc,
531 const git_diff_options *opts,
532 git_diff_file_cb file_cb,
533 git_diff_binary_cb binary_cb,
534 git_diff_hunk_cb hunk_cb,
535 git_diff_line_cb data_cb,
536 void *payload)
537 {
538 int error = 0;
539 patch_generated_with_delta pd;
540 git_xdiff_output xo;
541
542 memset(&xo, 0, sizeof(xo));
543 diff_output_init(
544 &xo.output, opts, file_cb, binary_cb, hunk_cb, data_cb, payload);
545 git_xdiff_init(&xo, opts);
546
547 memset(&pd, 0, sizeof(pd));
548
549 error = patch_generated_from_sources(&pd, &xo, oldsrc, newsrc, opts);
550
551 git_patch_free(&pd.patch.base);
552
553 return error;
554 }
555
556 static int patch_from_sources(
557 git_patch **out,
558 git_diff_file_content_src *oldsrc,
559 git_diff_file_content_src *newsrc,
560 const git_diff_options *opts)
561 {
562 int error = 0;
563 patch_generated_with_delta *pd;
564 git_xdiff_output xo;
565
566 assert(out);
567 *out = NULL;
568
569 if ((error = patch_generated_with_delta_alloc(
570 &pd, &oldsrc->as_path, &newsrc->as_path)) < 0)
571 return error;
572
573 memset(&xo, 0, sizeof(xo));
574 diff_output_to_patch(&xo.output, &pd->patch);
575 git_xdiff_init(&xo, opts);
576
577 if (!(error = patch_generated_from_sources(pd, &xo, oldsrc, newsrc, opts)))
578 *out = (git_patch *)pd;
579 else
580 git_patch_free((git_patch *)pd);
581
582 return error;
583 }
584
585 int git_diff_blobs(
586 const git_blob *old_blob,
587 const char *old_path,
588 const git_blob *new_blob,
589 const char *new_path,
590 const git_diff_options *opts,
591 git_diff_file_cb file_cb,
592 git_diff_binary_cb binary_cb,
593 git_diff_hunk_cb hunk_cb,
594 git_diff_line_cb data_cb,
595 void *payload)
596 {
597 git_diff_file_content_src osrc =
598 GIT_DIFF_FILE_CONTENT_SRC__BLOB(old_blob, old_path);
599 git_diff_file_content_src nsrc =
600 GIT_DIFF_FILE_CONTENT_SRC__BLOB(new_blob, new_path);
601 return diff_from_sources(
602 &osrc, &nsrc, opts, file_cb, binary_cb, hunk_cb, data_cb, payload);
603 }
604
605 int git_patch_from_blobs(
606 git_patch **out,
607 const git_blob *old_blob,
608 const char *old_path,
609 const git_blob *new_blob,
610 const char *new_path,
611 const git_diff_options *opts)
612 {
613 git_diff_file_content_src osrc =
614 GIT_DIFF_FILE_CONTENT_SRC__BLOB(old_blob, old_path);
615 git_diff_file_content_src nsrc =
616 GIT_DIFF_FILE_CONTENT_SRC__BLOB(new_blob, new_path);
617 return patch_from_sources(out, &osrc, &nsrc, opts);
618 }
619
620 int git_diff_blob_to_buffer(
621 const git_blob *old_blob,
622 const char *old_path,
623 const char *buf,
624 size_t buflen,
625 const char *buf_path,
626 const git_diff_options *opts,
627 git_diff_file_cb file_cb,
628 git_diff_binary_cb binary_cb,
629 git_diff_hunk_cb hunk_cb,
630 git_diff_line_cb data_cb,
631 void *payload)
632 {
633 git_diff_file_content_src osrc =
634 GIT_DIFF_FILE_CONTENT_SRC__BLOB(old_blob, old_path);
635 git_diff_file_content_src nsrc =
636 GIT_DIFF_FILE_CONTENT_SRC__BUF(buf, buflen, buf_path);
637 return diff_from_sources(
638 &osrc, &nsrc, opts, file_cb, binary_cb, hunk_cb, data_cb, payload);
639 }
640
641 int git_patch_from_blob_and_buffer(
642 git_patch **out,
643 const git_blob *old_blob,
644 const char *old_path,
645 const void *buf,
646 size_t buflen,
647 const char *buf_path,
648 const git_diff_options *opts)
649 {
650 git_diff_file_content_src osrc =
651 GIT_DIFF_FILE_CONTENT_SRC__BLOB(old_blob, old_path);
652 git_diff_file_content_src nsrc =
653 GIT_DIFF_FILE_CONTENT_SRC__BUF(buf, buflen, buf_path);
654 return patch_from_sources(out, &osrc, &nsrc, opts);
655 }
656
657 int git_diff_buffers(
658 const void *old_buf,
659 size_t old_len,
660 const char *old_path,
661 const void *new_buf,
662 size_t new_len,
663 const char *new_path,
664 const git_diff_options *opts,
665 git_diff_file_cb file_cb,
666 git_diff_binary_cb binary_cb,
667 git_diff_hunk_cb hunk_cb,
668 git_diff_line_cb data_cb,
669 void *payload)
670 {
671 git_diff_file_content_src osrc =
672 GIT_DIFF_FILE_CONTENT_SRC__BUF(old_buf, old_len, old_path);
673 git_diff_file_content_src nsrc =
674 GIT_DIFF_FILE_CONTENT_SRC__BUF(new_buf, new_len, new_path);
675 return diff_from_sources(
676 &osrc, &nsrc, opts, file_cb, binary_cb, hunk_cb, data_cb, payload);
677 }
678
679 int git_patch_from_buffers(
680 git_patch **out,
681 const void *old_buf,
682 size_t old_len,
683 const char *old_path,
684 const void *new_buf,
685 size_t new_len,
686 const char *new_path,
687 const git_diff_options *opts)
688 {
689 git_diff_file_content_src osrc =
690 GIT_DIFF_FILE_CONTENT_SRC__BUF(old_buf, old_len, old_path);
691 git_diff_file_content_src nsrc =
692 GIT_DIFF_FILE_CONTENT_SRC__BUF(new_buf, new_len, new_path);
693 return patch_from_sources(out, &osrc, &nsrc, opts);
694 }
695
696 int git_patch_generated_from_diff(
697 git_patch **patch_ptr, git_diff *diff, size_t idx)
698 {
699 int error = 0;
700 git_xdiff_output xo;
701 git_diff_delta *delta = NULL;
702 git_patch_generated *patch = NULL;
703
704 if (patch_ptr) *patch_ptr = NULL;
705
706 if (diff_required(diff, "git_patch_from_diff") < 0)
707 return -1;
708
709 delta = git_vector_get(&diff->deltas, idx);
710 if (!delta) {
711 git_error_set(GIT_ERROR_INVALID, "index out of range for delta in diff");
712 return GIT_ENOTFOUND;
713 }
714
715 if (git_diff_delta__should_skip(&diff->opts, delta))
716 return 0;
717
718 /* don't load the patch data unless we need it for binary check */
719 if (!patch_ptr &&
720 ((delta->flags & DIFF_FLAGS_KNOWN_BINARY) != 0 ||
721 (diff->opts.flags & GIT_DIFF_SKIP_BINARY_CHECK) != 0))
722 return 0;
723
724 if ((error = patch_generated_alloc_from_diff(&patch, diff, idx)) < 0)
725 return error;
726
727 memset(&xo, 0, sizeof(xo));
728 diff_output_to_patch(&xo.output, patch);
729 git_xdiff_init(&xo, &diff->opts);
730
731 error = patch_generated_invoke_file_callback(patch, &xo.output);
732
733 if (!error)
734 error = patch_generated_create(patch, &xo.output);
735
736 if (!error) {
737 /* TODO: if cumulative diff size is < 0.5 total size, flatten patch */
738 /* TODO: and unload the file content */
739 }
740
741 if (error || !patch_ptr)
742 git_patch_free(&patch->base);
743 else
744 *patch_ptr = &patch->base;
745
746 return error;
747 }
748
749 git_diff_driver *git_patch_generated_driver(git_patch_generated *patch)
750 {
751 /* ofile driver is representative for whole patch */
752 return patch->ofile.driver;
753 }
754
755 void git_patch_generated_old_data(
756 char **ptr, size_t *len, git_patch_generated *patch)
757 {
758 *ptr = patch->ofile.map.data;
759 *len = patch->ofile.map.len;
760 }
761
762 void git_patch_generated_new_data(
763 char **ptr, size_t *len, git_patch_generated *patch)
764 {
765 *ptr = patch->nfile.map.data;
766 *len = patch->nfile.map.len;
767 }
768
769 static int patch_generated_file_cb(
770 const git_diff_delta *delta,
771 float progress,
772 void *payload)
773 {
774 GIT_UNUSED(delta); GIT_UNUSED(progress); GIT_UNUSED(payload);
775 return 0;
776 }
777
778 static int patch_generated_binary_cb(
779 const git_diff_delta *delta,
780 const git_diff_binary *binary,
781 void *payload)
782 {
783 git_patch *patch = payload;
784
785 GIT_UNUSED(delta);
786
787 memcpy(&patch->binary, binary, sizeof(git_diff_binary));
788
789 if (binary->old_file.data) {
790 patch->binary.old_file.data = git__malloc(binary->old_file.datalen);
791 GIT_ERROR_CHECK_ALLOC(patch->binary.old_file.data);
792
793 memcpy((char *)patch->binary.old_file.data,
794 binary->old_file.data, binary->old_file.datalen);
795 }
796
797 if (binary->new_file.data) {
798 patch->binary.new_file.data = git__malloc(binary->new_file.datalen);
799 GIT_ERROR_CHECK_ALLOC(patch->binary.new_file.data);
800
801 memcpy((char *)patch->binary.new_file.data,
802 binary->new_file.data, binary->new_file.datalen);
803 }
804
805 return 0;
806 }
807
808 static int git_patch_hunk_cb(
809 const git_diff_delta *delta,
810 const git_diff_hunk *hunk_,
811 void *payload)
812 {
813 git_patch_generated *patch = payload;
814 git_patch_hunk *hunk;
815
816 GIT_UNUSED(delta);
817
818 hunk = git_array_alloc(patch->base.hunks);
819 GIT_ERROR_CHECK_ALLOC(hunk);
820
821 memcpy(&hunk->hunk, hunk_, sizeof(hunk->hunk));
822
823 patch->base.header_size += hunk_->header_len;
824
825 hunk->line_start = git_array_size(patch->base.lines);
826 hunk->line_count = 0;
827
828 return 0;
829 }
830
831 static int patch_generated_line_cb(
832 const git_diff_delta *delta,
833 const git_diff_hunk *hunk_,
834 const git_diff_line *line_,
835 void *payload)
836 {
837 git_patch_generated *patch = payload;
838 git_patch_hunk *hunk;
839 git_diff_line *line;
840
841 GIT_UNUSED(delta);
842 GIT_UNUSED(hunk_);
843
844 hunk = git_array_last(patch->base.hunks);
845 assert(hunk); /* programmer error if no hunk is available */
846
847 line = git_array_alloc(patch->base.lines);
848 GIT_ERROR_CHECK_ALLOC(line);
849
850 memcpy(line, line_, sizeof(*line));
851
852 /* do some bookkeeping so we can provide old/new line numbers */
853
854 patch->base.content_size += line->content_len;
855
856 if (line->origin == GIT_DIFF_LINE_ADDITION ||
857 line->origin == GIT_DIFF_LINE_DELETION)
858 patch->base.content_size += 1;
859 else if (line->origin == GIT_DIFF_LINE_CONTEXT) {
860 patch->base.content_size += 1;
861 patch->base.context_size += line->content_len + 1;
862 } else if (line->origin == GIT_DIFF_LINE_CONTEXT_EOFNL)
863 patch->base.context_size += line->content_len;
864
865 hunk->line_count++;
866
867 return 0;
868 }
869
870 static void diff_output_init(
871 git_patch_generated_output *out,
872 const git_diff_options *opts,
873 git_diff_file_cb file_cb,
874 git_diff_binary_cb binary_cb,
875 git_diff_hunk_cb hunk_cb,
876 git_diff_line_cb data_cb,
877 void *payload)
878 {
879 GIT_UNUSED(opts);
880
881 memset(out, 0, sizeof(*out));
882
883 out->file_cb = file_cb;
884 out->binary_cb = binary_cb;
885 out->hunk_cb = hunk_cb;
886 out->data_cb = data_cb;
887 out->payload = payload;
888 }
889
890 static void diff_output_to_patch(
891 git_patch_generated_output *out, git_patch_generated *patch)
892 {
893 diff_output_init(
894 out,
895 NULL,
896 patch_generated_file_cb,
897 patch_generated_binary_cb,
898 git_patch_hunk_cb,
899 patch_generated_line_cb,
900 patch);
901 }