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