]>
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 | ||
c25aa7cd | 131 | GIT_ASSERT_ARG((oitem != NULL) ^ (nitem != NULL)); |
9be638ec ET |
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 */ | |
c25aa7cd | 163 | GIT_ASSERT(status != GIT_DELTA_MODIFIED); |
9be638ec ET |
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 | ||
c25aa7cd PP |
411 | GIT_ASSERT_ARG_WITH_RETVAL(repo, NULL); |
412 | GIT_ASSERT_ARG_WITH_RETVAL(old_iter, NULL); | |
413 | GIT_ASSERT_ARG_WITH_RETVAL(new_iter, NULL); | |
9be638ec ET |
414 | |
415 | if ((diff = git__calloc(1, sizeof(git_diff_generated))) == NULL) | |
416 | return NULL; | |
417 | ||
eae0bfdc | 418 | GIT_REFCOUNT_INC(&diff->base); |
9be638ec ET |
419 | diff->base.type = GIT_DIFF_TYPE_GENERATED; |
420 | diff->base.repo = repo; | |
421 | diff->base.old_src = old_iter->type; | |
422 | diff->base.new_src = new_iter->type; | |
b859faa6 | 423 | diff->base.patch_fn = git_patch_generated_from_diff; |
9be638ec | 424 | diff->base.free_fn = diff_generated_free; |
eae0bfdc | 425 | git_attr_session__init(&diff->base.attrsession, repo); |
9be638ec ET |
426 | memcpy(&diff->base.opts, &dflt, sizeof(git_diff_options)); |
427 | ||
22a2d3d5 UG |
428 | if (git_pool_init(&diff->base.pool, 1) < 0 || |
429 | git_vector_init(&diff->base.deltas, 0, git_diff_delta__cmp) < 0) { | |
9be638ec ET |
430 | git_diff_free(&diff->base); |
431 | return NULL; | |
432 | } | |
433 | ||
434 | /* Use case-insensitive compare if either iterator has | |
435 | * the ignore_case bit set */ | |
22a2d3d5 | 436 | diff_set_ignore_case( |
9be638ec ET |
437 | &diff->base, |
438 | git_iterator_ignore_case(old_iter) || | |
439 | git_iterator_ignore_case(new_iter)); | |
440 | ||
441 | return diff; | |
442 | } | |
443 | ||
444 | static int diff_generated_apply_options( | |
445 | git_diff_generated *diff, | |
446 | const git_diff_options *opts) | |
447 | { | |
448 | git_config *cfg = NULL; | |
449 | git_repository *repo = diff->base.repo; | |
450 | git_pool *pool = &diff->base.pool; | |
451 | int val; | |
452 | ||
453 | if (opts) { | |
454 | /* copy user options (except case sensitivity info from iterators) */ | |
455 | bool icase = DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE); | |
456 | memcpy(&diff->base.opts, opts, sizeof(diff->base.opts)); | |
457 | DIFF_FLAG_SET(diff, GIT_DIFF_IGNORE_CASE, icase); | |
458 | ||
459 | /* initialize pathspec from options */ | |
460 | if (git_pathspec__vinit(&diff->pathspec, &opts->pathspec, pool) < 0) | |
461 | return -1; | |
462 | } | |
463 | ||
464 | /* flag INCLUDE_TYPECHANGE_TREES implies INCLUDE_TYPECHANGE */ | |
465 | if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE_TREES)) | |
466 | diff->base.opts.flags |= GIT_DIFF_INCLUDE_TYPECHANGE; | |
467 | ||
468 | /* flag INCLUDE_UNTRACKED_CONTENT implies INCLUDE_UNTRACKED */ | |
469 | if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_SHOW_UNTRACKED_CONTENT)) | |
470 | diff->base.opts.flags |= GIT_DIFF_INCLUDE_UNTRACKED; | |
471 | ||
472 | /* load config values that affect diff behavior */ | |
473 | if ((val = git_repository_config_snapshot(&cfg, repo)) < 0) | |
474 | return val; | |
475 | ||
22a2d3d5 | 476 | if (!git_config__configmap_lookup(&val, cfg, GIT_CONFIGMAP_SYMLINKS) && val) |
9be638ec ET |
477 | diff->diffcaps |= GIT_DIFFCAPS_HAS_SYMLINKS; |
478 | ||
22a2d3d5 | 479 | if (!git_config__configmap_lookup(&val, cfg, GIT_CONFIGMAP_IGNORESTAT) && val) |
9be638ec ET |
480 | diff->diffcaps |= GIT_DIFFCAPS_IGNORE_STAT; |
481 | ||
482 | if ((diff->base.opts.flags & GIT_DIFF_IGNORE_FILEMODE) == 0 && | |
22a2d3d5 | 483 | !git_config__configmap_lookup(&val, cfg, GIT_CONFIGMAP_FILEMODE) && val) |
9be638ec ET |
484 | diff->diffcaps |= GIT_DIFFCAPS_TRUST_MODE_BITS; |
485 | ||
22a2d3d5 | 486 | if (!git_config__configmap_lookup(&val, cfg, GIT_CONFIGMAP_TRUSTCTIME) && val) |
9be638ec ET |
487 | diff->diffcaps |= GIT_DIFFCAPS_TRUST_CTIME; |
488 | ||
489 | /* Don't set GIT_DIFFCAPS_USE_DEV - compile time option in core git */ | |
490 | ||
491 | /* If not given explicit `opts`, check `diff.xyz` configs */ | |
492 | if (!opts) { | |
493 | int context = git_config__get_int_force(cfg, "diff.context", 3); | |
494 | diff->base.opts.context_lines = context >= 0 ? (uint32_t)context : 3; | |
495 | ||
496 | /* add other defaults here */ | |
497 | } | |
498 | ||
499 | /* Reverse src info if diff is reversed */ | |
500 | if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) { | |
22a2d3d5 | 501 | git_iterator_t tmp_src = diff->base.old_src; |
9be638ec ET |
502 | diff->base.old_src = diff->base.new_src; |
503 | diff->base.new_src = tmp_src; | |
504 | } | |
505 | ||
506 | /* Unset UPDATE_INDEX unless diffing workdir and index */ | |
507 | if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_UPDATE_INDEX) && | |
22a2d3d5 UG |
508 | (!(diff->base.old_src == GIT_ITERATOR_WORKDIR || |
509 | diff->base.new_src == GIT_ITERATOR_WORKDIR) || | |
510 | !(diff->base.old_src == GIT_ITERATOR_INDEX || | |
511 | diff->base.new_src == GIT_ITERATOR_INDEX))) | |
9be638ec ET |
512 | diff->base.opts.flags &= ~GIT_DIFF_UPDATE_INDEX; |
513 | ||
514 | /* if ignore_submodules not explicitly set, check diff config */ | |
515 | if (diff->base.opts.ignore_submodules <= 0) { | |
516 | git_config_entry *entry; | |
517 | git_config__lookup_entry(&entry, cfg, "diff.ignoresubmodules", true); | |
518 | ||
519 | if (entry && git_submodule_parse_ignore( | |
520 | &diff->base.opts.ignore_submodules, entry->value) < 0) | |
ac3d33df | 521 | git_error_clear(); |
9be638ec ET |
522 | git_config_entry_free(entry); |
523 | } | |
524 | ||
525 | /* if either prefix is not set, figure out appropriate value */ | |
526 | if (!diff->base.opts.old_prefix || !diff->base.opts.new_prefix) { | |
527 | const char *use_old = DIFF_OLD_PREFIX_DEFAULT; | |
528 | const char *use_new = DIFF_NEW_PREFIX_DEFAULT; | |
529 | ||
530 | if (git_config__get_bool_force(cfg, "diff.noprefix", 0)) | |
531 | use_old = use_new = ""; | |
532 | else if (git_config__get_bool_force(cfg, "diff.mnemonicprefix", 0)) { | |
533 | use_old = diff_mnemonic_prefix(diff->base.old_src, true); | |
534 | use_new = diff_mnemonic_prefix(diff->base.new_src, false); | |
535 | } | |
536 | ||
537 | if (!diff->base.opts.old_prefix) | |
538 | diff->base.opts.old_prefix = use_old; | |
539 | if (!diff->base.opts.new_prefix) | |
540 | diff->base.opts.new_prefix = use_new; | |
541 | } | |
542 | ||
543 | /* strdup prefix from pool so we're not dependent on external data */ | |
544 | diff->base.opts.old_prefix = diff_strdup_prefix(pool, diff->base.opts.old_prefix); | |
545 | diff->base.opts.new_prefix = diff_strdup_prefix(pool, diff->base.opts.new_prefix); | |
546 | ||
547 | if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) { | |
548 | const char *tmp_prefix = diff->base.opts.old_prefix; | |
549 | diff->base.opts.old_prefix = diff->base.opts.new_prefix; | |
550 | diff->base.opts.new_prefix = tmp_prefix; | |
551 | } | |
552 | ||
553 | git_config_free(cfg); | |
554 | ||
555 | /* check strdup results for error */ | |
556 | return (!diff->base.opts.old_prefix || !diff->base.opts.new_prefix) ? -1 : 0; | |
557 | } | |
558 | ||
559 | int git_diff__oid_for_file( | |
560 | git_oid *out, | |
561 | git_diff *diff, | |
562 | const char *path, | |
563 | uint16_t mode, | |
22a2d3d5 | 564 | git_object_size_t size) |
9be638ec ET |
565 | { |
566 | git_index_entry entry; | |
567 | ||
22a2d3d5 | 568 | if (size > UINT32_MAX) { |
ac3d33df JK |
569 | git_error_set(GIT_ERROR_NOMEMORY, "file size overflow (for 32-bits) on '%s'", path); |
570 | return -1; | |
571 | } | |
572 | ||
9be638ec ET |
573 | memset(&entry, 0, sizeof(entry)); |
574 | entry.mode = mode; | |
ac3d33df | 575 | entry.file_size = (uint32_t)size; |
9be638ec ET |
576 | entry.path = (char *)path; |
577 | ||
578 | return git_diff__oid_for_entry(out, diff, &entry, mode, NULL); | |
579 | } | |
580 | ||
581 | int git_diff__oid_for_entry( | |
582 | git_oid *out, | |
583 | git_diff *d, | |
584 | const git_index_entry *src, | |
585 | uint16_t mode, | |
586 | const git_oid *update_match) | |
587 | { | |
588 | git_diff_generated *diff; | |
589 | git_buf full_path = GIT_BUF_INIT; | |
590 | git_index_entry entry = *src; | |
591 | git_filter_list *fl = NULL; | |
592 | int error = 0; | |
593 | ||
c25aa7cd | 594 | GIT_ASSERT(d->type == GIT_DIFF_TYPE_GENERATED); |
9be638ec ET |
595 | diff = (git_diff_generated *)d; |
596 | ||
597 | memset(out, 0, sizeof(*out)); | |
598 | ||
c25aa7cd | 599 | if (git_repository_workdir_path(&full_path, diff->base.repo, entry.path) < 0) |
9be638ec ET |
600 | return -1; |
601 | ||
602 | if (!mode) { | |
603 | struct stat st; | |
604 | ||
605 | diff->base.perf.stat_calls++; | |
606 | ||
607 | if (p_stat(full_path.ptr, &st) < 0) { | |
608 | error = git_path_set_error(errno, entry.path, "stat"); | |
ac3d33df | 609 | git_buf_dispose(&full_path); |
9be638ec ET |
610 | return error; |
611 | } | |
612 | ||
613 | git_index_entry__init_from_stat(&entry, | |
614 | &st, (diff->diffcaps & GIT_DIFFCAPS_TRUST_MODE_BITS) != 0); | |
615 | } | |
616 | ||
617 | /* calculate OID for file if possible */ | |
618 | if (S_ISGITLINK(mode)) { | |
619 | git_submodule *sm; | |
620 | ||
621 | if (!git_submodule_lookup(&sm, diff->base.repo, entry.path)) { | |
622 | const git_oid *sm_oid = git_submodule_wd_id(sm); | |
623 | if (sm_oid) | |
624 | git_oid_cpy(out, sm_oid); | |
625 | git_submodule_free(sm); | |
626 | } else { | |
627 | /* if submodule lookup failed probably just in an intermediate | |
628 | * state where some init hasn't happened, so ignore the error | |
629 | */ | |
ac3d33df | 630 | git_error_clear(); |
9be638ec ET |
631 | } |
632 | } else if (S_ISLNK(mode)) { | |
633 | error = git_odb__hashlink(out, full_path.ptr); | |
634 | diff->base.perf.oid_calculations++; | |
635 | } else if (!git__is_sizet(entry.file_size)) { | |
ac3d33df | 636 | git_error_set(GIT_ERROR_NOMEMORY, "file size overflow (for 32-bits) on '%s'", |
9be638ec ET |
637 | entry.path); |
638 | error = -1; | |
639 | } else if (!(error = git_filter_list_load(&fl, | |
640 | diff->base.repo, NULL, entry.path, | |
641 | GIT_FILTER_TO_ODB, GIT_FILTER_ALLOW_UNSAFE))) | |
642 | { | |
643 | int fd = git_futils_open_ro(full_path.ptr); | |
644 | if (fd < 0) | |
645 | error = fd; | |
646 | else { | |
647 | error = git_odb__hashfd_filtered( | |
ac3d33df | 648 | out, fd, (size_t)entry.file_size, GIT_OBJECT_BLOB, fl); |
9be638ec ET |
649 | p_close(fd); |
650 | diff->base.perf.oid_calculations++; | |
651 | } | |
652 | ||
653 | git_filter_list_free(fl); | |
654 | } | |
655 | ||
656 | /* update index for entry if requested */ | |
657 | if (!error && update_match && git_oid_equal(out, update_match)) { | |
658 | git_index *idx; | |
659 | git_index_entry updated_entry; | |
660 | ||
661 | memcpy(&updated_entry, &entry, sizeof(git_index_entry)); | |
662 | updated_entry.mode = mode; | |
663 | git_oid_cpy(&updated_entry.id, out); | |
664 | ||
665 | if (!(error = git_repository_index__weakptr(&idx, | |
666 | diff->base.repo))) { | |
667 | error = git_index_add(idx, &updated_entry); | |
668 | diff->index_updated = true; | |
669 | } | |
670 | } | |
671 | ||
ac3d33df | 672 | git_buf_dispose(&full_path); |
9be638ec ET |
673 | return error; |
674 | } | |
675 | ||
676 | typedef struct { | |
677 | git_repository *repo; | |
678 | git_iterator *old_iter; | |
679 | git_iterator *new_iter; | |
680 | const git_index_entry *oitem; | |
681 | const git_index_entry *nitem; | |
c25aa7cd PP |
682 | git_strmap *submodule_cache; |
683 | bool submodule_cache_initialized; | |
9be638ec ET |
684 | } diff_in_progress; |
685 | ||
686 | #define MODE_BITS_MASK 0000777 | |
687 | ||
688 | static int maybe_modified_submodule( | |
689 | git_delta_t *status, | |
690 | git_oid *found_oid, | |
691 | git_diff_generated *diff, | |
692 | diff_in_progress *info) | |
693 | { | |
694 | int error = 0; | |
695 | git_submodule *sub; | |
696 | unsigned int sm_status = 0; | |
697 | git_submodule_ignore_t ign = diff->base.opts.ignore_submodules; | |
c25aa7cd | 698 | git_strmap *submodule_cache = NULL; |
9be638ec ET |
699 | |
700 | *status = GIT_DELTA_UNMODIFIED; | |
701 | ||
702 | if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_SUBMODULES) || | |
703 | ign == GIT_SUBMODULE_IGNORE_ALL) | |
704 | return 0; | |
705 | ||
c25aa7cd PP |
706 | if (diff->base.repo->submodule_cache != NULL) { |
707 | submodule_cache = diff->base.repo->submodule_cache; | |
708 | } else { | |
709 | if (!info->submodule_cache_initialized) { | |
710 | info->submodule_cache_initialized = true; | |
711 | /* | |
712 | * Try to cache the submodule information to avoid having to parse it for | |
713 | * every submodule. It is okay if it fails, the cache will still be NULL | |
714 | * and the submodules will be attempted to be looked up individually. | |
715 | */ | |
716 | git_submodule_cache_init(&info->submodule_cache, diff->base.repo); | |
717 | } | |
718 | submodule_cache = info->submodule_cache; | |
719 | } | |
720 | ||
721 | if ((error = git_submodule__lookup_with_cache( | |
722 | &sub, diff->base.repo, info->nitem->path, submodule_cache)) < 0) { | |
9be638ec ET |
723 | |
724 | /* GIT_EEXISTS means dir with .git in it was found - ignore it */ | |
725 | if (error == GIT_EEXISTS) { | |
ac3d33df | 726 | git_error_clear(); |
9be638ec ET |
727 | error = 0; |
728 | } | |
729 | return error; | |
730 | } | |
731 | ||
732 | if (ign <= 0 && git_submodule_ignore(sub) == GIT_SUBMODULE_IGNORE_ALL) | |
733 | /* ignore it */; | |
734 | else if ((error = git_submodule__status( | |
735 | &sm_status, NULL, NULL, found_oid, sub, ign)) < 0) | |
736 | /* return error below */; | |
737 | ||
738 | /* check IS_WD_UNMODIFIED because this case is only used | |
739 | * when the new side of the diff is the working directory | |
740 | */ | |
741 | else if (!GIT_SUBMODULE_STATUS_IS_WD_UNMODIFIED(sm_status)) | |
742 | *status = GIT_DELTA_MODIFIED; | |
743 | ||
744 | /* now that we have a HEAD OID, check if HEAD moved */ | |
745 | else if ((sm_status & GIT_SUBMODULE_STATUS_IN_WD) != 0 && | |
746 | !git_oid_equal(&info->oitem->id, found_oid)) | |
747 | *status = GIT_DELTA_MODIFIED; | |
748 | ||
749 | git_submodule_free(sub); | |
750 | return error; | |
751 | } | |
752 | ||
753 | static int maybe_modified( | |
754 | git_diff_generated *diff, | |
755 | diff_in_progress *info) | |
756 | { | |
757 | git_oid noid; | |
758 | git_delta_t status = GIT_DELTA_MODIFIED; | |
759 | const git_index_entry *oitem = info->oitem; | |
760 | const git_index_entry *nitem = info->nitem; | |
761 | unsigned int omode = oitem->mode; | |
762 | unsigned int nmode = nitem->mode; | |
22a2d3d5 | 763 | bool new_is_workdir = (info->new_iter->type == GIT_ITERATOR_WORKDIR); |
9be638ec ET |
764 | bool modified_uncertain = false; |
765 | const char *matched_pathspec; | |
766 | int error = 0; | |
767 | ||
768 | if (!diff_pathspec_match(&matched_pathspec, diff, oitem)) | |
769 | return 0; | |
770 | ||
771 | memset(&noid, 0, sizeof(noid)); | |
772 | ||
773 | /* on platforms with no symlinks, preserve mode of existing symlinks */ | |
774 | if (S_ISLNK(omode) && S_ISREG(nmode) && new_is_workdir && | |
775 | !(diff->diffcaps & GIT_DIFFCAPS_HAS_SYMLINKS)) | |
776 | nmode = omode; | |
777 | ||
778 | /* on platforms with no execmode, just preserve old mode */ | |
779 | if (!(diff->diffcaps & GIT_DIFFCAPS_TRUST_MODE_BITS) && | |
780 | (nmode & MODE_BITS_MASK) != (omode & MODE_BITS_MASK) && | |
781 | new_is_workdir) | |
782 | nmode = (nmode & ~MODE_BITS_MASK) | (omode & MODE_BITS_MASK); | |
783 | ||
784 | /* if one side is a conflict, mark the whole delta as conflicted */ | |
785 | if (git_index_entry_is_conflict(oitem) || | |
786 | git_index_entry_is_conflict(nitem)) { | |
787 | status = GIT_DELTA_CONFLICTED; | |
788 | ||
789 | /* support "assume unchanged" (poorly, b/c we still stat everything) */ | |
ac3d33df | 790 | } else if ((oitem->flags & GIT_INDEX_ENTRY_VALID) != 0) { |
9be638ec ET |
791 | status = GIT_DELTA_UNMODIFIED; |
792 | ||
793 | /* support "skip worktree" index bit */ | |
ac3d33df | 794 | } else if ((oitem->flags_extended & GIT_INDEX_ENTRY_SKIP_WORKTREE) != 0) { |
9be638ec ET |
795 | status = GIT_DELTA_UNMODIFIED; |
796 | ||
797 | /* if basic type of file changed, then split into delete and add */ | |
798 | } else if (GIT_MODE_TYPE(omode) != GIT_MODE_TYPE(nmode)) { | |
799 | if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE)) { | |
800 | status = GIT_DELTA_TYPECHANGE; | |
801 | } | |
802 | ||
803 | else if (nmode == GIT_FILEMODE_UNREADABLE) { | |
804 | if (!(error = diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem, NULL))) | |
805 | error = diff_delta__from_one(diff, GIT_DELTA_UNREADABLE, NULL, nitem); | |
806 | return error; | |
807 | } | |
808 | ||
809 | else { | |
810 | if (!(error = diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem, NULL))) | |
811 | error = diff_delta__from_one(diff, GIT_DELTA_ADDED, NULL, nitem); | |
812 | return error; | |
813 | } | |
814 | ||
815 | /* if oids and modes match (and are valid), then file is unmodified */ | |
816 | } else if (git_oid_equal(&oitem->id, &nitem->id) && | |
817 | omode == nmode && | |
22a2d3d5 | 818 | !git_oid_is_zero(&oitem->id)) { |
9be638ec ET |
819 | status = GIT_DELTA_UNMODIFIED; |
820 | ||
821 | /* if we have an unknown OID and a workdir iterator, then check some | |
822 | * circumstances that can accelerate things or need special handling | |
823 | */ | |
22a2d3d5 | 824 | } else if (git_oid_is_zero(&nitem->id) && new_is_workdir) { |
9be638ec ET |
825 | bool use_ctime = |
826 | ((diff->diffcaps & GIT_DIFFCAPS_TRUST_CTIME) != 0); | |
827 | git_index *index = git_iterator_index(info->new_iter); | |
828 | ||
829 | status = GIT_DELTA_UNMODIFIED; | |
830 | ||
831 | if (S_ISGITLINK(nmode)) { | |
832 | if ((error = maybe_modified_submodule(&status, &noid, diff, info)) < 0) | |
833 | return error; | |
834 | } | |
835 | ||
836 | /* if the stat data looks different, then mark modified - this just | |
837 | * means that the OID will be recalculated below to confirm change | |
838 | */ | |
839 | else if (omode != nmode || oitem->file_size != nitem->file_size) { | |
840 | status = GIT_DELTA_MODIFIED; | |
841 | modified_uncertain = | |
842 | (oitem->file_size <= 0 && nitem->file_size > 0); | |
843 | } | |
844 | else if (!git_index_time_eq(&oitem->mtime, &nitem->mtime) || | |
845 | (use_ctime && !git_index_time_eq(&oitem->ctime, &nitem->ctime)) || | |
846 | oitem->ino != nitem->ino || | |
847 | oitem->uid != nitem->uid || | |
848 | oitem->gid != nitem->gid || | |
849 | git_index_entry_newer_than_index(nitem, index)) | |
850 | { | |
851 | status = GIT_DELTA_MODIFIED; | |
852 | modified_uncertain = true; | |
853 | } | |
854 | ||
855 | /* if mode is GITLINK and submodules are ignored, then skip */ | |
856 | } else if (S_ISGITLINK(nmode) && | |
857 | DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_SUBMODULES)) { | |
858 | status = GIT_DELTA_UNMODIFIED; | |
859 | } | |
860 | ||
861 | /* if we got here and decided that the files are modified, but we | |
862 | * haven't calculated the OID of the new item, then calculate it now | |
863 | */ | |
22a2d3d5 | 864 | if (modified_uncertain && git_oid_is_zero(&nitem->id)) { |
9be638ec ET |
865 | const git_oid *update_check = |
866 | DIFF_FLAG_IS_SET(diff, GIT_DIFF_UPDATE_INDEX) && omode == nmode ? | |
867 | &oitem->id : NULL; | |
868 | ||
869 | if ((error = git_diff__oid_for_entry( | |
870 | &noid, &diff->base, nitem, nmode, update_check)) < 0) | |
871 | return error; | |
872 | ||
873 | /* if oid matches, then mark unmodified (except submodules, where | |
874 | * the filesystem content may be modified even if the oid still | |
875 | * matches between the index and the workdir HEAD) | |
876 | */ | |
877 | if (omode == nmode && !S_ISGITLINK(omode) && | |
878 | git_oid_equal(&oitem->id, &noid)) | |
879 | status = GIT_DELTA_UNMODIFIED; | |
880 | } | |
881 | ||
882 | /* If we want case changes, then break this into a delete of the old | |
883 | * and an add of the new so that consumers can act accordingly (eg, | |
884 | * checkout will update the case on disk.) | |
885 | */ | |
886 | if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE) && | |
887 | DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_CASECHANGE) && | |
888 | strcmp(oitem->path, nitem->path) != 0) { | |
889 | ||
890 | if (!(error = diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem, NULL))) | |
891 | error = diff_delta__from_one(diff, GIT_DELTA_ADDED, NULL, nitem); | |
892 | ||
893 | return error; | |
894 | } | |
895 | ||
896 | return diff_delta__from_two( | |
897 | diff, status, oitem, omode, nitem, nmode, | |
22a2d3d5 | 898 | git_oid_is_zero(&noid) ? NULL : &noid, matched_pathspec); |
9be638ec ET |
899 | } |
900 | ||
901 | static bool entry_is_prefixed( | |
902 | git_diff_generated *diff, | |
903 | const git_index_entry *item, | |
904 | const git_index_entry *prefix_item) | |
905 | { | |
906 | size_t pathlen; | |
907 | ||
908 | if (!item || diff->base.pfxcomp(item->path, prefix_item->path) != 0) | |
909 | return false; | |
910 | ||
911 | pathlen = strlen(prefix_item->path); | |
912 | ||
913 | return (prefix_item->path[pathlen - 1] == '/' || | |
914 | item->path[pathlen] == '\0' || | |
915 | item->path[pathlen] == '/'); | |
916 | } | |
917 | ||
918 | static int iterator_current( | |
919 | const git_index_entry **entry, | |
920 | git_iterator *iterator) | |
921 | { | |
922 | int error; | |
923 | ||
924 | if ((error = git_iterator_current(entry, iterator)) == GIT_ITEROVER) { | |
925 | *entry = NULL; | |
926 | error = 0; | |
927 | } | |
928 | ||
929 | return error; | |
930 | } | |
931 | ||
932 | static int iterator_advance( | |
933 | const git_index_entry **entry, | |
934 | git_iterator *iterator) | |
935 | { | |
936 | const git_index_entry *prev_entry = *entry; | |
937 | int cmp, error; | |
938 | ||
939 | /* if we're looking for conflicts, we only want to report | |
940 | * one conflict for each file, instead of all three sides. | |
941 | * so if this entry is a conflict for this file, and the | |
942 | * previous one was a conflict for the same file, skip it. | |
943 | */ | |
944 | while ((error = git_iterator_advance(entry, iterator)) == 0) { | |
945 | if (!(iterator->flags & GIT_ITERATOR_INCLUDE_CONFLICTS) || | |
946 | !git_index_entry_is_conflict(prev_entry) || | |
947 | !git_index_entry_is_conflict(*entry)) | |
948 | break; | |
949 | ||
950 | cmp = (iterator->flags & GIT_ITERATOR_IGNORE_CASE) ? | |
951 | strcasecmp(prev_entry->path, (*entry)->path) : | |
952 | strcmp(prev_entry->path, (*entry)->path); | |
953 | ||
954 | if (cmp) | |
955 | break; | |
956 | } | |
957 | ||
958 | if (error == GIT_ITEROVER) { | |
959 | *entry = NULL; | |
960 | error = 0; | |
961 | } | |
962 | ||
963 | return error; | |
964 | } | |
965 | ||
966 | static int iterator_advance_into( | |
967 | const git_index_entry **entry, | |
968 | git_iterator *iterator) | |
969 | { | |
970 | int error; | |
971 | ||
972 | if ((error = git_iterator_advance_into(entry, iterator)) == GIT_ITEROVER) { | |
973 | *entry = NULL; | |
974 | error = 0; | |
975 | } | |
976 | ||
977 | return error; | |
978 | } | |
979 | ||
980 | static int iterator_advance_over( | |
981 | const git_index_entry **entry, | |
982 | git_iterator_status_t *status, | |
983 | git_iterator *iterator) | |
984 | { | |
985 | int error = git_iterator_advance_over(entry, status, iterator); | |
986 | ||
987 | if (error == GIT_ITEROVER) { | |
988 | *entry = NULL; | |
989 | error = 0; | |
990 | } | |
991 | ||
992 | return error; | |
993 | } | |
994 | ||
995 | static int handle_unmatched_new_item( | |
996 | git_diff_generated *diff, diff_in_progress *info) | |
997 | { | |
998 | int error = 0; | |
999 | const git_index_entry *nitem = info->nitem; | |
1000 | git_delta_t delta_type = GIT_DELTA_UNTRACKED; | |
1001 | bool contains_oitem; | |
1002 | ||
1003 | /* check if this is a prefix of the other side */ | |
1004 | contains_oitem = entry_is_prefixed(diff, info->oitem, nitem); | |
1005 | ||
1006 | /* update delta_type if this item is conflicted */ | |
1007 | if (git_index_entry_is_conflict(nitem)) | |
1008 | delta_type = GIT_DELTA_CONFLICTED; | |
1009 | ||
1010 | /* update delta_type if this item is ignored */ | |
1011 | else if (git_iterator_current_is_ignored(info->new_iter)) | |
1012 | delta_type = GIT_DELTA_IGNORED; | |
1013 | ||
1014 | if (nitem->mode == GIT_FILEMODE_TREE) { | |
1015 | bool recurse_into_dir = contains_oitem; | |
1016 | ||
1017 | /* check if user requests recursion into this type of dir */ | |
1018 | recurse_into_dir = contains_oitem || | |
1019 | (delta_type == GIT_DELTA_UNTRACKED && | |
1020 | DIFF_FLAG_IS_SET(diff, GIT_DIFF_RECURSE_UNTRACKED_DIRS)) || | |
1021 | (delta_type == GIT_DELTA_IGNORED && | |
1022 | DIFF_FLAG_IS_SET(diff, GIT_DIFF_RECURSE_IGNORED_DIRS)); | |
1023 | ||
1024 | /* do not advance into directories that contain a .git file */ | |
1025 | if (recurse_into_dir && !contains_oitem) { | |
1026 | git_buf *full = NULL; | |
1027 | if (git_iterator_current_workdir_path(&full, info->new_iter) < 0) | |
1028 | return -1; | |
1029 | if (full && git_path_contains(full, DOT_GIT)) { | |
1030 | /* TODO: warning if not a valid git repository */ | |
1031 | recurse_into_dir = false; | |
1032 | } | |
1033 | } | |
1034 | ||
1035 | /* still have to look into untracked directories to match core git - | |
1036 | * with no untracked files, directory is treated as ignored | |
1037 | */ | |
1038 | if (!recurse_into_dir && | |
1039 | delta_type == GIT_DELTA_UNTRACKED && | |
1040 | DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_ENABLE_FAST_UNTRACKED_DIRS)) | |
1041 | { | |
1042 | git_diff_delta *last; | |
1043 | git_iterator_status_t untracked_state; | |
1044 | ||
1045 | /* attempt to insert record for this directory */ | |
1046 | if ((error = diff_delta__from_one(diff, delta_type, NULL, nitem)) != 0) | |
1047 | return error; | |
1048 | ||
1049 | /* if delta wasn't created (because of rules), just skip ahead */ | |
1050 | last = diff_delta__last_for_item(diff, nitem); | |
1051 | if (!last) | |
1052 | return iterator_advance(&info->nitem, info->new_iter); | |
1053 | ||
1054 | /* iterate into dir looking for an actual untracked file */ | |
1055 | if ((error = iterator_advance_over( | |
1056 | &info->nitem, &untracked_state, info->new_iter)) < 0) | |
1057 | return error; | |
1058 | ||
1059 | /* if we found nothing that matched our pathlist filter, exclude */ | |
1060 | if (untracked_state == GIT_ITERATOR_STATUS_FILTERED) { | |
1061 | git_vector_pop(&diff->base.deltas); | |
1062 | git__free(last); | |
1063 | } | |
1064 | ||
1065 | /* if we found nothing or just ignored items, update the record */ | |
1066 | if (untracked_state == GIT_ITERATOR_STATUS_IGNORED || | |
1067 | untracked_state == GIT_ITERATOR_STATUS_EMPTY) { | |
1068 | last->status = GIT_DELTA_IGNORED; | |
1069 | ||
1070 | /* remove the record if we don't want ignored records */ | |
1071 | if (DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_IGNORED)) { | |
1072 | git_vector_pop(&diff->base.deltas); | |
1073 | git__free(last); | |
1074 | } | |
1075 | } | |
1076 | ||
1077 | return 0; | |
1078 | } | |
1079 | ||
1080 | /* try to advance into directory if necessary */ | |
1081 | if (recurse_into_dir) { | |
1082 | error = iterator_advance_into(&info->nitem, info->new_iter); | |
1083 | ||
1084 | /* if directory is empty, can't advance into it, so skip it */ | |
1085 | if (error == GIT_ENOTFOUND) { | |
ac3d33df | 1086 | git_error_clear(); |
9be638ec ET |
1087 | error = iterator_advance(&info->nitem, info->new_iter); |
1088 | } | |
1089 | ||
1090 | return error; | |
1091 | } | |
1092 | } | |
1093 | ||
1094 | else if (delta_type == GIT_DELTA_IGNORED && | |
1095 | DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_RECURSE_IGNORED_DIRS) && | |
1096 | git_iterator_current_tree_is_ignored(info->new_iter)) | |
1097 | /* item contained in ignored directory, so skip over it */ | |
1098 | return iterator_advance(&info->nitem, info->new_iter); | |
1099 | ||
22a2d3d5 | 1100 | else if (info->new_iter->type != GIT_ITERATOR_WORKDIR) { |
9be638ec ET |
1101 | if (delta_type != GIT_DELTA_CONFLICTED) |
1102 | delta_type = GIT_DELTA_ADDED; | |
1103 | } | |
1104 | ||
1105 | else if (nitem->mode == GIT_FILEMODE_COMMIT) { | |
1106 | /* ignore things that are not actual submodules */ | |
1107 | if (git_submodule_lookup(NULL, info->repo, nitem->path) != 0) { | |
ac3d33df | 1108 | git_error_clear(); |
9be638ec ET |
1109 | delta_type = GIT_DELTA_IGNORED; |
1110 | ||
1111 | /* if this contains a tracked item, treat as normal TREE */ | |
1112 | if (contains_oitem) { | |
1113 | error = iterator_advance_into(&info->nitem, info->new_iter); | |
1114 | if (error != GIT_ENOTFOUND) | |
1115 | return error; | |
1116 | ||
ac3d33df | 1117 | git_error_clear(); |
9be638ec ET |
1118 | return iterator_advance(&info->nitem, info->new_iter); |
1119 | } | |
1120 | } | |
1121 | } | |
1122 | ||
1123 | else if (nitem->mode == GIT_FILEMODE_UNREADABLE) { | |
1124 | if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_UNREADABLE_AS_UNTRACKED)) | |
1125 | delta_type = GIT_DELTA_UNTRACKED; | |
1126 | else | |
1127 | delta_type = GIT_DELTA_UNREADABLE; | |
1128 | } | |
1129 | ||
1130 | /* Actually create the record for this item if necessary */ | |
1131 | if ((error = diff_delta__from_one(diff, delta_type, NULL, nitem)) != 0) | |
1132 | return error; | |
1133 | ||
1134 | /* If user requested TYPECHANGE records, then check for that instead of | |
1135 | * just generating an ADDED/UNTRACKED record | |
1136 | */ | |
1137 | if (delta_type != GIT_DELTA_IGNORED && | |
1138 | DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE_TREES) && | |
1139 | contains_oitem) | |
1140 | { | |
1141 | /* this entry was prefixed with a tree - make TYPECHANGE */ | |
1142 | git_diff_delta *last = diff_delta__last_for_item(diff, nitem); | |
1143 | if (last) { | |
1144 | last->status = GIT_DELTA_TYPECHANGE; | |
1145 | last->old_file.mode = GIT_FILEMODE_TREE; | |
1146 | } | |
1147 | } | |
1148 | ||
1149 | return iterator_advance(&info->nitem, info->new_iter); | |
1150 | } | |
1151 | ||
1152 | static int handle_unmatched_old_item( | |
1153 | git_diff_generated *diff, diff_in_progress *info) | |
1154 | { | |
1155 | git_delta_t delta_type = GIT_DELTA_DELETED; | |
1156 | int error; | |
1157 | ||
1158 | /* update delta_type if this item is conflicted */ | |
1159 | if (git_index_entry_is_conflict(info->oitem)) | |
1160 | delta_type = GIT_DELTA_CONFLICTED; | |
1161 | ||
1162 | if ((error = diff_delta__from_one(diff, delta_type, info->oitem, NULL)) < 0) | |
1163 | return error; | |
1164 | ||
1165 | /* if we are generating TYPECHANGE records then check for that | |
1166 | * instead of just generating a DELETE record | |
1167 | */ | |
1168 | if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE_TREES) && | |
1169 | entry_is_prefixed(diff, info->nitem, info->oitem)) | |
1170 | { | |
1171 | /* this entry has become a tree! convert to TYPECHANGE */ | |
1172 | git_diff_delta *last = diff_delta__last_for_item(diff, info->oitem); | |
1173 | if (last) { | |
1174 | last->status = GIT_DELTA_TYPECHANGE; | |
1175 | last->new_file.mode = GIT_FILEMODE_TREE; | |
1176 | } | |
1177 | ||
1178 | /* If new_iter is a workdir iterator, then this situation | |
1179 | * will certainly be followed by a series of untracked items. | |
1180 | * Unless RECURSE_UNTRACKED_DIRS is set, skip over them... | |
1181 | */ | |
1182 | if (S_ISDIR(info->nitem->mode) && | |
1183 | DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_RECURSE_UNTRACKED_DIRS)) | |
1184 | return iterator_advance(&info->nitem, info->new_iter); | |
1185 | } | |
1186 | ||
1187 | return iterator_advance(&info->oitem, info->old_iter); | |
1188 | } | |
1189 | ||
1190 | static int handle_matched_item( | |
1191 | git_diff_generated *diff, diff_in_progress *info) | |
1192 | { | |
1193 | int error = 0; | |
1194 | ||
1195 | if ((error = maybe_modified(diff, info)) < 0) | |
1196 | return error; | |
1197 | ||
1198 | if (!(error = iterator_advance(&info->oitem, info->old_iter))) | |
1199 | error = iterator_advance(&info->nitem, info->new_iter); | |
1200 | ||
1201 | return error; | |
1202 | } | |
1203 | ||
1204 | int git_diff__from_iterators( | |
1205 | git_diff **out, | |
1206 | git_repository *repo, | |
1207 | git_iterator *old_iter, | |
1208 | git_iterator *new_iter, | |
1209 | const git_diff_options *opts) | |
1210 | { | |
1211 | git_diff_generated *diff; | |
c25aa7cd | 1212 | diff_in_progress info = {0}; |
9be638ec ET |
1213 | int error = 0; |
1214 | ||
1215 | *out = NULL; | |
1216 | ||
1217 | diff = diff_generated_alloc(repo, old_iter, new_iter); | |
ac3d33df | 1218 | GIT_ERROR_CHECK_ALLOC(diff); |
9be638ec ET |
1219 | |
1220 | info.repo = repo; | |
1221 | info.old_iter = old_iter; | |
1222 | info.new_iter = new_iter; | |
1223 | ||
1224 | /* make iterators have matching icase behavior */ | |
1225 | if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE)) { | |
c25aa7cd PP |
1226 | if ((error = git_iterator_set_ignore_case(old_iter, true)) < 0 || |
1227 | (error = git_iterator_set_ignore_case(new_iter, true)) < 0) | |
1228 | goto cleanup; | |
9be638ec ET |
1229 | } |
1230 | ||
1231 | /* finish initialization */ | |
1232 | if ((error = diff_generated_apply_options(diff, opts)) < 0) | |
1233 | goto cleanup; | |
1234 | ||
1235 | if ((error = iterator_current(&info.oitem, old_iter)) < 0 || | |
1236 | (error = iterator_current(&info.nitem, new_iter)) < 0) | |
1237 | goto cleanup; | |
1238 | ||
1239 | /* run iterators building diffs */ | |
1240 | while (!error && (info.oitem || info.nitem)) { | |
1241 | int cmp; | |
1242 | ||
1243 | /* report progress */ | |
1244 | if (opts && opts->progress_cb) { | |
1245 | if ((error = opts->progress_cb(&diff->base, | |
1246 | info.oitem ? info.oitem->path : NULL, | |
1247 | info.nitem ? info.nitem->path : NULL, | |
1248 | opts->payload))) | |
1249 | break; | |
1250 | } | |
1251 | ||
1252 | cmp = info.oitem ? | |
1253 | (info.nitem ? diff->base.entrycomp(info.oitem, info.nitem) : -1) : 1; | |
1254 | ||
1255 | /* create DELETED records for old items not matched in new */ | |
1256 | if (cmp < 0) | |
1257 | error = handle_unmatched_old_item(diff, &info); | |
1258 | ||
1259 | /* create ADDED, TRACKED, or IGNORED records for new items not | |
1260 | * matched in old (and/or descend into directories as needed) | |
1261 | */ | |
1262 | else if (cmp > 0) | |
1263 | error = handle_unmatched_new_item(diff, &info); | |
1264 | ||
1265 | /* otherwise item paths match, so create MODIFIED record | |
1266 | * (or ADDED and DELETED pair if type changed) | |
1267 | */ | |
1268 | else | |
1269 | error = handle_matched_item(diff, &info); | |
1270 | } | |
1271 | ||
1272 | diff->base.perf.stat_calls += | |
1273 | old_iter->stat_calls + new_iter->stat_calls; | |
1274 | ||
1275 | cleanup: | |
1276 | if (!error) | |
1277 | *out = &diff->base; | |
1278 | else | |
1279 | git_diff_free(&diff->base); | |
c25aa7cd PP |
1280 | if (info.submodule_cache) |
1281 | git_submodule_cache_free(info.submodule_cache); | |
9be638ec ET |
1282 | |
1283 | return error; | |
1284 | } | |
1285 | ||
22a2d3d5 UG |
1286 | static int diff_prepare_iterator_opts(char **prefix, git_iterator_options *a, int aflags, |
1287 | git_iterator_options *b, int bflags, | |
1288 | const git_diff_options *opts) | |
1289 | { | |
1290 | GIT_ERROR_CHECK_VERSION(opts, GIT_DIFF_OPTIONS_VERSION, "git_diff_options"); | |
1291 | ||
1292 | *prefix = NULL; | |
1293 | ||
1294 | if (opts && (opts->flags & GIT_DIFF_DISABLE_PATHSPEC_MATCH)) { | |
1295 | a->pathlist.strings = opts->pathspec.strings; | |
1296 | a->pathlist.count = opts->pathspec.count; | |
1297 | b->pathlist.strings = opts->pathspec.strings; | |
1298 | b->pathlist.count = opts->pathspec.count; | |
1299 | } else if (opts) { | |
1300 | *prefix = git_pathspec_prefix(&opts->pathspec); | |
1301 | GIT_ERROR_CHECK_ALLOC(prefix); | |
1302 | } | |
1303 | ||
1304 | a->flags = aflags; | |
1305 | b->flags = bflags; | |
1306 | a->start = b->start = *prefix; | |
1307 | a->end = b->end = *prefix; | |
1308 | ||
1309 | return 0; | |
1310 | } | |
9be638ec ET |
1311 | |
1312 | int git_diff_tree_to_tree( | |
1313 | git_diff **out, | |
1314 | git_repository *repo, | |
1315 | git_tree *old_tree, | |
1316 | git_tree *new_tree, | |
1317 | const git_diff_options *opts) | |
1318 | { | |
9be638ec | 1319 | git_iterator_flag_t iflag = GIT_ITERATOR_DONT_IGNORE_CASE; |
22a2d3d5 UG |
1320 | git_iterator_options a_opts = GIT_ITERATOR_OPTIONS_INIT, |
1321 | b_opts = GIT_ITERATOR_OPTIONS_INIT; | |
1322 | git_iterator *a = NULL, *b = NULL; | |
1323 | git_diff *diff = NULL; | |
1324 | char *prefix = NULL; | |
9be638ec ET |
1325 | int error = 0; |
1326 | ||
c25aa7cd PP |
1327 | GIT_ASSERT_ARG(out); |
1328 | GIT_ASSERT_ARG(repo); | |
9be638ec ET |
1329 | |
1330 | *out = NULL; | |
1331 | ||
1332 | /* for tree to tree diff, be case sensitive even if the index is | |
1333 | * currently case insensitive, unless the user explicitly asked | |
1334 | * for case insensitivity | |
1335 | */ | |
1336 | if (opts && (opts->flags & GIT_DIFF_IGNORE_CASE) != 0) | |
1337 | iflag = GIT_ITERATOR_IGNORE_CASE; | |
1338 | ||
22a2d3d5 UG |
1339 | if ((error = diff_prepare_iterator_opts(&prefix, &a_opts, iflag, &b_opts, iflag, opts)) < 0 || |
1340 | (error = git_iterator_for_tree(&a, old_tree, &a_opts)) < 0 || | |
1341 | (error = git_iterator_for_tree(&b, new_tree, &b_opts)) < 0 || | |
1342 | (error = git_diff__from_iterators(&diff, repo, a, b, opts)) < 0) | |
1343 | goto out; | |
9be638ec | 1344 | |
22a2d3d5 UG |
1345 | *out = diff; |
1346 | diff = NULL; | |
1347 | out: | |
1348 | git_iterator_free(a); | |
1349 | git_iterator_free(b); | |
1350 | git_diff_free(diff); | |
1351 | git__free(prefix); | |
9be638ec ET |
1352 | |
1353 | return error; | |
1354 | } | |
1355 | ||
1356 | static int diff_load_index(git_index **index, git_repository *repo) | |
1357 | { | |
1358 | int error = git_repository_index__weakptr(index, repo); | |
1359 | ||
1360 | /* reload the repository index when user did not pass one in */ | |
1361 | if (!error && git_index_read(*index, false) < 0) | |
ac3d33df | 1362 | git_error_clear(); |
9be638ec ET |
1363 | |
1364 | return error; | |
1365 | } | |
1366 | ||
1367 | int git_diff_tree_to_index( | |
1368 | git_diff **out, | |
1369 | git_repository *repo, | |
1370 | git_tree *old_tree, | |
1371 | git_index *index, | |
1372 | const git_diff_options *opts) | |
1373 | { | |
9be638ec ET |
1374 | git_iterator_flag_t iflag = GIT_ITERATOR_DONT_IGNORE_CASE | |
1375 | GIT_ITERATOR_INCLUDE_CONFLICTS; | |
22a2d3d5 UG |
1376 | git_iterator_options a_opts = GIT_ITERATOR_OPTIONS_INIT, |
1377 | b_opts = GIT_ITERATOR_OPTIONS_INIT; | |
1378 | git_iterator *a = NULL, *b = NULL; | |
1379 | git_diff *diff = NULL; | |
1380 | char *prefix = NULL; | |
9be638ec ET |
1381 | bool index_ignore_case = false; |
1382 | int error = 0; | |
1383 | ||
c25aa7cd PP |
1384 | GIT_ASSERT_ARG(out); |
1385 | GIT_ASSERT_ARG(repo); | |
9be638ec ET |
1386 | |
1387 | *out = NULL; | |
1388 | ||
1389 | if (!index && (error = diff_load_index(&index, repo)) < 0) | |
1390 | return error; | |
1391 | ||
1392 | index_ignore_case = index->ignore_case; | |
1393 | ||
22a2d3d5 UG |
1394 | if ((error = diff_prepare_iterator_opts(&prefix, &a_opts, iflag, &b_opts, iflag, opts)) < 0 || |
1395 | (error = git_iterator_for_tree(&a, old_tree, &a_opts)) < 0 || | |
1396 | (error = git_iterator_for_index(&b, repo, index, &b_opts)) < 0 || | |
1397 | (error = git_diff__from_iterators(&diff, repo, a, b, opts)) < 0) | |
1398 | goto out; | |
9be638ec ET |
1399 | |
1400 | /* if index is in case-insensitive order, re-sort deltas to match */ | |
22a2d3d5 UG |
1401 | if (index_ignore_case) |
1402 | diff_set_ignore_case(diff, true); | |
9be638ec | 1403 | |
22a2d3d5 UG |
1404 | *out = diff; |
1405 | diff = NULL; | |
1406 | out: | |
1407 | git_iterator_free(a); | |
1408 | git_iterator_free(b); | |
1409 | git_diff_free(diff); | |
1410 | git__free(prefix); | |
9be638ec ET |
1411 | |
1412 | return error; | |
1413 | } | |
1414 | ||
1415 | int git_diff_index_to_workdir( | |
1416 | git_diff **out, | |
1417 | git_repository *repo, | |
1418 | git_index *index, | |
1419 | const git_diff_options *opts) | |
1420 | { | |
22a2d3d5 UG |
1421 | git_iterator_options a_opts = GIT_ITERATOR_OPTIONS_INIT, |
1422 | b_opts = GIT_ITERATOR_OPTIONS_INIT; | |
1423 | git_iterator *a = NULL, *b = NULL; | |
9be638ec | 1424 | git_diff *diff = NULL; |
22a2d3d5 | 1425 | char *prefix = NULL; |
9be638ec ET |
1426 | int error = 0; |
1427 | ||
c25aa7cd PP |
1428 | GIT_ASSERT_ARG(out); |
1429 | GIT_ASSERT_ARG(repo); | |
9be638ec ET |
1430 | |
1431 | *out = NULL; | |
1432 | ||
1433 | if (!index && (error = diff_load_index(&index, repo)) < 0) | |
1434 | return error; | |
1435 | ||
22a2d3d5 UG |
1436 | if ((error = diff_prepare_iterator_opts(&prefix, &a_opts, GIT_ITERATOR_INCLUDE_CONFLICTS, |
1437 | &b_opts, GIT_ITERATOR_DONT_AUTOEXPAND, opts)) < 0 || | |
1438 | (error = git_iterator_for_index(&a, repo, index, &a_opts)) < 0 || | |
1439 | (error = git_iterator_for_workdir(&b, repo, index, NULL, &b_opts)) < 0 || | |
1440 | (error = git_diff__from_iterators(&diff, repo, a, b, opts)) < 0) | |
1441 | goto out; | |
1442 | ||
1443 | if ((diff->opts.flags & GIT_DIFF_UPDATE_INDEX) && ((git_diff_generated *)diff)->index_updated) | |
1444 | if ((error = git_index_write(index)) < 0) | |
1445 | goto out; | |
1446 | ||
1447 | *out = diff; | |
1448 | diff = NULL; | |
1449 | out: | |
1450 | git_iterator_free(a); | |
1451 | git_iterator_free(b); | |
1452 | git_diff_free(diff); | |
1453 | git__free(prefix); | |
9be638ec ET |
1454 | |
1455 | return error; | |
1456 | } | |
1457 | ||
1458 | int git_diff_tree_to_workdir( | |
1459 | git_diff **out, | |
1460 | git_repository *repo, | |
1461 | git_tree *old_tree, | |
1462 | const git_diff_options *opts) | |
1463 | { | |
22a2d3d5 UG |
1464 | git_iterator_options a_opts = GIT_ITERATOR_OPTIONS_INIT, |
1465 | b_opts = GIT_ITERATOR_OPTIONS_INIT; | |
1466 | git_iterator *a = NULL, *b = NULL; | |
9be638ec | 1467 | git_diff *diff = NULL; |
22a2d3d5 | 1468 | char *prefix = NULL; |
9be638ec | 1469 | git_index *index; |
22a2d3d5 | 1470 | int error; |
9be638ec | 1471 | |
c25aa7cd PP |
1472 | GIT_ASSERT_ARG(out); |
1473 | GIT_ASSERT_ARG(repo); | |
9be638ec ET |
1474 | |
1475 | *out = NULL; | |
1476 | ||
22a2d3d5 UG |
1477 | if ((error = diff_prepare_iterator_opts(&prefix, &a_opts, 0, |
1478 | &b_opts, GIT_ITERATOR_DONT_AUTOEXPAND, opts) < 0) || | |
1479 | (error = git_repository_index__weakptr(&index, repo)) < 0 || | |
1480 | (error = git_iterator_for_tree(&a, old_tree, &a_opts)) < 0 || | |
1481 | (error = git_iterator_for_workdir(&b, repo, index, old_tree, &b_opts)) < 0 || | |
1482 | (error = git_diff__from_iterators(&diff, repo, a, b, opts)) < 0) | |
1483 | goto out; | |
1484 | ||
1485 | *out = diff; | |
1486 | diff = NULL; | |
1487 | out: | |
1488 | git_iterator_free(a); | |
1489 | git_iterator_free(b); | |
1490 | git_diff_free(diff); | |
1491 | git__free(prefix); | |
9be638ec ET |
1492 | |
1493 | return error; | |
1494 | } | |
1495 | ||
1496 | int git_diff_tree_to_workdir_with_index( | |
1497 | git_diff **out, | |
1498 | git_repository *repo, | |
1499 | git_tree *tree, | |
1500 | const git_diff_options *opts) | |
1501 | { | |
1502 | git_diff *d1 = NULL, *d2 = NULL; | |
1503 | git_index *index = NULL; | |
1504 | int error = 0; | |
1505 | ||
c25aa7cd PP |
1506 | GIT_ASSERT_ARG(out); |
1507 | GIT_ASSERT_ARG(repo); | |
9be638ec ET |
1508 | |
1509 | *out = NULL; | |
1510 | ||
1511 | if ((error = diff_load_index(&index, repo)) < 0) | |
1512 | return error; | |
1513 | ||
1514 | if (!(error = git_diff_tree_to_index(&d1, repo, tree, index, opts)) && | |
1515 | !(error = git_diff_index_to_workdir(&d2, repo, index, opts))) | |
1516 | error = git_diff_merge(d1, d2); | |
1517 | ||
1518 | git_diff_free(d2); | |
1519 | ||
1520 | if (error) { | |
1521 | git_diff_free(d1); | |
1522 | d1 = NULL; | |
1523 | } | |
1524 | ||
1525 | *out = d1; | |
1526 | return error; | |
1527 | } | |
1528 | ||
1529 | int git_diff_index_to_index( | |
1530 | git_diff **out, | |
1531 | git_repository *repo, | |
1532 | git_index *old_index, | |
1533 | git_index *new_index, | |
1534 | const git_diff_options *opts) | |
1535 | { | |
22a2d3d5 UG |
1536 | git_iterator_options a_opts = GIT_ITERATOR_OPTIONS_INIT, |
1537 | b_opts = GIT_ITERATOR_OPTIONS_INIT; | |
1538 | git_iterator *a = NULL, *b = NULL; | |
1539 | git_diff *diff = NULL; | |
1540 | char *prefix = NULL; | |
1541 | int error; | |
9be638ec | 1542 | |
c25aa7cd PP |
1543 | GIT_ASSERT_ARG(out); |
1544 | GIT_ASSERT_ARG(old_index); | |
1545 | GIT_ASSERT_ARG(new_index); | |
9be638ec ET |
1546 | |
1547 | *out = NULL; | |
1548 | ||
22a2d3d5 UG |
1549 | if ((error = diff_prepare_iterator_opts(&prefix, &a_opts, GIT_ITERATOR_DONT_IGNORE_CASE, |
1550 | &b_opts, GIT_ITERATOR_DONT_IGNORE_CASE, opts) < 0) || | |
1551 | (error = git_iterator_for_index(&a, repo, old_index, &a_opts)) < 0 || | |
1552 | (error = git_iterator_for_index(&b, repo, new_index, &b_opts)) < 0 || | |
1553 | (error = git_diff__from_iterators(&diff, repo, a, b, opts)) < 0) | |
1554 | goto out; | |
9be638ec ET |
1555 | |
1556 | /* if index is in case-insensitive order, re-sort deltas to match */ | |
22a2d3d5 UG |
1557 | if (old_index->ignore_case || new_index->ignore_case) |
1558 | diff_set_ignore_case(diff, true); | |
9be638ec | 1559 | |
22a2d3d5 UG |
1560 | *out = diff; |
1561 | diff = NULL; | |
1562 | out: | |
1563 | git_iterator_free(a); | |
1564 | git_iterator_free(b); | |
1565 | git_diff_free(diff); | |
1566 | git__free(prefix); | |
9be638ec ET |
1567 | |
1568 | return error; | |
1569 | } | |
1570 | ||
1571 | int git_diff__paired_foreach( | |
1572 | git_diff *head2idx, | |
1573 | git_diff *idx2wd, | |
1574 | int (*cb)(git_diff_delta *h2i, git_diff_delta *i2w, void *payload), | |
1575 | void *payload) | |
1576 | { | |
1577 | int cmp, error = 0; | |
1578 | git_diff_delta *h2i, *i2w; | |
1579 | size_t i, j, i_max, j_max; | |
1580 | int (*strcomp)(const char *, const char *) = git__strcmp; | |
1581 | bool h2i_icase, i2w_icase, icase_mismatch; | |
1582 | ||
1583 | i_max = head2idx ? head2idx->deltas.length : 0; | |
1584 | j_max = idx2wd ? idx2wd->deltas.length : 0; | |
1585 | if (!i_max && !j_max) | |
1586 | return 0; | |
1587 | ||
1588 | /* At some point, tree-to-index diffs will probably never ignore case, | |
1589 | * even if that isn't true now. Index-to-workdir diffs may or may not | |
1590 | * ignore case, but the index filename for the idx2wd diff should | |
1591 | * still be using the canonical case-preserving name. | |
1592 | * | |
1593 | * Therefore the main thing we need to do here is make sure the diffs | |
1594 | * are traversed in a compatible order. To do this, we temporarily | |
1595 | * resort a mismatched diff to get the order correct. | |
1596 | * | |
1597 | * In order to traverse renames in the index->workdir, we need to | |
1598 | * ensure that we compare the index name on both sides, so we | |
1599 | * always sort by the old name in the i2w list. | |
1600 | */ | |
1601 | h2i_icase = head2idx != NULL && git_diff_is_sorted_icase(head2idx); | |
1602 | i2w_icase = idx2wd != NULL && git_diff_is_sorted_icase(idx2wd); | |
1603 | ||
1604 | icase_mismatch = | |
1605 | (head2idx != NULL && idx2wd != NULL && h2i_icase != i2w_icase); | |
1606 | ||
1607 | if (icase_mismatch && h2i_icase) { | |
1608 | git_vector_set_cmp(&head2idx->deltas, git_diff_delta__cmp); | |
1609 | git_vector_sort(&head2idx->deltas); | |
1610 | } | |
1611 | ||
1612 | if (i2w_icase && !icase_mismatch) { | |
1613 | strcomp = git__strcasecmp; | |
1614 | ||
22a2d3d5 | 1615 | git_vector_set_cmp(&idx2wd->deltas, diff_delta_i2w_casecmp); |
9be638ec ET |
1616 | git_vector_sort(&idx2wd->deltas); |
1617 | } else if (idx2wd != NULL) { | |
22a2d3d5 | 1618 | git_vector_set_cmp(&idx2wd->deltas, diff_delta_i2w_cmp); |
9be638ec ET |
1619 | git_vector_sort(&idx2wd->deltas); |
1620 | } | |
1621 | ||
1622 | for (i = 0, j = 0; i < i_max || j < j_max; ) { | |
1623 | h2i = head2idx ? GIT_VECTOR_GET(&head2idx->deltas, i) : NULL; | |
1624 | i2w = idx2wd ? GIT_VECTOR_GET(&idx2wd->deltas, j) : NULL; | |
1625 | ||
1626 | cmp = !i2w ? -1 : !h2i ? 1 : | |
1627 | strcomp(h2i->new_file.path, i2w->old_file.path); | |
1628 | ||
1629 | if (cmp < 0) { | |
1630 | i++; i2w = NULL; | |
1631 | } else if (cmp > 0) { | |
1632 | j++; h2i = NULL; | |
1633 | } else { | |
1634 | i++; j++; | |
1635 | } | |
1636 | ||
1637 | if ((error = cb(h2i, i2w, payload)) != 0) { | |
ac3d33df | 1638 | git_error_set_after_callback(error); |
9be638ec ET |
1639 | break; |
1640 | } | |
1641 | } | |
1642 | ||
1643 | /* restore case-insensitive delta sort */ | |
1644 | if (icase_mismatch && h2i_icase) { | |
1645 | git_vector_set_cmp(&head2idx->deltas, git_diff_delta__casecmp); | |
1646 | git_vector_sort(&head2idx->deltas); | |
1647 | } | |
1648 | ||
1649 | /* restore idx2wd sort by new path */ | |
1650 | if (idx2wd != NULL) { | |
1651 | git_vector_set_cmp(&idx2wd->deltas, | |
1652 | i2w_icase ? git_diff_delta__casecmp : git_diff_delta__cmp); | |
1653 | git_vector_sort(&idx2wd->deltas); | |
1654 | } | |
1655 | ||
1656 | return error; | |
1657 | } | |
1658 | ||
1659 | int git_diff__commit( | |
1660 | git_diff **out, | |
1661 | git_repository *repo, | |
1662 | const git_commit *commit, | |
1663 | const git_diff_options *opts) | |
1664 | { | |
1665 | git_commit *parent = NULL; | |
1666 | git_diff *commit_diff = NULL; | |
1667 | git_tree *old_tree = NULL, *new_tree = NULL; | |
1668 | size_t parents; | |
1669 | int error = 0; | |
1670 | ||
1671 | *out = NULL; | |
1672 | ||
1673 | if ((parents = git_commit_parentcount(commit)) > 1) { | |
1674 | char commit_oidstr[GIT_OID_HEXSZ + 1]; | |
1675 | ||
1676 | error = -1; | |
ac3d33df | 1677 | git_error_set(GIT_ERROR_INVALID, "commit %s is a merge commit", |
9be638ec ET |
1678 | git_oid_tostr(commit_oidstr, GIT_OID_HEXSZ + 1, git_commit_id(commit))); |
1679 | goto on_error; | |
1680 | } | |
1681 | ||
1682 | if (parents > 0) | |
1683 | if ((error = git_commit_parent(&parent, commit, 0)) < 0 || | |
1684 | (error = git_commit_tree(&old_tree, parent)) < 0) | |
1685 | goto on_error; | |
1686 | ||
1687 | if ((error = git_commit_tree(&new_tree, commit)) < 0 || | |
1688 | (error = git_diff_tree_to_tree(&commit_diff, repo, old_tree, new_tree, opts)) < 0) | |
1689 | goto on_error; | |
1690 | ||
1691 | *out = commit_diff; | |
1692 | ||
1693 | on_error: | |
1694 | git_tree_free(new_tree); | |
1695 | git_tree_free(old_tree); | |
1696 | git_commit_free(parent); | |
1697 | ||
1698 | return error; | |
1699 | } | |
1700 |