]>
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 | 34 | git_pool flattened; |
96869a4e | 35 | git_error_state error; |
360f42f4 RB |
36 | }; |
37 | ||
38 | enum { | |
39 | GIT_DIFF_PATCH_ALLOCATED = (1 << 0), | |
40 | GIT_DIFF_PATCH_INITIALIZED = (1 << 1), | |
41 | GIT_DIFF_PATCH_LOADED = (1 << 2), | |
42 | GIT_DIFF_PATCH_DIFFABLE = (1 << 3), | |
43 | GIT_DIFF_PATCH_DIFFED = (1 << 4), | |
44 | GIT_DIFF_PATCH_FLATTENED = (1 << 5), | |
45 | }; | |
46 | ||
3b5f7954 RB |
47 | static void diff_output_init( |
48 | git_diff_output*, const git_diff_options*, | |
3ff1d123 | 49 | git_diff_file_cb, git_diff_hunk_cb, git_diff_line_cb, void*); |
114f5a6c | 50 | |
3ff1d123 | 51 | static void diff_output_to_patch(git_diff_output *, git_patch *); |
114f5a6c | 52 | |
3ff1d123 | 53 | static void diff_patch_update_binary(git_patch *patch) |
114f5a6c RB |
54 | { |
55 | if ((patch->delta->flags & DIFF_FLAGS_KNOWN_BINARY) != 0) | |
56 | return; | |
57 | ||
74ded024 RB |
58 | if ((patch->ofile.file->flags & GIT_DIFF_FLAG_BINARY) != 0 || |
59 | (patch->nfile.file->flags & GIT_DIFF_FLAG_BINARY) != 0) | |
114f5a6c RB |
60 | patch->delta->flags |= GIT_DIFF_FLAG_BINARY; |
61 | ||
74ded024 RB |
62 | else if ((patch->ofile.file->flags & DIFF_FLAGS_NOT_BINARY) != 0 && |
63 | (patch->nfile.file->flags & DIFF_FLAGS_NOT_BINARY) != 0) | |
114f5a6c RB |
64 | patch->delta->flags |= GIT_DIFF_FLAG_NOT_BINARY; |
65 | } | |
66 | ||
3ff1d123 | 67 | static void diff_patch_init_common(git_patch *patch) |
114f5a6c RB |
68 | { |
69 | diff_patch_update_binary(patch); | |
70 | ||
71 | if ((patch->delta->flags & GIT_DIFF_FLAG_BINARY) != 0) | |
3b5f7954 | 72 | patch->flags |= GIT_DIFF_PATCH_LOADED; /* LOADED but not DIFFABLE */ |
114f5a6c RB |
73 | |
74 | patch->flags |= GIT_DIFF_PATCH_INITIALIZED; | |
75 | ||
76 | if (patch->diff) | |
3ff1d123 | 77 | git_diff_addref(patch->diff); |
114f5a6c RB |
78 | } |
79 | ||
80 | static int diff_patch_init_from_diff( | |
3ff1d123 | 81 | git_patch *patch, git_diff *diff, size_t delta_index) |
114f5a6c RB |
82 | { |
83 | int error = 0; | |
84 | ||
85 | memset(patch, 0, sizeof(*patch)); | |
86 | patch->diff = diff; | |
87 | patch->delta = git_vector_get(&diff->deltas, delta_index); | |
88 | patch->delta_index = delta_index; | |
89 | ||
360f42f4 | 90 | if ((error = git_diff_file_content__init_from_diff( |
114f5a6c | 91 | &patch->ofile, diff, delta_index, true)) < 0 || |
360f42f4 | 92 | (error = git_diff_file_content__init_from_diff( |
114f5a6c RB |
93 | &patch->nfile, diff, delta_index, false)) < 0) |
94 | return error; | |
95 | ||
96 | diff_patch_init_common(patch); | |
97 | ||
98 | return 0; | |
99 | } | |
100 | ||
101 | static int diff_patch_alloc_from_diff( | |
10672e3e | 102 | git_patch **out, git_diff *diff, size_t delta_index) |
114f5a6c RB |
103 | { |
104 | int error; | |
3ff1d123 | 105 | git_patch *patch = git__calloc(1, sizeof(git_patch)); |
114f5a6c RB |
106 | GITERR_CHECK_ALLOC(patch); |
107 | ||
108 | if (!(error = diff_patch_init_from_diff(patch, diff, delta_index))) { | |
109 | patch->flags |= GIT_DIFF_PATCH_ALLOCATED; | |
110 | GIT_REFCOUNT_INC(patch); | |
111 | } else { | |
112 | git__free(patch); | |
113 | patch = NULL; | |
114 | } | |
115 | ||
116 | *out = patch; | |
117 | return error; | |
118 | } | |
119 | ||
3ff1d123 | 120 | static int diff_patch_load(git_patch *patch, git_diff_output *output) |
114f5a6c RB |
121 | { |
122 | int error = 0; | |
123 | bool incomplete_data; | |
124 | ||
125 | if ((patch->flags & GIT_DIFF_PATCH_LOADED) != 0) | |
126 | return 0; | |
127 | ||
128 | /* if no hunk and data callbacks and user doesn't care if data looks | |
129 | * binary, then there is no need to actually load the data | |
130 | */ | |
5dc98298 | 131 | if ((patch->ofile.opts_flags & GIT_DIFF_SKIP_BINARY_CHECK) != 0 && |
114f5a6c RB |
132 | output && !output->hunk_cb && !output->data_cb) |
133 | return 0; | |
134 | ||
114f5a6c | 135 | incomplete_data = |
74ded024 RB |
136 | (((patch->ofile.flags & GIT_DIFF_FLAG__NO_DATA) != 0 || |
137 | (patch->ofile.file->flags & GIT_DIFF_FLAG_VALID_OID) != 0) && | |
138 | ((patch->nfile.flags & GIT_DIFF_FLAG__NO_DATA) != 0 || | |
139 | (patch->nfile.file->flags & GIT_DIFF_FLAG_VALID_OID) != 0)); | |
114f5a6c RB |
140 | |
141 | /* always try to load workdir content first because filtering may | |
142 | * need 2x data size and this minimizes peak memory footprint | |
143 | */ | |
144 | if (patch->ofile.src == GIT_ITERATOR_TYPE_WORKDIR) { | |
360f42f4 | 145 | if ((error = git_diff_file_content__load(&patch->ofile)) < 0 || |
74ded024 | 146 | (patch->ofile.file->flags & GIT_DIFF_FLAG_BINARY) != 0) |
114f5a6c RB |
147 | goto cleanup; |
148 | } | |
149 | if (patch->nfile.src == GIT_ITERATOR_TYPE_WORKDIR) { | |
360f42f4 | 150 | if ((error = git_diff_file_content__load(&patch->nfile)) < 0 || |
74ded024 | 151 | (patch->nfile.file->flags & GIT_DIFF_FLAG_BINARY) != 0) |
114f5a6c RB |
152 | goto cleanup; |
153 | } | |
154 | ||
155 | /* once workdir has been tried, load other data as needed */ | |
156 | if (patch->ofile.src != GIT_ITERATOR_TYPE_WORKDIR) { | |
360f42f4 | 157 | if ((error = git_diff_file_content__load(&patch->ofile)) < 0 || |
74ded024 | 158 | (patch->ofile.file->flags & GIT_DIFF_FLAG_BINARY) != 0) |
114f5a6c RB |
159 | goto cleanup; |
160 | } | |
161 | if (patch->nfile.src != GIT_ITERATOR_TYPE_WORKDIR) { | |
360f42f4 | 162 | if ((error = git_diff_file_content__load(&patch->nfile)) < 0 || |
74ded024 | 163 | (patch->nfile.file->flags & GIT_DIFF_FLAG_BINARY) != 0) |
114f5a6c RB |
164 | goto cleanup; |
165 | } | |
166 | ||
c67ff958 RB |
167 | /* if previously missing an oid, and now that we have it the two sides |
168 | * are the same (and not submodules), update MODIFIED -> UNMODIFIED | |
169 | */ | |
114f5a6c | 170 | if (incomplete_data && |
74ded024 | 171 | patch->ofile.file->mode == patch->nfile.file->mode && |
c67ff958 | 172 | patch->ofile.file->mode != GIT_FILEMODE_COMMIT && |
74ded024 | 173 | git_oid_equal(&patch->ofile.file->oid, &patch->nfile.file->oid) && |
a1683f28 | 174 | patch->delta->status == GIT_DELTA_MODIFIED) /* not RENAMED/COPIED! */ |
114f5a6c RB |
175 | patch->delta->status = GIT_DELTA_UNMODIFIED; |
176 | ||
177 | cleanup: | |
178 | diff_patch_update_binary(patch); | |
179 | ||
180 | if (!error) { | |
181 | /* patch is diffable only for non-binary, modified files where | |
182 | * at least one side has data and the data actually changed | |
183 | */ | |
184 | if ((patch->delta->flags & GIT_DIFF_FLAG_BINARY) == 0 && | |
185 | patch->delta->status != GIT_DELTA_UNMODIFIED && | |
186 | (patch->ofile.map.len || patch->nfile.map.len) && | |
187 | (patch->ofile.map.len != patch->nfile.map.len || | |
74ded024 | 188 | !git_oid_equal(&patch->ofile.file->oid, &patch->nfile.file->oid))) |
114f5a6c RB |
189 | patch->flags |= GIT_DIFF_PATCH_DIFFABLE; |
190 | ||
191 | patch->flags |= GIT_DIFF_PATCH_LOADED; | |
192 | } | |
193 | ||
194 | return error; | |
195 | } | |
196 | ||
96869a4e | 197 | static int diff_patch_invoke_file_callback( |
3ff1d123 | 198 | git_patch *patch, git_diff_output *output) |
114f5a6c | 199 | { |
96869a4e | 200 | float progress = patch->diff ? |
114f5a6c RB |
201 | ((float)patch->delta_index / patch->diff->deltas.length) : 1.0f; |
202 | ||
96869a4e RB |
203 | if (output->file_cb && |
204 | output->file_cb(patch->delta, progress, output->payload) != 0) | |
205 | return giterr_user_cancel(); | |
114f5a6c | 206 | |
96869a4e | 207 | return 0; |
114f5a6c RB |
208 | } |
209 | ||
3ff1d123 | 210 | static int diff_patch_generate(git_patch *patch, git_diff_output *output) |
114f5a6c RB |
211 | { |
212 | int error = 0; | |
213 | ||
214 | if ((patch->flags & GIT_DIFF_PATCH_DIFFED) != 0) | |
215 | return 0; | |
216 | ||
69c66b55 RB |
217 | /* if we are not looking at the hunks and lines, don't do the diff */ |
218 | if (!output->hunk_cb && !output->data_cb) | |
219 | return 0; | |
220 | ||
114f5a6c RB |
221 | if ((patch->flags & GIT_DIFF_PATCH_LOADED) == 0 && |
222 | (error = diff_patch_load(patch, output)) < 0) | |
223 | return error; | |
224 | ||
225 | if ((patch->flags & GIT_DIFF_PATCH_DIFFABLE) == 0) | |
226 | return 0; | |
227 | ||
228 | if (output->diff_cb != NULL && | |
96869a4e | 229 | (error = output->diff_cb(output, patch)) < 0) |
114f5a6c RB |
230 | patch->flags |= GIT_DIFF_PATCH_DIFFED; |
231 | ||
232 | return error; | |
233 | } | |
234 | ||
3ff1d123 | 235 | static void diff_patch_free(git_patch *patch) |
114f5a6c | 236 | { |
360f42f4 RB |
237 | git_diff_file_content__clear(&patch->ofile); |
238 | git_diff_file_content__clear(&patch->nfile); | |
114f5a6c RB |
239 | |
240 | git_array_clear(patch->lines); | |
241 | git_array_clear(patch->hunks); | |
242 | ||
3ff1d123 | 243 | git_diff_free(patch->diff); /* decrements refcount */ |
114f5a6c RB |
244 | patch->diff = NULL; |
245 | ||
246 | git_pool_clear(&patch->flattened); | |
247 | ||
248 | if (patch->flags & GIT_DIFF_PATCH_ALLOCATED) | |
249 | git__free(patch); | |
250 | } | |
251 | ||
3ff1d123 | 252 | static int diff_required(git_diff *diff, const char *action) |
114f5a6c RB |
253 | { |
254 | if (diff) | |
255 | return 0; | |
256 | giterr_set(GITERR_INVALID, "Must provide valid diff to %s", action); | |
257 | return -1; | |
258 | } | |
259 | ||
260 | int git_diff_foreach( | |
3ff1d123 | 261 | git_diff *diff, |
114f5a6c RB |
262 | git_diff_file_cb file_cb, |
263 | git_diff_hunk_cb hunk_cb, | |
3ff1d123 | 264 | git_diff_line_cb data_cb, |
114f5a6c RB |
265 | void *payload) |
266 | { | |
267 | int error = 0; | |
268 | git_xdiff_output xo; | |
269 | size_t idx; | |
3ff1d123 | 270 | git_patch patch; |
114f5a6c | 271 | |
96869a4e RB |
272 | if ((error = diff_required(diff, "git_diff_foreach")) < 0) |
273 | return error; | |
114f5a6c | 274 | |
96869a4e | 275 | memset(&xo, 0, sizeof(xo)); |
8dd8aa48 RB |
276 | diff_output_init( |
277 | &xo.output, &diff->opts, file_cb, hunk_cb, data_cb, payload); | |
114f5a6c RB |
278 | git_xdiff_init(&xo, &diff->opts); |
279 | ||
280 | git_vector_foreach(&diff->deltas, idx, patch.delta) { | |
a1683f28 | 281 | |
114f5a6c RB |
282 | /* check flags against patch status */ |
283 | if (git_diff_delta__should_skip(&diff->opts, patch.delta)) | |
284 | continue; | |
285 | ||
96869a4e RB |
286 | if ((error = diff_patch_init_from_diff(&patch, diff, idx)) < 0) |
287 | break; | |
114f5a6c | 288 | |
96869a4e RB |
289 | if (!(error = diff_patch_invoke_file_callback(&patch, &xo.output))) |
290 | error = diff_patch_generate(&patch, &xo.output); | |
114f5a6c | 291 | |
96869a4e | 292 | git_patch_free(&patch); |
114f5a6c RB |
293 | |
294 | if (error < 0) | |
295 | break; | |
296 | } | |
297 | ||
114f5a6c RB |
298 | return error; |
299 | } | |
300 | ||
301 | typedef struct { | |
3ff1d123 | 302 | git_patch patch; |
114f5a6c | 303 | git_diff_delta delta; |
74ded024 | 304 | char paths[GIT_FLEX_ARRAY]; |
f9c824c5 | 305 | } diff_patch_with_delta; |
114f5a6c | 306 | |
f9c824c5 | 307 | static int diff_single_generate(diff_patch_with_delta *pd, git_xdiff_output *xo) |
114f5a6c RB |
308 | { |
309 | int error = 0; | |
3ff1d123 | 310 | git_patch *patch = &pd->patch; |
74ded024 RB |
311 | bool has_old = ((patch->ofile.flags & GIT_DIFF_FLAG__NO_DATA) == 0); |
312 | bool has_new = ((patch->nfile.flags & GIT_DIFF_FLAG__NO_DATA) == 0); | |
114f5a6c | 313 | |
f9c824c5 | 314 | pd->delta.status = has_new ? |
114f5a6c RB |
315 | (has_old ? GIT_DELTA_MODIFIED : GIT_DELTA_ADDED) : |
316 | (has_old ? GIT_DELTA_DELETED : GIT_DELTA_UNTRACKED); | |
317 | ||
74ded024 | 318 | if (git_oid_equal(&patch->nfile.file->oid, &patch->ofile.file->oid)) |
f9c824c5 | 319 | pd->delta.status = GIT_DELTA_UNMODIFIED; |
114f5a6c | 320 | |
f9c824c5 | 321 | patch->delta = &pd->delta; |
114f5a6c RB |
322 | |
323 | diff_patch_init_common(patch); | |
324 | ||
74ded024 RB |
325 | if (pd->delta.status == GIT_DELTA_UNMODIFIED && |
326 | !(patch->ofile.opts_flags & GIT_DIFF_INCLUDE_UNMODIFIED)) | |
327 | return error; | |
328 | ||
96869a4e | 329 | error = diff_patch_invoke_file_callback(patch, (git_diff_output *)xo); |
114f5a6c RB |
330 | |
331 | if (!error) | |
f9c824c5 | 332 | error = diff_patch_generate(patch, (git_diff_output *)xo); |
114f5a6c RB |
333 | |
334 | if (error == GIT_EUSER) | |
335 | giterr_clear(); /* don't leave error message set invalidly */ | |
336 | ||
337 | return error; | |
338 | } | |
339 | ||
f9c824c5 RB |
340 | static int diff_patch_from_blobs( |
341 | diff_patch_with_delta *pd, | |
342 | git_xdiff_output *xo, | |
114f5a6c | 343 | const git_blob *old_blob, |
74ded024 | 344 | const char *old_path, |
114f5a6c | 345 | const git_blob *new_blob, |
74ded024 | 346 | const char *new_path, |
f9c824c5 | 347 | const git_diff_options *opts) |
114f5a6c RB |
348 | { |
349 | int error = 0; | |
114f5a6c RB |
350 | git_repository *repo = |
351 | new_blob ? git_object_owner((const git_object *)new_blob) : | |
352 | old_blob ? git_object_owner((const git_object *)old_blob) : NULL; | |
353 | ||
354 | GITERR_CHECK_VERSION(opts, GIT_DIFF_OPTIONS_VERSION, "git_diff_options"); | |
355 | ||
114f5a6c | 356 | if (opts && (opts->flags & GIT_DIFF_REVERSE) != 0) { |
74ded024 RB |
357 | const git_blob *tmp_blob; |
358 | const char *tmp_path; | |
359 | tmp_blob = old_blob; old_blob = new_blob; new_blob = tmp_blob; | |
360 | tmp_path = old_path; old_path = new_path; new_path = tmp_path; | |
114f5a6c RB |
361 | } |
362 | ||
74ded024 RB |
363 | pd->patch.delta = &pd->delta; |
364 | ||
365 | pd->delta.old_file.path = old_path; | |
366 | pd->delta.new_file.path = new_path; | |
367 | ||
360f42f4 | 368 | if ((error = git_diff_file_content__init_from_blob( |
74ded024 | 369 | &pd->patch.ofile, repo, opts, old_blob, &pd->delta.old_file)) < 0 || |
360f42f4 | 370 | (error = git_diff_file_content__init_from_blob( |
74ded024 | 371 | &pd->patch.nfile, repo, opts, new_blob, &pd->delta.new_file)) < 0) |
f9c824c5 RB |
372 | return error; |
373 | ||
374 | return diff_single_generate(pd, xo); | |
375 | } | |
376 | ||
74ded024 RB |
377 | static int diff_patch_with_delta_alloc( |
378 | diff_patch_with_delta **out, | |
379 | const char **old_path, | |
380 | const char **new_path) | |
381 | { | |
382 | diff_patch_with_delta *pd; | |
383 | size_t old_len = *old_path ? strlen(*old_path) : 0; | |
384 | size_t new_len = *new_path ? strlen(*new_path) : 0; | |
385 | ||
386 | *out = pd = git__calloc(1, sizeof(*pd) + old_len + new_len + 2); | |
387 | GITERR_CHECK_ALLOC(pd); | |
388 | ||
389 | pd->patch.flags = GIT_DIFF_PATCH_ALLOCATED; | |
390 | ||
391 | if (*old_path) { | |
392 | memcpy(&pd->paths[0], *old_path, old_len); | |
393 | *old_path = &pd->paths[0]; | |
394 | } else if (*new_path) | |
395 | *old_path = &pd->paths[old_len + 1]; | |
396 | ||
397 | if (*new_path) { | |
398 | memcpy(&pd->paths[old_len + 1], *new_path, new_len); | |
399 | *new_path = &pd->paths[old_len + 1]; | |
400 | } else if (*old_path) | |
401 | *new_path = &pd->paths[0]; | |
402 | ||
403 | return 0; | |
404 | } | |
405 | ||
f9c824c5 RB |
406 | int git_diff_blobs( |
407 | const git_blob *old_blob, | |
74ded024 | 408 | const char *old_path, |
f9c824c5 | 409 | const git_blob *new_blob, |
74ded024 | 410 | const char *new_path, |
f9c824c5 RB |
411 | const git_diff_options *opts, |
412 | git_diff_file_cb file_cb, | |
413 | git_diff_hunk_cb hunk_cb, | |
3ff1d123 | 414 | git_diff_line_cb data_cb, |
f9c824c5 RB |
415 | void *payload) |
416 | { | |
417 | int error = 0; | |
418 | diff_patch_with_delta pd; | |
419 | git_xdiff_output xo; | |
420 | ||
f9c824c5 | 421 | memset(&xo, 0, sizeof(xo)); |
f9c824c5 | 422 | diff_output_init( |
8dd8aa48 | 423 | &xo.output, opts, file_cb, hunk_cb, data_cb, payload); |
f9c824c5 | 424 | git_xdiff_init(&xo, opts); |
114f5a6c | 425 | |
74ded024 RB |
426 | if (!old_path && new_path) |
427 | old_path = new_path; | |
428 | else if (!new_path && old_path) | |
429 | new_path = old_path; | |
430 | ||
96869a4e | 431 | memset(&pd, 0, sizeof(pd)); |
74ded024 RB |
432 | error = diff_patch_from_blobs( |
433 | &pd, &xo, old_blob, old_path, new_blob, new_path, opts); | |
114f5a6c | 434 | |
3ff1d123 | 435 | git_patch_free(&pd.patch); |
114f5a6c RB |
436 | |
437 | return error; | |
438 | } | |
439 | ||
3ff1d123 RB |
440 | int git_patch_from_blobs( |
441 | git_patch **out, | |
f9c824c5 | 442 | const git_blob *old_blob, |
74ded024 | 443 | const char *old_path, |
f9c824c5 | 444 | const git_blob *new_blob, |
74ded024 | 445 | const char *new_path, |
f9c824c5 RB |
446 | const git_diff_options *opts) |
447 | { | |
448 | int error = 0; | |
449 | diff_patch_with_delta *pd; | |
450 | git_xdiff_output xo; | |
451 | ||
452 | assert(out); | |
453 | *out = NULL; | |
454 | ||
74ded024 RB |
455 | if (diff_patch_with_delta_alloc(&pd, &old_path, &new_path) < 0) |
456 | return -1; | |
f9c824c5 RB |
457 | |
458 | memset(&xo, 0, sizeof(xo)); | |
8dd8aa48 | 459 | diff_output_to_patch(&xo.output, &pd->patch); |
f9c824c5 RB |
460 | git_xdiff_init(&xo, opts); |
461 | ||
74ded024 RB |
462 | error = diff_patch_from_blobs( |
463 | pd, &xo, old_blob, old_path, new_blob, new_path, opts); | |
464 | ||
96869a4e RB |
465 | if (error == GIT_EUSER) |
466 | error = giterr_restore(&pd->patch.error); | |
467 | ||
74ded024 | 468 | if (!error) |
3ff1d123 | 469 | *out = (git_patch *)pd; |
f9c824c5 | 470 | else |
3ff1d123 | 471 | git_patch_free((git_patch *)pd); |
f9c824c5 RB |
472 | |
473 | return error; | |
474 | } | |
475 | ||
476 | static int diff_patch_from_blob_and_buffer( | |
477 | diff_patch_with_delta *pd, | |
478 | git_xdiff_output *xo, | |
114f5a6c | 479 | const git_blob *old_blob, |
74ded024 | 480 | const char *old_path, |
114f5a6c RB |
481 | const char *buf, |
482 | size_t buflen, | |
74ded024 | 483 | const char *buf_path, |
f9c824c5 | 484 | const git_diff_options *opts) |
114f5a6c RB |
485 | { |
486 | int error = 0; | |
114f5a6c RB |
487 | git_repository *repo = |
488 | old_blob ? git_object_owner((const git_object *)old_blob) : NULL; | |
489 | ||
490 | GITERR_CHECK_VERSION(opts, GIT_DIFF_OPTIONS_VERSION, "git_diff_options"); | |
491 | ||
f9c824c5 | 492 | pd->patch.delta = &pd->delta; |
114f5a6c | 493 | |
114f5a6c | 494 | if (opts && (opts->flags & GIT_DIFF_REVERSE) != 0) { |
74ded024 RB |
495 | pd->delta.old_file.path = buf_path; |
496 | pd->delta.new_file.path = old_path; | |
497 | ||
360f42f4 | 498 | if (!(error = git_diff_file_content__init_from_raw( |
74ded024 | 499 | &pd->patch.ofile, repo, opts, buf, buflen, &pd->delta.old_file))) |
360f42f4 | 500 | error = git_diff_file_content__init_from_blob( |
74ded024 | 501 | &pd->patch.nfile, repo, opts, old_blob, &pd->delta.new_file); |
114f5a6c | 502 | } else { |
74ded024 RB |
503 | pd->delta.old_file.path = old_path; |
504 | pd->delta.new_file.path = buf_path; | |
505 | ||
360f42f4 | 506 | if (!(error = git_diff_file_content__init_from_blob( |
74ded024 | 507 | &pd->patch.ofile, repo, opts, old_blob, &pd->delta.old_file))) |
360f42f4 | 508 | error = git_diff_file_content__init_from_raw( |
74ded024 | 509 | &pd->patch.nfile, repo, opts, buf, buflen, &pd->delta.new_file); |
114f5a6c RB |
510 | } |
511 | ||
74ded024 RB |
512 | if (error < 0) |
513 | return error; | |
514 | ||
f9c824c5 RB |
515 | return diff_single_generate(pd, xo); |
516 | } | |
517 | ||
518 | int git_diff_blob_to_buffer( | |
519 | const git_blob *old_blob, | |
74ded024 | 520 | const char *old_path, |
f9c824c5 RB |
521 | const char *buf, |
522 | size_t buflen, | |
74ded024 | 523 | const char *buf_path, |
f9c824c5 RB |
524 | const git_diff_options *opts, |
525 | git_diff_file_cb file_cb, | |
526 | git_diff_hunk_cb hunk_cb, | |
3ff1d123 | 527 | git_diff_line_cb data_cb, |
f9c824c5 RB |
528 | void *payload) |
529 | { | |
530 | int error = 0; | |
531 | diff_patch_with_delta pd; | |
532 | git_xdiff_output xo; | |
533 | ||
f9c824c5 | 534 | memset(&xo, 0, sizeof(xo)); |
f9c824c5 | 535 | diff_output_init( |
8dd8aa48 | 536 | &xo.output, opts, file_cb, hunk_cb, data_cb, payload); |
f9c824c5 RB |
537 | git_xdiff_init(&xo, opts); |
538 | ||
74ded024 RB |
539 | if (!old_path && buf_path) |
540 | old_path = buf_path; | |
541 | else if (!buf_path && old_path) | |
542 | buf_path = old_path; | |
543 | ||
96869a4e | 544 | memset(&pd, 0, sizeof(pd)); |
f9c824c5 | 545 | error = diff_patch_from_blob_and_buffer( |
74ded024 | 546 | &pd, &xo, old_blob, old_path, buf, buflen, buf_path, opts); |
114f5a6c | 547 | |
3ff1d123 | 548 | git_patch_free(&pd.patch); |
f9c824c5 RB |
549 | |
550 | return error; | |
551 | } | |
552 | ||
3ff1d123 RB |
553 | int git_patch_from_blob_and_buffer( |
554 | git_patch **out, | |
f9c824c5 | 555 | const git_blob *old_blob, |
74ded024 | 556 | const char *old_path, |
f9c824c5 RB |
557 | const char *buf, |
558 | size_t buflen, | |
74ded024 | 559 | const char *buf_path, |
f9c824c5 RB |
560 | const git_diff_options *opts) |
561 | { | |
562 | int error = 0; | |
563 | diff_patch_with_delta *pd; | |
564 | git_xdiff_output xo; | |
565 | ||
566 | assert(out); | |
567 | *out = NULL; | |
568 | ||
74ded024 RB |
569 | if (diff_patch_with_delta_alloc(&pd, &old_path, &buf_path) < 0) |
570 | return -1; | |
f9c824c5 RB |
571 | |
572 | memset(&xo, 0, sizeof(xo)); | |
8dd8aa48 | 573 | diff_output_to_patch(&xo.output, &pd->patch); |
f9c824c5 RB |
574 | git_xdiff_init(&xo, opts); |
575 | ||
74ded024 RB |
576 | error = diff_patch_from_blob_and_buffer( |
577 | pd, &xo, old_blob, old_path, buf, buflen, buf_path, opts); | |
578 | ||
96869a4e RB |
579 | if (error == GIT_EUSER) |
580 | error = giterr_restore(&pd->patch.error); | |
581 | ||
74ded024 | 582 | if (!error) |
3ff1d123 | 583 | *out = (git_patch *)pd; |
f9c824c5 | 584 | else |
3ff1d123 | 585 | git_patch_free((git_patch *)pd); |
114f5a6c RB |
586 | |
587 | return error; | |
588 | } | |
589 | ||
3ff1d123 | 590 | int git_patch_from_diff( |
10672e3e | 591 | git_patch **patch_ptr, git_diff *diff, size_t idx) |
114f5a6c RB |
592 | { |
593 | int error = 0; | |
594 | git_xdiff_output xo; | |
595 | git_diff_delta *delta = NULL; | |
3ff1d123 | 596 | git_patch *patch = NULL; |
114f5a6c RB |
597 | |
598 | if (patch_ptr) *patch_ptr = NULL; | |
114f5a6c | 599 | |
3ff1d123 | 600 | if (diff_required(diff, "git_patch_from_diff") < 0) |
114f5a6c RB |
601 | return -1; |
602 | ||
603 | delta = git_vector_get(&diff->deltas, idx); | |
604 | if (!delta) { | |
605 | giterr_set(GITERR_INVALID, "Index out of range for delta in diff"); | |
606 | return GIT_ENOTFOUND; | |
607 | } | |
608 | ||
114f5a6c RB |
609 | if (git_diff_delta__should_skip(&diff->opts, delta)) |
610 | return 0; | |
611 | ||
612 | /* don't load the patch data unless we need it for binary check */ | |
613 | if (!patch_ptr && | |
614 | ((delta->flags & DIFF_FLAGS_KNOWN_BINARY) != 0 || | |
615 | (diff->opts.flags & GIT_DIFF_SKIP_BINARY_CHECK) != 0)) | |
616 | return 0; | |
617 | ||
618 | if ((error = diff_patch_alloc_from_diff(&patch, diff, idx)) < 0) | |
619 | return error; | |
620 | ||
96869a4e | 621 | memset(&xo, 0, sizeof(xo)); |
8dd8aa48 | 622 | diff_output_to_patch(&xo.output, patch); |
114f5a6c RB |
623 | git_xdiff_init(&xo, &diff->opts); |
624 | ||
96869a4e | 625 | error = diff_patch_invoke_file_callback(patch, &xo.output); |
114f5a6c RB |
626 | |
627 | if (!error) | |
8dd8aa48 | 628 | error = diff_patch_generate(patch, &xo.output); |
114f5a6c | 629 | |
96869a4e RB |
630 | if (error == GIT_EUSER) |
631 | error = giterr_restore(&patch->error); | |
632 | ||
114f5a6c RB |
633 | if (!error) { |
634 | /* if cumulative diff size is < 0.5 total size, flatten the patch */ | |
635 | /* unload the file content */ | |
636 | } | |
637 | ||
638 | if (error || !patch_ptr) | |
3ff1d123 | 639 | git_patch_free(patch); |
114f5a6c RB |
640 | else |
641 | *patch_ptr = patch; | |
642 | ||
643 | if (error == GIT_EUSER) | |
644 | giterr_clear(); /* don't leave error message set invalidly */ | |
645 | return error; | |
646 | } | |
647 | ||
3ff1d123 | 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); |
96869a4e RB |
882 | if (!hunk) |
883 | return giterr_capture(&patch->error, -1); | |
114f5a6c | 884 | |
3ff1d123 | 885 | memcpy(&hunk->hunk, hunk_, sizeof(hunk->hunk)); |
114f5a6c | 886 | |
3b5f7954 | 887 | patch->header_size += hunk_->header_len; |
197b8966 | 888 | |
114f5a6c RB |
889 | hunk->line_start = git_array_size(patch->lines); |
890 | hunk->line_count = 0; | |
891 | ||
114f5a6c RB |
892 | return 0; |
893 | } | |
894 | ||
895 | static int diff_patch_line_cb( | |
896 | const git_diff_delta *delta, | |
3ff1d123 | 897 | const git_diff_hunk *hunk_, |
3b5f7954 | 898 | const git_diff_line *line_, |
114f5a6c RB |
899 | void *payload) |
900 | { | |
3ff1d123 | 901 | git_patch *patch = payload; |
114f5a6c | 902 | diff_patch_hunk *hunk; |
3b5f7954 | 903 | git_diff_line *line; |
114f5a6c RB |
904 | |
905 | GIT_UNUSED(delta); | |
3ff1d123 | 906 | GIT_UNUSED(hunk_); |
114f5a6c RB |
907 | |
908 | hunk = git_array_last(patch->hunks); | |
96869a4e | 909 | assert(hunk); /* programmer error if no hunk is available */ |
114f5a6c | 910 | |
ef3374a8 | 911 | line = git_array_alloc(patch->lines); |
96869a4e RB |
912 | if (!line) |
913 | return giterr_capture(&patch->error, -1); | |
114f5a6c | 914 | |
3b5f7954 | 915 | memcpy(line, line_, sizeof(*line)); |
114f5a6c | 916 | |
114f5a6c RB |
917 | /* do some bookkeeping so we can provide old/new line numbers */ |
918 | ||
3b5f7954 | 919 | patch->content_size += line->content_len; |
197b8966 | 920 | |
3b5f7954 RB |
921 | if (line->origin == GIT_DIFF_LINE_ADDITION || |
922 | line->origin == GIT_DIFF_LINE_DELETION) | |
197b8966 | 923 | patch->content_size += 1; |
3b5f7954 | 924 | else if (line->origin == GIT_DIFF_LINE_CONTEXT) { |
197b8966 | 925 | patch->content_size += 1; |
3b5f7954 RB |
926 | patch->context_size += line->content_len + 1; |
927 | } else if (line->origin == GIT_DIFF_LINE_CONTEXT_EOFNL) | |
928 | patch->context_size += line->content_len; | |
114f5a6c RB |
929 | |
930 | hunk->line_count++; | |
931 | ||
932 | return 0; | |
933 | } | |
934 | ||
935 | static void diff_output_init( | |
936 | git_diff_output *out, | |
937 | const git_diff_options *opts, | |
938 | git_diff_file_cb file_cb, | |
939 | git_diff_hunk_cb hunk_cb, | |
3ff1d123 | 940 | git_diff_line_cb data_cb, |
114f5a6c RB |
941 | void *payload) |
942 | { | |
943 | GIT_UNUSED(opts); | |
944 | ||
945 | memset(out, 0, sizeof(*out)); | |
946 | ||
947 | out->file_cb = file_cb; | |
948 | out->hunk_cb = hunk_cb; | |
949 | out->data_cb = data_cb; | |
950 | out->payload = payload; | |
951 | } | |
952 | ||
3ff1d123 | 953 | static void diff_output_to_patch(git_diff_output *out, git_patch *patch) |
114f5a6c RB |
954 | { |
955 | diff_output_init( | |
5dc98298 | 956 | out, NULL, |
114f5a6c RB |
957 | diff_patch_file_cb, diff_patch_hunk_cb, diff_patch_line_cb, patch); |
958 | } |