]> git.proxmox.com Git - libgit2.git/blame - src/diff_patch.c
binary diff: test index->workdir binary diffs
[libgit2.git] / src / diff_patch.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 */
7#include "common.h"
6789b7a7 8#include "git2/blob.h"
114f5a6c
RB
9#include "diff.h"
10#include "diff_file.h"
11#include "diff_driver.h"
12#include "diff_patch.h"
13#include "diff_xdiff.h"
c67ff958 14#include "fileops.h"
114f5a6c 15
360f42f4 16/* cached information about a hunk in a diff */
06280457 17typedef struct diff_patch_hunk {
3ff1d123 18 git_diff_hunk hunk;
360f42f4
RB
19 size_t line_start;
20 size_t line_count;
06280457 21} diff_patch_hunk;
360f42f4 22
3ff1d123 23struct git_patch {
360f42f4 24 git_refcount rc;
3ff1d123 25 git_diff *diff; /* for refcount purposes, maybe NULL for blob diffs */
360f42f4
RB
26 git_diff_delta *delta;
27 size_t delta_index;
28 git_diff_file_content ofile;
29 git_diff_file_content nfile;
30 uint32_t flags;
31 git_array_t(diff_patch_hunk) hunks;
3b5f7954 32 git_array_t(git_diff_line) lines;
197b8966 33 size_t content_size, context_size, header_size;
360f42f4
RB
34 git_pool flattened;
35};
36
37enum {
38 GIT_DIFF_PATCH_ALLOCATED = (1 << 0),
39 GIT_DIFF_PATCH_INITIALIZED = (1 << 1),
40 GIT_DIFF_PATCH_LOADED = (1 << 2),
41 GIT_DIFF_PATCH_DIFFABLE = (1 << 3),
42 GIT_DIFF_PATCH_DIFFED = (1 << 4),
43 GIT_DIFF_PATCH_FLATTENED = (1 << 5),
44};
45
3b5f7954
RB
46static void diff_output_init(
47 git_diff_output*, const git_diff_options*,
3ff1d123 48 git_diff_file_cb, git_diff_hunk_cb, git_diff_line_cb, void*);
114f5a6c 49
3ff1d123 50static void diff_output_to_patch(git_diff_output *, git_patch *);
114f5a6c 51
3ff1d123 52static void diff_patch_update_binary(git_patch *patch)
114f5a6c
RB
53{
54 if ((patch->delta->flags & DIFF_FLAGS_KNOWN_BINARY) != 0)
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)
114f5a6c
RB
59 patch->delta->flags |= GIT_DIFF_FLAG_BINARY;
60
74ded024
RB
61 else if ((patch->ofile.file->flags & DIFF_FLAGS_NOT_BINARY) != 0 &&
62 (patch->nfile.file->flags & DIFF_FLAGS_NOT_BINARY) != 0)
114f5a6c
RB
63 patch->delta->flags |= GIT_DIFF_FLAG_NOT_BINARY;
64}
65
3ff1d123 66static void diff_patch_init_common(git_patch *patch)
114f5a6c
RB
67{
68 diff_patch_update_binary(patch);
69
70 if ((patch->delta->flags & GIT_DIFF_FLAG_BINARY) != 0)
3b5f7954 71 patch->flags |= GIT_DIFF_PATCH_LOADED; /* LOADED but not DIFFABLE */
114f5a6c
RB
72
73 patch->flags |= GIT_DIFF_PATCH_INITIALIZED;
74
75 if (patch->diff)
3ff1d123 76 git_diff_addref(patch->diff);
114f5a6c
RB
77}
78
79static int diff_patch_init_from_diff(
3ff1d123 80 git_patch *patch, git_diff *diff, size_t delta_index)
114f5a6c
RB
81{
82 int error = 0;
83
84 memset(patch, 0, sizeof(*patch));
85 patch->diff = diff;
86 patch->delta = git_vector_get(&diff->deltas, delta_index);
87 patch->delta_index = delta_index;
88
360f42f4 89 if ((error = git_diff_file_content__init_from_diff(
114f5a6c 90 &patch->ofile, diff, delta_index, true)) < 0 ||
360f42f4 91 (error = git_diff_file_content__init_from_diff(
114f5a6c
RB
92 &patch->nfile, diff, delta_index, false)) < 0)
93 return error;
94
95 diff_patch_init_common(patch);
96
97 return 0;
98}
99
100static int diff_patch_alloc_from_diff(
10672e3e 101 git_patch **out, git_diff *diff, size_t delta_index)
114f5a6c
RB
102{
103 int error;
3ff1d123 104 git_patch *patch = git__calloc(1, sizeof(git_patch));
114f5a6c
RB
105 GITERR_CHECK_ALLOC(patch);
106
107 if (!(error = diff_patch_init_from_diff(patch, diff, delta_index))) {
108 patch->flags |= GIT_DIFF_PATCH_ALLOCATED;
109 GIT_REFCOUNT_INC(patch);
110 } else {
111 git__free(patch);
112 patch = NULL;
113 }
114
115 *out = patch;
116 return error;
117}
118
3ff1d123 119static int diff_patch_load(git_patch *patch, git_diff_output *output)
114f5a6c
RB
120{
121 int error = 0;
122 bool incomplete_data;
123
124 if ((patch->flags & GIT_DIFF_PATCH_LOADED) != 0)
125 return 0;
126
127 /* if no hunk and data callbacks and user doesn't care if data looks
128 * binary, then there is no need to actually load the data
129 */
5dc98298 130 if ((patch->ofile.opts_flags & GIT_DIFF_SKIP_BINARY_CHECK) != 0 &&
114f5a6c
RB
131 output && !output->hunk_cb && !output->data_cb)
132 return 0;
133
114f5a6c 134 incomplete_data =
74ded024 135 (((patch->ofile.flags & GIT_DIFF_FLAG__NO_DATA) != 0 ||
9950bb4e 136 (patch->ofile.file->flags & GIT_DIFF_FLAG_VALID_ID) != 0) &&
74ded024 137 ((patch->nfile.flags & GIT_DIFF_FLAG__NO_DATA) != 0 ||
9950bb4e 138 (patch->nfile.file->flags & GIT_DIFF_FLAG_VALID_ID) != 0));
114f5a6c
RB
139
140 /* always try to load workdir content first because filtering may
141 * need 2x data size and this minimizes peak memory footprint
142 */
143 if (patch->ofile.src == GIT_ITERATOR_TYPE_WORKDIR) {
360f42f4 144 if ((error = git_diff_file_content__load(&patch->ofile)) < 0 ||
74ded024 145 (patch->ofile.file->flags & GIT_DIFF_FLAG_BINARY) != 0)
114f5a6c
RB
146 goto cleanup;
147 }
148 if (patch->nfile.src == GIT_ITERATOR_TYPE_WORKDIR) {
360f42f4 149 if ((error = git_diff_file_content__load(&patch->nfile)) < 0 ||
74ded024 150 (patch->nfile.file->flags & GIT_DIFF_FLAG_BINARY) != 0)
114f5a6c
RB
151 goto cleanup;
152 }
153
154 /* once workdir has been tried, load other data as needed */
155 if (patch->ofile.src != GIT_ITERATOR_TYPE_WORKDIR) {
360f42f4 156 if ((error = git_diff_file_content__load(&patch->ofile)) < 0 ||
74ded024 157 (patch->ofile.file->flags & GIT_DIFF_FLAG_BINARY) != 0)
114f5a6c
RB
158 goto cleanup;
159 }
160 if (patch->nfile.src != GIT_ITERATOR_TYPE_WORKDIR) {
360f42f4 161 if ((error = git_diff_file_content__load(&patch->nfile)) < 0 ||
74ded024 162 (patch->nfile.file->flags & GIT_DIFF_FLAG_BINARY) != 0)
114f5a6c
RB
163 goto cleanup;
164 }
165
c67ff958
RB
166 /* if previously missing an oid, and now that we have it the two sides
167 * are the same (and not submodules), update MODIFIED -> UNMODIFIED
168 */
114f5a6c 169 if (incomplete_data &&
74ded024 170 patch->ofile.file->mode == patch->nfile.file->mode &&
c67ff958 171 patch->ofile.file->mode != GIT_FILEMODE_COMMIT &&
9950bb4e 172 git_oid_equal(&patch->ofile.file->id, &patch->nfile.file->id) &&
a1683f28 173 patch->delta->status == GIT_DELTA_MODIFIED) /* not RENAMED/COPIED! */
114f5a6c
RB
174 patch->delta->status = GIT_DELTA_UNMODIFIED;
175
176cleanup:
177 diff_patch_update_binary(patch);
178
179 if (!error) {
180 /* patch is diffable only for non-binary, modified files where
181 * at least one side has data and the data actually changed
182 */
183 if ((patch->delta->flags & GIT_DIFF_FLAG_BINARY) == 0 &&
184 patch->delta->status != GIT_DELTA_UNMODIFIED &&
185 (patch->ofile.map.len || patch->nfile.map.len) &&
186 (patch->ofile.map.len != patch->nfile.map.len ||
9950bb4e 187 !git_oid_equal(&patch->ofile.file->id, &patch->nfile.file->id)))
114f5a6c
RB
188 patch->flags |= GIT_DIFF_PATCH_DIFFABLE;
189
190 patch->flags |= GIT_DIFF_PATCH_LOADED;
191 }
192
193 return error;
194}
195
96869a4e 196static int diff_patch_invoke_file_callback(
3ff1d123 197 git_patch *patch, git_diff_output *output)
114f5a6c 198{
96869a4e 199 float progress = patch->diff ?
114f5a6c
RB
200 ((float)patch->delta_index / patch->diff->deltas.length) : 1.0f;
201
25e0b157
RB
202 if (!output->file_cb)
203 return 0;
114f5a6c 204
26c1cb91 205 return giterr_set_after_callback_function(
25e0b157
RB
206 output->file_cb(patch->delta, progress, output->payload),
207 "git_patch");
114f5a6c
RB
208}
209
3ff1d123 210static int diff_patch_generate(git_patch *patch, git_diff_output *output)
114f5a6c
RB
211{
212 int error = 0;
213
214 if ((patch->flags & GIT_DIFF_PATCH_DIFFED) != 0)
215 return 0;
216
69c66b55
RB
217 /* if we are not looking at the hunks and lines, don't do the diff */
218 if (!output->hunk_cb && !output->data_cb)
219 return 0;
220
114f5a6c
RB
221 if ((patch->flags & GIT_DIFF_PATCH_LOADED) == 0 &&
222 (error = diff_patch_load(patch, output)) < 0)
223 return error;
224
225 if ((patch->flags & GIT_DIFF_PATCH_DIFFABLE) == 0)
226 return 0;
227
228 if (output->diff_cb != NULL &&
96869a4e 229 (error = output->diff_cb(output, patch)) < 0)
114f5a6c
RB
230 patch->flags |= GIT_DIFF_PATCH_DIFFED;
231
232 return error;
233}
234
3ff1d123 235static void diff_patch_free(git_patch *patch)
114f5a6c 236{
360f42f4
RB
237 git_diff_file_content__clear(&patch->ofile);
238 git_diff_file_content__clear(&patch->nfile);
114f5a6c
RB
239
240 git_array_clear(patch->lines);
241 git_array_clear(patch->hunks);
242
3ff1d123 243 git_diff_free(patch->diff); /* decrements refcount */
114f5a6c
RB
244 patch->diff = NULL;
245
246 git_pool_clear(&patch->flattened);
247
248 if (patch->flags & GIT_DIFF_PATCH_ALLOCATED)
249 git__free(patch);
250}
251
3ff1d123 252static int diff_required(git_diff *diff, const char *action)
114f5a6c
RB
253{
254 if (diff)
255 return 0;
256 giterr_set(GITERR_INVALID, "Must provide valid diff to %s", action);
257 return -1;
258}
259
260int git_diff_foreach(
3ff1d123 261 git_diff *diff,
114f5a6c
RB
262 git_diff_file_cb file_cb,
263 git_diff_hunk_cb hunk_cb,
3ff1d123 264 git_diff_line_cb data_cb,
114f5a6c
RB
265 void *payload)
266{
267 int error = 0;
268 git_xdiff_output xo;
269 size_t idx;
3ff1d123 270 git_patch patch;
114f5a6c 271
96869a4e
RB
272 if ((error = diff_required(diff, "git_diff_foreach")) < 0)
273 return error;
114f5a6c 274
96869a4e 275 memset(&xo, 0, sizeof(xo));
5387cfee 276 memset(&patch, 0, sizeof(patch));
8dd8aa48
RB
277 diff_output_init(
278 &xo.output, &diff->opts, file_cb, hunk_cb, data_cb, payload);
114f5a6c
RB
279 git_xdiff_init(&xo, &diff->opts);
280
281 git_vector_foreach(&diff->deltas, idx, patch.delta) {
a1683f28 282
114f5a6c
RB
283 /* check flags against patch status */
284 if (git_diff_delta__should_skip(&diff->opts, patch.delta))
285 continue;
286
147d86fc
ET
287 if ((error = diff_patch_invoke_file_callback(&patch, &xo.output)) == 0) {
288 if (hunk_cb || data_cb) {
289 if ((error = diff_patch_init_from_diff(&patch, diff, idx)) == 0)
290 error = diff_patch_generate(&patch, &xo.output);
291 }
292 }
114f5a6c 293
96869a4e 294 git_patch_free(&patch);
114f5a6c 295
25e0b157 296 if (error)
114f5a6c
RB
297 break;
298 }
299
114f5a6c
RB
300 return error;
301}
302
303typedef struct {
3ff1d123 304 git_patch patch;
114f5a6c 305 git_diff_delta delta;
74ded024 306 char paths[GIT_FLEX_ARRAY];
f9c824c5 307} diff_patch_with_delta;
114f5a6c 308
f9c824c5 309static int diff_single_generate(diff_patch_with_delta *pd, git_xdiff_output *xo)
114f5a6c
RB
310{
311 int error = 0;
3ff1d123 312 git_patch *patch = &pd->patch;
74ded024
RB
313 bool has_old = ((patch->ofile.flags & GIT_DIFF_FLAG__NO_DATA) == 0);
314 bool has_new = ((patch->nfile.flags & GIT_DIFF_FLAG__NO_DATA) == 0);
114f5a6c 315
f9c824c5 316 pd->delta.status = has_new ?
114f5a6c
RB
317 (has_old ? GIT_DELTA_MODIFIED : GIT_DELTA_ADDED) :
318 (has_old ? GIT_DELTA_DELETED : GIT_DELTA_UNTRACKED);
319
9950bb4e 320 if (git_oid_equal(&patch->nfile.file->id, &patch->ofile.file->id))
f9c824c5 321 pd->delta.status = GIT_DELTA_UNMODIFIED;
114f5a6c 322
f9c824c5 323 patch->delta = &pd->delta;
114f5a6c
RB
324
325 diff_patch_init_common(patch);
326
74ded024
RB
327 if (pd->delta.status == GIT_DELTA_UNMODIFIED &&
328 !(patch->ofile.opts_flags & GIT_DIFF_INCLUDE_UNMODIFIED))
329 return error;
330
96869a4e 331 error = diff_patch_invoke_file_callback(patch, (git_diff_output *)xo);
114f5a6c
RB
332
333 if (!error)
f9c824c5 334 error = diff_patch_generate(patch, (git_diff_output *)xo);
114f5a6c 335
114f5a6c
RB
336 return error;
337}
338
6789b7a7 339static int diff_patch_from_sources(
f9c824c5
RB
340 diff_patch_with_delta *pd,
341 git_xdiff_output *xo,
6789b7a7
RB
342 git_diff_file_content_src *oldsrc,
343 git_diff_file_content_src *newsrc,
f9c824c5 344 const git_diff_options *opts)
114f5a6c
RB
345{
346 int error = 0;
114f5a6c 347 git_repository *repo =
6789b7a7
RB
348 oldsrc->blob ? git_blob_owner(oldsrc->blob) :
349 newsrc->blob ? git_blob_owner(newsrc->blob) : NULL;
350 git_diff_file *lfile = &pd->delta.old_file, *rfile = &pd->delta.new_file;
351 git_diff_file_content *ldata = &pd->patch.ofile, *rdata = &pd->patch.nfile;
114f5a6c
RB
352
353 GITERR_CHECK_VERSION(opts, GIT_DIFF_OPTIONS_VERSION, "git_diff_options");
354
114f5a6c 355 if (opts && (opts->flags & GIT_DIFF_REVERSE) != 0) {
6789b7a7
RB
356 void *tmp = lfile; lfile = rfile; rfile = tmp;
357 tmp = ldata; ldata = rdata; rdata = tmp;
114f5a6c
RB
358 }
359
74ded024
RB
360 pd->patch.delta = &pd->delta;
361
6789b7a7
RB
362 if (!oldsrc->as_path) {
363 if (newsrc->as_path)
364 oldsrc->as_path = newsrc->as_path;
365 else
366 oldsrc->as_path = newsrc->as_path = "file";
367 }
368 else if (!newsrc->as_path)
369 newsrc->as_path = oldsrc->as_path;
370
371 lfile->path = oldsrc->as_path;
372 rfile->path = newsrc->as_path;
74ded024 373
6789b7a7
RB
374 if ((error = git_diff_file_content__init_from_src(
375 ldata, repo, opts, oldsrc, lfile)) < 0 ||
376 (error = git_diff_file_content__init_from_src(
377 rdata, repo, opts, newsrc, rfile)) < 0)
f9c824c5
RB
378 return error;
379
380 return diff_single_generate(pd, xo);
381}
382
74ded024
RB
383static int diff_patch_with_delta_alloc(
384 diff_patch_with_delta **out,
385 const char **old_path,
386 const char **new_path)
387{
388 diff_patch_with_delta *pd;
389 size_t old_len = *old_path ? strlen(*old_path) : 0;
390 size_t new_len = *new_path ? strlen(*new_path) : 0;
f1453c59 391 size_t alloc_len;
74ded024 392
f1453c59
ET
393 GITERR_CHECK_ALLOC_ADD(&alloc_len, sizeof(*pd), old_len);
394 GITERR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, new_len);
395 GITERR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, 2);
392702ee
ET
396
397 *out = pd = git__calloc(1, alloc_len);
74ded024
RB
398 GITERR_CHECK_ALLOC(pd);
399
400 pd->patch.flags = GIT_DIFF_PATCH_ALLOCATED;
401
402 if (*old_path) {
403 memcpy(&pd->paths[0], *old_path, old_len);
404 *old_path = &pd->paths[0];
405 } else if (*new_path)
406 *old_path = &pd->paths[old_len + 1];
407
408 if (*new_path) {
409 memcpy(&pd->paths[old_len + 1], *new_path, new_len);
410 *new_path = &pd->paths[old_len + 1];
411 } else if (*old_path)
412 *new_path = &pd->paths[0];
413
414 return 0;
415}
416
6789b7a7
RB
417static int diff_from_sources(
418 git_diff_file_content_src *oldsrc,
419 git_diff_file_content_src *newsrc,
f9c824c5
RB
420 const git_diff_options *opts,
421 git_diff_file_cb file_cb,
422 git_diff_hunk_cb hunk_cb,
3ff1d123 423 git_diff_line_cb data_cb,
f9c824c5
RB
424 void *payload)
425{
426 int error = 0;
427 diff_patch_with_delta pd;
428 git_xdiff_output xo;
429
f9c824c5 430 memset(&xo, 0, sizeof(xo));
f9c824c5 431 diff_output_init(
8dd8aa48 432 &xo.output, opts, file_cb, hunk_cb, data_cb, payload);
f9c824c5 433 git_xdiff_init(&xo, opts);
114f5a6c 434
96869a4e 435 memset(&pd, 0, sizeof(pd));
6789b7a7
RB
436
437 error = diff_patch_from_sources(&pd, &xo, oldsrc, newsrc, opts);
114f5a6c 438
3ff1d123 439 git_patch_free(&pd.patch);
114f5a6c
RB
440
441 return error;
442}
443
6789b7a7 444static int patch_from_sources(
3ff1d123 445 git_patch **out,
6789b7a7
RB
446 git_diff_file_content_src *oldsrc,
447 git_diff_file_content_src *newsrc,
f9c824c5
RB
448 const git_diff_options *opts)
449{
450 int error = 0;
451 diff_patch_with_delta *pd;
452 git_xdiff_output xo;
453
454 assert(out);
455 *out = NULL;
456
6789b7a7
RB
457 if ((error = diff_patch_with_delta_alloc(
458 &pd, &oldsrc->as_path, &newsrc->as_path)) < 0)
459 return error;
f9c824c5
RB
460
461 memset(&xo, 0, sizeof(xo));
8dd8aa48 462 diff_output_to_patch(&xo.output, &pd->patch);
f9c824c5
RB
463 git_xdiff_init(&xo, opts);
464
6789b7a7 465 if (!(error = diff_patch_from_sources(pd, &xo, oldsrc, newsrc, opts)))
3ff1d123 466 *out = (git_patch *)pd;
f9c824c5 467 else
3ff1d123 468 git_patch_free((git_patch *)pd);
f9c824c5
RB
469
470 return error;
471}
472
6789b7a7 473int git_diff_blobs(
114f5a6c 474 const git_blob *old_blob,
74ded024 475 const char *old_path,
6789b7a7
RB
476 const git_blob *new_blob,
477 const char *new_path,
478 const git_diff_options *opts,
479 git_diff_file_cb file_cb,
480 git_diff_hunk_cb hunk_cb,
481 git_diff_line_cb data_cb,
482 void *payload)
114f5a6c 483{
6789b7a7
RB
484 git_diff_file_content_src osrc =
485 GIT_DIFF_FILE_CONTENT_SRC__BLOB(old_blob, old_path);
486 git_diff_file_content_src nsrc =
487 GIT_DIFF_FILE_CONTENT_SRC__BLOB(new_blob, new_path);
488 return diff_from_sources(
489 &osrc, &nsrc, opts, file_cb, hunk_cb, data_cb, payload);
490}
74ded024 491
6789b7a7
RB
492int git_patch_from_blobs(
493 git_patch **out,
494 const git_blob *old_blob,
495 const char *old_path,
496 const git_blob *new_blob,
497 const char *new_path,
498 const git_diff_options *opts)
499{
500 git_diff_file_content_src osrc =
501 GIT_DIFF_FILE_CONTENT_SRC__BLOB(old_blob, old_path);
502 git_diff_file_content_src nsrc =
503 GIT_DIFF_FILE_CONTENT_SRC__BLOB(new_blob, new_path);
504 return patch_from_sources(out, &osrc, &nsrc, opts);
f9c824c5
RB
505}
506
507int git_diff_blob_to_buffer(
508 const git_blob *old_blob,
74ded024 509 const char *old_path,
f9c824c5
RB
510 const char *buf,
511 size_t buflen,
74ded024 512 const char *buf_path,
f9c824c5
RB
513 const git_diff_options *opts,
514 git_diff_file_cb file_cb,
515 git_diff_hunk_cb hunk_cb,
3ff1d123 516 git_diff_line_cb data_cb,
f9c824c5
RB
517 void *payload)
518{
6789b7a7
RB
519 git_diff_file_content_src osrc =
520 GIT_DIFF_FILE_CONTENT_SRC__BLOB(old_blob, old_path);
521 git_diff_file_content_src nsrc =
522 GIT_DIFF_FILE_CONTENT_SRC__BUF(buf, buflen, buf_path);
523 return diff_from_sources(
524 &osrc, &nsrc, opts, file_cb, hunk_cb, data_cb, payload);
f9c824c5
RB
525}
526
3ff1d123
RB
527int git_patch_from_blob_and_buffer(
528 git_patch **out,
f9c824c5 529 const git_blob *old_blob,
74ded024 530 const char *old_path,
f9c824c5
RB
531 const char *buf,
532 size_t buflen,
74ded024 533 const char *buf_path,
f9c824c5
RB
534 const git_diff_options *opts)
535{
6789b7a7
RB
536 git_diff_file_content_src osrc =
537 GIT_DIFF_FILE_CONTENT_SRC__BLOB(old_blob, old_path);
538 git_diff_file_content_src nsrc =
539 GIT_DIFF_FILE_CONTENT_SRC__BUF(buf, buflen, buf_path);
540 return patch_from_sources(out, &osrc, &nsrc, opts);
541}
74ded024 542
6789b7a7
RB
543int git_diff_buffers(
544 const void *old_buf,
545 size_t old_len,
546 const char *old_path,
547 const void *new_buf,
548 size_t new_len,
549 const char *new_path,
550 const git_diff_options *opts,
551 git_diff_file_cb file_cb,
552 git_diff_hunk_cb hunk_cb,
553 git_diff_line_cb data_cb,
554 void *payload)
555{
556 git_diff_file_content_src osrc =
557 GIT_DIFF_FILE_CONTENT_SRC__BUF(old_buf, old_len, old_path);
558 git_diff_file_content_src nsrc =
559 GIT_DIFF_FILE_CONTENT_SRC__BUF(new_buf, new_len, new_path);
560 return diff_from_sources(
561 &osrc, &nsrc, opts, file_cb, hunk_cb, data_cb, payload);
562}
114f5a6c 563
6789b7a7
RB
564int git_patch_from_buffers(
565 git_patch **out,
566 const void *old_buf,
567 size_t old_len,
568 const char *old_path,
569 const char *new_buf,
570 size_t new_len,
571 const char *new_path,
572 const git_diff_options *opts)
573{
574 git_diff_file_content_src osrc =
575 GIT_DIFF_FILE_CONTENT_SRC__BUF(old_buf, old_len, old_path);
576 git_diff_file_content_src nsrc =
577 GIT_DIFF_FILE_CONTENT_SRC__BUF(new_buf, new_len, new_path);
578 return patch_from_sources(out, &osrc, &nsrc, opts);
114f5a6c
RB
579}
580
3ff1d123 581int git_patch_from_diff(
10672e3e 582 git_patch **patch_ptr, git_diff *diff, size_t idx)
114f5a6c
RB
583{
584 int error = 0;
585 git_xdiff_output xo;
586 git_diff_delta *delta = NULL;
3ff1d123 587 git_patch *patch = NULL;
114f5a6c
RB
588
589 if (patch_ptr) *patch_ptr = NULL;
114f5a6c 590
3ff1d123 591 if (diff_required(diff, "git_patch_from_diff") < 0)
114f5a6c
RB
592 return -1;
593
594 delta = git_vector_get(&diff->deltas, idx);
595 if (!delta) {
596 giterr_set(GITERR_INVALID, "Index out of range for delta in diff");
597 return GIT_ENOTFOUND;
598 }
599
114f5a6c
RB
600 if (git_diff_delta__should_skip(&diff->opts, delta))
601 return 0;
602
603 /* don't load the patch data unless we need it for binary check */
604 if (!patch_ptr &&
605 ((delta->flags & DIFF_FLAGS_KNOWN_BINARY) != 0 ||
606 (diff->opts.flags & GIT_DIFF_SKIP_BINARY_CHECK) != 0))
607 return 0;
608
609 if ((error = diff_patch_alloc_from_diff(&patch, diff, idx)) < 0)
610 return error;
611
96869a4e 612 memset(&xo, 0, sizeof(xo));
8dd8aa48 613 diff_output_to_patch(&xo.output, patch);
114f5a6c
RB
614 git_xdiff_init(&xo, &diff->opts);
615
96869a4e 616 error = diff_patch_invoke_file_callback(patch, &xo.output);
114f5a6c
RB
617
618 if (!error)
8dd8aa48 619 error = diff_patch_generate(patch, &xo.output);
114f5a6c
RB
620
621 if (!error) {
25e0b157
RB
622 /* TODO: if cumulative diff size is < 0.5 total size, flatten patch */
623 /* TODO: and unload the file content */
114f5a6c
RB
624 }
625
626 if (error || !patch_ptr)
3ff1d123 627 git_patch_free(patch);
114f5a6c
RB
628 else
629 *patch_ptr = patch;
630
114f5a6c
RB
631 return error;
632}
633
3ff1d123 634void git_patch_free(git_patch *patch)
114f5a6c
RB
635{
636 if (patch)
637 GIT_REFCOUNT_DEC(patch, diff_patch_free);
638}
639
7a28f268 640const git_diff_delta *git_patch_get_delta(const git_patch *patch)
114f5a6c
RB
641{
642 assert(patch);
643 return patch->delta;
644}
645
7a28f268 646size_t git_patch_num_hunks(const git_patch *patch)
114f5a6c
RB
647{
648 assert(patch);
649 return git_array_size(patch->hunks);
650}
651
3ff1d123 652int git_patch_line_stats(
114f5a6c
RB
653 size_t *total_ctxt,
654 size_t *total_adds,
655 size_t *total_dels,
3ff1d123 656 const git_patch *patch)
114f5a6c
RB
657{
658 size_t totals[3], idx;
659
660 memset(totals, 0, sizeof(totals));
661
662 for (idx = 0; idx < git_array_size(patch->lines); ++idx) {
3b5f7954 663 git_diff_line *line = git_array_get(patch->lines, idx);
114f5a6c
RB
664 if (!line)
665 continue;
666
667 switch (line->origin) {
668 case GIT_DIFF_LINE_CONTEXT: totals[0]++; break;
669 case GIT_DIFF_LINE_ADDITION: totals[1]++; break;
670 case GIT_DIFF_LINE_DELETION: totals[2]++; break;
671 default:
672 /* diff --stat and --numstat don't count EOFNL marks because
673 * they will always be paired with a ADDITION or DELETION line.
674 */
675 break;
676 }
677 }
678
679 if (total_ctxt)
680 *total_ctxt = totals[0];
681 if (total_adds)
682 *total_adds = totals[1];
683 if (total_dels)
684 *total_dels = totals[2];
685
686 return 0;
687}
688
689static int diff_error_outofrange(const char *thing)
690{
691 giterr_set(GITERR_INVALID, "Diff patch %s index out of range", thing);
692 return GIT_ENOTFOUND;
693}
694
3ff1d123
RB
695int git_patch_get_hunk(
696 const git_diff_hunk **out,
114f5a6c 697 size_t *lines_in_hunk,
3ff1d123 698 git_patch *patch,
114f5a6c
RB
699 size_t hunk_idx)
700{
701 diff_patch_hunk *hunk;
702 assert(patch);
703
704 hunk = git_array_get(patch->hunks, hunk_idx);
705
706 if (!hunk) {
3ff1d123 707 if (out) *out = NULL;
114f5a6c
RB
708 if (lines_in_hunk) *lines_in_hunk = 0;
709 return diff_error_outofrange("hunk");
710 }
711
3ff1d123 712 if (out) *out = &hunk->hunk;
114f5a6c
RB
713 if (lines_in_hunk) *lines_in_hunk = hunk->line_count;
714 return 0;
715}
716
7a28f268 717int git_patch_num_lines_in_hunk(const git_patch *patch, size_t hunk_idx)
114f5a6c
RB
718{
719 diff_patch_hunk *hunk;
720 assert(patch);
721
722 if (!(hunk = git_array_get(patch->hunks, hunk_idx)))
723 return diff_error_outofrange("hunk");
724 return (int)hunk->line_count;
725}
726
3ff1d123 727int git_patch_get_line_in_hunk(
3b5f7954 728 const git_diff_line **out,
3ff1d123 729 git_patch *patch,
114f5a6c
RB
730 size_t hunk_idx,
731 size_t line_of_hunk)
732{
733 diff_patch_hunk *hunk;
3b5f7954 734 git_diff_line *line;
114f5a6c
RB
735
736 assert(patch);
737
738 if (!(hunk = git_array_get(patch->hunks, hunk_idx))) {
3b5f7954
RB
739 if (out) *out = NULL;
740 return diff_error_outofrange("hunk");
114f5a6c
RB
741 }
742
743 if (line_of_hunk >= hunk->line_count ||
744 !(line = git_array_get(
745 patch->lines, hunk->line_start + line_of_hunk))) {
3b5f7954
RB
746 if (out) *out = NULL;
747 return diff_error_outofrange("line");
114f5a6c
RB
748 }
749
3b5f7954 750 if (out) *out = line;
114f5a6c 751 return 0;
114f5a6c
RB
752}
753
3ff1d123
RB
754size_t git_patch_size(
755 git_patch *patch,
197b8966
RB
756 int include_context,
757 int include_hunk_headers,
758 int include_file_headers)
b4a4cf24
RB
759{
760 size_t out;
761
762 assert(patch);
763
764 out = patch->content_size;
765
766 if (!include_context)
767 out -= patch->context_size;
768
197b8966
RB
769 if (include_hunk_headers)
770 out += patch->header_size;
771
772 if (include_file_headers) {
773 git_buf file_header = GIT_BUF_INIT;
774
775 if (git_diff_delta__format_file_header(
776 &file_header, patch->delta, NULL, NULL, 0) < 0)
777 giterr_clear();
778 else
779 out += git_buf_len(&file_header);
780
781 git_buf_free(&file_header);
782 }
783
b4a4cf24
RB
784 return out;
785}
786
3ff1d123 787git_diff *git_patch__diff(git_patch *patch)
360f42f4
RB
788{
789 return patch->diff;
790}
791
3ff1d123 792git_diff_driver *git_patch__driver(git_patch *patch)
360f42f4
RB
793{
794 /* ofile driver is representative for whole patch */
795 return patch->ofile.driver;
796}
797
3ff1d123
RB
798void git_patch__old_data(
799 char **ptr, size_t *len, git_patch *patch)
360f42f4
RB
800{
801 *ptr = patch->ofile.map.data;
802 *len = patch->ofile.map.len;
803}
804
3ff1d123
RB
805void git_patch__new_data(
806 char **ptr, size_t *len, git_patch *patch)
360f42f4
RB
807{
808 *ptr = patch->nfile.map.data;
809 *len = patch->nfile.map.len;
810}
811
3ff1d123
RB
812int git_patch__invoke_callbacks(
813 git_patch *patch,
360f42f4
RB
814 git_diff_file_cb file_cb,
815 git_diff_hunk_cb hunk_cb,
3ff1d123 816 git_diff_line_cb line_cb,
360f42f4
RB
817 void *payload)
818{
819 int error = 0;
820 uint32_t i, j;
821
822 if (file_cb)
823 error = file_cb(patch->delta, 0, payload);
824
825 if (!hunk_cb && !line_cb)
826 return error;
827
828 for (i = 0; !error && i < git_array_size(patch->hunks); ++i) {
829 diff_patch_hunk *h = git_array_get(patch->hunks, i);
830
bb6aafe8
JG
831 if (hunk_cb)
832 error = hunk_cb(patch->delta, &h->hunk, payload);
360f42f4
RB
833
834 if (!line_cb)
835 continue;
836
837 for (j = 0; !error && j < h->line_count; ++j) {
3b5f7954 838 git_diff_line *l =
360f42f4
RB
839 git_array_get(patch->lines, h->line_start + j);
840
3b5f7954 841 error = line_cb(patch->delta, &h->hunk, l, payload);
360f42f4
RB
842 }
843 }
844
845 return error;
846}
847
114f5a6c
RB
848
849static int diff_patch_file_cb(
850 const git_diff_delta *delta,
851 float progress,
852 void *payload)
853{
f9c824c5 854 GIT_UNUSED(delta); GIT_UNUSED(progress); GIT_UNUSED(payload);
114f5a6c
RB
855 return 0;
856}
857
858static int diff_patch_hunk_cb(
859 const git_diff_delta *delta,
3ff1d123 860 const git_diff_hunk *hunk_,
114f5a6c
RB
861 void *payload)
862{
3ff1d123 863 git_patch *patch = payload;
114f5a6c
RB
864 diff_patch_hunk *hunk;
865
866 GIT_UNUSED(delta);
867
ef3374a8 868 hunk = git_array_alloc(patch->hunks);
25e0b157 869 GITERR_CHECK_ALLOC(hunk);
114f5a6c 870
3ff1d123 871 memcpy(&hunk->hunk, hunk_, sizeof(hunk->hunk));
114f5a6c 872
3b5f7954 873 patch->header_size += hunk_->header_len;
197b8966 874
114f5a6c
RB
875 hunk->line_start = git_array_size(patch->lines);
876 hunk->line_count = 0;
877
114f5a6c
RB
878 return 0;
879}
880
881static int diff_patch_line_cb(
882 const git_diff_delta *delta,
3ff1d123 883 const git_diff_hunk *hunk_,
3b5f7954 884 const git_diff_line *line_,
114f5a6c
RB
885 void *payload)
886{
3ff1d123 887 git_patch *patch = payload;
114f5a6c 888 diff_patch_hunk *hunk;
3b5f7954 889 git_diff_line *line;
114f5a6c
RB
890
891 GIT_UNUSED(delta);
3ff1d123 892 GIT_UNUSED(hunk_);
114f5a6c
RB
893
894 hunk = git_array_last(patch->hunks);
96869a4e 895 assert(hunk); /* programmer error if no hunk is available */
114f5a6c 896
ef3374a8 897 line = git_array_alloc(patch->lines);
25e0b157 898 GITERR_CHECK_ALLOC(line);
114f5a6c 899
3b5f7954 900 memcpy(line, line_, sizeof(*line));
114f5a6c 901
114f5a6c
RB
902 /* do some bookkeeping so we can provide old/new line numbers */
903
3b5f7954 904 patch->content_size += line->content_len;
197b8966 905
3b5f7954
RB
906 if (line->origin == GIT_DIFF_LINE_ADDITION ||
907 line->origin == GIT_DIFF_LINE_DELETION)
197b8966 908 patch->content_size += 1;
3b5f7954 909 else if (line->origin == GIT_DIFF_LINE_CONTEXT) {
197b8966 910 patch->content_size += 1;
3b5f7954
RB
911 patch->context_size += line->content_len + 1;
912 } else if (line->origin == GIT_DIFF_LINE_CONTEXT_EOFNL)
913 patch->context_size += line->content_len;
114f5a6c
RB
914
915 hunk->line_count++;
916
917 return 0;
918}
919
920static void diff_output_init(
921 git_diff_output *out,
922 const git_diff_options *opts,
923 git_diff_file_cb file_cb,
924 git_diff_hunk_cb hunk_cb,
3ff1d123 925 git_diff_line_cb data_cb,
114f5a6c
RB
926 void *payload)
927{
928 GIT_UNUSED(opts);
929
930 memset(out, 0, sizeof(*out));
931
932 out->file_cb = file_cb;
933 out->hunk_cb = hunk_cb;
934 out->data_cb = data_cb;
935 out->payload = payload;
936}
937
3ff1d123 938static void diff_output_to_patch(git_diff_output *out, git_patch *patch)
114f5a6c
RB
939{
940 diff_output_init(
5dc98298 941 out, NULL,
114f5a6c
RB
942 diff_patch_file_cb, diff_patch_hunk_cb, diff_patch_line_cb, patch);
943}