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