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