File Coverage

deps/libgit2/src/libgit2/patch_generate.c
Criterion Covered Total %
statement 298 414 71.9
branch 137 280 48.9
condition n/a
subroutine n/a
pod n/a
total 435 694 62.6


line stmt bran cond sub pod time code
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 50           static void patch_generated_free(git_patch *p)
28             {
29 50           git_patch_generated *patch = (git_patch_generated *)p;
30              
31 50           git_array_clear(patch->base.lines);
32 50           git_array_clear(patch->base.hunks);
33              
34 50           git__free((char *)patch->base.binary.old_file.data);
35 50           git__free((char *)patch->base.binary.new_file.data);
36              
37 50           git_diff_file_content__clear(&patch->ofile);
38 50           git_diff_file_content__clear(&patch->nfile);
39              
40 50           git_diff_free(patch->diff); /* decrements refcount */
41 50           patch->diff = NULL;
42              
43 50           git_pool_clear(&patch->flattened);
44              
45 50           git__free((char *)patch->base.diff_opts.old_prefix);
46 50           git__free((char *)patch->base.diff_opts.new_prefix);
47              
48 50 100         if (patch->flags & GIT_PATCH_GENERATED_ALLOCATED)
49 49           git__free(patch);
50 50           }
51              
52 100           static void patch_generated_update_binary(git_patch_generated *patch)
53             {
54 100 100         if ((patch->base.delta->flags & DIFF_FLAGS_KNOWN_BINARY) != 0)
55 47           return;
56              
57 53 100         if ((patch->ofile.file->flags & GIT_DIFF_FLAG_BINARY) != 0 ||
    50          
58 51           (patch->nfile.file->flags & GIT_DIFF_FLAG_BINARY) != 0)
59 2           patch->base.delta->flags |= GIT_DIFF_FLAG_BINARY;
60              
61 51 50         else if (patch->ofile.file->size > GIT_XDIFF_MAX_SIZE ||
    50          
62 51           patch->nfile.file->size > GIT_XDIFF_MAX_SIZE)
63 0           patch->base.delta->flags |= GIT_DIFF_FLAG_BINARY;
64              
65 51 100         else if ((patch->ofile.file->flags & DIFF_FLAGS_NOT_BINARY) != 0 &&
    100          
66 41           (patch->nfile.file->flags & DIFF_FLAGS_NOT_BINARY) != 0)
67 30           patch->base.delta->flags |= GIT_DIFF_FLAG_NOT_BINARY;
68             }
69              
70 50           static void patch_generated_init_common(git_patch_generated *patch)
71             {
72 50           patch->base.free_fn = patch_generated_free;
73              
74 50           patch_generated_update_binary(patch);
75              
76 50           patch->flags |= GIT_PATCH_GENERATED_INITIALIZED;
77              
78 50 100         if (patch->diff)
79 49           git_diff_addref(patch->diff);
80 50           }
81              
82 50           static int patch_generated_normalize_options(
83             git_diff_options *out,
84             const git_diff_options *opts)
85             {
86 50 50         if (opts) {
87 50 50         GIT_ERROR_CHECK_VERSION(opts, GIT_DIFF_OPTIONS_VERSION, "git_diff_options");
88 50           memcpy(out, opts, sizeof(git_diff_options));
89             } else {
90 0           git_diff_options default_opts = GIT_DIFF_OPTIONS_INIT;
91 0           memcpy(out, &default_opts, sizeof(git_diff_options));
92             }
93              
94 50 50         out->old_prefix = opts && opts->old_prefix ?
    100          
95 49           git__strdup(opts->old_prefix) :
96 1           git__strdup(DIFF_OLD_PREFIX_DEFAULT);
97              
98 50 50         out->new_prefix = opts && opts->new_prefix ?
    100          
99 49           git__strdup(opts->new_prefix) :
100 1           git__strdup(DIFF_NEW_PREFIX_DEFAULT);
101              
102 50 50         GIT_ERROR_CHECK_ALLOC(out->old_prefix);
103 50 50         GIT_ERROR_CHECK_ALLOC(out->new_prefix);
104              
105 50           return 0;
106             }
107              
108 49           static int patch_generated_init(
109             git_patch_generated *patch, git_diff *diff, size_t delta_index)
110             {
111 49           int error = 0;
112              
113 49           memset(patch, 0, sizeof(*patch));
114              
115 49           patch->diff = diff;
116 49           patch->base.repo = diff->repo;
117 49           patch->base.delta = git_vector_get(&diff->deltas, delta_index);
118 49           patch->delta_index = delta_index;
119              
120 49 50         if ((error = patch_generated_normalize_options(
121 49 50         &patch->base.diff_opts, &diff->opts)) < 0 ||
122 49           (error = git_diff_file_content__init_from_diff(
123 49 50         &patch->ofile, diff, patch->base.delta, true)) < 0 ||
124 49           (error = git_diff_file_content__init_from_diff(
125             &patch->nfile, diff, patch->base.delta, false)) < 0)
126 0           return error;
127              
128 49           patch_generated_init_common(patch);
129              
130 49           return 0;
131             }
132              
133 49           static int patch_generated_alloc_from_diff(
134             git_patch_generated **out, git_diff *diff, size_t delta_index)
135             {
136             int error;
137 49           git_patch_generated *patch = git__calloc(1, sizeof(git_patch_generated));
138 49 50         GIT_ERROR_CHECK_ALLOC(patch);
139              
140 49 50         if (!(error = patch_generated_init(patch, diff, delta_index))) {
141 49           patch->flags |= GIT_PATCH_GENERATED_ALLOCATED;
142 49           GIT_REFCOUNT_INC(&patch->base);
143             } else {
144 0           git__free(patch);
145 0           patch = NULL;
146             }
147              
148 49           *out = patch;
149 49           return error;
150             }
151              
152 50           GIT_INLINE(bool) should_skip_binary(git_patch_generated *patch, git_diff_file *file)
153             {
154 50 100         if ((patch->base.diff_opts.flags & GIT_DIFF_SHOW_BINARY) != 0)
155 2           return false;
156              
157 48           return (file->flags & GIT_DIFF_FLAG_BINARY) != 0;
158             }
159              
160 50           static bool patch_generated_diffable(git_patch_generated *patch)
161             {
162             size_t olen, nlen;
163              
164 50 50         if (patch->base.delta->status == GIT_DELTA_UNMODIFIED)
165 0           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 50 100         if ((patch->base.delta->flags & GIT_DIFF_FLAG_BINARY) != 0 &&
    50          
172 2           (patch->base.diff_opts.flags & GIT_DIFF_SHOW_BINARY) == 0) {
173 0           olen = (size_t)patch->ofile.file->size;
174 0           nlen = (size_t)patch->nfile.file->size;
175             } else {
176 50           olen = patch->ofile.map.len;
177 50           nlen = patch->nfile.map.len;
178             }
179              
180             /* if both sides are empty, files are identical */
181 50 100         if (!olen && !nlen)
    100          
182 8           return false;
183              
184             /* otherwise, check the file sizes and the oid */
185 45           return (olen != nlen ||
186 3           !git_oid_equal(&patch->ofile.file->id, &patch->nfile.file->id));
187             }
188              
189 50           static int patch_generated_load(git_patch_generated *patch, git_patch_generated_output *output)
190             {
191 50           int error = 0;
192             bool incomplete_data;
193              
194 50 50         if ((patch->flags & GIT_PATCH_GENERATED_LOADED) != 0)
195 0           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 50 50         if ((patch->ofile.opts_flags & GIT_DIFF_SKIP_BINARY_CHECK) != 0 &&
    0          
201 0 0         output && !output->binary_cb && !output->hunk_cb && !output->data_cb)
    0          
    0          
202 0           return 0;
203              
204 50           incomplete_data =
205 15 50         (((patch->ofile.flags & GIT_DIFF_FLAG__NO_DATA) != 0 ||
206 103 100         (patch->ofile.file->flags & GIT_DIFF_FLAG_VALID_ID) != 0) &&
    100          
207 38 100         ((patch->nfile.flags & GIT_DIFF_FLAG__NO_DATA) != 0 ||
208 38           (patch->nfile.file->flags & GIT_DIFF_FLAG_VALID_ID) != 0));
209              
210 50 50         if ((error = git_diff_file_content__load(
211 50 50         &patch->ofile, &patch->base.diff_opts)) < 0 ||
212 50           (error = git_diff_file_content__load(
213 50 50         &patch->nfile, &patch->base.diff_opts)) < 0 ||
214 50           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 50 100         if (incomplete_data &&
    100          
221 7 50         patch->ofile.file->mode == patch->nfile.file->mode &&
222 7 100         patch->ofile.file->mode != GIT_FILEMODE_COMMIT &&
223 9 50         git_oid_equal(&patch->ofile.file->id, &patch->nfile.file->id) &&
224 2           patch->base.delta->status == GIT_DELTA_MODIFIED) /* not RENAMED/COPIED! */
225 0           patch->base.delta->status = GIT_DELTA_UNMODIFIED;
226              
227             cleanup:
228 50           patch_generated_update_binary(patch);
229              
230 50 50         if (!error) {
231 50 100         if (patch_generated_diffable(patch))
232 40           patch->flags |= GIT_PATCH_GENERATED_DIFFABLE;
233              
234 50           patch->flags |= GIT_PATCH_GENERATED_LOADED;
235             }
236              
237 50           return error;
238             }
239              
240 50           static int patch_generated_invoke_file_callback(
241             git_patch_generated *patch, git_patch_generated_output *output)
242             {
243 100           float progress = patch->diff ?
244 50 100         ((float)patch->delta_index / patch->diff->deltas.length) : 1.0f;
245              
246 50 100         if (!output->file_cb)
247 1           return 0;
248              
249 49           return git_error_set_after_callback_function(
250 98           output->file_cb(patch->base.delta, progress, output->payload),
251             "git_patch");
252             }
253              
254 4           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 4           git_str deflate = GIT_STR_INIT, delta = GIT_STR_INIT;
265 4           size_t delta_data_len = 0;
266             int error;
267              
268             /* The git_delta function accepts unsigned long only */
269 4 50         if (!git__is_ulong(a_datalen) || !git__is_ulong(b_datalen))
    50          
270 0           return GIT_EBUFS;
271              
272 4 50         if ((error = git_zstream_deflatebuf(&deflate, b_data, b_datalen)) < 0)
273 0           goto done;
274              
275             /* The git_delta function accepts unsigned long only */
276 4 50         if (!git__is_ulong(deflate.size)) {
277 0           error = GIT_EBUFS;
278 0           goto done;
279             }
280              
281 4 100         if (a_datalen && b_datalen) {
    50          
282             void *delta_data;
283              
284 0           error = git_delta(&delta_data, &delta_data_len,
285             a_data, a_datalen,
286             b_data, b_datalen,
287             deflate.size);
288              
289 0 0         if (error == 0) {
290 0           error = git_zstream_deflatebuf(
291             &delta, delta_data, delta_data_len);
292              
293 0           git__free(delta_data);
294 0 0         } else if (error == GIT_EBUFS) {
295 0           error = 0;
296             }
297              
298 0 0         if (error < 0)
299 0           goto done;
300             }
301              
302 4 50         if (delta.size && delta.size < deflate.size) {
    0          
303 0           *out_type = GIT_DIFF_BINARY_DELTA;
304 0           *out_datalen = delta.size;
305 0           *out_data = git_str_detach(&delta);
306 0           *out_inflatedlen = delta_data_len;
307             } else {
308 4           *out_type = GIT_DIFF_BINARY_LITERAL;
309 4           *out_datalen = deflate.size;
310 4           *out_data = git_str_detach(&deflate);
311 4           *out_inflatedlen = b_datalen;
312             }
313              
314             done:
315 4           git_str_dispose(&deflate);
316 4           git_str_dispose(&delta);
317              
318 4           return error;
319             }
320              
321 2           static int diff_binary(git_patch_generated_output *output, git_patch_generated *patch)
322             {
323 2           git_diff_binary binary = {0};
324 2           const char *old_data = patch->ofile.map.data;
325 2           const char *new_data = patch->nfile.map.data;
326 2           size_t old_len = patch->ofile.map.len,
327 2           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 2 50         if (patch->base.diff_opts.flags & GIT_DIFF_SHOW_BINARY) {
333 2           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 2 50         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 2 50         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 0           return error;
349             }
350              
351 2           error = git_error_set_after_callback_function(
352 4           output->binary_cb(patch->base.delta, &binary, output->payload),
353             "git_patch");
354              
355 2           git__free((char *) binary.old_file.data);
356 2           git__free((char *) binary.new_file.data);
357              
358 2           return error;
359             }
360              
361 50           static int patch_generated_create(
362             git_patch_generated *patch,
363             git_patch_generated_output *output)
364             {
365 50           int error = 0;
366              
367 50 50         if ((patch->flags & GIT_PATCH_GENERATED_DIFFED) != 0)
368 0           return 0;
369              
370             /* if we are not looking at the binary or text data, don't do the diff */
371 50 100         if (!output->binary_cb && !output->hunk_cb && !output->data_cb)
    50          
    0          
372 0           return 0;
373              
374 50 50         if ((patch->flags & GIT_PATCH_GENERATED_LOADED) == 0 &&
    50          
375             (error = patch_generated_load(patch, output)) < 0)
376 0           return error;
377              
378 50 100         if ((patch->flags & GIT_PATCH_GENERATED_DIFFABLE) == 0)
379 10           return 0;
380              
381 40 100         if ((patch->base.delta->flags & GIT_DIFF_FLAG_BINARY) != 0) {
382 2 50         if (output->binary_cb)
383 2           error = diff_binary(output, patch);
384             }
385             else {
386 38 50         if (output->diff_cb)
387 38           error = output->diff_cb(output, patch);
388             }
389              
390 40           patch->flags |= GIT_PATCH_GENERATED_DIFFED;
391 40           return error;
392             }
393              
394 49           static int diff_required(git_diff *diff, const char *action)
395             {
396 49 50         if (diff)
397 49           return 0;
398 0           git_error_set(GIT_ERROR_INVALID, "must provide valid diff to %s", action);
399 0           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 1           static int diff_single_generate(patch_generated_with_delta *pd, git_xdiff_output *xo)
409             {
410 1           int error = 0;
411 1           git_patch_generated *patch = &pd->patch;
412 1           bool has_old = ((patch->ofile.flags & GIT_DIFF_FLAG__NO_DATA) == 0);
413 1           bool has_new = ((patch->nfile.flags & GIT_DIFF_FLAG__NO_DATA) == 0);
414              
415 1 50         pd->delta.status = has_new ?
    50          
    0          
416             (has_old ? GIT_DELTA_MODIFIED : GIT_DELTA_ADDED) :
417             (has_old ? GIT_DELTA_DELETED : GIT_DELTA_UNTRACKED);
418              
419 1 50         if (git_oid_equal(&patch->nfile.file->id, &patch->ofile.file->id))
420 0           pd->delta.status = GIT_DELTA_UNMODIFIED;
421              
422 1           patch->base.delta = &pd->delta;
423              
424 1           patch_generated_init_common(patch);
425              
426 1 50         if (pd->delta.status == GIT_DELTA_UNMODIFIED &&
    0          
427 0           !(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 0 0         if (patch->base.diff_opts.flags & GIT_DIFF_SHOW_BINARY)
434 0           patch->base.binary.contains_data = 1;
435              
436 0           return error;
437             }
438              
439 1           error = patch_generated_invoke_file_callback(patch, (git_patch_generated_output *)xo);
440              
441 1 50         if (!error)
442 1           error = patch_generated_create(patch, (git_patch_generated_output *)xo);
443              
444 1           return error;
445             }
446              
447 1           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 1           int error = 0;
455 1           git_repository *repo =
456 1 50         oldsrc->blob ? git_blob_owner(oldsrc->blob) :
457 0 0         newsrc->blob ? git_blob_owner(newsrc->blob) : NULL;
458 1           git_diff_file *lfile = &pd->delta.old_file, *rfile = &pd->delta.new_file;
459 1           git_diff_file_content *ldata = &pd->patch.ofile, *rdata = &pd->patch.nfile;
460              
461 1 50         if ((error = patch_generated_normalize_options(&pd->patch.base.diff_opts, opts)) < 0)
462 0           return error;
463              
464 1 50         if (opts && (opts->flags & GIT_DIFF_REVERSE) != 0) {
    50          
465 0           void *tmp = lfile; lfile = rfile; rfile = tmp;
466 0           tmp = ldata; ldata = rdata; rdata = tmp;
467             }
468              
469 1           pd->patch.base.delta = &pd->delta;
470              
471 1 50         if (!oldsrc->as_path) {
472 0 0         if (newsrc->as_path)
473 0           oldsrc->as_path = newsrc->as_path;
474             else
475 0           oldsrc->as_path = newsrc->as_path = "file";
476             }
477 1 50         else if (!newsrc->as_path)
478 0           newsrc->as_path = oldsrc->as_path;
479              
480 1           lfile->path = oldsrc->as_path;
481 1           rfile->path = newsrc->as_path;
482              
483 1 50         if ((error = git_diff_file_content__init_from_src(
484 1 50         ldata, repo, opts, oldsrc, lfile)) < 0 ||
485             (error = git_diff_file_content__init_from_src(
486             rdata, repo, opts, newsrc, rfile)) < 0)
487 0           return error;
488              
489 1           return diff_single_generate(pd, xo);
490             }
491              
492 0           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 0 0         size_t old_len = *old_path ? strlen(*old_path) : 0;
499 0 0         size_t new_len = *new_path ? strlen(*new_path) : 0;
500             size_t alloc_len;
501              
502 0 0         GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, sizeof(*pd), old_len);
    0          
503 0 0         GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, new_len);
    0          
504 0 0         GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, 2);
    0          
505              
506 0           *out = pd = git__calloc(1, alloc_len);
507 0 0         GIT_ERROR_CHECK_ALLOC(pd);
508              
509 0           pd->patch.flags = GIT_PATCH_GENERATED_ALLOCATED;
510              
511 0 0         if (*old_path) {
512 0           memcpy(&pd->paths[0], *old_path, old_len);
513 0           *old_path = &pd->paths[0];
514 0 0         } else if (*new_path)
515 0           *old_path = &pd->paths[old_len + 1];
516              
517 0 0         if (*new_path) {
518 0           memcpy(&pd->paths[old_len + 1], *new_path, new_len);
519 0           *new_path = &pd->paths[old_len + 1];
520 0 0         } else if (*old_path)
521 0           *new_path = &pd->paths[0];
522              
523 0           return 0;
524             }
525              
526 1           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 1           int error = 0;
537             patch_generated_with_delta pd;
538             git_xdiff_output xo;
539              
540 1           memset(&xo, 0, sizeof(xo));
541 1           diff_output_init(
542             &xo.output, opts, file_cb, binary_cb, hunk_cb, data_cb, payload);
543 1           git_xdiff_init(&xo, opts);
544              
545 1           memset(&pd, 0, sizeof(pd));
546              
547 1           error = patch_generated_from_sources(&pd, &xo, oldsrc, newsrc, opts);
548              
549 1           git_patch_free(&pd.patch.base);
550              
551 1           return error;
552             }
553              
554 0           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 0           int error = 0;
561             patch_generated_with_delta *pd;
562             git_xdiff_output xo;
563              
564 0 0         GIT_ASSERT_ARG(out);
565 0           *out = NULL;
566              
567 0 0         if ((error = patch_generated_with_delta_alloc(
568             &pd, &oldsrc->as_path, &newsrc->as_path)) < 0)
569 0           return error;
570              
571 0           memset(&xo, 0, sizeof(xo));
572 0           diff_output_to_patch(&xo.output, &pd->patch);
573 0           git_xdiff_init(&xo, opts);
574              
575 0 0         if (!(error = patch_generated_from_sources(pd, &xo, oldsrc, newsrc, opts)))
576 0           *out = (git_patch *)pd;
577             else
578 0           git_patch_free((git_patch *)pd);
579              
580 0           return error;
581             }
582              
583 0           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 0           git_diff_file_content_src osrc =
596             GIT_DIFF_FILE_CONTENT_SRC__BLOB(old_blob, old_path);
597 0           git_diff_file_content_src nsrc =
598             GIT_DIFF_FILE_CONTENT_SRC__BLOB(new_blob, new_path);
599 0           return diff_from_sources(
600             &osrc, &nsrc, opts, file_cb, binary_cb, hunk_cb, data_cb, payload);
601             }
602              
603 0           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 0           git_diff_file_content_src osrc =
612             GIT_DIFF_FILE_CONTENT_SRC__BLOB(old_blob, old_path);
613 0           git_diff_file_content_src nsrc =
614             GIT_DIFF_FILE_CONTENT_SRC__BLOB(new_blob, new_path);
615 0           return patch_from_sources(out, &osrc, &nsrc, opts);
616             }
617              
618 1           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 1           git_diff_file_content_src osrc =
632             GIT_DIFF_FILE_CONTENT_SRC__BLOB(old_blob, old_path);
633 1           git_diff_file_content_src nsrc =
634             GIT_DIFF_FILE_CONTENT_SRC__BUF(buf, buflen, buf_path);
635 1           return diff_from_sources(
636             &osrc, &nsrc, opts, file_cb, binary_cb, hunk_cb, data_cb, payload);
637             }
638              
639 0           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 0           git_diff_file_content_src osrc =
649             GIT_DIFF_FILE_CONTENT_SRC__BLOB(old_blob, old_path);
650 0           git_diff_file_content_src nsrc =
651             GIT_DIFF_FILE_CONTENT_SRC__BUF(buf, buflen, buf_path);
652 0           return patch_from_sources(out, &osrc, &nsrc, opts);
653             }
654              
655 0           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 0           git_diff_file_content_src osrc =
670             GIT_DIFF_FILE_CONTENT_SRC__BUF(old_buf, old_len, old_path);
671 0           git_diff_file_content_src nsrc =
672             GIT_DIFF_FILE_CONTENT_SRC__BUF(new_buf, new_len, new_path);
673 0           return diff_from_sources(
674             &osrc, &nsrc, opts, file_cb, binary_cb, hunk_cb, data_cb, payload);
675             }
676              
677 0           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 0           git_diff_file_content_src osrc =
688             GIT_DIFF_FILE_CONTENT_SRC__BUF(old_buf, old_len, old_path);
689 0           git_diff_file_content_src nsrc =
690             GIT_DIFF_FILE_CONTENT_SRC__BUF(new_buf, new_len, new_path);
691 0           return patch_from_sources(out, &osrc, &nsrc, opts);
692             }
693              
694 49           int git_patch_generated_from_diff(
695             git_patch **patch_ptr, git_diff *diff, size_t idx)
696             {
697 49           int error = 0;
698             git_xdiff_output xo;
699 49           git_diff_delta *delta = NULL;
700 49           git_patch_generated *patch = NULL;
701              
702 49 50         if (patch_ptr) *patch_ptr = NULL;
703              
704 49 50         if (diff_required(diff, "git_patch_from_diff") < 0)
705 0           return -1;
706              
707 49           delta = git_vector_get(&diff->deltas, idx);
708 49 50         if (!delta) {
709 0           git_error_set(GIT_ERROR_INVALID, "index out of range for delta in diff");
710 0           return GIT_ENOTFOUND;
711             }
712              
713 49 50         if (git_diff_delta__should_skip(&diff->opts, delta))
714 0           return 0;
715              
716             /* don't load the patch data unless we need it for binary check */
717 49 50         if (!patch_ptr &&
    0          
718 0 0         ((delta->flags & DIFF_FLAGS_KNOWN_BINARY) != 0 ||
719 0           (diff->opts.flags & GIT_DIFF_SKIP_BINARY_CHECK) != 0))
720 0           return 0;
721              
722 49 50         if ((error = patch_generated_alloc_from_diff(&patch, diff, idx)) < 0)
723 0           return error;
724              
725 49           memset(&xo, 0, sizeof(xo));
726 49           diff_output_to_patch(&xo.output, patch);
727 49           git_xdiff_init(&xo, &diff->opts);
728              
729 49           error = patch_generated_invoke_file_callback(patch, &xo.output);
730              
731 49 50         if (!error)
732 49           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 49 50         if (error || !patch_ptr)
    50          
740 0           git_patch_free(&patch->base);
741             else
742 49           *patch_ptr = &patch->base;
743              
744 49           return error;
745             }
746              
747 38           git_diff_driver *git_patch_generated_driver(git_patch_generated *patch)
748             {
749             /* ofile driver is representative for whole patch */
750 38           return patch->ofile.driver;
751             }
752              
753 38           int git_patch_generated_old_data(
754             char **ptr, long *len, git_patch_generated *patch)
755             {
756 38 50         if (patch->ofile.map.len > LONG_MAX ||
    50          
757 38           patch->ofile.map.len > GIT_XDIFF_MAX_SIZE) {
758 0           git_error_set(GIT_ERROR_INVALID, "files too large for diff");
759 0           return -1;
760             }
761              
762 38           *ptr = patch->ofile.map.data;
763 38           *len = (long)patch->ofile.map.len;
764              
765 38           return 0;
766             }
767              
768 38           int git_patch_generated_new_data(
769             char **ptr, long *len, git_patch_generated *patch)
770             {
771 38 50         if (patch->ofile.map.len > LONG_MAX ||
    50          
772 38           patch->ofile.map.len > GIT_XDIFF_MAX_SIZE) {
773 0           git_error_set(GIT_ERROR_INVALID, "files too large for diff");
774 0           return -1;
775             }
776              
777 38           *ptr = patch->nfile.map.data;
778 38           *len = (long)patch->nfile.map.len;
779              
780 38           return 0;
781             }
782              
783 49           static int patch_generated_file_cb(
784             const git_diff_delta *delta,
785             float progress,
786             void *payload)
787             {
788 49           GIT_UNUSED(delta); GIT_UNUSED(progress); GIT_UNUSED(payload);
789 49           return 0;
790             }
791              
792 2           static int patch_generated_binary_cb(
793             const git_diff_delta *delta,
794             const git_diff_binary *binary,
795             void *payload)
796             {
797 2           git_patch *patch = payload;
798              
799 2           GIT_UNUSED(delta);
800              
801 2           memcpy(&patch->binary, binary, sizeof(git_diff_binary));
802              
803 2 50         if (binary->old_file.data) {
804 2           patch->binary.old_file.data = git__malloc(binary->old_file.datalen);
805 2 50         GIT_ERROR_CHECK_ALLOC(patch->binary.old_file.data);
806              
807 2           memcpy((char *)patch->binary.old_file.data,
808 2           binary->old_file.data, binary->old_file.datalen);
809             }
810              
811 2 50         if (binary->new_file.data) {
812 2           patch->binary.new_file.data = git__malloc(binary->new_file.datalen);
813 2 50         GIT_ERROR_CHECK_ALLOC(patch->binary.new_file.data);
814              
815 2           memcpy((char *)patch->binary.new_file.data,
816 2           binary->new_file.data, binary->new_file.datalen);
817             }
818              
819 2           return 0;
820             }
821              
822 37           static int git_patch_hunk_cb(
823             const git_diff_delta *delta,
824             const git_diff_hunk *hunk_,
825             void *payload)
826             {
827 37           git_patch_generated *patch = payload;
828             git_patch_hunk *hunk;
829              
830 37           GIT_UNUSED(delta);
831              
832 37 50         hunk = git_array_alloc(patch->base.hunks);
    50          
833 37 50         GIT_ERROR_CHECK_ALLOC(hunk);
834              
835 37           memcpy(&hunk->hunk, hunk_, sizeof(hunk->hunk));
836              
837 37           patch->base.header_size += hunk_->header_len;
838              
839 37           hunk->line_start = git_array_size(patch->base.lines);
840 37           hunk->line_count = 0;
841              
842 37           return 0;
843             }
844              
845 79           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 79           git_patch_generated *patch = payload;
852             git_patch_hunk *hunk;
853             git_diff_line *line;
854              
855 79           GIT_UNUSED(delta);
856 79           GIT_UNUSED(hunk_);
857              
858 79 50         hunk = git_array_last(patch->base.hunks);
859 79 50         GIT_ASSERT(hunk); /* programmer error if no hunk is available */
860              
861 79 100         line = git_array_alloc(patch->base.lines);
    50          
862 79 50         GIT_ERROR_CHECK_ALLOC(line);
863              
864 79           memcpy(line, line_, sizeof(*line));
865              
866             /* do some bookkeeping so we can provide old/new line numbers */
867              
868 79           patch->base.content_size += line->content_len;
869              
870 79 100         if (line->origin == GIT_DIFF_LINE_ADDITION ||
    100          
871 43           line->origin == GIT_DIFF_LINE_DELETION)
872 49           patch->base.content_size += 1;
873 30 100         else if (line->origin == GIT_DIFF_LINE_CONTEXT) {
874 6           patch->base.content_size += 1;
875 6           patch->base.context_size += line->content_len + 1;
876 24 50         } else if (line->origin == GIT_DIFF_LINE_CONTEXT_EOFNL)
877 0           patch->base.context_size += line->content_len;
878              
879 79           hunk->line_count++;
880              
881 79           return 0;
882             }
883              
884 50           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 50           GIT_UNUSED(opts);
894              
895 50           memset(out, 0, sizeof(*out));
896              
897 50           out->file_cb = file_cb;
898 50           out->binary_cb = binary_cb;
899 50           out->hunk_cb = hunk_cb;
900 50           out->data_cb = data_cb;
901 50           out->payload = payload;
902 50           }
903              
904 49           static void diff_output_to_patch(
905             git_patch_generated_output *out, git_patch_generated *patch)
906             {
907 49           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 49           }