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