File Coverage

deps/libgit2/src/patch_generate.c
Criterion Covered Total %
statement 289 401 72.0
branch 133 274 48.5
condition n/a
subroutine n/a
pod n/a
total 422 675 62.5


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 100           GIT_INLINE(bool) should_skip_binary(git_patch_generated *patch, git_diff_file *file)
153             {
154 100 100         if ((patch->base.diff_opts.flags & GIT_DIFF_SHOW_BINARY) != 0)
155 4           return false;
156              
157 96           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           should_skip_binary(patch, patch->ofile.file))
213             goto cleanup;
214 50 50         if ((error = git_diff_file_content__load(
215 50 50         &patch->nfile, &patch->base.diff_opts)) < 0 ||
216 50           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 50 100         if (incomplete_data &&
    100          
223 7 50         patch->ofile.file->mode == patch->nfile.file->mode &&
224 7 100         patch->ofile.file->mode != GIT_FILEMODE_COMMIT &&
225 9 50         git_oid_equal(&patch->ofile.file->id, &patch->nfile.file->id) &&
226 2           patch->base.delta->status == GIT_DELTA_MODIFIED) /* not RENAMED/COPIED! */
227 0           patch->base.delta->status = GIT_DELTA_UNMODIFIED;
228              
229             cleanup:
230 50           patch_generated_update_binary(patch);
231              
232 50 50         if (!error) {
233 50 100         if (patch_generated_diffable(patch))
234 40           patch->flags |= GIT_PATCH_GENERATED_DIFFABLE;
235              
236 50           patch->flags |= GIT_PATCH_GENERATED_LOADED;
237             }
238              
239 50           return error;
240             }
241              
242 50           static int patch_generated_invoke_file_callback(
243             git_patch_generated *patch, git_patch_generated_output *output)
244             {
245 100           float progress = patch->diff ?
246 50 100         ((float)patch->delta_index / patch->diff->deltas.length) : 1.0f;
247              
248 50 100         if (!output->file_cb)
249 1           return 0;
250              
251 49           return git_error_set_after_callback_function(
252 98           output->file_cb(patch->base.delta, progress, output->payload),
253             "git_patch");
254             }
255              
256 4           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 4           git_buf deflate = GIT_BUF_INIT, delta = GIT_BUF_INIT;
267 4           size_t delta_data_len = 0;
268             int error;
269              
270             /* The git_delta function accepts unsigned long only */
271 4 50         if (!git__is_ulong(a_datalen) || !git__is_ulong(b_datalen))
    50          
272 0           return GIT_EBUFS;
273              
274 4 50         if ((error = git_zstream_deflatebuf(&deflate, b_data, b_datalen)) < 0)
275 0           goto done;
276              
277             /* The git_delta function accepts unsigned long only */
278 4 50         if (!git__is_ulong(deflate.size)) {
279 0           error = GIT_EBUFS;
280 0           goto done;
281             }
282              
283 4 100         if (a_datalen && b_datalen) {
    50          
284             void *delta_data;
285              
286 0           error = git_delta(&delta_data, &delta_data_len,
287             a_data, a_datalen,
288             b_data, b_datalen,
289             deflate.size);
290              
291 0 0         if (error == 0) {
292 0           error = git_zstream_deflatebuf(
293             &delta, delta_data, delta_data_len);
294              
295 0           git__free(delta_data);
296 0 0         } else if (error == GIT_EBUFS) {
297 0           error = 0;
298             }
299              
300 0 0         if (error < 0)
301 0           goto done;
302             }
303              
304 4 50         if (delta.size && delta.size < deflate.size) {
    0          
305 0           *out_type = GIT_DIFF_BINARY_DELTA;
306 0           *out_datalen = delta.size;
307 0           *out_data = git_buf_detach(&delta);
308 0           *out_inflatedlen = delta_data_len;
309             } else {
310 4           *out_type = GIT_DIFF_BINARY_LITERAL;
311 4           *out_datalen = deflate.size;
312 4           *out_data = git_buf_detach(&deflate);
313 4           *out_inflatedlen = b_datalen;
314             }
315              
316             done:
317 4           git_buf_dispose(&deflate);
318 4           git_buf_dispose(&delta);
319              
320 4           return error;
321             }
322              
323 2           static int diff_binary(git_patch_generated_output *output, git_patch_generated *patch)
324             {
325 2           git_diff_binary binary = {0};
326 2           const char *old_data = patch->ofile.map.data;
327 2           const char *new_data = patch->nfile.map.data;
328 2           size_t old_len = patch->ofile.map.len,
329 2           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 2 50         if (patch->base.diff_opts.flags & GIT_DIFF_SHOW_BINARY) {
335 2           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 2 50         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 2 50         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 0           return error;
351             }
352              
353 2           error = git_error_set_after_callback_function(
354 4           output->binary_cb(patch->base.delta, &binary, output->payload),
355             "git_patch");
356              
357 2           git__free((char *) binary.old_file.data);
358 2           git__free((char *) binary.new_file.data);
359              
360 2           return error;
361             }
362              
363 50           static int patch_generated_create(
364             git_patch_generated *patch,
365             git_patch_generated_output *output)
366             {
367 50           int error = 0;
368              
369 50 50         if ((patch->flags & GIT_PATCH_GENERATED_DIFFED) != 0)
370 0           return 0;
371              
372             /* if we are not looking at the binary or text data, don't do the diff */
373 50 100         if (!output->binary_cb && !output->hunk_cb && !output->data_cb)
    50          
    0          
374 0           return 0;
375              
376 50 50         if ((patch->flags & GIT_PATCH_GENERATED_LOADED) == 0 &&
    50          
377             (error = patch_generated_load(patch, output)) < 0)
378 0           return error;
379              
380 50 100         if ((patch->flags & GIT_PATCH_GENERATED_DIFFABLE) == 0)
381 10           return 0;
382              
383 40 100         if ((patch->base.delta->flags & GIT_DIFF_FLAG_BINARY) != 0) {
384 2 50         if (output->binary_cb)
385 2           error = diff_binary(output, patch);
386             }
387             else {
388 38 50         if (output->diff_cb)
389 38           error = output->diff_cb(output, patch);
390             }
391              
392 40           patch->flags |= GIT_PATCH_GENERATED_DIFFED;
393 40           return error;
394             }
395              
396 49           static int diff_required(git_diff *diff, const char *action)
397             {
398 49 50         if (diff)
399 49           return 0;
400 0           git_error_set(GIT_ERROR_INVALID, "must provide valid diff to %s", action);
401 0           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 1           static int diff_single_generate(patch_generated_with_delta *pd, git_xdiff_output *xo)
411             {
412 1           int error = 0;
413 1           git_patch_generated *patch = &pd->patch;
414 1           bool has_old = ((patch->ofile.flags & GIT_DIFF_FLAG__NO_DATA) == 0);
415 1           bool has_new = ((patch->nfile.flags & GIT_DIFF_FLAG__NO_DATA) == 0);
416              
417 1 50         pd->delta.status = has_new ?
    50          
    0          
418             (has_old ? GIT_DELTA_MODIFIED : GIT_DELTA_ADDED) :
419             (has_old ? GIT_DELTA_DELETED : GIT_DELTA_UNTRACKED);
420              
421 1 50         if (git_oid_equal(&patch->nfile.file->id, &patch->ofile.file->id))
422 0           pd->delta.status = GIT_DELTA_UNMODIFIED;
423              
424 1           patch->base.delta = &pd->delta;
425              
426 1           patch_generated_init_common(patch);
427              
428 1 50         if (pd->delta.status == GIT_DELTA_UNMODIFIED &&
    0          
429 0           !(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 0 0         if (patch->base.diff_opts.flags & GIT_DIFF_SHOW_BINARY)
436 0           patch->base.binary.contains_data = 1;
437              
438 0           return error;
439             }
440              
441 1           error = patch_generated_invoke_file_callback(patch, (git_patch_generated_output *)xo);
442              
443 1 50         if (!error)
444 1           error = patch_generated_create(patch, (git_patch_generated_output *)xo);
445              
446 1           return error;
447             }
448              
449 1           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 1           int error = 0;
457 1           git_repository *repo =
458 1 50         oldsrc->blob ? git_blob_owner(oldsrc->blob) :
459 0 0         newsrc->blob ? git_blob_owner(newsrc->blob) : NULL;
460 1           git_diff_file *lfile = &pd->delta.old_file, *rfile = &pd->delta.new_file;
461 1           git_diff_file_content *ldata = &pd->patch.ofile, *rdata = &pd->patch.nfile;
462              
463 1 50         if ((error = patch_generated_normalize_options(&pd->patch.base.diff_opts, opts)) < 0)
464 0           return error;
465              
466 1 50         if (opts && (opts->flags & GIT_DIFF_REVERSE) != 0) {
    50          
467 0           void *tmp = lfile; lfile = rfile; rfile = tmp;
468 0           tmp = ldata; ldata = rdata; rdata = tmp;
469             }
470              
471 1           pd->patch.base.delta = &pd->delta;
472              
473 1 50         if (!oldsrc->as_path) {
474 0 0         if (newsrc->as_path)
475 0           oldsrc->as_path = newsrc->as_path;
476             else
477 0           oldsrc->as_path = newsrc->as_path = "file";
478             }
479 1 50         else if (!newsrc->as_path)
480 0           newsrc->as_path = oldsrc->as_path;
481              
482 1           lfile->path = oldsrc->as_path;
483 1           rfile->path = newsrc->as_path;
484              
485 1 50         if ((error = git_diff_file_content__init_from_src(
486 1 50         ldata, repo, opts, oldsrc, lfile)) < 0 ||
487             (error = git_diff_file_content__init_from_src(
488             rdata, repo, opts, newsrc, rfile)) < 0)
489 0           return error;
490              
491 1           return diff_single_generate(pd, xo);
492             }
493              
494 0           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 0 0         size_t old_len = *old_path ? strlen(*old_path) : 0;
501 0 0         size_t new_len = *new_path ? strlen(*new_path) : 0;
502             size_t alloc_len;
503              
504 0 0         GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, sizeof(*pd), old_len);
    0          
505 0 0         GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, new_len);
    0          
506 0 0         GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, 2);
    0          
507              
508 0           *out = pd = git__calloc(1, alloc_len);
509 0 0         GIT_ERROR_CHECK_ALLOC(pd);
510              
511 0           pd->patch.flags = GIT_PATCH_GENERATED_ALLOCATED;
512              
513 0 0         if (*old_path) {
514 0           memcpy(&pd->paths[0], *old_path, old_len);
515 0           *old_path = &pd->paths[0];
516 0 0         } else if (*new_path)
517 0           *old_path = &pd->paths[old_len + 1];
518              
519 0 0         if (*new_path) {
520 0           memcpy(&pd->paths[old_len + 1], *new_path, new_len);
521 0           *new_path = &pd->paths[old_len + 1];
522 0 0         } else if (*old_path)
523 0           *new_path = &pd->paths[0];
524              
525 0           return 0;
526             }
527              
528 1           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 1           int error = 0;
539             patch_generated_with_delta pd;
540             git_xdiff_output xo;
541              
542 1           memset(&xo, 0, sizeof(xo));
543 1           diff_output_init(
544             &xo.output, opts, file_cb, binary_cb, hunk_cb, data_cb, payload);
545 1           git_xdiff_init(&xo, opts);
546              
547 1           memset(&pd, 0, sizeof(pd));
548              
549 1           error = patch_generated_from_sources(&pd, &xo, oldsrc, newsrc, opts);
550              
551 1           git_patch_free(&pd.patch.base);
552              
553 1           return error;
554             }
555              
556 0           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 0           int error = 0;
563             patch_generated_with_delta *pd;
564             git_xdiff_output xo;
565              
566 0 0         assert(out);
567 0           *out = NULL;
568              
569 0 0         if ((error = patch_generated_with_delta_alloc(
570             &pd, &oldsrc->as_path, &newsrc->as_path)) < 0)
571 0           return error;
572              
573 0           memset(&xo, 0, sizeof(xo));
574 0           diff_output_to_patch(&xo.output, &pd->patch);
575 0           git_xdiff_init(&xo, opts);
576              
577 0 0         if (!(error = patch_generated_from_sources(pd, &xo, oldsrc, newsrc, opts)))
578 0           *out = (git_patch *)pd;
579             else
580 0           git_patch_free((git_patch *)pd);
581              
582 0           return error;
583             }
584              
585 0           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 0           git_diff_file_content_src osrc =
598             GIT_DIFF_FILE_CONTENT_SRC__BLOB(old_blob, old_path);
599 0           git_diff_file_content_src nsrc =
600             GIT_DIFF_FILE_CONTENT_SRC__BLOB(new_blob, new_path);
601 0           return diff_from_sources(
602             &osrc, &nsrc, opts, file_cb, binary_cb, hunk_cb, data_cb, payload);
603             }
604              
605 0           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 0           git_diff_file_content_src osrc =
614             GIT_DIFF_FILE_CONTENT_SRC__BLOB(old_blob, old_path);
615 0           git_diff_file_content_src nsrc =
616             GIT_DIFF_FILE_CONTENT_SRC__BLOB(new_blob, new_path);
617 0           return patch_from_sources(out, &osrc, &nsrc, opts);
618             }
619              
620 1           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 1           git_diff_file_content_src osrc =
634             GIT_DIFF_FILE_CONTENT_SRC__BLOB(old_blob, old_path);
635 1           git_diff_file_content_src nsrc =
636             GIT_DIFF_FILE_CONTENT_SRC__BUF(buf, buflen, buf_path);
637 1           return diff_from_sources(
638             &osrc, &nsrc, opts, file_cb, binary_cb, hunk_cb, data_cb, payload);
639             }
640              
641 0           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 0           git_diff_file_content_src osrc =
651             GIT_DIFF_FILE_CONTENT_SRC__BLOB(old_blob, old_path);
652 0           git_diff_file_content_src nsrc =
653             GIT_DIFF_FILE_CONTENT_SRC__BUF(buf, buflen, buf_path);
654 0           return patch_from_sources(out, &osrc, &nsrc, opts);
655             }
656              
657 0           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 0           git_diff_file_content_src osrc =
672             GIT_DIFF_FILE_CONTENT_SRC__BUF(old_buf, old_len, old_path);
673 0           git_diff_file_content_src nsrc =
674             GIT_DIFF_FILE_CONTENT_SRC__BUF(new_buf, new_len, new_path);
675 0           return diff_from_sources(
676             &osrc, &nsrc, opts, file_cb, binary_cb, hunk_cb, data_cb, payload);
677             }
678              
679 0           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 0           git_diff_file_content_src osrc =
690             GIT_DIFF_FILE_CONTENT_SRC__BUF(old_buf, old_len, old_path);
691 0           git_diff_file_content_src nsrc =
692             GIT_DIFF_FILE_CONTENT_SRC__BUF(new_buf, new_len, new_path);
693 0           return patch_from_sources(out, &osrc, &nsrc, opts);
694             }
695              
696 49           int git_patch_generated_from_diff(
697             git_patch **patch_ptr, git_diff *diff, size_t idx)
698             {
699 49           int error = 0;
700             git_xdiff_output xo;
701 49           git_diff_delta *delta = NULL;
702 49           git_patch_generated *patch = NULL;
703              
704 49 50         if (patch_ptr) *patch_ptr = NULL;
705              
706 49 50         if (diff_required(diff, "git_patch_from_diff") < 0)
707 0           return -1;
708              
709 49           delta = git_vector_get(&diff->deltas, idx);
710 49 50         if (!delta) {
711 0           git_error_set(GIT_ERROR_INVALID, "index out of range for delta in diff");
712 0           return GIT_ENOTFOUND;
713             }
714              
715 49 50         if (git_diff_delta__should_skip(&diff->opts, delta))
716 0           return 0;
717              
718             /* don't load the patch data unless we need it for binary check */
719 49 50         if (!patch_ptr &&
    0          
720 0 0         ((delta->flags & DIFF_FLAGS_KNOWN_BINARY) != 0 ||
721 0           (diff->opts.flags & GIT_DIFF_SKIP_BINARY_CHECK) != 0))
722 0           return 0;
723              
724 49 50         if ((error = patch_generated_alloc_from_diff(&patch, diff, idx)) < 0)
725 0           return error;
726              
727 49           memset(&xo, 0, sizeof(xo));
728 49           diff_output_to_patch(&xo.output, patch);
729 49           git_xdiff_init(&xo, &diff->opts);
730              
731 49           error = patch_generated_invoke_file_callback(patch, &xo.output);
732              
733 49 50         if (!error)
734 49           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 49 50         if (error || !patch_ptr)
    50          
742 0           git_patch_free(&patch->base);
743             else
744 49           *patch_ptr = &patch->base;
745              
746 49           return error;
747             }
748              
749 38           git_diff_driver *git_patch_generated_driver(git_patch_generated *patch)
750             {
751             /* ofile driver is representative for whole patch */
752 38           return patch->ofile.driver;
753             }
754              
755 38           void git_patch_generated_old_data(
756             char **ptr, size_t *len, git_patch_generated *patch)
757             {
758 38           *ptr = patch->ofile.map.data;
759 38           *len = patch->ofile.map.len;
760 38           }
761              
762 38           void git_patch_generated_new_data(
763             char **ptr, size_t *len, git_patch_generated *patch)
764             {
765 38           *ptr = patch->nfile.map.data;
766 38           *len = patch->nfile.map.len;
767 38           }
768              
769 49           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 49           return 0;
776             }
777              
778 2           static int patch_generated_binary_cb(
779             const git_diff_delta *delta,
780             const git_diff_binary *binary,
781             void *payload)
782             {
783 2           git_patch *patch = payload;
784              
785             GIT_UNUSED(delta);
786              
787 2           memcpy(&patch->binary, binary, sizeof(git_diff_binary));
788              
789 2 50         if (binary->old_file.data) {
790 2           patch->binary.old_file.data = git__malloc(binary->old_file.datalen);
791 2 50         GIT_ERROR_CHECK_ALLOC(patch->binary.old_file.data);
792              
793 2           memcpy((char *)patch->binary.old_file.data,
794 2           binary->old_file.data, binary->old_file.datalen);
795             }
796              
797 2 50         if (binary->new_file.data) {
798 2           patch->binary.new_file.data = git__malloc(binary->new_file.datalen);
799 2 50         GIT_ERROR_CHECK_ALLOC(patch->binary.new_file.data);
800              
801 2           memcpy((char *)patch->binary.new_file.data,
802 2           binary->new_file.data, binary->new_file.datalen);
803             }
804              
805 2           return 0;
806             }
807              
808 37           static int git_patch_hunk_cb(
809             const git_diff_delta *delta,
810             const git_diff_hunk *hunk_,
811             void *payload)
812             {
813 37           git_patch_generated *patch = payload;
814             git_patch_hunk *hunk;
815              
816             GIT_UNUSED(delta);
817              
818 37 50         hunk = git_array_alloc(patch->base.hunks);
    0          
819 37 50         GIT_ERROR_CHECK_ALLOC(hunk);
820              
821 37           memcpy(&hunk->hunk, hunk_, sizeof(hunk->hunk));
822              
823 37           patch->base.header_size += hunk_->header_len;
824              
825 37           hunk->line_start = git_array_size(patch->base.lines);
826 37           hunk->line_count = 0;
827              
828 37           return 0;
829             }
830              
831 79           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 79           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 79 50         hunk = git_array_last(patch->base.hunks);
845 79 50         assert(hunk); /* programmer error if no hunk is available */
846              
847 79 100         line = git_array_alloc(patch->base.lines);
    50          
848 79 50         GIT_ERROR_CHECK_ALLOC(line);
849              
850 79           memcpy(line, line_, sizeof(*line));
851              
852             /* do some bookkeeping so we can provide old/new line numbers */
853              
854 79           patch->base.content_size += line->content_len;
855              
856 79 100         if (line->origin == GIT_DIFF_LINE_ADDITION ||
    100          
857 43           line->origin == GIT_DIFF_LINE_DELETION)
858 49           patch->base.content_size += 1;
859 30 100         else if (line->origin == GIT_DIFF_LINE_CONTEXT) {
860 6           patch->base.content_size += 1;
861 6           patch->base.context_size += line->content_len + 1;
862 24 50         } else if (line->origin == GIT_DIFF_LINE_CONTEXT_EOFNL)
863 0           patch->base.context_size += line->content_len;
864              
865 79           hunk->line_count++;
866              
867 79           return 0;
868             }
869              
870 50           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 50           memset(out, 0, sizeof(*out));
882              
883 50           out->file_cb = file_cb;
884 50           out->binary_cb = binary_cb;
885 50           out->hunk_cb = hunk_cb;
886 50           out->data_cb = data_cb;
887 50           out->payload = payload;
888 50           }
889              
890 49           static void diff_output_to_patch(
891             git_patch_generated_output *out, git_patch_generated *patch)
892             {
893 49           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 49           }