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