]>
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 hunk in a diff */ |
16 | typedef struct diff_patch_hunk diff_patch_hunk; | |
17 | struct diff_patch_hunk { | |
3ff1d123 | 18 | git_diff_hunk hunk; |
360f42f4 RB |
19 | size_t line_start; |
20 | size_t line_count; | |
21 | }; | |
22 | ||
3ff1d123 | 23 | struct 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 | ||
37 | enum { | |
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 |
46 | static 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 | 50 | static void diff_output_to_patch(git_diff_output *, git_patch *); |
114f5a6c | 51 | |
3ff1d123 | 52 | static 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 | 66 | static 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 | ||
79 | static 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 | ||
100 | static 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 | 119 | static 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 | ||
176 | cleanup: | |
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 | ||
196 | static 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 | 213 | static 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 | 238 | static 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 | 255 | static 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 | ||
263 | int 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 | ||
307 | typedef 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 | 313 | static 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 |
346 | static 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 |
383 | static 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 |
412 | int 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 |
447 | int 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 | ||
481 | static 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 | ||
523 | int 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 |
559 | int 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 | 594 | int 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 | 648 | void git_patch_free(git_patch *patch) |
114f5a6c RB |
649 | { |
650 | if (patch) | |
651 | GIT_REFCOUNT_DEC(patch, diff_patch_free); | |
652 | } | |
653 | ||
10672e3e | 654 | const git_diff_delta *git_patch_get_delta(git_patch *patch) |
114f5a6c RB |
655 | { |
656 | assert(patch); | |
657 | return patch->delta; | |
658 | } | |
659 | ||
3ff1d123 | 660 | size_t git_patch_num_hunks(git_patch *patch) |
114f5a6c RB |
661 | { |
662 | assert(patch); | |
663 | return git_array_size(patch->hunks); | |
664 | } | |
665 | ||
3ff1d123 | 666 | int 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 | ||
703 | static 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 |
709 | int 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 | 731 | int 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 | 741 | int 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 |
768 | size_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 | 801 | git_diff *git_patch__diff(git_patch *patch) |
360f42f4 RB |
802 | { |
803 | return patch->diff; | |
804 | } | |
805 | ||
3ff1d123 | 806 | git_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 |
812 | void 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 |
819 | void 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 |
826 | int 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 | |
862 | static 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 | ||
871 | static 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 | ||
894 | static 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 | ||
933 | static 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 | 951 | static 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 | } |