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