]>
Commit | Line | Data |
---|---|---|
9be638ec ET |
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 | */ | |
eae0bfdc | 7 | |
9be638ec | 8 | #include "diff_generate.h" |
eae0bfdc PP |
9 | |
10 | #include "diff.h" | |
b859faa6 | 11 | #include "patch_generate.h" |
22a2d3d5 | 12 | #include "futils.h" |
9be638ec ET |
13 | #include "config.h" |
14 | #include "attr_file.h" | |
15 | #include "filter.h" | |
16 | #include "pathspec.h" | |
17 | #include "index.h" | |
18 | #include "odb.h" | |
19 | #include "submodule.h" | |
20 | ||
21 | #define DIFF_FLAG_IS_SET(DIFF,FLAG) \ | |
22 | (((DIFF)->base.opts.flags & (FLAG)) != 0) | |
23 | #define DIFF_FLAG_ISNT_SET(DIFF,FLAG) \ | |
24 | (((DIFF)->base.opts.flags & (FLAG)) == 0) | |
25 | #define DIFF_FLAG_SET(DIFF,FLAG,VAL) (DIFF)->base.opts.flags = \ | |
26 | (VAL) ? ((DIFF)->base.opts.flags | (FLAG)) : \ | |
eae0bfdc | 27 | ((DIFF)->base.opts.flags & ~(FLAG)) |
9be638ec ET |
28 | |
29 | typedef struct { | |
30 | struct git_diff base; | |
31 | ||
32 | git_vector pathspec; | |
33 | ||
34 | uint32_t diffcaps; | |
35 | bool index_updated; | |
36 | } git_diff_generated; | |
37 | ||
38 | static git_diff_delta *diff_delta__alloc( | |
39 | git_diff_generated *diff, | |
40 | git_delta_t status, | |
41 | const char *path) | |
42 | { | |
43 | git_diff_delta *delta = git__calloc(1, sizeof(git_diff_delta)); | |
44 | if (!delta) | |
45 | return NULL; | |
46 | ||
47 | delta->old_file.path = git_pool_strdup(&diff->base.pool, path); | |
48 | if (delta->old_file.path == NULL) { | |
49 | git__free(delta); | |
50 | return NULL; | |
51 | } | |
52 | ||
53 | delta->new_file.path = delta->old_file.path; | |
54 | ||
55 | if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) { | |
56 | switch (status) { | |
57 | case GIT_DELTA_ADDED: status = GIT_DELTA_DELETED; break; | |
58 | case GIT_DELTA_DELETED: status = GIT_DELTA_ADDED; break; | |
59 | default: break; /* leave other status values alone */ | |
60 | } | |
61 | } | |
62 | delta->status = status; | |
63 | ||
64 | return delta; | |
65 | } | |
66 | ||
67 | static int diff_insert_delta( | |
68 | git_diff_generated *diff, | |
69 | git_diff_delta *delta, | |
70 | const char *matched_pathspec) | |
71 | { | |
72 | int error = 0; | |
73 | ||
74 | if (diff->base.opts.notify_cb) { | |
75 | error = diff->base.opts.notify_cb( | |
76 | &diff->base, delta, matched_pathspec, diff->base.opts.payload); | |
77 | ||
78 | if (error) { | |
79 | git__free(delta); | |
80 | ||
81 | if (error > 0) /* positive value means to skip this delta */ | |
82 | return 0; | |
83 | else /* negative value means to cancel diff */ | |
ac3d33df | 84 | return git_error_set_after_callback_function(error, "git_diff"); |
9be638ec ET |
85 | } |
86 | } | |
87 | ||
88 | if ((error = git_vector_insert(&diff->base.deltas, delta)) < 0) | |
89 | git__free(delta); | |
90 | ||
91 | return error; | |
92 | } | |
93 | ||
94 | static bool diff_pathspec_match( | |
95 | const char **matched_pathspec, | |
96 | git_diff_generated *diff, | |
97 | const git_index_entry *entry) | |
98 | { | |
99 | bool disable_pathspec_match = | |
100 | DIFF_FLAG_IS_SET(diff, GIT_DIFF_DISABLE_PATHSPEC_MATCH); | |
101 | ||
102 | /* If we're disabling fnmatch, then the iterator has already applied | |
103 | * the filters to the files for us and we don't have to do anything. | |
104 | * However, this only applies to *files* - the iterator will include | |
105 | * directories that we need to recurse into when not autoexpanding, | |
106 | * so we still need to apply the pathspec match to directories. | |
107 | */ | |
108 | if ((S_ISLNK(entry->mode) || S_ISREG(entry->mode)) && | |
109 | disable_pathspec_match) { | |
110 | *matched_pathspec = entry->path; | |
111 | return true; | |
112 | } | |
113 | ||
114 | return git_pathspec__match( | |
115 | &diff->pathspec, entry->path, disable_pathspec_match, | |
116 | DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE), | |
117 | matched_pathspec, NULL); | |
118 | } | |
119 | ||
120 | static int diff_delta__from_one( | |
121 | git_diff_generated *diff, | |
122 | git_delta_t status, | |
123 | const git_index_entry *oitem, | |
124 | const git_index_entry *nitem) | |
125 | { | |
126 | const git_index_entry *entry = nitem; | |
127 | bool has_old = false; | |
128 | git_diff_delta *delta; | |
129 | const char *matched_pathspec; | |
130 | ||
131 | assert((oitem != NULL) ^ (nitem != NULL)); | |
132 | ||
133 | if (oitem) { | |
134 | entry = oitem; | |
135 | has_old = true; | |
136 | } | |
137 | ||
138 | if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) | |
139 | has_old = !has_old; | |
140 | ||
ac3d33df | 141 | if ((entry->flags & GIT_INDEX_ENTRY_VALID) != 0) |
9be638ec ET |
142 | return 0; |
143 | ||
144 | if (status == GIT_DELTA_IGNORED && | |
145 | DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_IGNORED)) | |
146 | return 0; | |
147 | ||
148 | if (status == GIT_DELTA_UNTRACKED && | |
149 | DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_UNTRACKED)) | |
150 | return 0; | |
151 | ||
152 | if (status == GIT_DELTA_UNREADABLE && | |
153 | DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_UNREADABLE)) | |
154 | return 0; | |
155 | ||
156 | if (!diff_pathspec_match(&matched_pathspec, diff, entry)) | |
157 | return 0; | |
158 | ||
159 | delta = diff_delta__alloc(diff, status, entry->path); | |
ac3d33df | 160 | GIT_ERROR_CHECK_ALLOC(delta); |
9be638ec ET |
161 | |
162 | /* This fn is just for single-sided diffs */ | |
163 | assert(status != GIT_DELTA_MODIFIED); | |
164 | delta->nfiles = 1; | |
165 | ||
166 | if (has_old) { | |
167 | delta->old_file.mode = entry->mode; | |
168 | delta->old_file.size = entry->file_size; | |
169 | delta->old_file.flags |= GIT_DIFF_FLAG_EXISTS; | |
170 | git_oid_cpy(&delta->old_file.id, &entry->id); | |
171 | delta->old_file.id_abbrev = GIT_OID_HEXSZ; | |
172 | } else /* ADDED, IGNORED, UNTRACKED */ { | |
173 | delta->new_file.mode = entry->mode; | |
174 | delta->new_file.size = entry->file_size; | |
175 | delta->new_file.flags |= GIT_DIFF_FLAG_EXISTS; | |
176 | git_oid_cpy(&delta->new_file.id, &entry->id); | |
177 | delta->new_file.id_abbrev = GIT_OID_HEXSZ; | |
178 | } | |
179 | ||
180 | delta->old_file.flags |= GIT_DIFF_FLAG_VALID_ID; | |
181 | ||
22a2d3d5 | 182 | if (has_old || !git_oid_is_zero(&delta->new_file.id)) |
9be638ec ET |
183 | delta->new_file.flags |= GIT_DIFF_FLAG_VALID_ID; |
184 | ||
185 | return diff_insert_delta(diff, delta, matched_pathspec); | |
186 | } | |
187 | ||
188 | static int diff_delta__from_two( | |
189 | git_diff_generated *diff, | |
190 | git_delta_t status, | |
191 | const git_index_entry *old_entry, | |
192 | uint32_t old_mode, | |
193 | const git_index_entry *new_entry, | |
194 | uint32_t new_mode, | |
195 | const git_oid *new_id, | |
196 | const char *matched_pathspec) | |
197 | { | |
198 | const git_oid *old_id = &old_entry->id; | |
199 | git_diff_delta *delta; | |
200 | const char *canonical_path = old_entry->path; | |
201 | ||
202 | if (status == GIT_DELTA_UNMODIFIED && | |
203 | DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_UNMODIFIED)) | |
204 | return 0; | |
205 | ||
206 | if (!new_id) | |
207 | new_id = &new_entry->id; | |
208 | ||
209 | if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) { | |
210 | uint32_t temp_mode = old_mode; | |
211 | const git_index_entry *temp_entry = old_entry; | |
212 | const git_oid *temp_id = old_id; | |
213 | ||
214 | old_entry = new_entry; | |
215 | new_entry = temp_entry; | |
216 | old_mode = new_mode; | |
217 | new_mode = temp_mode; | |
218 | old_id = new_id; | |
219 | new_id = temp_id; | |
220 | } | |
221 | ||
222 | delta = diff_delta__alloc(diff, status, canonical_path); | |
ac3d33df | 223 | GIT_ERROR_CHECK_ALLOC(delta); |
9be638ec ET |
224 | delta->nfiles = 2; |
225 | ||
226 | if (!git_index_entry_is_conflict(old_entry)) { | |
227 | delta->old_file.size = old_entry->file_size; | |
228 | delta->old_file.mode = old_mode; | |
229 | git_oid_cpy(&delta->old_file.id, old_id); | |
230 | delta->old_file.id_abbrev = GIT_OID_HEXSZ; | |
231 | delta->old_file.flags |= GIT_DIFF_FLAG_VALID_ID | | |
232 | GIT_DIFF_FLAG_EXISTS; | |
233 | } | |
234 | ||
235 | if (!git_index_entry_is_conflict(new_entry)) { | |
236 | git_oid_cpy(&delta->new_file.id, new_id); | |
237 | delta->new_file.id_abbrev = GIT_OID_HEXSZ; | |
238 | delta->new_file.size = new_entry->file_size; | |
239 | delta->new_file.mode = new_mode; | |
240 | delta->old_file.flags |= GIT_DIFF_FLAG_EXISTS; | |
241 | delta->new_file.flags |= GIT_DIFF_FLAG_EXISTS; | |
242 | ||
22a2d3d5 | 243 | if (!git_oid_is_zero(&new_entry->id)) |
9be638ec ET |
244 | delta->new_file.flags |= GIT_DIFF_FLAG_VALID_ID; |
245 | } | |
246 | ||
247 | return diff_insert_delta(diff, delta, matched_pathspec); | |
248 | } | |
249 | ||
250 | static git_diff_delta *diff_delta__last_for_item( | |
251 | git_diff_generated *diff, | |
252 | const git_index_entry *item) | |
253 | { | |
254 | git_diff_delta *delta = git_vector_last(&diff->base.deltas); | |
255 | if (!delta) | |
256 | return NULL; | |
257 | ||
258 | switch (delta->status) { | |
259 | case GIT_DELTA_UNMODIFIED: | |
260 | case GIT_DELTA_DELETED: | |
261 | if (git_oid__cmp(&delta->old_file.id, &item->id) == 0) | |
262 | return delta; | |
263 | break; | |
264 | case GIT_DELTA_ADDED: | |
265 | if (git_oid__cmp(&delta->new_file.id, &item->id) == 0) | |
266 | return delta; | |
267 | break; | |
268 | case GIT_DELTA_UNREADABLE: | |
269 | case GIT_DELTA_UNTRACKED: | |
270 | if (diff->base.strcomp(delta->new_file.path, item->path) == 0 && | |
271 | git_oid__cmp(&delta->new_file.id, &item->id) == 0) | |
272 | return delta; | |
273 | break; | |
274 | case GIT_DELTA_MODIFIED: | |
275 | if (git_oid__cmp(&delta->old_file.id, &item->id) == 0 || | |
ac3d33df JK |
276 | (delta->new_file.mode == item->mode && |
277 | git_oid__cmp(&delta->new_file.id, &item->id) == 0)) | |
9be638ec ET |
278 | return delta; |
279 | break; | |
280 | default: | |
281 | break; | |
282 | } | |
283 | ||
284 | return NULL; | |
285 | } | |
286 | ||
287 | static char *diff_strdup_prefix(git_pool *pool, const char *prefix) | |
288 | { | |
289 | size_t len = strlen(prefix); | |
290 | ||
291 | /* append '/' at end if needed */ | |
292 | if (len > 0 && prefix[len - 1] != '/') | |
293 | return git_pool_strcat(pool, prefix, "/"); | |
294 | else | |
295 | return git_pool_strndup(pool, prefix, len + 1); | |
296 | } | |
297 | ||
298 | GIT_INLINE(const char *) diff_delta__i2w_path(const git_diff_delta *delta) | |
299 | { | |
300 | return delta->old_file.path ? | |
301 | delta->old_file.path : delta->new_file.path; | |
302 | } | |
303 | ||
22a2d3d5 | 304 | static int diff_delta_i2w_cmp(const void *a, const void *b) |
9be638ec ET |
305 | { |
306 | const git_diff_delta *da = a, *db = b; | |
307 | int val = strcmp(diff_delta__i2w_path(da), diff_delta__i2w_path(db)); | |
308 | return val ? val : ((int)da->status - (int)db->status); | |
309 | } | |
310 | ||
22a2d3d5 | 311 | static int diff_delta_i2w_casecmp(const void *a, const void *b) |
9be638ec ET |
312 | { |
313 | const git_diff_delta *da = a, *db = b; | |
314 | int val = strcasecmp(diff_delta__i2w_path(da), diff_delta__i2w_path(db)); | |
315 | return val ? val : ((int)da->status - (int)db->status); | |
316 | } | |
317 | ||
318 | bool git_diff_delta__should_skip( | |
319 | const git_diff_options *opts, const git_diff_delta *delta) | |
320 | { | |
321 | uint32_t flags = opts ? opts->flags : 0; | |
322 | ||
323 | if (delta->status == GIT_DELTA_UNMODIFIED && | |
324 | (flags & GIT_DIFF_INCLUDE_UNMODIFIED) == 0) | |
325 | return true; | |
326 | ||
327 | if (delta->status == GIT_DELTA_IGNORED && | |
328 | (flags & GIT_DIFF_INCLUDE_IGNORED) == 0) | |
329 | return true; | |
330 | ||
331 | if (delta->status == GIT_DELTA_UNTRACKED && | |
332 | (flags & GIT_DIFF_INCLUDE_UNTRACKED) == 0) | |
333 | return true; | |
334 | ||
335 | if (delta->status == GIT_DELTA_UNREADABLE && | |
336 | (flags & GIT_DIFF_INCLUDE_UNREADABLE) == 0) | |
337 | return true; | |
338 | ||
339 | return false; | |
340 | } | |
341 | ||
342 | ||
343 | static const char *diff_mnemonic_prefix( | |
22a2d3d5 | 344 | git_iterator_t type, bool left_side) |
9be638ec ET |
345 | { |
346 | const char *pfx = ""; | |
347 | ||
348 | switch (type) { | |
22a2d3d5 UG |
349 | case GIT_ITERATOR_EMPTY: pfx = "c"; break; |
350 | case GIT_ITERATOR_TREE: pfx = "c"; break; | |
351 | case GIT_ITERATOR_INDEX: pfx = "i"; break; | |
352 | case GIT_ITERATOR_WORKDIR: pfx = "w"; break; | |
353 | case GIT_ITERATOR_FS: pfx = left_side ? "1" : "2"; break; | |
9be638ec ET |
354 | default: break; |
355 | } | |
356 | ||
357 | /* note: without a deeper look at pathspecs, there is no easy way | |
358 | * to get the (o)bject / (w)ork tree mnemonics working... | |
359 | */ | |
360 | ||
361 | return pfx; | |
362 | } | |
363 | ||
22a2d3d5 | 364 | static void diff_set_ignore_case(git_diff *diff, bool ignore_case) |
9be638ec ET |
365 | { |
366 | if (!ignore_case) { | |
367 | diff->opts.flags &= ~GIT_DIFF_IGNORE_CASE; | |
368 | ||
369 | diff->strcomp = git__strcmp; | |
370 | diff->strncomp = git__strncmp; | |
371 | diff->pfxcomp = git__prefixcmp; | |
7166bb16 | 372 | diff->entrycomp = git_diff__entry_cmp; |
9be638ec ET |
373 | |
374 | git_vector_set_cmp(&diff->deltas, git_diff_delta__cmp); | |
375 | } else { | |
376 | diff->opts.flags |= GIT_DIFF_IGNORE_CASE; | |
377 | ||
378 | diff->strcomp = git__strcasecmp; | |
379 | diff->strncomp = git__strncasecmp; | |
380 | diff->pfxcomp = git__prefixcmp_icase; | |
7166bb16 | 381 | diff->entrycomp = git_diff__entry_icmp; |
9be638ec ET |
382 | |
383 | git_vector_set_cmp(&diff->deltas, git_diff_delta__casecmp); | |
384 | } | |
385 | ||
386 | git_vector_sort(&diff->deltas); | |
387 | } | |
388 | ||
389 | static void diff_generated_free(git_diff *d) | |
390 | { | |
391 | git_diff_generated *diff = (git_diff_generated *)d; | |
392 | ||
eae0bfdc | 393 | git_attr_session__free(&diff->base.attrsession); |
9be638ec ET |
394 | git_vector_free_deep(&diff->base.deltas); |
395 | ||
396 | git_pathspec__vfree(&diff->pathspec); | |
397 | git_pool_clear(&diff->base.pool); | |
398 | ||
399 | git__memzero(diff, sizeof(*diff)); | |
400 | git__free(diff); | |
401 | } | |
402 | ||
403 | static git_diff_generated *diff_generated_alloc( | |
404 | git_repository *repo, | |
405 | git_iterator *old_iter, | |
406 | git_iterator *new_iter) | |
407 | { | |
408 | git_diff_generated *diff; | |
409 | git_diff_options dflt = GIT_DIFF_OPTIONS_INIT; | |
410 | ||
411 | assert(repo && old_iter && new_iter); | |
412 | ||
413 | if ((diff = git__calloc(1, sizeof(git_diff_generated))) == NULL) | |
414 | return NULL; | |
415 | ||
eae0bfdc | 416 | GIT_REFCOUNT_INC(&diff->base); |
9be638ec ET |
417 | diff->base.type = GIT_DIFF_TYPE_GENERATED; |
418 | diff->base.repo = repo; | |
419 | diff->base.old_src = old_iter->type; | |
420 | diff->base.new_src = new_iter->type; | |
b859faa6 | 421 | diff->base.patch_fn = git_patch_generated_from_diff; |
9be638ec | 422 | diff->base.free_fn = diff_generated_free; |
eae0bfdc | 423 | git_attr_session__init(&diff->base.attrsession, repo); |
9be638ec ET |
424 | memcpy(&diff->base.opts, &dflt, sizeof(git_diff_options)); |
425 | ||
22a2d3d5 UG |
426 | if (git_pool_init(&diff->base.pool, 1) < 0 || |
427 | git_vector_init(&diff->base.deltas, 0, git_diff_delta__cmp) < 0) { | |
9be638ec ET |
428 | git_diff_free(&diff->base); |
429 | return NULL; | |
430 | } | |
431 | ||
432 | /* Use case-insensitive compare if either iterator has | |
433 | * the ignore_case bit set */ | |
22a2d3d5 | 434 | diff_set_ignore_case( |
9be638ec ET |
435 | &diff->base, |
436 | git_iterator_ignore_case(old_iter) || | |
437 | git_iterator_ignore_case(new_iter)); | |
438 | ||
439 | return diff; | |
440 | } | |
441 | ||
442 | static int diff_generated_apply_options( | |
443 | git_diff_generated *diff, | |
444 | const git_diff_options *opts) | |
445 | { | |
446 | git_config *cfg = NULL; | |
447 | git_repository *repo = diff->base.repo; | |
448 | git_pool *pool = &diff->base.pool; | |
449 | int val; | |
450 | ||
451 | if (opts) { | |
452 | /* copy user options (except case sensitivity info from iterators) */ | |
453 | bool icase = DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE); | |
454 | memcpy(&diff->base.opts, opts, sizeof(diff->base.opts)); | |
455 | DIFF_FLAG_SET(diff, GIT_DIFF_IGNORE_CASE, icase); | |
456 | ||
457 | /* initialize pathspec from options */ | |
458 | if (git_pathspec__vinit(&diff->pathspec, &opts->pathspec, pool) < 0) | |
459 | return -1; | |
460 | } | |
461 | ||
462 | /* flag INCLUDE_TYPECHANGE_TREES implies INCLUDE_TYPECHANGE */ | |
463 | if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE_TREES)) | |
464 | diff->base.opts.flags |= GIT_DIFF_INCLUDE_TYPECHANGE; | |
465 | ||
466 | /* flag INCLUDE_UNTRACKED_CONTENT implies INCLUDE_UNTRACKED */ | |
467 | if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_SHOW_UNTRACKED_CONTENT)) | |
468 | diff->base.opts.flags |= GIT_DIFF_INCLUDE_UNTRACKED; | |
469 | ||
470 | /* load config values that affect diff behavior */ | |
471 | if ((val = git_repository_config_snapshot(&cfg, repo)) < 0) | |
472 | return val; | |
473 | ||
22a2d3d5 | 474 | if (!git_config__configmap_lookup(&val, cfg, GIT_CONFIGMAP_SYMLINKS) && val) |
9be638ec ET |
475 | diff->diffcaps |= GIT_DIFFCAPS_HAS_SYMLINKS; |
476 | ||
22a2d3d5 | 477 | if (!git_config__configmap_lookup(&val, cfg, GIT_CONFIGMAP_IGNORESTAT) && val) |
9be638ec ET |
478 | diff->diffcaps |= GIT_DIFFCAPS_IGNORE_STAT; |
479 | ||
480 | if ((diff->base.opts.flags & GIT_DIFF_IGNORE_FILEMODE) == 0 && | |
22a2d3d5 | 481 | !git_config__configmap_lookup(&val, cfg, GIT_CONFIGMAP_FILEMODE) && val) |
9be638ec ET |
482 | diff->diffcaps |= GIT_DIFFCAPS_TRUST_MODE_BITS; |
483 | ||
22a2d3d5 | 484 | if (!git_config__configmap_lookup(&val, cfg, GIT_CONFIGMAP_TRUSTCTIME) && val) |
9be638ec ET |
485 | diff->diffcaps |= GIT_DIFFCAPS_TRUST_CTIME; |
486 | ||
487 | /* Don't set GIT_DIFFCAPS_USE_DEV - compile time option in core git */ | |
488 | ||
489 | /* If not given explicit `opts`, check `diff.xyz` configs */ | |
490 | if (!opts) { | |
491 | int context = git_config__get_int_force(cfg, "diff.context", 3); | |
492 | diff->base.opts.context_lines = context >= 0 ? (uint32_t)context : 3; | |
493 | ||
494 | /* add other defaults here */ | |
495 | } | |
496 | ||
497 | /* Reverse src info if diff is reversed */ | |
498 | if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) { | |
22a2d3d5 | 499 | git_iterator_t tmp_src = diff->base.old_src; |
9be638ec ET |
500 | diff->base.old_src = diff->base.new_src; |
501 | diff->base.new_src = tmp_src; | |
502 | } | |
503 | ||
504 | /* Unset UPDATE_INDEX unless diffing workdir and index */ | |
505 | if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_UPDATE_INDEX) && | |
22a2d3d5 UG |
506 | (!(diff->base.old_src == GIT_ITERATOR_WORKDIR || |
507 | diff->base.new_src == GIT_ITERATOR_WORKDIR) || | |
508 | !(diff->base.old_src == GIT_ITERATOR_INDEX || | |
509 | diff->base.new_src == GIT_ITERATOR_INDEX))) | |
9be638ec ET |
510 | diff->base.opts.flags &= ~GIT_DIFF_UPDATE_INDEX; |
511 | ||
512 | /* if ignore_submodules not explicitly set, check diff config */ | |
513 | if (diff->base.opts.ignore_submodules <= 0) { | |
514 | git_config_entry *entry; | |
515 | git_config__lookup_entry(&entry, cfg, "diff.ignoresubmodules", true); | |
516 | ||
517 | if (entry && git_submodule_parse_ignore( | |
518 | &diff->base.opts.ignore_submodules, entry->value) < 0) | |
ac3d33df | 519 | git_error_clear(); |
9be638ec ET |
520 | git_config_entry_free(entry); |
521 | } | |
522 | ||
523 | /* if either prefix is not set, figure out appropriate value */ | |
524 | if (!diff->base.opts.old_prefix || !diff->base.opts.new_prefix) { | |
525 | const char *use_old = DIFF_OLD_PREFIX_DEFAULT; | |
526 | const char *use_new = DIFF_NEW_PREFIX_DEFAULT; | |
527 | ||
528 | if (git_config__get_bool_force(cfg, "diff.noprefix", 0)) | |
529 | use_old = use_new = ""; | |
530 | else if (git_config__get_bool_force(cfg, "diff.mnemonicprefix", 0)) { | |
531 | use_old = diff_mnemonic_prefix(diff->base.old_src, true); | |
532 | use_new = diff_mnemonic_prefix(diff->base.new_src, false); | |
533 | } | |
534 | ||
535 | if (!diff->base.opts.old_prefix) | |
536 | diff->base.opts.old_prefix = use_old; | |
537 | if (!diff->base.opts.new_prefix) | |
538 | diff->base.opts.new_prefix = use_new; | |
539 | } | |
540 | ||
541 | /* strdup prefix from pool so we're not dependent on external data */ | |
542 | diff->base.opts.old_prefix = diff_strdup_prefix(pool, diff->base.opts.old_prefix); | |
543 | diff->base.opts.new_prefix = diff_strdup_prefix(pool, diff->base.opts.new_prefix); | |
544 | ||
545 | if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) { | |
546 | const char *tmp_prefix = diff->base.opts.old_prefix; | |
547 | diff->base.opts.old_prefix = diff->base.opts.new_prefix; | |
548 | diff->base.opts.new_prefix = tmp_prefix; | |
549 | } | |
550 | ||
551 | git_config_free(cfg); | |
552 | ||
553 | /* check strdup results for error */ | |
554 | return (!diff->base.opts.old_prefix || !diff->base.opts.new_prefix) ? -1 : 0; | |
555 | } | |
556 | ||
557 | int git_diff__oid_for_file( | |
558 | git_oid *out, | |
559 | git_diff *diff, | |
560 | const char *path, | |
561 | uint16_t mode, | |
22a2d3d5 | 562 | git_object_size_t size) |
9be638ec ET |
563 | { |
564 | git_index_entry entry; | |
565 | ||
22a2d3d5 | 566 | if (size > UINT32_MAX) { |
ac3d33df JK |
567 | git_error_set(GIT_ERROR_NOMEMORY, "file size overflow (for 32-bits) on '%s'", path); |
568 | return -1; | |
569 | } | |
570 | ||
9be638ec ET |
571 | memset(&entry, 0, sizeof(entry)); |
572 | entry.mode = mode; | |
ac3d33df | 573 | entry.file_size = (uint32_t)size; |
9be638ec ET |
574 | entry.path = (char *)path; |
575 | ||
576 | return git_diff__oid_for_entry(out, diff, &entry, mode, NULL); | |
577 | } | |
578 | ||
579 | int git_diff__oid_for_entry( | |
580 | git_oid *out, | |
581 | git_diff *d, | |
582 | const git_index_entry *src, | |
583 | uint16_t mode, | |
584 | const git_oid *update_match) | |
585 | { | |
586 | git_diff_generated *diff; | |
587 | git_buf full_path = GIT_BUF_INIT; | |
588 | git_index_entry entry = *src; | |
589 | git_filter_list *fl = NULL; | |
590 | int error = 0; | |
591 | ||
592 | assert(d->type == GIT_DIFF_TYPE_GENERATED); | |
593 | diff = (git_diff_generated *)d; | |
594 | ||
595 | memset(out, 0, sizeof(*out)); | |
596 | ||
597 | if (git_buf_joinpath(&full_path, | |
598 | git_repository_workdir(diff->base.repo), entry.path) < 0) | |
599 | return -1; | |
600 | ||
601 | if (!mode) { | |
602 | struct stat st; | |
603 | ||
604 | diff->base.perf.stat_calls++; | |
605 | ||
606 | if (p_stat(full_path.ptr, &st) < 0) { | |
607 | error = git_path_set_error(errno, entry.path, "stat"); | |
ac3d33df | 608 | git_buf_dispose(&full_path); |
9be638ec ET |
609 | return error; |
610 | } | |
611 | ||
612 | git_index_entry__init_from_stat(&entry, | |
613 | &st, (diff->diffcaps & GIT_DIFFCAPS_TRUST_MODE_BITS) != 0); | |
614 | } | |
615 | ||
616 | /* calculate OID for file if possible */ | |
617 | if (S_ISGITLINK(mode)) { | |
618 | git_submodule *sm; | |
619 | ||
620 | if (!git_submodule_lookup(&sm, diff->base.repo, entry.path)) { | |
621 | const git_oid *sm_oid = git_submodule_wd_id(sm); | |
622 | if (sm_oid) | |
623 | git_oid_cpy(out, sm_oid); | |
624 | git_submodule_free(sm); | |
625 | } else { | |
626 | /* if submodule lookup failed probably just in an intermediate | |
627 | * state where some init hasn't happened, so ignore the error | |
628 | */ | |
ac3d33df | 629 | git_error_clear(); |
9be638ec ET |
630 | } |
631 | } else if (S_ISLNK(mode)) { | |
632 | error = git_odb__hashlink(out, full_path.ptr); | |
633 | diff->base.perf.oid_calculations++; | |
634 | } else if (!git__is_sizet(entry.file_size)) { | |
ac3d33df | 635 | git_error_set(GIT_ERROR_NOMEMORY, "file size overflow (for 32-bits) on '%s'", |
9be638ec ET |
636 | entry.path); |
637 | error = -1; | |
638 | } else if (!(error = git_filter_list_load(&fl, | |
639 | diff->base.repo, NULL, entry.path, | |
640 | GIT_FILTER_TO_ODB, GIT_FILTER_ALLOW_UNSAFE))) | |
641 | { | |
642 | int fd = git_futils_open_ro(full_path.ptr); | |
643 | if (fd < 0) | |
644 | error = fd; | |
645 | else { | |
646 | error = git_odb__hashfd_filtered( | |
ac3d33df | 647 | out, fd, (size_t)entry.file_size, GIT_OBJECT_BLOB, fl); |
9be638ec ET |
648 | p_close(fd); |
649 | diff->base.perf.oid_calculations++; | |
650 | } | |
651 | ||
652 | git_filter_list_free(fl); | |
653 | } | |
654 | ||
655 | /* update index for entry if requested */ | |
656 | if (!error && update_match && git_oid_equal(out, update_match)) { | |
657 | git_index *idx; | |
658 | git_index_entry updated_entry; | |
659 | ||
660 | memcpy(&updated_entry, &entry, sizeof(git_index_entry)); | |
661 | updated_entry.mode = mode; | |
662 | git_oid_cpy(&updated_entry.id, out); | |
663 | ||
664 | if (!(error = git_repository_index__weakptr(&idx, | |
665 | diff->base.repo))) { | |
666 | error = git_index_add(idx, &updated_entry); | |
667 | diff->index_updated = true; | |
668 | } | |
669 | } | |
670 | ||
ac3d33df | 671 | git_buf_dispose(&full_path); |
9be638ec ET |
672 | return error; |
673 | } | |
674 | ||
675 | typedef struct { | |
676 | git_repository *repo; | |
677 | git_iterator *old_iter; | |
678 | git_iterator *new_iter; | |
679 | const git_index_entry *oitem; | |
680 | const git_index_entry *nitem; | |
681 | } diff_in_progress; | |
682 | ||
683 | #define MODE_BITS_MASK 0000777 | |
684 | ||
685 | static int maybe_modified_submodule( | |
686 | git_delta_t *status, | |
687 | git_oid *found_oid, | |
688 | git_diff_generated *diff, | |
689 | diff_in_progress *info) | |
690 | { | |
691 | int error = 0; | |
692 | git_submodule *sub; | |
693 | unsigned int sm_status = 0; | |
694 | git_submodule_ignore_t ign = diff->base.opts.ignore_submodules; | |
695 | ||
696 | *status = GIT_DELTA_UNMODIFIED; | |
697 | ||
698 | if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_SUBMODULES) || | |
699 | ign == GIT_SUBMODULE_IGNORE_ALL) | |
700 | return 0; | |
701 | ||
702 | if ((error = git_submodule_lookup( | |
703 | &sub, diff->base.repo, info->nitem->path)) < 0) { | |
704 | ||
705 | /* GIT_EEXISTS means dir with .git in it was found - ignore it */ | |
706 | if (error == GIT_EEXISTS) { | |
ac3d33df | 707 | git_error_clear(); |
9be638ec ET |
708 | error = 0; |
709 | } | |
710 | return error; | |
711 | } | |
712 | ||
713 | if (ign <= 0 && git_submodule_ignore(sub) == GIT_SUBMODULE_IGNORE_ALL) | |
714 | /* ignore it */; | |
715 | else if ((error = git_submodule__status( | |
716 | &sm_status, NULL, NULL, found_oid, sub, ign)) < 0) | |
717 | /* return error below */; | |
718 | ||
719 | /* check IS_WD_UNMODIFIED because this case is only used | |
720 | * when the new side of the diff is the working directory | |
721 | */ | |
722 | else if (!GIT_SUBMODULE_STATUS_IS_WD_UNMODIFIED(sm_status)) | |
723 | *status = GIT_DELTA_MODIFIED; | |
724 | ||
725 | /* now that we have a HEAD OID, check if HEAD moved */ | |
726 | else if ((sm_status & GIT_SUBMODULE_STATUS_IN_WD) != 0 && | |
727 | !git_oid_equal(&info->oitem->id, found_oid)) | |
728 | *status = GIT_DELTA_MODIFIED; | |
729 | ||
730 | git_submodule_free(sub); | |
731 | return error; | |
732 | } | |
733 | ||
734 | static int maybe_modified( | |
735 | git_diff_generated *diff, | |
736 | diff_in_progress *info) | |
737 | { | |
738 | git_oid noid; | |
739 | git_delta_t status = GIT_DELTA_MODIFIED; | |
740 | const git_index_entry *oitem = info->oitem; | |
741 | const git_index_entry *nitem = info->nitem; | |
742 | unsigned int omode = oitem->mode; | |
743 | unsigned int nmode = nitem->mode; | |
22a2d3d5 | 744 | bool new_is_workdir = (info->new_iter->type == GIT_ITERATOR_WORKDIR); |
9be638ec ET |
745 | bool modified_uncertain = false; |
746 | const char *matched_pathspec; | |
747 | int error = 0; | |
748 | ||
749 | if (!diff_pathspec_match(&matched_pathspec, diff, oitem)) | |
750 | return 0; | |
751 | ||
752 | memset(&noid, 0, sizeof(noid)); | |
753 | ||
754 | /* on platforms with no symlinks, preserve mode of existing symlinks */ | |
755 | if (S_ISLNK(omode) && S_ISREG(nmode) && new_is_workdir && | |
756 | !(diff->diffcaps & GIT_DIFFCAPS_HAS_SYMLINKS)) | |
757 | nmode = omode; | |
758 | ||
759 | /* on platforms with no execmode, just preserve old mode */ | |
760 | if (!(diff->diffcaps & GIT_DIFFCAPS_TRUST_MODE_BITS) && | |
761 | (nmode & MODE_BITS_MASK) != (omode & MODE_BITS_MASK) && | |
762 | new_is_workdir) | |
763 | nmode = (nmode & ~MODE_BITS_MASK) | (omode & MODE_BITS_MASK); | |
764 | ||
765 | /* if one side is a conflict, mark the whole delta as conflicted */ | |
766 | if (git_index_entry_is_conflict(oitem) || | |
767 | git_index_entry_is_conflict(nitem)) { | |
768 | status = GIT_DELTA_CONFLICTED; | |
769 | ||
770 | /* support "assume unchanged" (poorly, b/c we still stat everything) */ | |
ac3d33df | 771 | } else if ((oitem->flags & GIT_INDEX_ENTRY_VALID) != 0) { |
9be638ec ET |
772 | status = GIT_DELTA_UNMODIFIED; |
773 | ||
774 | /* support "skip worktree" index bit */ | |
ac3d33df | 775 | } else if ((oitem->flags_extended & GIT_INDEX_ENTRY_SKIP_WORKTREE) != 0) { |
9be638ec ET |
776 | status = GIT_DELTA_UNMODIFIED; |
777 | ||
778 | /* if basic type of file changed, then split into delete and add */ | |
779 | } else if (GIT_MODE_TYPE(omode) != GIT_MODE_TYPE(nmode)) { | |
780 | if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE)) { | |
781 | status = GIT_DELTA_TYPECHANGE; | |
782 | } | |
783 | ||
784 | else if (nmode == GIT_FILEMODE_UNREADABLE) { | |
785 | if (!(error = diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem, NULL))) | |
786 | error = diff_delta__from_one(diff, GIT_DELTA_UNREADABLE, NULL, nitem); | |
787 | return error; | |
788 | } | |
789 | ||
790 | else { | |
791 | if (!(error = diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem, NULL))) | |
792 | error = diff_delta__from_one(diff, GIT_DELTA_ADDED, NULL, nitem); | |
793 | return error; | |
794 | } | |
795 | ||
796 | /* if oids and modes match (and are valid), then file is unmodified */ | |
797 | } else if (git_oid_equal(&oitem->id, &nitem->id) && | |
798 | omode == nmode && | |
22a2d3d5 | 799 | !git_oid_is_zero(&oitem->id)) { |
9be638ec ET |
800 | status = GIT_DELTA_UNMODIFIED; |
801 | ||
802 | /* if we have an unknown OID and a workdir iterator, then check some | |
803 | * circumstances that can accelerate things or need special handling | |
804 | */ | |
22a2d3d5 | 805 | } else if (git_oid_is_zero(&nitem->id) && new_is_workdir) { |
9be638ec ET |
806 | bool use_ctime = |
807 | ((diff->diffcaps & GIT_DIFFCAPS_TRUST_CTIME) != 0); | |
808 | git_index *index = git_iterator_index(info->new_iter); | |
809 | ||
810 | status = GIT_DELTA_UNMODIFIED; | |
811 | ||
812 | if (S_ISGITLINK(nmode)) { | |
813 | if ((error = maybe_modified_submodule(&status, &noid, diff, info)) < 0) | |
814 | return error; | |
815 | } | |
816 | ||
817 | /* if the stat data looks different, then mark modified - this just | |
818 | * means that the OID will be recalculated below to confirm change | |
819 | */ | |
820 | else if (omode != nmode || oitem->file_size != nitem->file_size) { | |
821 | status = GIT_DELTA_MODIFIED; | |
822 | modified_uncertain = | |
823 | (oitem->file_size <= 0 && nitem->file_size > 0); | |
824 | } | |
825 | else if (!git_index_time_eq(&oitem->mtime, &nitem->mtime) || | |
826 | (use_ctime && !git_index_time_eq(&oitem->ctime, &nitem->ctime)) || | |
827 | oitem->ino != nitem->ino || | |
828 | oitem->uid != nitem->uid || | |
829 | oitem->gid != nitem->gid || | |
830 | git_index_entry_newer_than_index(nitem, index)) | |
831 | { | |
832 | status = GIT_DELTA_MODIFIED; | |
833 | modified_uncertain = true; | |
834 | } | |
835 | ||
836 | /* if mode is GITLINK and submodules are ignored, then skip */ | |
837 | } else if (S_ISGITLINK(nmode) && | |
838 | DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_SUBMODULES)) { | |
839 | status = GIT_DELTA_UNMODIFIED; | |
840 | } | |
841 | ||
842 | /* if we got here and decided that the files are modified, but we | |
843 | * haven't calculated the OID of the new item, then calculate it now | |
844 | */ | |
22a2d3d5 | 845 | if (modified_uncertain && git_oid_is_zero(&nitem->id)) { |
9be638ec ET |
846 | const git_oid *update_check = |
847 | DIFF_FLAG_IS_SET(diff, GIT_DIFF_UPDATE_INDEX) && omode == nmode ? | |
848 | &oitem->id : NULL; | |
849 | ||
850 | if ((error = git_diff__oid_for_entry( | |
851 | &noid, &diff->base, nitem, nmode, update_check)) < 0) | |
852 | return error; | |
853 | ||
854 | /* if oid matches, then mark unmodified (except submodules, where | |
855 | * the filesystem content may be modified even if the oid still | |
856 | * matches between the index and the workdir HEAD) | |
857 | */ | |
858 | if (omode == nmode && !S_ISGITLINK(omode) && | |
859 | git_oid_equal(&oitem->id, &noid)) | |
860 | status = GIT_DELTA_UNMODIFIED; | |
861 | } | |
862 | ||
863 | /* If we want case changes, then break this into a delete of the old | |
864 | * and an add of the new so that consumers can act accordingly (eg, | |
865 | * checkout will update the case on disk.) | |
866 | */ | |
867 | if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE) && | |
868 | DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_CASECHANGE) && | |
869 | strcmp(oitem->path, nitem->path) != 0) { | |
870 | ||
871 | if (!(error = diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem, NULL))) | |
872 | error = diff_delta__from_one(diff, GIT_DELTA_ADDED, NULL, nitem); | |
873 | ||
874 | return error; | |
875 | } | |
876 | ||
877 | return diff_delta__from_two( | |
878 | diff, status, oitem, omode, nitem, nmode, | |
22a2d3d5 | 879 | git_oid_is_zero(&noid) ? NULL : &noid, matched_pathspec); |
9be638ec ET |
880 | } |
881 | ||
882 | static bool entry_is_prefixed( | |
883 | git_diff_generated *diff, | |
884 | const git_index_entry *item, | |
885 | const git_index_entry *prefix_item) | |
886 | { | |
887 | size_t pathlen; | |
888 | ||
889 | if (!item || diff->base.pfxcomp(item->path, prefix_item->path) != 0) | |
890 | return false; | |
891 | ||
892 | pathlen = strlen(prefix_item->path); | |
893 | ||
894 | return (prefix_item->path[pathlen - 1] == '/' || | |
895 | item->path[pathlen] == '\0' || | |
896 | item->path[pathlen] == '/'); | |
897 | } | |
898 | ||
899 | static int iterator_current( | |
900 | const git_index_entry **entry, | |
901 | git_iterator *iterator) | |
902 | { | |
903 | int error; | |
904 | ||
905 | if ((error = git_iterator_current(entry, iterator)) == GIT_ITEROVER) { | |
906 | *entry = NULL; | |
907 | error = 0; | |
908 | } | |
909 | ||
910 | return error; | |
911 | } | |
912 | ||
913 | static int iterator_advance( | |
914 | const git_index_entry **entry, | |
915 | git_iterator *iterator) | |
916 | { | |
917 | const git_index_entry *prev_entry = *entry; | |
918 | int cmp, error; | |
919 | ||
920 | /* if we're looking for conflicts, we only want to report | |
921 | * one conflict for each file, instead of all three sides. | |
922 | * so if this entry is a conflict for this file, and the | |
923 | * previous one was a conflict for the same file, skip it. | |
924 | */ | |
925 | while ((error = git_iterator_advance(entry, iterator)) == 0) { | |
926 | if (!(iterator->flags & GIT_ITERATOR_INCLUDE_CONFLICTS) || | |
927 | !git_index_entry_is_conflict(prev_entry) || | |
928 | !git_index_entry_is_conflict(*entry)) | |
929 | break; | |
930 | ||
931 | cmp = (iterator->flags & GIT_ITERATOR_IGNORE_CASE) ? | |
932 | strcasecmp(prev_entry->path, (*entry)->path) : | |
933 | strcmp(prev_entry->path, (*entry)->path); | |
934 | ||
935 | if (cmp) | |
936 | break; | |
937 | } | |
938 | ||
939 | if (error == GIT_ITEROVER) { | |
940 | *entry = NULL; | |
941 | error = 0; | |
942 | } | |
943 | ||
944 | return error; | |
945 | } | |
946 | ||
947 | static int iterator_advance_into( | |
948 | const git_index_entry **entry, | |
949 | git_iterator *iterator) | |
950 | { | |
951 | int error; | |
952 | ||
953 | if ((error = git_iterator_advance_into(entry, iterator)) == GIT_ITEROVER) { | |
954 | *entry = NULL; | |
955 | error = 0; | |
956 | } | |
957 | ||
958 | return error; | |
959 | } | |
960 | ||
961 | static int iterator_advance_over( | |
962 | const git_index_entry **entry, | |
963 | git_iterator_status_t *status, | |
964 | git_iterator *iterator) | |
965 | { | |
966 | int error = git_iterator_advance_over(entry, status, iterator); | |
967 | ||
968 | if (error == GIT_ITEROVER) { | |
969 | *entry = NULL; | |
970 | error = 0; | |
971 | } | |
972 | ||
973 | return error; | |
974 | } | |
975 | ||
976 | static int handle_unmatched_new_item( | |
977 | git_diff_generated *diff, diff_in_progress *info) | |
978 | { | |
979 | int error = 0; | |
980 | const git_index_entry *nitem = info->nitem; | |
981 | git_delta_t delta_type = GIT_DELTA_UNTRACKED; | |
982 | bool contains_oitem; | |
983 | ||
984 | /* check if this is a prefix of the other side */ | |
985 | contains_oitem = entry_is_prefixed(diff, info->oitem, nitem); | |
986 | ||
987 | /* update delta_type if this item is conflicted */ | |
988 | if (git_index_entry_is_conflict(nitem)) | |
989 | delta_type = GIT_DELTA_CONFLICTED; | |
990 | ||
991 | /* update delta_type if this item is ignored */ | |
992 | else if (git_iterator_current_is_ignored(info->new_iter)) | |
993 | delta_type = GIT_DELTA_IGNORED; | |
994 | ||
995 | if (nitem->mode == GIT_FILEMODE_TREE) { | |
996 | bool recurse_into_dir = contains_oitem; | |
997 | ||
998 | /* check if user requests recursion into this type of dir */ | |
999 | recurse_into_dir = contains_oitem || | |
1000 | (delta_type == GIT_DELTA_UNTRACKED && | |
1001 | DIFF_FLAG_IS_SET(diff, GIT_DIFF_RECURSE_UNTRACKED_DIRS)) || | |
1002 | (delta_type == GIT_DELTA_IGNORED && | |
1003 | DIFF_FLAG_IS_SET(diff, GIT_DIFF_RECURSE_IGNORED_DIRS)); | |
1004 | ||
1005 | /* do not advance into directories that contain a .git file */ | |
1006 | if (recurse_into_dir && !contains_oitem) { | |
1007 | git_buf *full = NULL; | |
1008 | if (git_iterator_current_workdir_path(&full, info->new_iter) < 0) | |
1009 | return -1; | |
1010 | if (full && git_path_contains(full, DOT_GIT)) { | |
1011 | /* TODO: warning if not a valid git repository */ | |
1012 | recurse_into_dir = false; | |
1013 | } | |
1014 | } | |
1015 | ||
1016 | /* still have to look into untracked directories to match core git - | |
1017 | * with no untracked files, directory is treated as ignored | |
1018 | */ | |
1019 | if (!recurse_into_dir && | |
1020 | delta_type == GIT_DELTA_UNTRACKED && | |
1021 | DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_ENABLE_FAST_UNTRACKED_DIRS)) | |
1022 | { | |
1023 | git_diff_delta *last; | |
1024 | git_iterator_status_t untracked_state; | |
1025 | ||
1026 | /* attempt to insert record for this directory */ | |
1027 | if ((error = diff_delta__from_one(diff, delta_type, NULL, nitem)) != 0) | |
1028 | return error; | |
1029 | ||
1030 | /* if delta wasn't created (because of rules), just skip ahead */ | |
1031 | last = diff_delta__last_for_item(diff, nitem); | |
1032 | if (!last) | |
1033 | return iterator_advance(&info->nitem, info->new_iter); | |
1034 | ||
1035 | /* iterate into dir looking for an actual untracked file */ | |
1036 | if ((error = iterator_advance_over( | |
1037 | &info->nitem, &untracked_state, info->new_iter)) < 0) | |
1038 | return error; | |
1039 | ||
1040 | /* if we found nothing that matched our pathlist filter, exclude */ | |
1041 | if (untracked_state == GIT_ITERATOR_STATUS_FILTERED) { | |
1042 | git_vector_pop(&diff->base.deltas); | |
1043 | git__free(last); | |
1044 | } | |
1045 | ||
1046 | /* if we found nothing or just ignored items, update the record */ | |
1047 | if (untracked_state == GIT_ITERATOR_STATUS_IGNORED || | |
1048 | untracked_state == GIT_ITERATOR_STATUS_EMPTY) { | |
1049 | last->status = GIT_DELTA_IGNORED; | |
1050 | ||
1051 | /* remove the record if we don't want ignored records */ | |
1052 | if (DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_IGNORED)) { | |
1053 | git_vector_pop(&diff->base.deltas); | |
1054 | git__free(last); | |
1055 | } | |
1056 | } | |
1057 | ||
1058 | return 0; | |
1059 | } | |
1060 | ||
1061 | /* try to advance into directory if necessary */ | |
1062 | if (recurse_into_dir) { | |
1063 | error = iterator_advance_into(&info->nitem, info->new_iter); | |
1064 | ||
1065 | /* if directory is empty, can't advance into it, so skip it */ | |
1066 | if (error == GIT_ENOTFOUND) { | |
ac3d33df | 1067 | git_error_clear(); |
9be638ec ET |
1068 | error = iterator_advance(&info->nitem, info->new_iter); |
1069 | } | |
1070 | ||
1071 | return error; | |
1072 | } | |
1073 | } | |
1074 | ||
1075 | else if (delta_type == GIT_DELTA_IGNORED && | |
1076 | DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_RECURSE_IGNORED_DIRS) && | |
1077 | git_iterator_current_tree_is_ignored(info->new_iter)) | |
1078 | /* item contained in ignored directory, so skip over it */ | |
1079 | return iterator_advance(&info->nitem, info->new_iter); | |
1080 | ||
22a2d3d5 | 1081 | else if (info->new_iter->type != GIT_ITERATOR_WORKDIR) { |
9be638ec ET |
1082 | if (delta_type != GIT_DELTA_CONFLICTED) |
1083 | delta_type = GIT_DELTA_ADDED; | |
1084 | } | |
1085 | ||
1086 | else if (nitem->mode == GIT_FILEMODE_COMMIT) { | |
1087 | /* ignore things that are not actual submodules */ | |
1088 | if (git_submodule_lookup(NULL, info->repo, nitem->path) != 0) { | |
ac3d33df | 1089 | git_error_clear(); |
9be638ec ET |
1090 | delta_type = GIT_DELTA_IGNORED; |
1091 | ||
1092 | /* if this contains a tracked item, treat as normal TREE */ | |
1093 | if (contains_oitem) { | |
1094 | error = iterator_advance_into(&info->nitem, info->new_iter); | |
1095 | if (error != GIT_ENOTFOUND) | |
1096 | return error; | |
1097 | ||
ac3d33df | 1098 | git_error_clear(); |
9be638ec ET |
1099 | return iterator_advance(&info->nitem, info->new_iter); |
1100 | } | |
1101 | } | |
1102 | } | |
1103 | ||
1104 | else if (nitem->mode == GIT_FILEMODE_UNREADABLE) { | |
1105 | if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_UNREADABLE_AS_UNTRACKED)) | |
1106 | delta_type = GIT_DELTA_UNTRACKED; | |
1107 | else | |
1108 | delta_type = GIT_DELTA_UNREADABLE; | |
1109 | } | |
1110 | ||
1111 | /* Actually create the record for this item if necessary */ | |
1112 | if ((error = diff_delta__from_one(diff, delta_type, NULL, nitem)) != 0) | |
1113 | return error; | |
1114 | ||
1115 | /* If user requested TYPECHANGE records, then check for that instead of | |
1116 | * just generating an ADDED/UNTRACKED record | |
1117 | */ | |
1118 | if (delta_type != GIT_DELTA_IGNORED && | |
1119 | DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE_TREES) && | |
1120 | contains_oitem) | |
1121 | { | |
1122 | /* this entry was prefixed with a tree - make TYPECHANGE */ | |
1123 | git_diff_delta *last = diff_delta__last_for_item(diff, nitem); | |
1124 | if (last) { | |
1125 | last->status = GIT_DELTA_TYPECHANGE; | |
1126 | last->old_file.mode = GIT_FILEMODE_TREE; | |
1127 | } | |
1128 | } | |
1129 | ||
1130 | return iterator_advance(&info->nitem, info->new_iter); | |
1131 | } | |
1132 | ||
1133 | static int handle_unmatched_old_item( | |
1134 | git_diff_generated *diff, diff_in_progress *info) | |
1135 | { | |
1136 | git_delta_t delta_type = GIT_DELTA_DELETED; | |
1137 | int error; | |
1138 | ||
1139 | /* update delta_type if this item is conflicted */ | |
1140 | if (git_index_entry_is_conflict(info->oitem)) | |
1141 | delta_type = GIT_DELTA_CONFLICTED; | |
1142 | ||
1143 | if ((error = diff_delta__from_one(diff, delta_type, info->oitem, NULL)) < 0) | |
1144 | return error; | |
1145 | ||
1146 | /* if we are generating TYPECHANGE records then check for that | |
1147 | * instead of just generating a DELETE record | |
1148 | */ | |
1149 | if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE_TREES) && | |
1150 | entry_is_prefixed(diff, info->nitem, info->oitem)) | |
1151 | { | |
1152 | /* this entry has become a tree! convert to TYPECHANGE */ | |
1153 | git_diff_delta *last = diff_delta__last_for_item(diff, info->oitem); | |
1154 | if (last) { | |
1155 | last->status = GIT_DELTA_TYPECHANGE; | |
1156 | last->new_file.mode = GIT_FILEMODE_TREE; | |
1157 | } | |
1158 | ||
1159 | /* If new_iter is a workdir iterator, then this situation | |
1160 | * will certainly be followed by a series of untracked items. | |
1161 | * Unless RECURSE_UNTRACKED_DIRS is set, skip over them... | |
1162 | */ | |
1163 | if (S_ISDIR(info->nitem->mode) && | |
1164 | DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_RECURSE_UNTRACKED_DIRS)) | |
1165 | return iterator_advance(&info->nitem, info->new_iter); | |
1166 | } | |
1167 | ||
1168 | return iterator_advance(&info->oitem, info->old_iter); | |
1169 | } | |
1170 | ||
1171 | static int handle_matched_item( | |
1172 | git_diff_generated *diff, diff_in_progress *info) | |
1173 | { | |
1174 | int error = 0; | |
1175 | ||
1176 | if ((error = maybe_modified(diff, info)) < 0) | |
1177 | return error; | |
1178 | ||
1179 | if (!(error = iterator_advance(&info->oitem, info->old_iter))) | |
1180 | error = iterator_advance(&info->nitem, info->new_iter); | |
1181 | ||
1182 | return error; | |
1183 | } | |
1184 | ||
1185 | int git_diff__from_iterators( | |
1186 | git_diff **out, | |
1187 | git_repository *repo, | |
1188 | git_iterator *old_iter, | |
1189 | git_iterator *new_iter, | |
1190 | const git_diff_options *opts) | |
1191 | { | |
1192 | git_diff_generated *diff; | |
1193 | diff_in_progress info; | |
1194 | int error = 0; | |
1195 | ||
1196 | *out = NULL; | |
1197 | ||
1198 | diff = diff_generated_alloc(repo, old_iter, new_iter); | |
ac3d33df | 1199 | GIT_ERROR_CHECK_ALLOC(diff); |
9be638ec ET |
1200 | |
1201 | info.repo = repo; | |
1202 | info.old_iter = old_iter; | |
1203 | info.new_iter = new_iter; | |
1204 | ||
1205 | /* make iterators have matching icase behavior */ | |
1206 | if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE)) { | |
1207 | git_iterator_set_ignore_case(old_iter, true); | |
1208 | git_iterator_set_ignore_case(new_iter, true); | |
1209 | } | |
1210 | ||
1211 | /* finish initialization */ | |
1212 | if ((error = diff_generated_apply_options(diff, opts)) < 0) | |
1213 | goto cleanup; | |
1214 | ||
1215 | if ((error = iterator_current(&info.oitem, old_iter)) < 0 || | |
1216 | (error = iterator_current(&info.nitem, new_iter)) < 0) | |
1217 | goto cleanup; | |
1218 | ||
1219 | /* run iterators building diffs */ | |
1220 | while (!error && (info.oitem || info.nitem)) { | |
1221 | int cmp; | |
1222 | ||
1223 | /* report progress */ | |
1224 | if (opts && opts->progress_cb) { | |
1225 | if ((error = opts->progress_cb(&diff->base, | |
1226 | info.oitem ? info.oitem->path : NULL, | |
1227 | info.nitem ? info.nitem->path : NULL, | |
1228 | opts->payload))) | |
1229 | break; | |
1230 | } | |
1231 | ||
1232 | cmp = info.oitem ? | |
1233 | (info.nitem ? diff->base.entrycomp(info.oitem, info.nitem) : -1) : 1; | |
1234 | ||
1235 | /* create DELETED records for old items not matched in new */ | |
1236 | if (cmp < 0) | |
1237 | error = handle_unmatched_old_item(diff, &info); | |
1238 | ||
1239 | /* create ADDED, TRACKED, or IGNORED records for new items not | |
1240 | * matched in old (and/or descend into directories as needed) | |
1241 | */ | |
1242 | else if (cmp > 0) | |
1243 | error = handle_unmatched_new_item(diff, &info); | |
1244 | ||
1245 | /* otherwise item paths match, so create MODIFIED record | |
1246 | * (or ADDED and DELETED pair if type changed) | |
1247 | */ | |
1248 | else | |
1249 | error = handle_matched_item(diff, &info); | |
1250 | } | |
1251 | ||
1252 | diff->base.perf.stat_calls += | |
1253 | old_iter->stat_calls + new_iter->stat_calls; | |
1254 | ||
1255 | cleanup: | |
1256 | if (!error) | |
1257 | *out = &diff->base; | |
1258 | else | |
1259 | git_diff_free(&diff->base); | |
1260 | ||
1261 | return error; | |
1262 | } | |
1263 | ||
22a2d3d5 UG |
1264 | static int diff_prepare_iterator_opts(char **prefix, git_iterator_options *a, int aflags, |
1265 | git_iterator_options *b, int bflags, | |
1266 | const git_diff_options *opts) | |
1267 | { | |
1268 | GIT_ERROR_CHECK_VERSION(opts, GIT_DIFF_OPTIONS_VERSION, "git_diff_options"); | |
1269 | ||
1270 | *prefix = NULL; | |
1271 | ||
1272 | if (opts && (opts->flags & GIT_DIFF_DISABLE_PATHSPEC_MATCH)) { | |
1273 | a->pathlist.strings = opts->pathspec.strings; | |
1274 | a->pathlist.count = opts->pathspec.count; | |
1275 | b->pathlist.strings = opts->pathspec.strings; | |
1276 | b->pathlist.count = opts->pathspec.count; | |
1277 | } else if (opts) { | |
1278 | *prefix = git_pathspec_prefix(&opts->pathspec); | |
1279 | GIT_ERROR_CHECK_ALLOC(prefix); | |
1280 | } | |
1281 | ||
1282 | a->flags = aflags; | |
1283 | b->flags = bflags; | |
1284 | a->start = b->start = *prefix; | |
1285 | a->end = b->end = *prefix; | |
1286 | ||
1287 | return 0; | |
1288 | } | |
9be638ec ET |
1289 | |
1290 | int git_diff_tree_to_tree( | |
1291 | git_diff **out, | |
1292 | git_repository *repo, | |
1293 | git_tree *old_tree, | |
1294 | git_tree *new_tree, | |
1295 | const git_diff_options *opts) | |
1296 | { | |
9be638ec | 1297 | git_iterator_flag_t iflag = GIT_ITERATOR_DONT_IGNORE_CASE; |
22a2d3d5 UG |
1298 | git_iterator_options a_opts = GIT_ITERATOR_OPTIONS_INIT, |
1299 | b_opts = GIT_ITERATOR_OPTIONS_INIT; | |
1300 | git_iterator *a = NULL, *b = NULL; | |
1301 | git_diff *diff = NULL; | |
1302 | char *prefix = NULL; | |
9be638ec ET |
1303 | int error = 0; |
1304 | ||
1305 | assert(out && repo); | |
1306 | ||
1307 | *out = NULL; | |
1308 | ||
1309 | /* for tree to tree diff, be case sensitive even if the index is | |
1310 | * currently case insensitive, unless the user explicitly asked | |
1311 | * for case insensitivity | |
1312 | */ | |
1313 | if (opts && (opts->flags & GIT_DIFF_IGNORE_CASE) != 0) | |
1314 | iflag = GIT_ITERATOR_IGNORE_CASE; | |
1315 | ||
22a2d3d5 UG |
1316 | if ((error = diff_prepare_iterator_opts(&prefix, &a_opts, iflag, &b_opts, iflag, opts)) < 0 || |
1317 | (error = git_iterator_for_tree(&a, old_tree, &a_opts)) < 0 || | |
1318 | (error = git_iterator_for_tree(&b, new_tree, &b_opts)) < 0 || | |
1319 | (error = git_diff__from_iterators(&diff, repo, a, b, opts)) < 0) | |
1320 | goto out; | |
9be638ec | 1321 | |
22a2d3d5 UG |
1322 | *out = diff; |
1323 | diff = NULL; | |
1324 | out: | |
1325 | git_iterator_free(a); | |
1326 | git_iterator_free(b); | |
1327 | git_diff_free(diff); | |
1328 | git__free(prefix); | |
9be638ec ET |
1329 | |
1330 | return error; | |
1331 | } | |
1332 | ||
1333 | static int diff_load_index(git_index **index, git_repository *repo) | |
1334 | { | |
1335 | int error = git_repository_index__weakptr(index, repo); | |
1336 | ||
1337 | /* reload the repository index when user did not pass one in */ | |
1338 | if (!error && git_index_read(*index, false) < 0) | |
ac3d33df | 1339 | git_error_clear(); |
9be638ec ET |
1340 | |
1341 | return error; | |
1342 | } | |
1343 | ||
1344 | int git_diff_tree_to_index( | |
1345 | git_diff **out, | |
1346 | git_repository *repo, | |
1347 | git_tree *old_tree, | |
1348 | git_index *index, | |
1349 | const git_diff_options *opts) | |
1350 | { | |
9be638ec ET |
1351 | git_iterator_flag_t iflag = GIT_ITERATOR_DONT_IGNORE_CASE | |
1352 | GIT_ITERATOR_INCLUDE_CONFLICTS; | |
22a2d3d5 UG |
1353 | git_iterator_options a_opts = GIT_ITERATOR_OPTIONS_INIT, |
1354 | b_opts = GIT_ITERATOR_OPTIONS_INIT; | |
1355 | git_iterator *a = NULL, *b = NULL; | |
1356 | git_diff *diff = NULL; | |
1357 | char *prefix = NULL; | |
9be638ec ET |
1358 | bool index_ignore_case = false; |
1359 | int error = 0; | |
1360 | ||
1361 | assert(out && repo); | |
1362 | ||
1363 | *out = NULL; | |
1364 | ||
1365 | if (!index && (error = diff_load_index(&index, repo)) < 0) | |
1366 | return error; | |
1367 | ||
1368 | index_ignore_case = index->ignore_case; | |
1369 | ||
22a2d3d5 UG |
1370 | if ((error = diff_prepare_iterator_opts(&prefix, &a_opts, iflag, &b_opts, iflag, opts)) < 0 || |
1371 | (error = git_iterator_for_tree(&a, old_tree, &a_opts)) < 0 || | |
1372 | (error = git_iterator_for_index(&b, repo, index, &b_opts)) < 0 || | |
1373 | (error = git_diff__from_iterators(&diff, repo, a, b, opts)) < 0) | |
1374 | goto out; | |
9be638ec ET |
1375 | |
1376 | /* if index is in case-insensitive order, re-sort deltas to match */ | |
22a2d3d5 UG |
1377 | if (index_ignore_case) |
1378 | diff_set_ignore_case(diff, true); | |
9be638ec | 1379 | |
22a2d3d5 UG |
1380 | *out = diff; |
1381 | diff = NULL; | |
1382 | out: | |
1383 | git_iterator_free(a); | |
1384 | git_iterator_free(b); | |
1385 | git_diff_free(diff); | |
1386 | git__free(prefix); | |
9be638ec ET |
1387 | |
1388 | return error; | |
1389 | } | |
1390 | ||
1391 | int git_diff_index_to_workdir( | |
1392 | git_diff **out, | |
1393 | git_repository *repo, | |
1394 | git_index *index, | |
1395 | const git_diff_options *opts) | |
1396 | { | |
22a2d3d5 UG |
1397 | git_iterator_options a_opts = GIT_ITERATOR_OPTIONS_INIT, |
1398 | b_opts = GIT_ITERATOR_OPTIONS_INIT; | |
1399 | git_iterator *a = NULL, *b = NULL; | |
9be638ec | 1400 | git_diff *diff = NULL; |
22a2d3d5 | 1401 | char *prefix = NULL; |
9be638ec ET |
1402 | int error = 0; |
1403 | ||
1404 | assert(out && repo); | |
1405 | ||
1406 | *out = NULL; | |
1407 | ||
1408 | if (!index && (error = diff_load_index(&index, repo)) < 0) | |
1409 | return error; | |
1410 | ||
22a2d3d5 UG |
1411 | if ((error = diff_prepare_iterator_opts(&prefix, &a_opts, GIT_ITERATOR_INCLUDE_CONFLICTS, |
1412 | &b_opts, GIT_ITERATOR_DONT_AUTOEXPAND, opts)) < 0 || | |
1413 | (error = git_iterator_for_index(&a, repo, index, &a_opts)) < 0 || | |
1414 | (error = git_iterator_for_workdir(&b, repo, index, NULL, &b_opts)) < 0 || | |
1415 | (error = git_diff__from_iterators(&diff, repo, a, b, opts)) < 0) | |
1416 | goto out; | |
1417 | ||
1418 | if ((diff->opts.flags & GIT_DIFF_UPDATE_INDEX) && ((git_diff_generated *)diff)->index_updated) | |
1419 | if ((error = git_index_write(index)) < 0) | |
1420 | goto out; | |
1421 | ||
1422 | *out = diff; | |
1423 | diff = NULL; | |
1424 | out: | |
1425 | git_iterator_free(a); | |
1426 | git_iterator_free(b); | |
1427 | git_diff_free(diff); | |
1428 | git__free(prefix); | |
9be638ec ET |
1429 | |
1430 | return error; | |
1431 | } | |
1432 | ||
1433 | int git_diff_tree_to_workdir( | |
1434 | git_diff **out, | |
1435 | git_repository *repo, | |
1436 | git_tree *old_tree, | |
1437 | const git_diff_options *opts) | |
1438 | { | |
22a2d3d5 UG |
1439 | git_iterator_options a_opts = GIT_ITERATOR_OPTIONS_INIT, |
1440 | b_opts = GIT_ITERATOR_OPTIONS_INIT; | |
1441 | git_iterator *a = NULL, *b = NULL; | |
9be638ec | 1442 | git_diff *diff = NULL; |
22a2d3d5 | 1443 | char *prefix = NULL; |
9be638ec | 1444 | git_index *index; |
22a2d3d5 | 1445 | int error; |
9be638ec ET |
1446 | |
1447 | assert(out && repo); | |
1448 | ||
1449 | *out = NULL; | |
1450 | ||
22a2d3d5 UG |
1451 | if ((error = diff_prepare_iterator_opts(&prefix, &a_opts, 0, |
1452 | &b_opts, GIT_ITERATOR_DONT_AUTOEXPAND, opts) < 0) || | |
1453 | (error = git_repository_index__weakptr(&index, repo)) < 0 || | |
1454 | (error = git_iterator_for_tree(&a, old_tree, &a_opts)) < 0 || | |
1455 | (error = git_iterator_for_workdir(&b, repo, index, old_tree, &b_opts)) < 0 || | |
1456 | (error = git_diff__from_iterators(&diff, repo, a, b, opts)) < 0) | |
1457 | goto out; | |
1458 | ||
1459 | *out = diff; | |
1460 | diff = NULL; | |
1461 | out: | |
1462 | git_iterator_free(a); | |
1463 | git_iterator_free(b); | |
1464 | git_diff_free(diff); | |
1465 | git__free(prefix); | |
9be638ec ET |
1466 | |
1467 | return error; | |
1468 | } | |
1469 | ||
1470 | int git_diff_tree_to_workdir_with_index( | |
1471 | git_diff **out, | |
1472 | git_repository *repo, | |
1473 | git_tree *tree, | |
1474 | const git_diff_options *opts) | |
1475 | { | |
1476 | git_diff *d1 = NULL, *d2 = NULL; | |
1477 | git_index *index = NULL; | |
1478 | int error = 0; | |
1479 | ||
1480 | assert(out && repo); | |
1481 | ||
1482 | *out = NULL; | |
1483 | ||
1484 | if ((error = diff_load_index(&index, repo)) < 0) | |
1485 | return error; | |
1486 | ||
1487 | if (!(error = git_diff_tree_to_index(&d1, repo, tree, index, opts)) && | |
1488 | !(error = git_diff_index_to_workdir(&d2, repo, index, opts))) | |
1489 | error = git_diff_merge(d1, d2); | |
1490 | ||
1491 | git_diff_free(d2); | |
1492 | ||
1493 | if (error) { | |
1494 | git_diff_free(d1); | |
1495 | d1 = NULL; | |
1496 | } | |
1497 | ||
1498 | *out = d1; | |
1499 | return error; | |
1500 | } | |
1501 | ||
1502 | int git_diff_index_to_index( | |
1503 | git_diff **out, | |
1504 | git_repository *repo, | |
1505 | git_index *old_index, | |
1506 | git_index *new_index, | |
1507 | const git_diff_options *opts) | |
1508 | { | |
22a2d3d5 UG |
1509 | git_iterator_options a_opts = GIT_ITERATOR_OPTIONS_INIT, |
1510 | b_opts = GIT_ITERATOR_OPTIONS_INIT; | |
1511 | git_iterator *a = NULL, *b = NULL; | |
1512 | git_diff *diff = NULL; | |
1513 | char *prefix = NULL; | |
1514 | int error; | |
9be638ec ET |
1515 | |
1516 | assert(out && old_index && new_index); | |
1517 | ||
1518 | *out = NULL; | |
1519 | ||
22a2d3d5 UG |
1520 | if ((error = diff_prepare_iterator_opts(&prefix, &a_opts, GIT_ITERATOR_DONT_IGNORE_CASE, |
1521 | &b_opts, GIT_ITERATOR_DONT_IGNORE_CASE, opts) < 0) || | |
1522 | (error = git_iterator_for_index(&a, repo, old_index, &a_opts)) < 0 || | |
1523 | (error = git_iterator_for_index(&b, repo, new_index, &b_opts)) < 0 || | |
1524 | (error = git_diff__from_iterators(&diff, repo, a, b, opts)) < 0) | |
1525 | goto out; | |
9be638ec ET |
1526 | |
1527 | /* if index is in case-insensitive order, re-sort deltas to match */ | |
22a2d3d5 UG |
1528 | if (old_index->ignore_case || new_index->ignore_case) |
1529 | diff_set_ignore_case(diff, true); | |
9be638ec | 1530 | |
22a2d3d5 UG |
1531 | *out = diff; |
1532 | diff = NULL; | |
1533 | out: | |
1534 | git_iterator_free(a); | |
1535 | git_iterator_free(b); | |
1536 | git_diff_free(diff); | |
1537 | git__free(prefix); | |
9be638ec ET |
1538 | |
1539 | return error; | |
1540 | } | |
1541 | ||
1542 | int git_diff__paired_foreach( | |
1543 | git_diff *head2idx, | |
1544 | git_diff *idx2wd, | |
1545 | int (*cb)(git_diff_delta *h2i, git_diff_delta *i2w, void *payload), | |
1546 | void *payload) | |
1547 | { | |
1548 | int cmp, error = 0; | |
1549 | git_diff_delta *h2i, *i2w; | |
1550 | size_t i, j, i_max, j_max; | |
1551 | int (*strcomp)(const char *, const char *) = git__strcmp; | |
1552 | bool h2i_icase, i2w_icase, icase_mismatch; | |
1553 | ||
1554 | i_max = head2idx ? head2idx->deltas.length : 0; | |
1555 | j_max = idx2wd ? idx2wd->deltas.length : 0; | |
1556 | if (!i_max && !j_max) | |
1557 | return 0; | |
1558 | ||
1559 | /* At some point, tree-to-index diffs will probably never ignore case, | |
1560 | * even if that isn't true now. Index-to-workdir diffs may or may not | |
1561 | * ignore case, but the index filename for the idx2wd diff should | |
1562 | * still be using the canonical case-preserving name. | |
1563 | * | |
1564 | * Therefore the main thing we need to do here is make sure the diffs | |
1565 | * are traversed in a compatible order. To do this, we temporarily | |
1566 | * resort a mismatched diff to get the order correct. | |
1567 | * | |
1568 | * In order to traverse renames in the index->workdir, we need to | |
1569 | * ensure that we compare the index name on both sides, so we | |
1570 | * always sort by the old name in the i2w list. | |
1571 | */ | |
1572 | h2i_icase = head2idx != NULL && git_diff_is_sorted_icase(head2idx); | |
1573 | i2w_icase = idx2wd != NULL && git_diff_is_sorted_icase(idx2wd); | |
1574 | ||
1575 | icase_mismatch = | |
1576 | (head2idx != NULL && idx2wd != NULL && h2i_icase != i2w_icase); | |
1577 | ||
1578 | if (icase_mismatch && h2i_icase) { | |
1579 | git_vector_set_cmp(&head2idx->deltas, git_diff_delta__cmp); | |
1580 | git_vector_sort(&head2idx->deltas); | |
1581 | } | |
1582 | ||
1583 | if (i2w_icase && !icase_mismatch) { | |
1584 | strcomp = git__strcasecmp; | |
1585 | ||
22a2d3d5 | 1586 | git_vector_set_cmp(&idx2wd->deltas, diff_delta_i2w_casecmp); |
9be638ec ET |
1587 | git_vector_sort(&idx2wd->deltas); |
1588 | } else if (idx2wd != NULL) { | |
22a2d3d5 | 1589 | git_vector_set_cmp(&idx2wd->deltas, diff_delta_i2w_cmp); |
9be638ec ET |
1590 | git_vector_sort(&idx2wd->deltas); |
1591 | } | |
1592 | ||
1593 | for (i = 0, j = 0; i < i_max || j < j_max; ) { | |
1594 | h2i = head2idx ? GIT_VECTOR_GET(&head2idx->deltas, i) : NULL; | |
1595 | i2w = idx2wd ? GIT_VECTOR_GET(&idx2wd->deltas, j) : NULL; | |
1596 | ||
1597 | cmp = !i2w ? -1 : !h2i ? 1 : | |
1598 | strcomp(h2i->new_file.path, i2w->old_file.path); | |
1599 | ||
1600 | if (cmp < 0) { | |
1601 | i++; i2w = NULL; | |
1602 | } else if (cmp > 0) { | |
1603 | j++; h2i = NULL; | |
1604 | } else { | |
1605 | i++; j++; | |
1606 | } | |
1607 | ||
1608 | if ((error = cb(h2i, i2w, payload)) != 0) { | |
ac3d33df | 1609 | git_error_set_after_callback(error); |
9be638ec ET |
1610 | break; |
1611 | } | |
1612 | } | |
1613 | ||
1614 | /* restore case-insensitive delta sort */ | |
1615 | if (icase_mismatch && h2i_icase) { | |
1616 | git_vector_set_cmp(&head2idx->deltas, git_diff_delta__casecmp); | |
1617 | git_vector_sort(&head2idx->deltas); | |
1618 | } | |
1619 | ||
1620 | /* restore idx2wd sort by new path */ | |
1621 | if (idx2wd != NULL) { | |
1622 | git_vector_set_cmp(&idx2wd->deltas, | |
1623 | i2w_icase ? git_diff_delta__casecmp : git_diff_delta__cmp); | |
1624 | git_vector_sort(&idx2wd->deltas); | |
1625 | } | |
1626 | ||
1627 | return error; | |
1628 | } | |
1629 | ||
1630 | int git_diff__commit( | |
1631 | git_diff **out, | |
1632 | git_repository *repo, | |
1633 | const git_commit *commit, | |
1634 | const git_diff_options *opts) | |
1635 | { | |
1636 | git_commit *parent = NULL; | |
1637 | git_diff *commit_diff = NULL; | |
1638 | git_tree *old_tree = NULL, *new_tree = NULL; | |
1639 | size_t parents; | |
1640 | int error = 0; | |
1641 | ||
1642 | *out = NULL; | |
1643 | ||
1644 | if ((parents = git_commit_parentcount(commit)) > 1) { | |
1645 | char commit_oidstr[GIT_OID_HEXSZ + 1]; | |
1646 | ||
1647 | error = -1; | |
ac3d33df | 1648 | git_error_set(GIT_ERROR_INVALID, "commit %s is a merge commit", |
9be638ec ET |
1649 | git_oid_tostr(commit_oidstr, GIT_OID_HEXSZ + 1, git_commit_id(commit))); |
1650 | goto on_error; | |
1651 | } | |
1652 | ||
1653 | if (parents > 0) | |
1654 | if ((error = git_commit_parent(&parent, commit, 0)) < 0 || | |
1655 | (error = git_commit_tree(&old_tree, parent)) < 0) | |
1656 | goto on_error; | |
1657 | ||
1658 | if ((error = git_commit_tree(&new_tree, commit)) < 0 || | |
1659 | (error = git_diff_tree_to_tree(&commit_diff, repo, old_tree, new_tree, opts)) < 0) | |
1660 | goto on_error; | |
1661 | ||
1662 | *out = commit_diff; | |
1663 | ||
1664 | on_error: | |
1665 | git_tree_free(new_tree); | |
1666 | git_tree_free(old_tree); | |
1667 | git_commit_free(parent); | |
1668 | ||
1669 | return error; | |
1670 | } | |
1671 |