]> git.proxmox.com Git - libgit2.git/blame - src/diff_patch.c
remote: create FETCH_HEAD with a refspecless remote
[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
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
RB
135 (((patch->ofile.flags & GIT_DIFF_FLAG__NO_DATA) != 0 ||
136 (patch->ofile.file->flags & GIT_DIFF_FLAG_VALID_OID) != 0) &&
137 ((patch->nfile.flags & GIT_DIFF_FLAG__NO_DATA) != 0 ||
138 (patch->nfile.file->flags & GIT_DIFF_FLAG_VALID_OID) != 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 &&
74ded024 172 git_oid_equal(&patch->ofile.file->oid, &patch->nfile.file->oid) &&
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 ||
74ded024 187 !git_oid_equal(&patch->ofile.file->oid, &patch->nfile.file->oid)))
114f5a6c
RB
188 patch->flags |= GIT_DIFF_PATCH_DIFFABLE;
189
190 patch->flags |= GIT_DIFF_PATCH_LOADED;
191 }
192
193 return error;
194}
195
196static int diff_patch_file_callback(
3ff1d123 197 git_patch *patch, git_diff_output *output)
114f5a6c
RB
198{
199 float progress;
200
201 if (!output->file_cb)
202 return 0;
203
204 progress = patch->diff ?
205 ((float)patch->delta_index / patch->diff->deltas.length) : 1.0f;
206
207 if (output->file_cb(patch->delta, progress, output->payload) != 0)
208 output->error = GIT_EUSER;
209
210 return output->error;
211}
212
3ff1d123 213static int diff_patch_generate(git_patch *patch, git_diff_output *output)
114f5a6c
RB
214{
215 int error = 0;
216
217 if ((patch->flags & GIT_DIFF_PATCH_DIFFED) != 0)
218 return 0;
219
69c66b55
RB
220 /* if we are not looking at the hunks and lines, don't do the diff */
221 if (!output->hunk_cb && !output->data_cb)
222 return 0;
223
114f5a6c
RB
224 if ((patch->flags & GIT_DIFF_PATCH_LOADED) == 0 &&
225 (error = diff_patch_load(patch, output)) < 0)
226 return error;
227
228 if ((patch->flags & GIT_DIFF_PATCH_DIFFABLE) == 0)
229 return 0;
230
231 if (output->diff_cb != NULL &&
232 !(error = output->diff_cb(output, patch)))
233 patch->flags |= GIT_DIFF_PATCH_DIFFED;
234
235 return error;
236}
237
3ff1d123 238static void diff_patch_free(git_patch *patch)
114f5a6c 239{
360f42f4
RB
240 git_diff_file_content__clear(&patch->ofile);
241 git_diff_file_content__clear(&patch->nfile);
114f5a6c
RB
242
243 git_array_clear(patch->lines);
244 git_array_clear(patch->hunks);
245
3ff1d123 246 git_diff_free(patch->diff); /* decrements refcount */
114f5a6c
RB
247 patch->diff = NULL;
248
249 git_pool_clear(&patch->flattened);
250
251 if (patch->flags & GIT_DIFF_PATCH_ALLOCATED)
252 git__free(patch);
253}
254
3ff1d123 255static int diff_required(git_diff *diff, const char *action)
114f5a6c
RB
256{
257 if (diff)
258 return 0;
259 giterr_set(GITERR_INVALID, "Must provide valid diff to %s", action);
260 return -1;
261}
262
263int git_diff_foreach(
3ff1d123 264 git_diff *diff,
114f5a6c
RB
265 git_diff_file_cb file_cb,
266 git_diff_hunk_cb hunk_cb,
3ff1d123 267 git_diff_line_cb data_cb,
114f5a6c
RB
268 void *payload)
269{
270 int error = 0;
271 git_xdiff_output xo;
272 size_t idx;
3ff1d123 273 git_patch patch;
114f5a6c
RB
274
275 if (diff_required(diff, "git_diff_foreach") < 0)
276 return -1;
277
8dd8aa48
RB
278 diff_output_init(
279 &xo.output, &diff->opts, file_cb, hunk_cb, data_cb, payload);
114f5a6c
RB
280 git_xdiff_init(&xo, &diff->opts);
281
282 git_vector_foreach(&diff->deltas, idx, patch.delta) {
a1683f28 283
114f5a6c
RB
284 /* check flags against patch status */
285 if (git_diff_delta__should_skip(&diff->opts, patch.delta))
286 continue;
287
288 if (!(error = diff_patch_init_from_diff(&patch, diff, idx))) {
289
8dd8aa48 290 error = diff_patch_file_callback(&patch, &xo.output);
114f5a6c
RB
291
292 if (!error)
8dd8aa48 293 error = diff_patch_generate(&patch, &xo.output);
114f5a6c 294
3ff1d123 295 git_patch_free(&patch);
114f5a6c
RB
296 }
297
298 if (error < 0)
299 break;
300 }
301
302 if (error == GIT_EUSER)
303 giterr_clear(); /* don't leave error message set invalidly */
304 return error;
305}
306
307typedef struct {
3ff1d123 308 git_patch patch;
114f5a6c 309 git_diff_delta delta;
74ded024 310 char paths[GIT_FLEX_ARRAY];
f9c824c5 311} diff_patch_with_delta;
114f5a6c 312
f9c824c5 313static int diff_single_generate(diff_patch_with_delta *pd, git_xdiff_output *xo)
114f5a6c
RB
314{
315 int error = 0;
3ff1d123 316 git_patch *patch = &pd->patch;
74ded024
RB
317 bool has_old = ((patch->ofile.flags & GIT_DIFF_FLAG__NO_DATA) == 0);
318 bool has_new = ((patch->nfile.flags & GIT_DIFF_FLAG__NO_DATA) == 0);
114f5a6c 319
f9c824c5 320 pd->delta.status = has_new ?
114f5a6c
RB
321 (has_old ? GIT_DELTA_MODIFIED : GIT_DELTA_ADDED) :
322 (has_old ? GIT_DELTA_DELETED : GIT_DELTA_UNTRACKED);
323
74ded024 324 if (git_oid_equal(&patch->nfile.file->oid, &patch->ofile.file->oid))
f9c824c5 325 pd->delta.status = GIT_DELTA_UNMODIFIED;
114f5a6c 326
f9c824c5 327 patch->delta = &pd->delta;
114f5a6c
RB
328
329 diff_patch_init_common(patch);
330
74ded024
RB
331 if (pd->delta.status == GIT_DELTA_UNMODIFIED &&
332 !(patch->ofile.opts_flags & GIT_DIFF_INCLUDE_UNMODIFIED))
333 return error;
334
f9c824c5 335 error = diff_patch_file_callback(patch, (git_diff_output *)xo);
114f5a6c
RB
336
337 if (!error)
f9c824c5 338 error = diff_patch_generate(patch, (git_diff_output *)xo);
114f5a6c
RB
339
340 if (error == GIT_EUSER)
341 giterr_clear(); /* don't leave error message set invalidly */
342
343 return error;
344}
345
f9c824c5
RB
346static int diff_patch_from_blobs(
347 diff_patch_with_delta *pd,
348 git_xdiff_output *xo,
114f5a6c 349 const git_blob *old_blob,
74ded024 350 const char *old_path,
114f5a6c 351 const git_blob *new_blob,
74ded024 352 const char *new_path,
f9c824c5 353 const git_diff_options *opts)
114f5a6c
RB
354{
355 int error = 0;
114f5a6c
RB
356 git_repository *repo =
357 new_blob ? git_object_owner((const git_object *)new_blob) :
358 old_blob ? git_object_owner((const git_object *)old_blob) : NULL;
359
360 GITERR_CHECK_VERSION(opts, GIT_DIFF_OPTIONS_VERSION, "git_diff_options");
361
114f5a6c 362 if (opts && (opts->flags & GIT_DIFF_REVERSE) != 0) {
74ded024
RB
363 const git_blob *tmp_blob;
364 const char *tmp_path;
365 tmp_blob = old_blob; old_blob = new_blob; new_blob = tmp_blob;
366 tmp_path = old_path; old_path = new_path; new_path = tmp_path;
114f5a6c
RB
367 }
368
74ded024
RB
369 pd->patch.delta = &pd->delta;
370
371 pd->delta.old_file.path = old_path;
372 pd->delta.new_file.path = new_path;
373
360f42f4 374 if ((error = git_diff_file_content__init_from_blob(
74ded024 375 &pd->patch.ofile, repo, opts, old_blob, &pd->delta.old_file)) < 0 ||
360f42f4 376 (error = git_diff_file_content__init_from_blob(
74ded024 377 &pd->patch.nfile, repo, opts, new_blob, &pd->delta.new_file)) < 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;
391
392 *out = pd = git__calloc(1, sizeof(*pd) + old_len + new_len + 2);
393 GITERR_CHECK_ALLOC(pd);
394
395 pd->patch.flags = GIT_DIFF_PATCH_ALLOCATED;
396
397 if (*old_path) {
398 memcpy(&pd->paths[0], *old_path, old_len);
399 *old_path = &pd->paths[0];
400 } else if (*new_path)
401 *old_path = &pd->paths[old_len + 1];
402
403 if (*new_path) {
404 memcpy(&pd->paths[old_len + 1], *new_path, new_len);
405 *new_path = &pd->paths[old_len + 1];
406 } else if (*old_path)
407 *new_path = &pd->paths[0];
408
409 return 0;
410}
411
f9c824c5
RB
412int git_diff_blobs(
413 const git_blob *old_blob,
74ded024 414 const char *old_path,
f9c824c5 415 const git_blob *new_blob,
74ded024 416 const char *new_path,
f9c824c5
RB
417 const git_diff_options *opts,
418 git_diff_file_cb file_cb,
419 git_diff_hunk_cb hunk_cb,
3ff1d123 420 git_diff_line_cb data_cb,
f9c824c5
RB
421 void *payload)
422{
423 int error = 0;
424 diff_patch_with_delta pd;
425 git_xdiff_output xo;
426
427 memset(&pd, 0, sizeof(pd));
428 memset(&xo, 0, sizeof(xo));
114f5a6c 429
f9c824c5 430 diff_output_init(
8dd8aa48 431 &xo.output, opts, file_cb, hunk_cb, data_cb, payload);
f9c824c5 432 git_xdiff_init(&xo, opts);
114f5a6c 433
74ded024
RB
434 if (!old_path && new_path)
435 old_path = new_path;
436 else if (!new_path && old_path)
437 new_path = old_path;
438
439 error = diff_patch_from_blobs(
440 &pd, &xo, old_blob, old_path, new_blob, new_path, opts);
114f5a6c 441
3ff1d123 442 git_patch_free(&pd.patch);
114f5a6c
RB
443
444 return error;
445}
446
3ff1d123
RB
447int git_patch_from_blobs(
448 git_patch **out,
f9c824c5 449 const git_blob *old_blob,
74ded024 450 const char *old_path,
f9c824c5 451 const git_blob *new_blob,
74ded024 452 const char *new_path,
f9c824c5
RB
453 const git_diff_options *opts)
454{
455 int error = 0;
456 diff_patch_with_delta *pd;
457 git_xdiff_output xo;
458
459 assert(out);
460 *out = NULL;
461
74ded024
RB
462 if (diff_patch_with_delta_alloc(&pd, &old_path, &new_path) < 0)
463 return -1;
f9c824c5
RB
464
465 memset(&xo, 0, sizeof(xo));
466
8dd8aa48 467 diff_output_to_patch(&xo.output, &pd->patch);
f9c824c5
RB
468 git_xdiff_init(&xo, opts);
469
74ded024
RB
470 error = diff_patch_from_blobs(
471 pd, &xo, old_blob, old_path, new_blob, new_path, opts);
472
473 if (!error)
3ff1d123 474 *out = (git_patch *)pd;
f9c824c5 475 else
3ff1d123 476 git_patch_free((git_patch *)pd);
f9c824c5
RB
477
478 return error;
479}
480
481static int diff_patch_from_blob_and_buffer(
482 diff_patch_with_delta *pd,
483 git_xdiff_output *xo,
114f5a6c 484 const git_blob *old_blob,
74ded024 485 const char *old_path,
114f5a6c
RB
486 const char *buf,
487 size_t buflen,
74ded024 488 const char *buf_path,
f9c824c5 489 const git_diff_options *opts)
114f5a6c
RB
490{
491 int error = 0;
114f5a6c
RB
492 git_repository *repo =
493 old_blob ? git_object_owner((const git_object *)old_blob) : NULL;
494
495 GITERR_CHECK_VERSION(opts, GIT_DIFF_OPTIONS_VERSION, "git_diff_options");
496
f9c824c5 497 pd->patch.delta = &pd->delta;
114f5a6c 498
114f5a6c 499 if (opts && (opts->flags & GIT_DIFF_REVERSE) != 0) {
74ded024
RB
500 pd->delta.old_file.path = buf_path;
501 pd->delta.new_file.path = old_path;
502
360f42f4 503 if (!(error = git_diff_file_content__init_from_raw(
74ded024 504 &pd->patch.ofile, repo, opts, buf, buflen, &pd->delta.old_file)))
360f42f4 505 error = git_diff_file_content__init_from_blob(
74ded024 506 &pd->patch.nfile, repo, opts, old_blob, &pd->delta.new_file);
114f5a6c 507 } else {
74ded024
RB
508 pd->delta.old_file.path = old_path;
509 pd->delta.new_file.path = buf_path;
510
360f42f4 511 if (!(error = git_diff_file_content__init_from_blob(
74ded024 512 &pd->patch.ofile, repo, opts, old_blob, &pd->delta.old_file)))
360f42f4 513 error = git_diff_file_content__init_from_raw(
74ded024 514 &pd->patch.nfile, repo, opts, buf, buflen, &pd->delta.new_file);
114f5a6c
RB
515 }
516
74ded024
RB
517 if (error < 0)
518 return error;
519
f9c824c5
RB
520 return diff_single_generate(pd, xo);
521}
522
523int git_diff_blob_to_buffer(
524 const git_blob *old_blob,
74ded024 525 const char *old_path,
f9c824c5
RB
526 const char *buf,
527 size_t buflen,
74ded024 528 const char *buf_path,
f9c824c5
RB
529 const git_diff_options *opts,
530 git_diff_file_cb file_cb,
531 git_diff_hunk_cb hunk_cb,
3ff1d123 532 git_diff_line_cb data_cb,
f9c824c5
RB
533 void *payload)
534{
535 int error = 0;
536 diff_patch_with_delta pd;
537 git_xdiff_output xo;
538
539 memset(&pd, 0, sizeof(pd));
540 memset(&xo, 0, sizeof(xo));
541
542 diff_output_init(
8dd8aa48 543 &xo.output, opts, file_cb, hunk_cb, data_cb, payload);
f9c824c5
RB
544 git_xdiff_init(&xo, opts);
545
74ded024
RB
546 if (!old_path && buf_path)
547 old_path = buf_path;
548 else if (!buf_path && old_path)
549 buf_path = old_path;
550
f9c824c5 551 error = diff_patch_from_blob_and_buffer(
74ded024 552 &pd, &xo, old_blob, old_path, buf, buflen, buf_path, opts);
114f5a6c 553
3ff1d123 554 git_patch_free(&pd.patch);
f9c824c5
RB
555
556 return error;
557}
558
3ff1d123
RB
559int git_patch_from_blob_and_buffer(
560 git_patch **out,
f9c824c5 561 const git_blob *old_blob,
74ded024 562 const char *old_path,
f9c824c5
RB
563 const char *buf,
564 size_t buflen,
74ded024 565 const char *buf_path,
f9c824c5
RB
566 const git_diff_options *opts)
567{
568 int error = 0;
569 diff_patch_with_delta *pd;
570 git_xdiff_output xo;
571
572 assert(out);
573 *out = NULL;
574
74ded024
RB
575 if (diff_patch_with_delta_alloc(&pd, &old_path, &buf_path) < 0)
576 return -1;
f9c824c5
RB
577
578 memset(&xo, 0, sizeof(xo));
579
8dd8aa48 580 diff_output_to_patch(&xo.output, &pd->patch);
f9c824c5
RB
581 git_xdiff_init(&xo, opts);
582
74ded024
RB
583 error = diff_patch_from_blob_and_buffer(
584 pd, &xo, old_blob, old_path, buf, buflen, buf_path, opts);
585
586 if (!error)
3ff1d123 587 *out = (git_patch *)pd;
f9c824c5 588 else
3ff1d123 589 git_patch_free((git_patch *)pd);
114f5a6c
RB
590
591 return error;
592}
593
3ff1d123 594int git_patch_from_diff(
10672e3e 595 git_patch **patch_ptr, git_diff *diff, size_t idx)
114f5a6c
RB
596{
597 int error = 0;
598 git_xdiff_output xo;
599 git_diff_delta *delta = NULL;
3ff1d123 600 git_patch *patch = NULL;
114f5a6c
RB
601
602 if (patch_ptr) *patch_ptr = NULL;
114f5a6c 603
3ff1d123 604 if (diff_required(diff, "git_patch_from_diff") < 0)
114f5a6c
RB
605 return -1;
606
607 delta = git_vector_get(&diff->deltas, idx);
608 if (!delta) {
609 giterr_set(GITERR_INVALID, "Index out of range for delta in diff");
610 return GIT_ENOTFOUND;
611 }
612
114f5a6c
RB
613 if (git_diff_delta__should_skip(&diff->opts, delta))
614 return 0;
615
616 /* don't load the patch data unless we need it for binary check */
617 if (!patch_ptr &&
618 ((delta->flags & DIFF_FLAGS_KNOWN_BINARY) != 0 ||
619 (diff->opts.flags & GIT_DIFF_SKIP_BINARY_CHECK) != 0))
620 return 0;
621
622 if ((error = diff_patch_alloc_from_diff(&patch, diff, idx)) < 0)
623 return error;
624
8dd8aa48 625 diff_output_to_patch(&xo.output, patch);
114f5a6c
RB
626 git_xdiff_init(&xo, &diff->opts);
627
8dd8aa48 628 error = diff_patch_file_callback(patch, &xo.output);
114f5a6c
RB
629
630 if (!error)
8dd8aa48 631 error = diff_patch_generate(patch, &xo.output);
114f5a6c
RB
632
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);
114f5a6c
RB
882 GITERR_CHECK_ALLOC(hunk);
883
3ff1d123 884 memcpy(&hunk->hunk, hunk_, sizeof(hunk->hunk));
114f5a6c 885
3b5f7954 886 patch->header_size += hunk_->header_len;
197b8966 887
114f5a6c
RB
888 hunk->line_start = git_array_size(patch->lines);
889 hunk->line_count = 0;
890
114f5a6c
RB
891 return 0;
892}
893
894static int diff_patch_line_cb(
895 const git_diff_delta *delta,
3ff1d123 896 const git_diff_hunk *hunk_,
3b5f7954 897 const git_diff_line *line_,
114f5a6c
RB
898 void *payload)
899{
3ff1d123 900 git_patch *patch = payload;
114f5a6c 901 diff_patch_hunk *hunk;
3b5f7954 902 git_diff_line *line;
114f5a6c
RB
903
904 GIT_UNUSED(delta);
3ff1d123 905 GIT_UNUSED(hunk_);
114f5a6c
RB
906
907 hunk = git_array_last(patch->hunks);
908 GITERR_CHECK_ALLOC(hunk);
909
ef3374a8 910 line = git_array_alloc(patch->lines);
114f5a6c
RB
911 GITERR_CHECK_ALLOC(line);
912
3b5f7954 913 memcpy(line, line_, sizeof(*line));
114f5a6c 914
114f5a6c
RB
915 /* do some bookkeeping so we can provide old/new line numbers */
916
3b5f7954 917 patch->content_size += line->content_len;
197b8966 918
3b5f7954
RB
919 if (line->origin == GIT_DIFF_LINE_ADDITION ||
920 line->origin == GIT_DIFF_LINE_DELETION)
197b8966 921 patch->content_size += 1;
3b5f7954 922 else if (line->origin == GIT_DIFF_LINE_CONTEXT) {
197b8966 923 patch->content_size += 1;
3b5f7954
RB
924 patch->context_size += line->content_len + 1;
925 } else if (line->origin == GIT_DIFF_LINE_CONTEXT_EOFNL)
926 patch->context_size += line->content_len;
114f5a6c
RB
927
928 hunk->line_count++;
929
930 return 0;
931}
932
933static void diff_output_init(
934 git_diff_output *out,
935 const git_diff_options *opts,
936 git_diff_file_cb file_cb,
937 git_diff_hunk_cb hunk_cb,
3ff1d123 938 git_diff_line_cb data_cb,
114f5a6c
RB
939 void *payload)
940{
941 GIT_UNUSED(opts);
942
943 memset(out, 0, sizeof(*out));
944
945 out->file_cb = file_cb;
946 out->hunk_cb = hunk_cb;
947 out->data_cb = data_cb;
948 out->payload = payload;
949}
950
3ff1d123 951static void diff_output_to_patch(git_diff_output *out, git_patch *patch)
114f5a6c
RB
952{
953 diff_output_init(
5dc98298 954 out, NULL,
114f5a6c
RB
955 diff_patch_file_cb, diff_patch_hunk_cb, diff_patch_line_cb, patch);
956}