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