]>
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" | |
6789b7a7 | 8 | #include "git2/blob.h" |
114f5a6c RB |
9 | #include "diff.h" |
10 | #include "diff_file.h" | |
11 | #include "diff_driver.h" | |
12 | #include "diff_patch.h" | |
13 | #include "diff_xdiff.h" | |
c67ff958 | 14 | #include "fileops.h" |
114f5a6c | 15 | |
360f42f4 | 16 | /* cached information about a hunk in a diff */ |
06280457 | 17 | typedef struct diff_patch_hunk { |
3ff1d123 | 18 | git_diff_hunk hunk; |
360f42f4 RB |
19 | size_t line_start; |
20 | size_t line_count; | |
06280457 | 21 | } diff_patch_hunk; |
360f42f4 | 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 | 135 | (((patch->ofile.flags & GIT_DIFF_FLAG__NO_DATA) != 0 || |
9950bb4e | 136 | (patch->ofile.file->flags & GIT_DIFF_FLAG_VALID_ID) != 0) && |
74ded024 | 137 | ((patch->nfile.flags & GIT_DIFF_FLAG__NO_DATA) != 0 || |
9950bb4e | 138 | (patch->nfile.file->flags & GIT_DIFF_FLAG_VALID_ID) != 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 && |
9950bb4e | 172 | git_oid_equal(&patch->ofile.file->id, &patch->nfile.file->id) && |
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 || | |
9950bb4e | 187 | !git_oid_equal(&patch->ofile.file->id, &patch->nfile.file->id))) |
114f5a6c RB |
188 | patch->flags |= GIT_DIFF_PATCH_DIFFABLE; |
189 | ||
190 | patch->flags |= GIT_DIFF_PATCH_LOADED; | |
191 | } | |
192 | ||
193 | return error; | |
194 | } | |
195 | ||
96869a4e | 196 | static int diff_patch_invoke_file_callback( |
3ff1d123 | 197 | git_patch *patch, git_diff_output *output) |
114f5a6c | 198 | { |
96869a4e | 199 | float progress = patch->diff ? |
114f5a6c RB |
200 | ((float)patch->delta_index / patch->diff->deltas.length) : 1.0f; |
201 | ||
25e0b157 RB |
202 | if (!output->file_cb) |
203 | return 0; | |
114f5a6c | 204 | |
26c1cb91 | 205 | return giterr_set_after_callback_function( |
25e0b157 RB |
206 | output->file_cb(patch->delta, progress, output->payload), |
207 | "git_patch"); | |
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)); |
5387cfee | 276 | memset(&patch, 0, sizeof(patch)); |
8dd8aa48 RB |
277 | diff_output_init( |
278 | &xo.output, &diff->opts, file_cb, hunk_cb, data_cb, payload); | |
114f5a6c RB |
279 | git_xdiff_init(&xo, &diff->opts); |
280 | ||
281 | git_vector_foreach(&diff->deltas, idx, patch.delta) { | |
a1683f28 | 282 | |
114f5a6c RB |
283 | /* check flags against patch status */ |
284 | if (git_diff_delta__should_skip(&diff->opts, patch.delta)) | |
285 | continue; | |
286 | ||
147d86fc ET |
287 | if ((error = diff_patch_invoke_file_callback(&patch, &xo.output)) == 0) { |
288 | if (hunk_cb || data_cb) { | |
289 | if ((error = diff_patch_init_from_diff(&patch, diff, idx)) == 0) | |
290 | error = diff_patch_generate(&patch, &xo.output); | |
291 | } | |
292 | } | |
114f5a6c | 293 | |
96869a4e | 294 | git_patch_free(&patch); |
114f5a6c | 295 | |
25e0b157 | 296 | if (error) |
114f5a6c RB |
297 | break; |
298 | } | |
299 | ||
114f5a6c RB |
300 | return error; |
301 | } | |
302 | ||
303 | typedef struct { | |
3ff1d123 | 304 | git_patch patch; |
114f5a6c | 305 | git_diff_delta delta; |
74ded024 | 306 | char paths[GIT_FLEX_ARRAY]; |
f9c824c5 | 307 | } diff_patch_with_delta; |
114f5a6c | 308 | |
f9c824c5 | 309 | static int diff_single_generate(diff_patch_with_delta *pd, git_xdiff_output *xo) |
114f5a6c RB |
310 | { |
311 | int error = 0; | |
3ff1d123 | 312 | git_patch *patch = &pd->patch; |
74ded024 RB |
313 | bool has_old = ((patch->ofile.flags & GIT_DIFF_FLAG__NO_DATA) == 0); |
314 | bool has_new = ((patch->nfile.flags & GIT_DIFF_FLAG__NO_DATA) == 0); | |
114f5a6c | 315 | |
f9c824c5 | 316 | pd->delta.status = has_new ? |
114f5a6c RB |
317 | (has_old ? GIT_DELTA_MODIFIED : GIT_DELTA_ADDED) : |
318 | (has_old ? GIT_DELTA_DELETED : GIT_DELTA_UNTRACKED); | |
319 | ||
9950bb4e | 320 | if (git_oid_equal(&patch->nfile.file->id, &patch->ofile.file->id)) |
f9c824c5 | 321 | pd->delta.status = GIT_DELTA_UNMODIFIED; |
114f5a6c | 322 | |
f9c824c5 | 323 | patch->delta = &pd->delta; |
114f5a6c RB |
324 | |
325 | diff_patch_init_common(patch); | |
326 | ||
74ded024 RB |
327 | if (pd->delta.status == GIT_DELTA_UNMODIFIED && |
328 | !(patch->ofile.opts_flags & GIT_DIFF_INCLUDE_UNMODIFIED)) | |
329 | return error; | |
330 | ||
96869a4e | 331 | error = diff_patch_invoke_file_callback(patch, (git_diff_output *)xo); |
114f5a6c RB |
332 | |
333 | if (!error) | |
f9c824c5 | 334 | error = diff_patch_generate(patch, (git_diff_output *)xo); |
114f5a6c | 335 | |
114f5a6c RB |
336 | return error; |
337 | } | |
338 | ||
6789b7a7 | 339 | static int diff_patch_from_sources( |
f9c824c5 RB |
340 | diff_patch_with_delta *pd, |
341 | git_xdiff_output *xo, | |
6789b7a7 RB |
342 | git_diff_file_content_src *oldsrc, |
343 | git_diff_file_content_src *newsrc, | |
f9c824c5 | 344 | const git_diff_options *opts) |
114f5a6c RB |
345 | { |
346 | int error = 0; | |
114f5a6c | 347 | git_repository *repo = |
6789b7a7 RB |
348 | oldsrc->blob ? git_blob_owner(oldsrc->blob) : |
349 | newsrc->blob ? git_blob_owner(newsrc->blob) : NULL; | |
350 | git_diff_file *lfile = &pd->delta.old_file, *rfile = &pd->delta.new_file; | |
351 | git_diff_file_content *ldata = &pd->patch.ofile, *rdata = &pd->patch.nfile; | |
114f5a6c RB |
352 | |
353 | GITERR_CHECK_VERSION(opts, GIT_DIFF_OPTIONS_VERSION, "git_diff_options"); | |
354 | ||
114f5a6c | 355 | if (opts && (opts->flags & GIT_DIFF_REVERSE) != 0) { |
6789b7a7 RB |
356 | void *tmp = lfile; lfile = rfile; rfile = tmp; |
357 | tmp = ldata; ldata = rdata; rdata = tmp; | |
114f5a6c RB |
358 | } |
359 | ||
74ded024 RB |
360 | pd->patch.delta = &pd->delta; |
361 | ||
6789b7a7 RB |
362 | if (!oldsrc->as_path) { |
363 | if (newsrc->as_path) | |
364 | oldsrc->as_path = newsrc->as_path; | |
365 | else | |
366 | oldsrc->as_path = newsrc->as_path = "file"; | |
367 | } | |
368 | else if (!newsrc->as_path) | |
369 | newsrc->as_path = oldsrc->as_path; | |
370 | ||
371 | lfile->path = oldsrc->as_path; | |
372 | rfile->path = newsrc->as_path; | |
74ded024 | 373 | |
6789b7a7 RB |
374 | if ((error = git_diff_file_content__init_from_src( |
375 | ldata, repo, opts, oldsrc, lfile)) < 0 || | |
376 | (error = git_diff_file_content__init_from_src( | |
377 | rdata, repo, opts, newsrc, rfile)) < 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; | |
f1453c59 | 391 | size_t alloc_len; |
74ded024 | 392 | |
f1453c59 ET |
393 | GITERR_CHECK_ALLOC_ADD(&alloc_len, sizeof(*pd), old_len); |
394 | GITERR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, new_len); | |
395 | GITERR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, 2); | |
392702ee ET |
396 | |
397 | *out = pd = git__calloc(1, alloc_len); | |
74ded024 RB |
398 | GITERR_CHECK_ALLOC(pd); |
399 | ||
400 | pd->patch.flags = GIT_DIFF_PATCH_ALLOCATED; | |
401 | ||
402 | if (*old_path) { | |
403 | memcpy(&pd->paths[0], *old_path, old_len); | |
404 | *old_path = &pd->paths[0]; | |
405 | } else if (*new_path) | |
406 | *old_path = &pd->paths[old_len + 1]; | |
407 | ||
408 | if (*new_path) { | |
409 | memcpy(&pd->paths[old_len + 1], *new_path, new_len); | |
410 | *new_path = &pd->paths[old_len + 1]; | |
411 | } else if (*old_path) | |
412 | *new_path = &pd->paths[0]; | |
413 | ||
414 | return 0; | |
415 | } | |
416 | ||
6789b7a7 RB |
417 | static int diff_from_sources( |
418 | git_diff_file_content_src *oldsrc, | |
419 | git_diff_file_content_src *newsrc, | |
f9c824c5 RB |
420 | const git_diff_options *opts, |
421 | git_diff_file_cb file_cb, | |
422 | git_diff_hunk_cb hunk_cb, | |
3ff1d123 | 423 | git_diff_line_cb data_cb, |
f9c824c5 RB |
424 | void *payload) |
425 | { | |
426 | int error = 0; | |
427 | diff_patch_with_delta pd; | |
428 | git_xdiff_output xo; | |
429 | ||
f9c824c5 | 430 | memset(&xo, 0, sizeof(xo)); |
f9c824c5 | 431 | diff_output_init( |
8dd8aa48 | 432 | &xo.output, opts, file_cb, hunk_cb, data_cb, payload); |
f9c824c5 | 433 | git_xdiff_init(&xo, opts); |
114f5a6c | 434 | |
96869a4e | 435 | memset(&pd, 0, sizeof(pd)); |
6789b7a7 RB |
436 | |
437 | error = diff_patch_from_sources(&pd, &xo, oldsrc, newsrc, opts); | |
114f5a6c | 438 | |
3ff1d123 | 439 | git_patch_free(&pd.patch); |
114f5a6c RB |
440 | |
441 | return error; | |
442 | } | |
443 | ||
6789b7a7 | 444 | static int patch_from_sources( |
3ff1d123 | 445 | git_patch **out, |
6789b7a7 RB |
446 | git_diff_file_content_src *oldsrc, |
447 | git_diff_file_content_src *newsrc, | |
f9c824c5 RB |
448 | const git_diff_options *opts) |
449 | { | |
450 | int error = 0; | |
451 | diff_patch_with_delta *pd; | |
452 | git_xdiff_output xo; | |
453 | ||
454 | assert(out); | |
455 | *out = NULL; | |
456 | ||
6789b7a7 RB |
457 | if ((error = diff_patch_with_delta_alloc( |
458 | &pd, &oldsrc->as_path, &newsrc->as_path)) < 0) | |
459 | return error; | |
f9c824c5 RB |
460 | |
461 | memset(&xo, 0, sizeof(xo)); | |
8dd8aa48 | 462 | diff_output_to_patch(&xo.output, &pd->patch); |
f9c824c5 RB |
463 | git_xdiff_init(&xo, opts); |
464 | ||
6789b7a7 | 465 | if (!(error = diff_patch_from_sources(pd, &xo, oldsrc, newsrc, opts))) |
3ff1d123 | 466 | *out = (git_patch *)pd; |
f9c824c5 | 467 | else |
3ff1d123 | 468 | git_patch_free((git_patch *)pd); |
f9c824c5 RB |
469 | |
470 | return error; | |
471 | } | |
472 | ||
6789b7a7 | 473 | int git_diff_blobs( |
114f5a6c | 474 | const git_blob *old_blob, |
74ded024 | 475 | const char *old_path, |
6789b7a7 RB |
476 | const git_blob *new_blob, |
477 | const char *new_path, | |
478 | const git_diff_options *opts, | |
479 | git_diff_file_cb file_cb, | |
480 | git_diff_hunk_cb hunk_cb, | |
481 | git_diff_line_cb data_cb, | |
482 | void *payload) | |
114f5a6c | 483 | { |
6789b7a7 RB |
484 | git_diff_file_content_src osrc = |
485 | GIT_DIFF_FILE_CONTENT_SRC__BLOB(old_blob, old_path); | |
486 | git_diff_file_content_src nsrc = | |
487 | GIT_DIFF_FILE_CONTENT_SRC__BLOB(new_blob, new_path); | |
488 | return diff_from_sources( | |
489 | &osrc, &nsrc, opts, file_cb, hunk_cb, data_cb, payload); | |
490 | } | |
74ded024 | 491 | |
6789b7a7 RB |
492 | int git_patch_from_blobs( |
493 | git_patch **out, | |
494 | const git_blob *old_blob, | |
495 | const char *old_path, | |
496 | const git_blob *new_blob, | |
497 | const char *new_path, | |
498 | const git_diff_options *opts) | |
499 | { | |
500 | git_diff_file_content_src osrc = | |
501 | GIT_DIFF_FILE_CONTENT_SRC__BLOB(old_blob, old_path); | |
502 | git_diff_file_content_src nsrc = | |
503 | GIT_DIFF_FILE_CONTENT_SRC__BLOB(new_blob, new_path); | |
504 | return patch_from_sources(out, &osrc, &nsrc, opts); | |
f9c824c5 RB |
505 | } |
506 | ||
507 | int git_diff_blob_to_buffer( | |
508 | const git_blob *old_blob, | |
74ded024 | 509 | const char *old_path, |
f9c824c5 RB |
510 | const char *buf, |
511 | size_t buflen, | |
74ded024 | 512 | const char *buf_path, |
f9c824c5 RB |
513 | const git_diff_options *opts, |
514 | git_diff_file_cb file_cb, | |
515 | git_diff_hunk_cb hunk_cb, | |
3ff1d123 | 516 | git_diff_line_cb data_cb, |
f9c824c5 RB |
517 | void *payload) |
518 | { | |
6789b7a7 RB |
519 | git_diff_file_content_src osrc = |
520 | GIT_DIFF_FILE_CONTENT_SRC__BLOB(old_blob, old_path); | |
521 | git_diff_file_content_src nsrc = | |
522 | GIT_DIFF_FILE_CONTENT_SRC__BUF(buf, buflen, buf_path); | |
523 | return diff_from_sources( | |
524 | &osrc, &nsrc, opts, file_cb, hunk_cb, data_cb, payload); | |
f9c824c5 RB |
525 | } |
526 | ||
3ff1d123 RB |
527 | int git_patch_from_blob_and_buffer( |
528 | git_patch **out, | |
f9c824c5 | 529 | const git_blob *old_blob, |
74ded024 | 530 | const char *old_path, |
f9c824c5 RB |
531 | const char *buf, |
532 | size_t buflen, | |
74ded024 | 533 | const char *buf_path, |
f9c824c5 RB |
534 | const git_diff_options *opts) |
535 | { | |
6789b7a7 RB |
536 | git_diff_file_content_src osrc = |
537 | GIT_DIFF_FILE_CONTENT_SRC__BLOB(old_blob, old_path); | |
538 | git_diff_file_content_src nsrc = | |
539 | GIT_DIFF_FILE_CONTENT_SRC__BUF(buf, buflen, buf_path); | |
540 | return patch_from_sources(out, &osrc, &nsrc, opts); | |
541 | } | |
74ded024 | 542 | |
6789b7a7 RB |
543 | int git_diff_buffers( |
544 | const void *old_buf, | |
545 | size_t old_len, | |
546 | const char *old_path, | |
547 | const void *new_buf, | |
548 | size_t new_len, | |
549 | const char *new_path, | |
550 | const git_diff_options *opts, | |
551 | git_diff_file_cb file_cb, | |
552 | git_diff_hunk_cb hunk_cb, | |
553 | git_diff_line_cb data_cb, | |
554 | void *payload) | |
555 | { | |
556 | git_diff_file_content_src osrc = | |
557 | GIT_DIFF_FILE_CONTENT_SRC__BUF(old_buf, old_len, old_path); | |
558 | git_diff_file_content_src nsrc = | |
559 | GIT_DIFF_FILE_CONTENT_SRC__BUF(new_buf, new_len, new_path); | |
560 | return diff_from_sources( | |
561 | &osrc, &nsrc, opts, file_cb, hunk_cb, data_cb, payload); | |
562 | } | |
114f5a6c | 563 | |
6789b7a7 RB |
564 | int git_patch_from_buffers( |
565 | git_patch **out, | |
566 | const void *old_buf, | |
567 | size_t old_len, | |
568 | const char *old_path, | |
569 | const char *new_buf, | |
570 | size_t new_len, | |
571 | const char *new_path, | |
572 | const git_diff_options *opts) | |
573 | { | |
574 | git_diff_file_content_src osrc = | |
575 | GIT_DIFF_FILE_CONTENT_SRC__BUF(old_buf, old_len, old_path); | |
576 | git_diff_file_content_src nsrc = | |
577 | GIT_DIFF_FILE_CONTENT_SRC__BUF(new_buf, new_len, new_path); | |
578 | return patch_from_sources(out, &osrc, &nsrc, opts); | |
114f5a6c RB |
579 | } |
580 | ||
3ff1d123 | 581 | int git_patch_from_diff( |
10672e3e | 582 | git_patch **patch_ptr, git_diff *diff, size_t idx) |
114f5a6c RB |
583 | { |
584 | int error = 0; | |
585 | git_xdiff_output xo; | |
586 | git_diff_delta *delta = NULL; | |
3ff1d123 | 587 | git_patch *patch = NULL; |
114f5a6c RB |
588 | |
589 | if (patch_ptr) *patch_ptr = NULL; | |
114f5a6c | 590 | |
3ff1d123 | 591 | if (diff_required(diff, "git_patch_from_diff") < 0) |
114f5a6c RB |
592 | return -1; |
593 | ||
594 | delta = git_vector_get(&diff->deltas, idx); | |
595 | if (!delta) { | |
596 | giterr_set(GITERR_INVALID, "Index out of range for delta in diff"); | |
597 | return GIT_ENOTFOUND; | |
598 | } | |
599 | ||
114f5a6c RB |
600 | if (git_diff_delta__should_skip(&diff->opts, delta)) |
601 | return 0; | |
602 | ||
603 | /* don't load the patch data unless we need it for binary check */ | |
604 | if (!patch_ptr && | |
605 | ((delta->flags & DIFF_FLAGS_KNOWN_BINARY) != 0 || | |
606 | (diff->opts.flags & GIT_DIFF_SKIP_BINARY_CHECK) != 0)) | |
607 | return 0; | |
608 | ||
609 | if ((error = diff_patch_alloc_from_diff(&patch, diff, idx)) < 0) | |
610 | return error; | |
611 | ||
96869a4e | 612 | memset(&xo, 0, sizeof(xo)); |
8dd8aa48 | 613 | diff_output_to_patch(&xo.output, patch); |
114f5a6c RB |
614 | git_xdiff_init(&xo, &diff->opts); |
615 | ||
96869a4e | 616 | error = diff_patch_invoke_file_callback(patch, &xo.output); |
114f5a6c RB |
617 | |
618 | if (!error) | |
8dd8aa48 | 619 | error = diff_patch_generate(patch, &xo.output); |
114f5a6c RB |
620 | |
621 | if (!error) { | |
25e0b157 RB |
622 | /* TODO: if cumulative diff size is < 0.5 total size, flatten patch */ |
623 | /* TODO: and unload the file content */ | |
114f5a6c RB |
624 | } |
625 | ||
626 | if (error || !patch_ptr) | |
3ff1d123 | 627 | git_patch_free(patch); |
114f5a6c RB |
628 | else |
629 | *patch_ptr = patch; | |
630 | ||
114f5a6c RB |
631 | return error; |
632 | } | |
633 | ||
3ff1d123 | 634 | void git_patch_free(git_patch *patch) |
114f5a6c RB |
635 | { |
636 | if (patch) | |
637 | GIT_REFCOUNT_DEC(patch, diff_patch_free); | |
638 | } | |
639 | ||
7a28f268 | 640 | const git_diff_delta *git_patch_get_delta(const git_patch *patch) |
114f5a6c RB |
641 | { |
642 | assert(patch); | |
643 | return patch->delta; | |
644 | } | |
645 | ||
7a28f268 | 646 | size_t git_patch_num_hunks(const git_patch *patch) |
114f5a6c RB |
647 | { |
648 | assert(patch); | |
649 | return git_array_size(patch->hunks); | |
650 | } | |
651 | ||
3ff1d123 | 652 | int git_patch_line_stats( |
114f5a6c RB |
653 | size_t *total_ctxt, |
654 | size_t *total_adds, | |
655 | size_t *total_dels, | |
3ff1d123 | 656 | const git_patch *patch) |
114f5a6c RB |
657 | { |
658 | size_t totals[3], idx; | |
659 | ||
660 | memset(totals, 0, sizeof(totals)); | |
661 | ||
662 | for (idx = 0; idx < git_array_size(patch->lines); ++idx) { | |
3b5f7954 | 663 | git_diff_line *line = git_array_get(patch->lines, idx); |
114f5a6c RB |
664 | if (!line) |
665 | continue; | |
666 | ||
667 | switch (line->origin) { | |
668 | case GIT_DIFF_LINE_CONTEXT: totals[0]++; break; | |
669 | case GIT_DIFF_LINE_ADDITION: totals[1]++; break; | |
670 | case GIT_DIFF_LINE_DELETION: totals[2]++; break; | |
671 | default: | |
672 | /* diff --stat and --numstat don't count EOFNL marks because | |
673 | * they will always be paired with a ADDITION or DELETION line. | |
674 | */ | |
675 | break; | |
676 | } | |
677 | } | |
678 | ||
679 | if (total_ctxt) | |
680 | *total_ctxt = totals[0]; | |
681 | if (total_adds) | |
682 | *total_adds = totals[1]; | |
683 | if (total_dels) | |
684 | *total_dels = totals[2]; | |
685 | ||
686 | return 0; | |
687 | } | |
688 | ||
689 | static int diff_error_outofrange(const char *thing) | |
690 | { | |
691 | giterr_set(GITERR_INVALID, "Diff patch %s index out of range", thing); | |
692 | return GIT_ENOTFOUND; | |
693 | } | |
694 | ||
3ff1d123 RB |
695 | int git_patch_get_hunk( |
696 | const git_diff_hunk **out, | |
114f5a6c | 697 | size_t *lines_in_hunk, |
3ff1d123 | 698 | git_patch *patch, |
114f5a6c RB |
699 | size_t hunk_idx) |
700 | { | |
701 | diff_patch_hunk *hunk; | |
702 | assert(patch); | |
703 | ||
704 | hunk = git_array_get(patch->hunks, hunk_idx); | |
705 | ||
706 | if (!hunk) { | |
3ff1d123 | 707 | if (out) *out = NULL; |
114f5a6c RB |
708 | if (lines_in_hunk) *lines_in_hunk = 0; |
709 | return diff_error_outofrange("hunk"); | |
710 | } | |
711 | ||
3ff1d123 | 712 | if (out) *out = &hunk->hunk; |
114f5a6c RB |
713 | if (lines_in_hunk) *lines_in_hunk = hunk->line_count; |
714 | return 0; | |
715 | } | |
716 | ||
7a28f268 | 717 | int git_patch_num_lines_in_hunk(const git_patch *patch, size_t hunk_idx) |
114f5a6c RB |
718 | { |
719 | diff_patch_hunk *hunk; | |
720 | assert(patch); | |
721 | ||
722 | if (!(hunk = git_array_get(patch->hunks, hunk_idx))) | |
723 | return diff_error_outofrange("hunk"); | |
724 | return (int)hunk->line_count; | |
725 | } | |
726 | ||
3ff1d123 | 727 | int git_patch_get_line_in_hunk( |
3b5f7954 | 728 | const git_diff_line **out, |
3ff1d123 | 729 | git_patch *patch, |
114f5a6c RB |
730 | size_t hunk_idx, |
731 | size_t line_of_hunk) | |
732 | { | |
733 | diff_patch_hunk *hunk; | |
3b5f7954 | 734 | git_diff_line *line; |
114f5a6c RB |
735 | |
736 | assert(patch); | |
737 | ||
738 | if (!(hunk = git_array_get(patch->hunks, hunk_idx))) { | |
3b5f7954 RB |
739 | if (out) *out = NULL; |
740 | return diff_error_outofrange("hunk"); | |
114f5a6c RB |
741 | } |
742 | ||
743 | if (line_of_hunk >= hunk->line_count || | |
744 | !(line = git_array_get( | |
745 | patch->lines, hunk->line_start + line_of_hunk))) { | |
3b5f7954 RB |
746 | if (out) *out = NULL; |
747 | return diff_error_outofrange("line"); | |
114f5a6c RB |
748 | } |
749 | ||
3b5f7954 | 750 | if (out) *out = line; |
114f5a6c | 751 | return 0; |
114f5a6c RB |
752 | } |
753 | ||
3ff1d123 RB |
754 | size_t git_patch_size( |
755 | git_patch *patch, | |
197b8966 RB |
756 | int include_context, |
757 | int include_hunk_headers, | |
758 | int include_file_headers) | |
b4a4cf24 RB |
759 | { |
760 | size_t out; | |
761 | ||
762 | assert(patch); | |
763 | ||
764 | out = patch->content_size; | |
765 | ||
766 | if (!include_context) | |
767 | out -= patch->context_size; | |
768 | ||
197b8966 RB |
769 | if (include_hunk_headers) |
770 | out += patch->header_size; | |
771 | ||
772 | if (include_file_headers) { | |
773 | git_buf file_header = GIT_BUF_INIT; | |
774 | ||
775 | if (git_diff_delta__format_file_header( | |
776 | &file_header, patch->delta, NULL, NULL, 0) < 0) | |
777 | giterr_clear(); | |
778 | else | |
779 | out += git_buf_len(&file_header); | |
780 | ||
781 | git_buf_free(&file_header); | |
782 | } | |
783 | ||
b4a4cf24 RB |
784 | return out; |
785 | } | |
786 | ||
3ff1d123 | 787 | git_diff *git_patch__diff(git_patch *patch) |
360f42f4 RB |
788 | { |
789 | return patch->diff; | |
790 | } | |
791 | ||
3ff1d123 | 792 | git_diff_driver *git_patch__driver(git_patch *patch) |
360f42f4 RB |
793 | { |
794 | /* ofile driver is representative for whole patch */ | |
795 | return patch->ofile.driver; | |
796 | } | |
797 | ||
3ff1d123 RB |
798 | void git_patch__old_data( |
799 | char **ptr, size_t *len, git_patch *patch) | |
360f42f4 RB |
800 | { |
801 | *ptr = patch->ofile.map.data; | |
802 | *len = patch->ofile.map.len; | |
803 | } | |
804 | ||
3ff1d123 RB |
805 | void git_patch__new_data( |
806 | char **ptr, size_t *len, git_patch *patch) | |
360f42f4 RB |
807 | { |
808 | *ptr = patch->nfile.map.data; | |
809 | *len = patch->nfile.map.len; | |
810 | } | |
811 | ||
3ff1d123 RB |
812 | int git_patch__invoke_callbacks( |
813 | git_patch *patch, | |
360f42f4 RB |
814 | git_diff_file_cb file_cb, |
815 | git_diff_hunk_cb hunk_cb, | |
3ff1d123 | 816 | git_diff_line_cb line_cb, |
360f42f4 RB |
817 | void *payload) |
818 | { | |
819 | int error = 0; | |
820 | uint32_t i, j; | |
821 | ||
822 | if (file_cb) | |
823 | error = file_cb(patch->delta, 0, payload); | |
824 | ||
825 | if (!hunk_cb && !line_cb) | |
826 | return error; | |
827 | ||
828 | for (i = 0; !error && i < git_array_size(patch->hunks); ++i) { | |
829 | diff_patch_hunk *h = git_array_get(patch->hunks, i); | |
830 | ||
bb6aafe8 JG |
831 | if (hunk_cb) |
832 | error = hunk_cb(patch->delta, &h->hunk, payload); | |
360f42f4 RB |
833 | |
834 | if (!line_cb) | |
835 | continue; | |
836 | ||
837 | for (j = 0; !error && j < h->line_count; ++j) { | |
3b5f7954 | 838 | git_diff_line *l = |
360f42f4 RB |
839 | git_array_get(patch->lines, h->line_start + j); |
840 | ||
3b5f7954 | 841 | error = line_cb(patch->delta, &h->hunk, l, payload); |
360f42f4 RB |
842 | } |
843 | } | |
844 | ||
845 | return error; | |
846 | } | |
847 | ||
114f5a6c RB |
848 | |
849 | static int diff_patch_file_cb( | |
850 | const git_diff_delta *delta, | |
851 | float progress, | |
852 | void *payload) | |
853 | { | |
f9c824c5 | 854 | GIT_UNUSED(delta); GIT_UNUSED(progress); GIT_UNUSED(payload); |
114f5a6c RB |
855 | return 0; |
856 | } | |
857 | ||
858 | static int diff_patch_hunk_cb( | |
859 | const git_diff_delta *delta, | |
3ff1d123 | 860 | const git_diff_hunk *hunk_, |
114f5a6c RB |
861 | void *payload) |
862 | { | |
3ff1d123 | 863 | git_patch *patch = payload; |
114f5a6c RB |
864 | diff_patch_hunk *hunk; |
865 | ||
866 | GIT_UNUSED(delta); | |
867 | ||
ef3374a8 | 868 | hunk = git_array_alloc(patch->hunks); |
25e0b157 | 869 | GITERR_CHECK_ALLOC(hunk); |
114f5a6c | 870 | |
3ff1d123 | 871 | memcpy(&hunk->hunk, hunk_, sizeof(hunk->hunk)); |
114f5a6c | 872 | |
3b5f7954 | 873 | patch->header_size += hunk_->header_len; |
197b8966 | 874 | |
114f5a6c RB |
875 | hunk->line_start = git_array_size(patch->lines); |
876 | hunk->line_count = 0; | |
877 | ||
114f5a6c RB |
878 | return 0; |
879 | } | |
880 | ||
881 | static int diff_patch_line_cb( | |
882 | const git_diff_delta *delta, | |
3ff1d123 | 883 | const git_diff_hunk *hunk_, |
3b5f7954 | 884 | const git_diff_line *line_, |
114f5a6c RB |
885 | void *payload) |
886 | { | |
3ff1d123 | 887 | git_patch *patch = payload; |
114f5a6c | 888 | diff_patch_hunk *hunk; |
3b5f7954 | 889 | git_diff_line *line; |
114f5a6c RB |
890 | |
891 | GIT_UNUSED(delta); | |
3ff1d123 | 892 | GIT_UNUSED(hunk_); |
114f5a6c RB |
893 | |
894 | hunk = git_array_last(patch->hunks); | |
96869a4e | 895 | assert(hunk); /* programmer error if no hunk is available */ |
114f5a6c | 896 | |
ef3374a8 | 897 | line = git_array_alloc(patch->lines); |
25e0b157 | 898 | GITERR_CHECK_ALLOC(line); |
114f5a6c | 899 | |
3b5f7954 | 900 | memcpy(line, line_, sizeof(*line)); |
114f5a6c | 901 | |
114f5a6c RB |
902 | /* do some bookkeeping so we can provide old/new line numbers */ |
903 | ||
3b5f7954 | 904 | patch->content_size += line->content_len; |
197b8966 | 905 | |
3b5f7954 RB |
906 | if (line->origin == GIT_DIFF_LINE_ADDITION || |
907 | line->origin == GIT_DIFF_LINE_DELETION) | |
197b8966 | 908 | patch->content_size += 1; |
3b5f7954 | 909 | else if (line->origin == GIT_DIFF_LINE_CONTEXT) { |
197b8966 | 910 | patch->content_size += 1; |
3b5f7954 RB |
911 | patch->context_size += line->content_len + 1; |
912 | } else if (line->origin == GIT_DIFF_LINE_CONTEXT_EOFNL) | |
913 | patch->context_size += line->content_len; | |
114f5a6c RB |
914 | |
915 | hunk->line_count++; | |
916 | ||
917 | return 0; | |
918 | } | |
919 | ||
920 | static void diff_output_init( | |
921 | git_diff_output *out, | |
922 | const git_diff_options *opts, | |
923 | git_diff_file_cb file_cb, | |
924 | git_diff_hunk_cb hunk_cb, | |
3ff1d123 | 925 | git_diff_line_cb data_cb, |
114f5a6c RB |
926 | void *payload) |
927 | { | |
928 | GIT_UNUSED(opts); | |
929 | ||
930 | memset(out, 0, sizeof(*out)); | |
931 | ||
932 | out->file_cb = file_cb; | |
933 | out->hunk_cb = hunk_cb; | |
934 | out->data_cb = data_cb; | |
935 | out->payload = payload; | |
936 | } | |
937 | ||
3ff1d123 | 938 | static void diff_output_to_patch(git_diff_output *out, git_patch *patch) |
114f5a6c RB |
939 | { |
940 | diff_output_init( | |
5dc98298 | 941 | out, NULL, |
114f5a6c RB |
942 | diff_patch_file_cb, diff_patch_hunk_cb, diff_patch_line_cb, patch); |
943 | } |