]>
Commit | Line | Data |
---|---|---|
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 */ |
16 | typedef struct diff_patch_line diff_patch_line; | |
17 | struct 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 */ | |
25 | typedef struct diff_patch_hunk diff_patch_hunk; | |
26 | struct 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 | ||
34 | struct 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 | ||
49 | enum { | |
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 |
58 | static 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 | ||
61 | static void diff_output_to_patch(git_diff_output *, git_diff_patch *); | |
62 | ||
63 | static 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 | ||
77 | static 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 | ||
90 | static 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 | ||
111 | static 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 | ||
132 | static 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 | ||
189 | cleanup: | |
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 | ||
209 | static 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 | ||
226 | static 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 | ||
247 | static 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 | ||
264 | static 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 | ||
272 | int 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 | ||
316 | typedef 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 | 322 | static 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 |
355 | static 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 |
392 | static 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 |
421 | int 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 |
456 | int 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 | ||
490 | static 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 | ||
532 | int 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 | ||
568 | int 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 | ||
603 | int 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 | ||
664 | void git_diff_patch_free(git_diff_patch *patch) | |
665 | { | |
666 | if (patch) | |
667 | GIT_REFCOUNT_DEC(patch, diff_patch_free); | |
668 | } | |
669 | ||
670 | const git_diff_delta *git_diff_patch_delta(git_diff_patch *patch) | |
671 | { | |
672 | assert(patch); | |
673 | return patch->delta; | |
674 | } | |
675 | ||
676 | size_t git_diff_patch_num_hunks(git_diff_patch *patch) | |
677 | { | |
678 | assert(patch); | |
679 | return git_array_size(patch->hunks); | |
680 | } | |
681 | ||
682 | int 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 | ||
719 | static 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 | ||
725 | int 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 | ||
753 | int 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 | ||
763 | int 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 | ||
799 | notfound: | |
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 |
809 | git_diff_list *git_diff_patch__diff(git_diff_patch *patch) |
810 | { | |
811 | return patch->diff; | |
812 | } | |
813 | ||
814 | git_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 | ||
820 | void 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 | ||
827 | void 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 | ||
834 | int 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 | |
872 | static 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 | ||
881 | static 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 | ||
912 | static 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 | ||
972 | static 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 | ||
990 | static 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 | } |