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