File Coverage

deps/libgit2/src/diff_generate.c
Criterion Covered Total %
statement 580 841 68.9
branch 338 672 50.3
condition n/a
subroutine n/a
pod n/a
total 918 1513 60.6


line stmt bran cond sub pod time code
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 293           static git_diff_delta *diff_delta__alloc(
39             git_diff_generated *diff,
40             git_delta_t status,
41             const char *path)
42             {
43 293           git_diff_delta *delta = git__calloc(1, sizeof(git_diff_delta));
44 293 50         if (!delta)
45 0           return NULL;
46              
47 293           delta->old_file.path = git_pool_strdup(&diff->base.pool, path);
48 293 50         if (delta->old_file.path == NULL) {
49 0           git__free(delta);
50 0           return NULL;
51             }
52              
53 293           delta->new_file.path = delta->old_file.path;
54              
55 293 100         if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) {
56 2           switch (status) {
57 1           case GIT_DELTA_ADDED: status = GIT_DELTA_DELETED; break;
58 0           case GIT_DELTA_DELETED: status = GIT_DELTA_ADDED; break;
59 1           default: break; /* leave other status values alone */
60             }
61             }
62 293           delta->status = status;
63              
64 293           return delta;
65             }
66              
67 293           static int diff_insert_delta(
68             git_diff_generated *diff,
69             git_diff_delta *delta,
70             const char *matched_pathspec)
71             {
72 293           int error = 0;
73              
74 293 50         if (diff->base.opts.notify_cb) {
75 0           error = diff->base.opts.notify_cb(
76 0           &diff->base, delta, matched_pathspec, diff->base.opts.payload);
77              
78 0 0         if (error) {
79 0           git__free(delta);
80              
81 0 0         if (error > 0) /* positive value means to skip this delta */
82 0           return 0;
83             else /* negative value means to cancel diff */
84 0           return git_error_set_after_callback_function(error, "git_diff");
85             }
86             }
87              
88 293 50         if ((error = git_vector_insert(&diff->base.deltas, delta)) < 0)
89 0           git__free(delta);
90              
91 293           return error;
92             }
93              
94 681           static bool diff_pathspec_match(
95             const char **matched_pathspec,
96             git_diff_generated *diff,
97             const git_index_entry *entry)
98             {
99 681           bool disable_pathspec_match =
100 681           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 681 50         if ((S_ISLNK(entry->mode) || S_ISREG(entry->mode)) &&
    100          
    100          
109             disable_pathspec_match) {
110 43           *matched_pathspec = entry->path;
111 43           return true;
112             }
113              
114 638           return git_pathspec__match(
115 638           &diff->pathspec, entry->path, disable_pathspec_match,
116 638           DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE),
117             matched_pathspec, NULL);
118             }
119              
120 281           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 281           const git_index_entry *entry = nitem;
127 281           bool has_old = false;
128             git_diff_delta *delta;
129             const char *matched_pathspec;
130              
131 281 50         assert((oitem != NULL) ^ (nitem != NULL));
132              
133 281 100         if (oitem) {
134 21           entry = oitem;
135 21           has_old = true;
136             }
137              
138 281 100         if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE))
139 1           has_old = !has_old;
140              
141 281 50         if ((entry->flags & GIT_INDEX_ENTRY_VALID) != 0)
142 0           return 0;
143              
144 281 100         if (status == GIT_DELTA_IGNORED &&
    100          
145 9           DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_IGNORED))
146 3           return 0;
147              
148 278 100         if (status == GIT_DELTA_UNTRACKED &&
    100          
149 202           DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_UNTRACKED))
150 136           return 0;
151              
152 142 50         if (status == GIT_DELTA_UNREADABLE &&
    0          
153 0           DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_UNREADABLE))
154 0           return 0;
155              
156 142 50         if (!diff_pathspec_match(&matched_pathspec, diff, entry))
157 0           return 0;
158              
159 142           delta = diff_delta__alloc(diff, status, entry->path);
160 142 50         GIT_ERROR_CHECK_ALLOC(delta);
161              
162             /* This fn is just for single-sided diffs */
163 142 50         assert(status != GIT_DELTA_MODIFIED);
164 142           delta->nfiles = 1;
165              
166 142 100         if (has_old) {
167 22           delta->old_file.mode = entry->mode;
168 22           delta->old_file.size = entry->file_size;
169 22           delta->old_file.flags |= GIT_DIFF_FLAG_EXISTS;
170 22           git_oid_cpy(&delta->old_file.id, &entry->id);
171 22           delta->old_file.id_abbrev = GIT_OID_HEXSZ;
172             } else /* ADDED, IGNORED, UNTRACKED */ {
173 120           delta->new_file.mode = entry->mode;
174 120           delta->new_file.size = entry->file_size;
175 120           delta->new_file.flags |= GIT_DIFF_FLAG_EXISTS;
176 120           git_oid_cpy(&delta->new_file.id, &entry->id);
177 120           delta->new_file.id_abbrev = GIT_OID_HEXSZ;
178             }
179              
180 142           delta->old_file.flags |= GIT_DIFF_FLAG_VALID_ID;
181              
182 142 100         if (has_old || !git_oid_is_zero(&delta->new_file.id))
    100          
183 70           delta->new_file.flags |= GIT_DIFF_FLAG_VALID_ID;
184              
185 281           return diff_insert_delta(diff, delta, matched_pathspec);
186             }
187              
188 537           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 537           const git_oid *old_id = &old_entry->id;
199             git_diff_delta *delta;
200 537           const char *canonical_path = old_entry->path;
201              
202 537 100         if (status == GIT_DELTA_UNMODIFIED &&
    100          
203 474           DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_UNMODIFIED))
204 386           return 0;
205              
206 151 100         if (!new_id)
207 139           new_id = &new_entry->id;
208              
209 151 100         if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) {
210 1           uint32_t temp_mode = old_mode;
211 1           const git_index_entry *temp_entry = old_entry;
212 1           const git_oid *temp_id = old_id;
213              
214 1           old_entry = new_entry;
215 1           new_entry = temp_entry;
216 1           old_mode = new_mode;
217 1           new_mode = temp_mode;
218 1           old_id = new_id;
219 1           new_id = temp_id;
220             }
221              
222 151           delta = diff_delta__alloc(diff, status, canonical_path);
223 151 50         GIT_ERROR_CHECK_ALLOC(delta);
224 151           delta->nfiles = 2;
225              
226 151 50         if (!git_index_entry_is_conflict(old_entry)) {
227 151           delta->old_file.size = old_entry->file_size;
228 151           delta->old_file.mode = old_mode;
229 151           git_oid_cpy(&delta->old_file.id, old_id);
230 151           delta->old_file.id_abbrev = GIT_OID_HEXSZ;
231 151           delta->old_file.flags |= GIT_DIFF_FLAG_VALID_ID |
232             GIT_DIFF_FLAG_EXISTS;
233             }
234              
235 151 50         if (!git_index_entry_is_conflict(new_entry)) {
236 151           git_oid_cpy(&delta->new_file.id, new_id);
237 151           delta->new_file.id_abbrev = GIT_OID_HEXSZ;
238 151           delta->new_file.size = new_entry->file_size;
239 151           delta->new_file.mode = new_mode;
240 151           delta->old_file.flags |= GIT_DIFF_FLAG_EXISTS;
241 151           delta->new_file.flags |= GIT_DIFF_FLAG_EXISTS;
242              
243 151 100         if (!git_oid_is_zero(&new_entry->id))
244 132           delta->new_file.flags |= GIT_DIFF_FLAG_VALID_ID;
245             }
246              
247 151           return diff_insert_delta(diff, delta, matched_pathspec);
248             }
249              
250 41           static git_diff_delta *diff_delta__last_for_item(
251             git_diff_generated *diff,
252             const git_index_entry *item)
253             {
254 41           git_diff_delta *delta = git_vector_last(&diff->base.deltas);
255 41 100         if (!delta)
256 29           return NULL;
257              
258 12           switch (delta->status) {
259             case GIT_DELTA_UNMODIFIED:
260             case GIT_DELTA_DELETED:
261 0 0         if (git_oid__cmp(&delta->old_file.id, &item->id) == 0)
262 0           return delta;
263 0           break;
264             case GIT_DELTA_ADDED:
265 0 0         if (git_oid__cmp(&delta->new_file.id, &item->id) == 0)
266 0           return delta;
267 0           break;
268             case GIT_DELTA_UNREADABLE:
269             case GIT_DELTA_UNTRACKED:
270 22           if (diff->base.strcomp(delta->new_file.path, item->path) == 0 &&
271 11           git_oid__cmp(&delta->new_file.id, &item->id) == 0)
272 11           return delta;
273 0           break;
274             case GIT_DELTA_MODIFIED:
275 0 0         if (git_oid__cmp(&delta->old_file.id, &item->id) == 0 ||
    0          
276 0 0         (delta->new_file.mode == item->mode &&
277 0           git_oid__cmp(&delta->new_file.id, &item->id) == 0))
278 0           return delta;
279 0           break;
280             default:
281 1           break;
282             }
283              
284 1           return NULL;
285             }
286              
287 604           static char *diff_strdup_prefix(git_pool *pool, const char *prefix)
288             {
289 604           size_t len = strlen(prefix);
290              
291             /* append '/' at end if needed */
292 604 50         if (len > 0 && prefix[len - 1] != '/')
    100          
293 6           return git_pool_strcat(pool, prefix, "/");
294             else
295 598           return git_pool_strndup(pool, prefix, len + 1);
296             }
297              
298 40           GIT_INLINE(const char *) diff_delta__i2w_path(const git_diff_delta *delta)
299             {
300 40 50         return delta->old_file.path ?
301             delta->old_file.path : delta->new_file.path;
302             }
303              
304 20           static int diff_delta_i2w_cmp(const void *a, const void *b)
305             {
306 20           const git_diff_delta *da = a, *db = b;
307 20           int val = strcmp(diff_delta__i2w_path(da), diff_delta__i2w_path(db));
308 20 50         return val ? val : ((int)da->status - (int)db->status);
309             }
310              
311 0           static int diff_delta_i2w_casecmp(const void *a, const void *b)
312             {
313 0           const git_diff_delta *da = a, *db = b;
314 0           int val = strcasecmp(diff_delta__i2w_path(da), diff_delta__i2w_path(db));
315 0 0         return val ? val : ((int)da->status - (int)db->status);
316             }
317              
318 108           bool git_diff_delta__should_skip(
319             const git_diff_options *opts, const git_diff_delta *delta)
320             {
321 108 50         uint32_t flags = opts ? opts->flags : 0;
322              
323 108 50         if (delta->status == GIT_DELTA_UNMODIFIED &&
    0          
324 0           (flags & GIT_DIFF_INCLUDE_UNMODIFIED) == 0)
325 0           return true;
326              
327 108 100         if (delta->status == GIT_DELTA_IGNORED &&
    50          
328 2           (flags & GIT_DIFF_INCLUDE_IGNORED) == 0)
329 0           return true;
330              
331 108 100         if (delta->status == GIT_DELTA_UNTRACKED &&
    50          
332 38           (flags & GIT_DIFF_INCLUDE_UNTRACKED) == 0)
333 0           return true;
334              
335 108 50         if (delta->status == GIT_DELTA_UNREADABLE &&
    0          
336 0           (flags & GIT_DIFF_INCLUDE_UNREADABLE) == 0)
337 0           return true;
338              
339 108           return false;
340             }
341              
342              
343 0           static const char *diff_mnemonic_prefix(
344             git_iterator_t type, bool left_side)
345             {
346 0           const char *pfx = "";
347              
348 0           switch (type) {
349 0           case GIT_ITERATOR_EMPTY: pfx = "c"; break;
350 0           case GIT_ITERATOR_TREE: pfx = "c"; break;
351 0           case GIT_ITERATOR_INDEX: pfx = "i"; break;
352 0           case GIT_ITERATOR_WORKDIR: pfx = "w"; break;
353 0 0         case GIT_ITERATOR_FS: pfx = left_side ? "1" : "2"; break;
354 0           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 0           return pfx;
362             }
363              
364 302           static void diff_set_ignore_case(git_diff *diff, bool ignore_case)
365             {
366 302 50         if (!ignore_case) {
367 302           diff->opts.flags &= ~GIT_DIFF_IGNORE_CASE;
368              
369 302           diff->strcomp = git__strcmp;
370 302           diff->strncomp = git__strncmp;
371 302           diff->pfxcomp = git__prefixcmp;
372 302           diff->entrycomp = git_diff__entry_cmp;
373              
374 302           git_vector_set_cmp(&diff->deltas, git_diff_delta__cmp);
375             } else {
376 0           diff->opts.flags |= GIT_DIFF_IGNORE_CASE;
377              
378 0           diff->strcomp = git__strcasecmp;
379 0           diff->strncomp = git__strncasecmp;
380 0           diff->pfxcomp = git__prefixcmp_icase;
381 0           diff->entrycomp = git_diff__entry_icmp;
382              
383 0           git_vector_set_cmp(&diff->deltas, git_diff_delta__casecmp);
384             }
385              
386 302           git_vector_sort(&diff->deltas);
387 302           }
388              
389 302           static void diff_generated_free(git_diff *d)
390             {
391 302           git_diff_generated *diff = (git_diff_generated *)d;
392              
393 302           git_attr_session__free(&diff->base.attrsession);
394 302           git_vector_free_deep(&diff->base.deltas);
395              
396 302           git_pathspec__vfree(&diff->pathspec);
397 302           git_pool_clear(&diff->base.pool);
398              
399 302           git__memzero(diff, sizeof(*diff));
400 302           git__free(diff);
401 302           }
402              
403 302           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 302           git_diff_options dflt = GIT_DIFF_OPTIONS_INIT;
410              
411 302 50         assert(repo && old_iter && new_iter);
    50          
    50          
412              
413 302 50         if ((diff = git__calloc(1, sizeof(git_diff_generated))) == NULL)
414 0           return NULL;
415              
416 302           GIT_REFCOUNT_INC(&diff->base);
417 302           diff->base.type = GIT_DIFF_TYPE_GENERATED;
418 302           diff->base.repo = repo;
419 302           diff->base.old_src = old_iter->type;
420 302           diff->base.new_src = new_iter->type;
421 302           diff->base.patch_fn = git_patch_generated_from_diff;
422 302           diff->base.free_fn = diff_generated_free;
423 302           git_attr_session__init(&diff->base.attrsession, repo);
424 302           memcpy(&diff->base.opts, &dflt, sizeof(git_diff_options));
425              
426 604           if (git_pool_init(&diff->base.pool, 1) < 0 ||
427 302           git_vector_init(&diff->base.deltas, 0, git_diff_delta__cmp) < 0) {
428 0           git_diff_free(&diff->base);
429 0           return NULL;
430             }
431              
432             /* Use case-insensitive compare if either iterator has
433             * the ignore_case bit set */
434 302           diff_set_ignore_case(
435 302           &diff->base,
436 604           git_iterator_ignore_case(old_iter) ||
437 302           git_iterator_ignore_case(new_iter));
438              
439 302           return diff;
440             }
441              
442 302           static int diff_generated_apply_options(
443             git_diff_generated *diff,
444             const git_diff_options *opts)
445             {
446 302           git_config *cfg = NULL;
447 302           git_repository *repo = diff->base.repo;
448 302           git_pool *pool = &diff->base.pool;
449             int val;
450              
451 302 100         if (opts) {
452             /* copy user options (except case sensitivity info from iterators) */
453 298           bool icase = DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE);
454 298           memcpy(&diff->base.opts, opts, sizeof(diff->base.opts));
455 298 50         DIFF_FLAG_SET(diff, GIT_DIFF_IGNORE_CASE, icase);
456              
457             /* initialize pathspec from options */
458 298 50         if (git_pathspec__vinit(&diff->pathspec, &opts->pathspec, pool) < 0)
459 0           return -1;
460             }
461              
462             /* flag INCLUDE_TYPECHANGE_TREES implies INCLUDE_TYPECHANGE */
463 302 100         if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE_TREES))
464 53           diff->base.opts.flags |= GIT_DIFF_INCLUDE_TYPECHANGE;
465              
466             /* flag INCLUDE_UNTRACKED_CONTENT implies INCLUDE_UNTRACKED */
467 302 50         if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_SHOW_UNTRACKED_CONTENT))
468 0           diff->base.opts.flags |= GIT_DIFF_INCLUDE_UNTRACKED;
469              
470             /* load config values that affect diff behavior */
471 302 50         if ((val = git_repository_config_snapshot(&cfg, repo)) < 0)
472 0           return val;
473              
474 302 50         if (!git_config__configmap_lookup(&val, cfg, GIT_CONFIGMAP_SYMLINKS) && val)
    50          
475 302           diff->diffcaps |= GIT_DIFFCAPS_HAS_SYMLINKS;
476              
477 302 50         if (!git_config__configmap_lookup(&val, cfg, GIT_CONFIGMAP_IGNORESTAT) && val)
    50          
478 0           diff->diffcaps |= GIT_DIFFCAPS_IGNORE_STAT;
479              
480 604           if ((diff->base.opts.flags & GIT_DIFF_IGNORE_FILEMODE) == 0 &&
481 604 50         !git_config__configmap_lookup(&val, cfg, GIT_CONFIGMAP_FILEMODE) && val)
482 302           diff->diffcaps |= GIT_DIFFCAPS_TRUST_MODE_BITS;
483              
484 302 50         if (!git_config__configmap_lookup(&val, cfg, GIT_CONFIGMAP_TRUSTCTIME) && val)
    50          
485 302           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 302 100         if (!opts) {
491 4           int context = git_config__get_int_force(cfg, "diff.context", 3);
492 4 50         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 302 100         if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) {
499 2           git_iterator_t tmp_src = diff->base.old_src;
500 2           diff->base.old_src = diff->base.new_src;
501 2           diff->base.new_src = tmp_src;
502             }
503              
504             /* Unset UPDATE_INDEX unless diffing workdir and index */
505 302 50         if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_UPDATE_INDEX) &&
    0          
506 0 0         (!(diff->base.old_src == GIT_ITERATOR_WORKDIR ||
507 0 0         diff->base.new_src == GIT_ITERATOR_WORKDIR) ||
508 0 0         !(diff->base.old_src == GIT_ITERATOR_INDEX ||
509 0           diff->base.new_src == GIT_ITERATOR_INDEX)))
510 0           diff->base.opts.flags &= ~GIT_DIFF_UPDATE_INDEX;
511              
512             /* if ignore_submodules not explicitly set, check diff config */
513 302 100         if (diff->base.opts.ignore_submodules <= 0) {
514             git_config_entry *entry;
515 284           git_config__lookup_entry(&entry, cfg, "diff.ignoresubmodules", true);
516              
517 284 50         if (entry && git_submodule_parse_ignore(
    0          
518 0           &diff->base.opts.ignore_submodules, entry->value) < 0)
519 0           git_error_clear();
520 284           git_config_entry_free(entry);
521             }
522              
523             /* if either prefix is not set, figure out appropriate value */
524 302 100         if (!diff->base.opts.old_prefix || !diff->base.opts.new_prefix) {
    50          
525 299           const char *use_old = DIFF_OLD_PREFIX_DEFAULT;
526 299           const char *use_new = DIFF_NEW_PREFIX_DEFAULT;
527              
528 299 50         if (git_config__get_bool_force(cfg, "diff.noprefix", 0))
529 0           use_old = use_new = "";
530 299 50         else if (git_config__get_bool_force(cfg, "diff.mnemonicprefix", 0)) {
531 0           use_old = diff_mnemonic_prefix(diff->base.old_src, true);
532 0           use_new = diff_mnemonic_prefix(diff->base.new_src, false);
533             }
534              
535 299 50         if (!diff->base.opts.old_prefix)
536 299           diff->base.opts.old_prefix = use_old;
537 299 50         if (!diff->base.opts.new_prefix)
538 299           diff->base.opts.new_prefix = use_new;
539             }
540              
541             /* strdup prefix from pool so we're not dependent on external data */
542 302           diff->base.opts.old_prefix = diff_strdup_prefix(pool, diff->base.opts.old_prefix);
543 302           diff->base.opts.new_prefix = diff_strdup_prefix(pool, diff->base.opts.new_prefix);
544              
545 302 100         if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) {
546 2           const char *tmp_prefix = diff->base.opts.old_prefix;
547 2           diff->base.opts.old_prefix = diff->base.opts.new_prefix;
548 2           diff->base.opts.new_prefix = tmp_prefix;
549             }
550              
551 302           git_config_free(cfg);
552              
553             /* check strdup results for error */
554 302 50         return (!diff->base.opts.old_prefix || !diff->base.opts.new_prefix) ? -1 : 0;
    50          
555             }
556              
557 1           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 1 50         if (size > UINT32_MAX) {
567 0           git_error_set(GIT_ERROR_NOMEMORY, "file size overflow (for 32-bits) on '%s'", path);
568 0           return -1;
569             }
570              
571 1           memset(&entry, 0, sizeof(entry));
572 1           entry.mode = mode;
573 1           entry.file_size = (uint32_t)size;
574 1           entry.path = (char *)path;
575              
576 1           return git_diff__oid_for_entry(out, diff, &entry, mode, NULL);
577             }
578              
579 165           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 165           git_buf full_path = GIT_BUF_INIT;
588 165           git_index_entry entry = *src;
589 165           git_filter_list *fl = NULL;
590 165           int error = 0;
591              
592 165 50         assert(d->type == GIT_DIFF_TYPE_GENERATED);
593 165           diff = (git_diff_generated *)d;
594              
595 165           memset(out, 0, sizeof(*out));
596              
597 165 50         if (git_buf_joinpath(&full_path,
598 165           git_repository_workdir(diff->base.repo), entry.path) < 0)
599 0           return -1;
600              
601 165 50         if (!mode) {
602             struct stat st;
603              
604 0           diff->base.perf.stat_calls++;
605              
606 0 0         if (p_stat(full_path.ptr, &st) < 0) {
607 0           error = git_path_set_error(errno, entry.path, "stat");
608 0           git_buf_dispose(&full_path);
609 0           return error;
610             }
611              
612 0           git_index_entry__init_from_stat(&entry,
613 0           &st, (diff->diffcaps & GIT_DIFFCAPS_TRUST_MODE_BITS) != 0);
614             }
615              
616             /* calculate OID for file if possible */
617 165 50         if (S_ISGITLINK(mode)) {
618             git_submodule *sm;
619              
620 0 0         if (!git_submodule_lookup(&sm, diff->base.repo, entry.path)) {
621 0           const git_oid *sm_oid = git_submodule_wd_id(sm);
622 0 0         if (sm_oid)
623 0           git_oid_cpy(out, sm_oid);
624 0           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 0           git_error_clear();
630             }
631 165 50         } else if (S_ISLNK(mode)) {
632 0           error = git_odb__hashlink(out, full_path.ptr);
633 0           diff->base.perf.oid_calculations++;
634 165 50         } else if (!git__is_sizet(entry.file_size)) {
635 0           git_error_set(GIT_ERROR_NOMEMORY, "file size overflow (for 32-bits) on '%s'",
636             entry.path);
637 0           error = -1;
638 165 50         } 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 165           int fd = git_futils_open_ro(full_path.ptr);
643 165 50         if (fd < 0)
644 0           error = fd;
645             else {
646 165           error = git_odb__hashfd_filtered(
647 165           out, fd, (size_t)entry.file_size, GIT_OBJECT_BLOB, fl);
648 165           p_close(fd);
649 165           diff->base.perf.oid_calculations++;
650             }
651              
652 165           git_filter_list_free(fl);
653             }
654              
655             /* update index for entry if requested */
656 165 50         if (!error && update_match && git_oid_equal(out, update_match)) {
    50          
    0          
657             git_index *idx;
658             git_index_entry updated_entry;
659              
660 0           memcpy(&updated_entry, &entry, sizeof(git_index_entry));
661 0           updated_entry.mode = mode;
662 0           git_oid_cpy(&updated_entry.id, out);
663              
664 0 0         if (!(error = git_repository_index__weakptr(&idx,
665             diff->base.repo))) {
666 0           error = git_index_add(idx, &updated_entry);
667 0           diff->index_updated = true;
668             }
669             }
670              
671 165           git_buf_dispose(&full_path);
672 165           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 0           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 0           int error = 0;
692             git_submodule *sub;
693 0           unsigned int sm_status = 0;
694 0           git_submodule_ignore_t ign = diff->base.opts.ignore_submodules;
695              
696 0           *status = GIT_DELTA_UNMODIFIED;
697              
698 0 0         if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_SUBMODULES) ||
    0          
699             ign == GIT_SUBMODULE_IGNORE_ALL)
700 0           return 0;
701              
702 0 0         if ((error = git_submodule_lookup(
703 0           &sub, diff->base.repo, info->nitem->path)) < 0) {
704              
705             /* GIT_EEXISTS means dir with .git in it was found - ignore it */
706 0 0         if (error == GIT_EEXISTS) {
707 0           git_error_clear();
708 0           error = 0;
709             }
710 0           return error;
711             }
712              
713 0 0         if (ign <= 0 && git_submodule_ignore(sub) == GIT_SUBMODULE_IGNORE_ALL)
    0          
714             /* ignore it */;
715 0 0         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 0 0         else if (!GIT_SUBMODULE_STATUS_IS_WD_UNMODIFIED(sm_status))
723 0           *status = GIT_DELTA_MODIFIED;
724              
725             /* now that we have a HEAD OID, check if HEAD moved */
726 0           else if ((sm_status & GIT_SUBMODULE_STATUS_IN_WD) != 0 &&
727 0           !git_oid_equal(&info->oitem->id, found_oid))
728 0           *status = GIT_DELTA_MODIFIED;
729              
730 0           git_submodule_free(sub);
731 0           return error;
732             }
733              
734 539           static int maybe_modified(
735             git_diff_generated *diff,
736             diff_in_progress *info)
737             {
738             git_oid noid;
739 539           git_delta_t status = GIT_DELTA_MODIFIED;
740 539           const git_index_entry *oitem = info->oitem;
741 539           const git_index_entry *nitem = info->nitem;
742 539           unsigned int omode = oitem->mode;
743 539           unsigned int nmode = nitem->mode;
744 539           bool new_is_workdir = (info->new_iter->type == GIT_ITERATOR_WORKDIR);
745 539           bool modified_uncertain = false;
746             const char *matched_pathspec;
747 539           int error = 0;
748              
749 539 100         if (!diff_pathspec_match(&matched_pathspec, diff, oitem))
750 2           return 0;
751              
752 537           memset(&noid, 0, sizeof(noid));
753              
754             /* on platforms with no symlinks, preserve mode of existing symlinks */
755 537 50         if (S_ISLNK(omode) && S_ISREG(nmode) && new_is_workdir &&
    0          
    0          
    0          
756 0           !(diff->diffcaps & GIT_DIFFCAPS_HAS_SYMLINKS))
757 0           nmode = omode;
758              
759             /* on platforms with no execmode, just preserve old mode */
760 537 50         if (!(diff->diffcaps & GIT_DIFFCAPS_TRUST_MODE_BITS) &&
    0          
761 0 0         (nmode & MODE_BITS_MASK) != (omode & MODE_BITS_MASK) &&
762             new_is_workdir)
763 0           nmode = (nmode & ~MODE_BITS_MASK) | (omode & MODE_BITS_MASK);
764              
765             /* if one side is a conflict, mark the whole delta as conflicted */
766 1074           if (git_index_entry_is_conflict(oitem) ||
767 537           git_index_entry_is_conflict(nitem)) {
768 0           status = GIT_DELTA_CONFLICTED;
769              
770             /* support "assume unchanged" (poorly, b/c we still stat everything) */
771 537 50         } else if ((oitem->flags & GIT_INDEX_ENTRY_VALID) != 0) {
772 0           status = GIT_DELTA_UNMODIFIED;
773              
774             /* support "skip worktree" index bit */
775 537 50         } else if ((oitem->flags_extended & GIT_INDEX_ENTRY_SKIP_WORKTREE) != 0) {
776 0           status = GIT_DELTA_UNMODIFIED;
777              
778             /* if basic type of file changed, then split into delete and add */
779 537 50         } else if (GIT_MODE_TYPE(omode) != GIT_MODE_TYPE(nmode)) {
780 0 0         if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE)) {
781 0           status = GIT_DELTA_TYPECHANGE;
782             }
783              
784 0 0         else if (nmode == GIT_FILEMODE_UNREADABLE) {
785 0 0         if (!(error = diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem, NULL)))
786 0           error = diff_delta__from_one(diff, GIT_DELTA_UNREADABLE, NULL, nitem);
787 0           return error;
788             }
789              
790             else {
791 0 0         if (!(error = diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem, NULL)))
792 0           error = diff_delta__from_one(diff, GIT_DELTA_ADDED, NULL, nitem);
793 0           return error;
794             }
795              
796             /* if oids and modes match (and are valid), then file is unmodified */
797 537 100         } else if (git_oid_equal(&oitem->id, &nitem->id) &&
    50          
798 289 50         omode == nmode &&
799 289           !git_oid_is_zero(&oitem->id)) {
800 289           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 452 100         } else if (git_oid_is_zero(&nitem->id) && new_is_workdir) {
    50          
806 204           bool use_ctime =
807 204           ((diff->diffcaps & GIT_DIFFCAPS_TRUST_CTIME) != 0);
808 204           git_index *index = git_iterator_index(info->new_iter);
809              
810 204           status = GIT_DELTA_UNMODIFIED;
811              
812 204 50         if (S_ISGITLINK(nmode)) {
813 0 0         if ((error = maybe_modified_submodule(&status, &noid, diff, info)) < 0)
814 0           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 204 50         else if (omode != nmode || oitem->file_size != nitem->file_size) {
    100          
821 26           status = GIT_DELTA_MODIFIED;
822 26           modified_uncertain =
823 26 100         (oitem->file_size <= 0 && nitem->file_size > 0);
    50          
824             }
825 178 50         else if (!git_index_time_eq(&oitem->mtime, &nitem->mtime) ||
    50          
826 178 50         (use_ctime && !git_index_time_eq(&oitem->ctime, &nitem->ctime)) ||
    50          
827 178 50         oitem->ino != nitem->ino ||
828 178 50         oitem->uid != nitem->uid ||
829 178 100         oitem->gid != nitem->gid ||
830 178           git_index_entry_newer_than_index(nitem, index))
831             {
832 97           status = GIT_DELTA_MODIFIED;
833 97           modified_uncertain = true;
834             }
835              
836             /* if mode is GITLINK and submodules are ignored, then skip */
837 44 50         } else if (S_ISGITLINK(nmode) &&
    0          
838 0           DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_SUBMODULES)) {
839 0           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 537 100         if (modified_uncertain && git_oid_is_zero(&nitem->id)) {
    50          
846 116           const git_oid *update_check =
847 0 0         DIFF_FLAG_IS_SET(diff, GIT_DIFF_UPDATE_INDEX) && omode == nmode ?
848 116 50         &oitem->id : NULL;
849              
850 116 50         if ((error = git_diff__oid_for_entry(
851 116           &noid, &diff->base, nitem, nmode, update_check)) < 0)
852 0           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 232 50         if (omode == nmode && !S_ISGITLINK(omode) &&
859 116           git_oid_equal(&oitem->id, &noid))
860 104           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 537 50         if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE) &&
    0          
868 0 0         DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_CASECHANGE) &&
869 0           strcmp(oitem->path, nitem->path) != 0) {
870              
871 0 0         if (!(error = diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem, NULL)))
872 0           error = diff_delta__from_one(diff, GIT_DELTA_ADDED, NULL, nitem);
873              
874 0           return error;
875             }
876              
877 539 100         return diff_delta__from_two(
878             diff, status, oitem, omode, nitem, nmode,
879 537           git_oid_is_zero(&noid) ? NULL : &noid, matched_pathspec);
880             }
881              
882 382           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 382 100         if (!item || diff->base.pfxcomp(item->path, prefix_item->path) != 0)
    100          
890 272           return false;
891              
892 110           pathlen = strlen(prefix_item->path);
893              
894 112 50         return (prefix_item->path[pathlen - 1] == '/' ||
895 112 100         item->path[pathlen] == '\0' ||
    50          
896 2           item->path[pathlen] == '/');
897             }
898              
899 604           static int iterator_current(
900             const git_index_entry **entry,
901             git_iterator *iterator)
902             {
903             int error;
904              
905 604 100         if ((error = git_iterator_current(entry, iterator)) == GIT_ITEROVER) {
906 50           *entry = NULL;
907 50           error = 0;
908             }
909              
910 604           return error;
911             }
912              
913 1348           static int iterator_advance(
914             const git_index_entry **entry,
915             git_iterator *iterator)
916             {
917 1348           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 1348 100         while ((error = git_iterator_advance(entry, iterator)) == 0) {
926 1107           if (!(iterator->flags & GIT_ITERATOR_INCLUDE_CONFLICTS) ||
927 312 0         !git_index_entry_is_conflict(prev_entry) ||
928 0           !git_index_entry_is_conflict(*entry))
929             break;
930              
931 0           cmp = (iterator->flags & GIT_ITERATOR_IGNORE_CASE) ?
932 0 0         strcasecmp(prev_entry->path, (*entry)->path) :
933 0           strcmp(prev_entry->path, (*entry)->path);
934              
935 0 0         if (cmp)
936 0           break;
937             }
938              
939 1348 100         if (error == GIT_ITEROVER) {
940 553           *entry = NULL;
941 553           error = 0;
942             }
943              
944 1348           return error;
945             }
946              
947 114           static int iterator_advance_into(
948             const git_index_entry **entry,
949             git_iterator *iterator)
950             {
951             int error;
952              
953 114 50         if ((error = git_iterator_advance_into(entry, iterator)) == GIT_ITEROVER) {
954 0           *entry = NULL;
955 0           error = 0;
956             }
957              
958 114           return error;
959             }
960              
961 11           static int iterator_advance_over(
962             const git_index_entry **entry,
963             git_iterator_status_t *status,
964             git_iterator *iterator)
965             {
966 11           int error = git_iterator_advance_over(entry, status, iterator);
967              
968 11 100         if (error == GIT_ITEROVER) {
969 1           *entry = NULL;
970 1           error = 0;
971             }
972              
973 11           return error;
974             }
975              
976 374           static int handle_unmatched_new_item(
977             git_diff_generated *diff, diff_in_progress *info)
978             {
979 374           int error = 0;
980 374           const git_index_entry *nitem = info->nitem;
981 374           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 374           contains_oitem = entry_is_prefixed(diff, info->oitem, nitem);
986              
987             /* update delta_type if this item is conflicted */
988 374 50         if (git_index_entry_is_conflict(nitem))
989 0           delta_type = GIT_DELTA_CONFLICTED;
990              
991             /* update delta_type if this item is ignored */
992 374 100         else if (git_iterator_current_is_ignored(info->new_iter))
993 9           delta_type = GIT_DELTA_IGNORED;
994              
995 374 100         if (nitem->mode == GIT_FILEMODE_TREE) {
996 155           bool recurse_into_dir = contains_oitem;
997              
998             /* check if user requests recursion into this type of dir */
999 47 50         recurse_into_dir = contains_oitem ||
1000 47 100         (delta_type == GIT_DELTA_UNTRACKED &&
1001 202 100         DIFF_FLAG_IS_SET(diff, GIT_DIFF_RECURSE_UNTRACKED_DIRS)) ||
    50          
1002 0 0         (delta_type == GIT_DELTA_IGNORED &&
1003 0           DIFF_FLAG_IS_SET(diff, GIT_DIFF_RECURSE_IGNORED_DIRS));
1004              
1005             /* do not advance into directories that contain a .git file */
1006 155 100         if (recurse_into_dir && !contains_oitem) {
    100          
1007 6           git_buf *full = NULL;
1008 6 50         if (git_iterator_current_workdir_path(&full, info->new_iter) < 0)
1009 0           return -1;
1010 6 50         if (full && git_path_contains(full, DOT_GIT)) {
    50          
1011             /* TODO: warning if not a valid git repository */
1012 6           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 155 100         if (!recurse_into_dir &&
    50          
1020 41 50         delta_type == GIT_DELTA_UNTRACKED &&
1021 41           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 41 50         if ((error = diff_delta__from_one(diff, delta_type, NULL, nitem)) != 0)
1028 0           return error;
1029              
1030             /* if delta wasn't created (because of rules), just skip ahead */
1031 41           last = diff_delta__last_for_item(diff, nitem);
1032 41 100         if (!last)
1033 30           return iterator_advance(&info->nitem, info->new_iter);
1034              
1035             /* iterate into dir looking for an actual untracked file */
1036 11 50         if ((error = iterator_advance_over(
1037             &info->nitem, &untracked_state, info->new_iter)) < 0)
1038 0           return error;
1039              
1040             /* if we found nothing that matched our pathlist filter, exclude */
1041 11 50         if (untracked_state == GIT_ITERATOR_STATUS_FILTERED) {
1042 0           git_vector_pop(&diff->base.deltas);
1043 0           git__free(last);
1044             }
1045              
1046             /* if we found nothing or just ignored items, update the record */
1047 11 50         if (untracked_state == GIT_ITERATOR_STATUS_IGNORED ||
    100          
1048 11           untracked_state == GIT_ITERATOR_STATUS_EMPTY) {
1049 1           last->status = GIT_DELTA_IGNORED;
1050              
1051             /* remove the record if we don't want ignored records */
1052 1 50         if (DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_IGNORED)) {
1053 0           git_vector_pop(&diff->base.deltas);
1054 0           git__free(last);
1055             }
1056             }
1057              
1058 41           return 0;
1059             }
1060              
1061             /* try to advance into directory if necessary */
1062 114 50         if (recurse_into_dir) {
1063 114           error = iterator_advance_into(&info->nitem, info->new_iter);
1064              
1065             /* if directory is empty, can't advance into it, so skip it */
1066 114 50         if (error == GIT_ENOTFOUND) {
1067 0           git_error_clear();
1068 0           error = iterator_advance(&info->nitem, info->new_iter);
1069             }
1070              
1071 114           return error;
1072             }
1073             }
1074              
1075 219 100         else if (delta_type == GIT_DELTA_IGNORED &&
    100          
1076 7 50         DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_RECURSE_IGNORED_DIRS) &&
1077 7           git_iterator_current_tree_is_ignored(info->new_iter))
1078             /* item contained in ignored directory, so skip over it */
1079 0           return iterator_advance(&info->nitem, info->new_iter);
1080              
1081 219 100         else if (info->new_iter->type != GIT_ITERATOR_WORKDIR) {
1082 49 50         if (delta_type != GIT_DELTA_CONFLICTED)
1083 49           delta_type = GIT_DELTA_ADDED;
1084             }
1085              
1086 170 50         else if (nitem->mode == GIT_FILEMODE_COMMIT) {
1087             /* ignore things that are not actual submodules */
1088 0 0         if (git_submodule_lookup(NULL, info->repo, nitem->path) != 0) {
1089 0           git_error_clear();
1090 0           delta_type = GIT_DELTA_IGNORED;
1091              
1092             /* if this contains a tracked item, treat as normal TREE */
1093 0 0         if (contains_oitem) {
1094 0           error = iterator_advance_into(&info->nitem, info->new_iter);
1095 0 0         if (error != GIT_ENOTFOUND)
1096 0           return error;
1097              
1098 0           git_error_clear();
1099 0           return iterator_advance(&info->nitem, info->new_iter);
1100             }
1101             }
1102             }
1103              
1104 170 50         else if (nitem->mode == GIT_FILEMODE_UNREADABLE) {
1105 0 0         if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_UNREADABLE_AS_UNTRACKED))
1106 0           delta_type = GIT_DELTA_UNTRACKED;
1107             else
1108 0           delta_type = GIT_DELTA_UNREADABLE;
1109             }
1110              
1111             /* Actually create the record for this item if necessary */
1112 219 50         if ((error = diff_delta__from_one(diff, delta_type, NULL, nitem)) != 0)
1113 0           return error;
1114              
1115             /* If user requested TYPECHANGE records, then check for that instead of
1116             * just generating an ADDED/UNTRACKED record
1117             */
1118 219 100         if (delta_type != GIT_DELTA_IGNORED &&
    100          
1119 7 50         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 0           git_diff_delta *last = diff_delta__last_for_item(diff, nitem);
1124 0 0         if (last) {
1125 0           last->status = GIT_DELTA_TYPECHANGE;
1126 0           last->old_file.mode = GIT_FILEMODE_TREE;
1127             }
1128             }
1129              
1130 219           return iterator_advance(&info->nitem, info->new_iter);
1131             }
1132              
1133 21           static int handle_unmatched_old_item(
1134             git_diff_generated *diff, diff_in_progress *info)
1135             {
1136 21           git_delta_t delta_type = GIT_DELTA_DELETED;
1137             int error;
1138              
1139             /* update delta_type if this item is conflicted */
1140 21 50         if (git_index_entry_is_conflict(info->oitem))
1141 0           delta_type = GIT_DELTA_CONFLICTED;
1142              
1143 21 50         if ((error = diff_delta__from_one(diff, delta_type, info->oitem, NULL)) < 0)
1144 0           return error;
1145              
1146             /* if we are generating TYPECHANGE records then check for that
1147             * instead of just generating a DELETE record
1148             */
1149 29           if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE_TREES) &&
1150 8           entry_is_prefixed(diff, info->nitem, info->oitem))
1151             {
1152             /* this entry has become a tree! convert to TYPECHANGE */
1153 0           git_diff_delta *last = diff_delta__last_for_item(diff, info->oitem);
1154 0 0         if (last) {
1155 0           last->status = GIT_DELTA_TYPECHANGE;
1156 0           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 0 0         if (S_ISDIR(info->nitem->mode) &&
    0          
1164 0           DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_RECURSE_UNTRACKED_DIRS))
1165 0           return iterator_advance(&info->nitem, info->new_iter);
1166             }
1167              
1168 21           return iterator_advance(&info->oitem, info->old_iter);
1169             }
1170              
1171 539           static int handle_matched_item(
1172             git_diff_generated *diff, diff_in_progress *info)
1173             {
1174 539           int error = 0;
1175              
1176 539 50         if ((error = maybe_modified(diff, info)) < 0)
1177 0           return error;
1178              
1179 539 50         if (!(error = iterator_advance(&info->oitem, info->old_iter)))
1180 539           error = iterator_advance(&info->nitem, info->new_iter);
1181              
1182 539           return error;
1183             }
1184              
1185 302           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 302           int error = 0;
1195              
1196 302           *out = NULL;
1197              
1198 302           diff = diff_generated_alloc(repo, old_iter, new_iter);
1199 302 50         GIT_ERROR_CHECK_ALLOC(diff);
1200              
1201 302           info.repo = repo;
1202 302           info.old_iter = old_iter;
1203 302           info.new_iter = new_iter;
1204              
1205             /* make iterators have matching icase behavior */
1206 302 50         if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE)) {
1207 0           git_iterator_set_ignore_case(old_iter, true);
1208 0           git_iterator_set_ignore_case(new_iter, true);
1209             }
1210              
1211             /* finish initialization */
1212 302 50         if ((error = diff_generated_apply_options(diff, opts)) < 0)
1213 0           goto cleanup;
1214              
1215 302 50         if ((error = iterator_current(&info.oitem, old_iter)) < 0 ||
    50          
1216             (error = iterator_current(&info.nitem, new_iter)) < 0)
1217             goto cleanup;
1218              
1219             /* run iterators building diffs */
1220 1236 50         while (!error && (info.oitem || info.nitem)) {
    100          
    100          
1221             int cmp;
1222              
1223             /* report progress */
1224 934 100         if (opts && opts->progress_cb) {
    50          
1225 0 0         if ((error = opts->progress_cb(&diff->base,
    0          
    0          
1226 0           info.oitem ? info.oitem->path : NULL,
1227 0           info.nitem ? info.nitem->path : NULL,
1228             opts->payload)))
1229 0           break;
1230             }
1231              
1232 1868           cmp = info.oitem ?
1233 934 100         (info.nitem ? diff->base.entrycomp(info.oitem, info.nitem) : -1) : 1;
    100          
1234              
1235             /* create DELETED records for old items not matched in new */
1236 934 100         if (cmp < 0)
1237 21           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 913 100         else if (cmp > 0)
1243 374           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 539           error = handle_matched_item(diff, &info);
1250             }
1251              
1252 302           diff->base.perf.stat_calls +=
1253 302           old_iter->stat_calls + new_iter->stat_calls;
1254              
1255             cleanup:
1256 302 50         if (!error)
1257 302           *out = &diff->base;
1258             else
1259 0           git_diff_free(&diff->base);
1260              
1261 302           return error;
1262             }
1263              
1264 231           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 231 50         GIT_ERROR_CHECK_VERSION(opts, GIT_DIFF_OPTIONS_VERSION, "git_diff_options");
1269              
1270 231           *prefix = NULL;
1271              
1272 231 100         if (opts && (opts->flags & GIT_DIFF_DISABLE_PATHSPEC_MATCH)) {
    100          
1273 40           a->pathlist.strings = opts->pathspec.strings;
1274 40           a->pathlist.count = opts->pathspec.count;
1275 40           b->pathlist.strings = opts->pathspec.strings;
1276 40           b->pathlist.count = opts->pathspec.count;
1277 191 100         } else if (opts) {
1278 187           *prefix = git_pathspec_prefix(&opts->pathspec);
1279 187 50         GIT_ERROR_CHECK_ALLOC(prefix);
1280             }
1281              
1282 231           a->flags = aflags;
1283 231           b->flags = bflags;
1284 231           a->start = b->start = *prefix;
1285 231           a->end = b->end = *prefix;
1286              
1287 231           return 0;
1288             }
1289              
1290 18           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 18           git_iterator_flag_t iflag = GIT_ITERATOR_DONT_IGNORE_CASE;
1298 18           git_iterator_options a_opts = GIT_ITERATOR_OPTIONS_INIT,
1299 18           b_opts = GIT_ITERATOR_OPTIONS_INIT;
1300 18           git_iterator *a = NULL, *b = NULL;
1301 18           git_diff *diff = NULL;
1302 18           char *prefix = NULL;
1303 18           int error = 0;
1304              
1305 18 50         assert(out && repo);
    50          
1306              
1307 18           *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 18 50         if (opts && (opts->flags & GIT_DIFF_IGNORE_CASE) != 0)
    50          
1314 0           iflag = GIT_ITERATOR_IGNORE_CASE;
1315              
1316 18 50         if ((error = diff_prepare_iterator_opts(&prefix, &a_opts, iflag, &b_opts, iflag, opts)) < 0 ||
    50          
1317 18 50         (error = git_iterator_for_tree(&a, old_tree, &a_opts)) < 0 ||
1318 18 50         (error = git_iterator_for_tree(&b, new_tree, &b_opts)) < 0 ||
1319 18           (error = git_diff__from_iterators(&diff, repo, a, b, opts)) < 0)
1320             goto out;
1321              
1322 18           *out = diff;
1323 18           diff = NULL;
1324             out:
1325 18           git_iterator_free(a);
1326 18           git_iterator_free(b);
1327 18           git_diff_free(diff);
1328 18           git__free(prefix);
1329              
1330 18           return error;
1331             }
1332              
1333 16           static int diff_load_index(git_index **index, git_repository *repo)
1334             {
1335 16           int error = git_repository_index__weakptr(index, repo);
1336              
1337             /* reload the repository index when user did not pass one in */
1338 16 50         if (!error && git_index_read(*index, false) < 0)
    50          
1339 0           git_error_clear();
1340              
1341 16           return error;
1342             }
1343              
1344 95           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 95           git_iterator_flag_t iflag = GIT_ITERATOR_DONT_IGNORE_CASE |
1352             GIT_ITERATOR_INCLUDE_CONFLICTS;
1353 95           git_iterator_options a_opts = GIT_ITERATOR_OPTIONS_INIT,
1354 95           b_opts = GIT_ITERATOR_OPTIONS_INIT;
1355 95           git_iterator *a = NULL, *b = NULL;
1356 95           git_diff *diff = NULL;
1357 95           char *prefix = NULL;
1358 95           bool index_ignore_case = false;
1359 95           int error = 0;
1360              
1361 95 50         assert(out && repo);
    50          
1362              
1363 95           *out = NULL;
1364              
1365 95 50         if (!index && (error = diff_load_index(&index, repo)) < 0)
    0          
1366 0           return error;
1367              
1368 95           index_ignore_case = index->ignore_case;
1369              
1370 95 50         if ((error = diff_prepare_iterator_opts(&prefix, &a_opts, iflag, &b_opts, iflag, opts)) < 0 ||
    50          
1371 95 50         (error = git_iterator_for_tree(&a, old_tree, &a_opts)) < 0 ||
1372 95 50         (error = git_iterator_for_index(&b, repo, index, &b_opts)) < 0 ||
1373 95           (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 95 50         if (index_ignore_case)
1378 0           diff_set_ignore_case(diff, true);
1379              
1380 95           *out = diff;
1381 95           diff = NULL;
1382             out:
1383 95           git_iterator_free(a);
1384 95           git_iterator_free(b);
1385 95           git_diff_free(diff);
1386 95           git__free(prefix);
1387              
1388 95           return error;
1389             }
1390              
1391 116           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 116           git_iterator_options a_opts = GIT_ITERATOR_OPTIONS_INIT,
1398 116           b_opts = GIT_ITERATOR_OPTIONS_INIT;
1399 116           git_iterator *a = NULL, *b = NULL;
1400 116           git_diff *diff = NULL;
1401 116           char *prefix = NULL;
1402 116           int error = 0;
1403              
1404 116 50         assert(out && repo);
    50          
1405              
1406 116           *out = NULL;
1407              
1408 116 100         if (!index && (error = diff_load_index(&index, repo)) < 0)
    50          
1409 0           return error;
1410              
1411 116 50         if ((error = diff_prepare_iterator_opts(&prefix, &a_opts, GIT_ITERATOR_INCLUDE_CONFLICTS,
1412 116 50         &b_opts, GIT_ITERATOR_DONT_AUTOEXPAND, opts)) < 0 ||
1413 116 50         (error = git_iterator_for_index(&a, repo, index, &a_opts)) < 0 ||
1414 116 50         (error = git_iterator_for_workdir(&b, repo, index, NULL, &b_opts)) < 0 ||
1415 116           (error = git_diff__from_iterators(&diff, repo, a, b, opts)) < 0)
1416             goto out;
1417              
1418 116 50         if ((diff->opts.flags & GIT_DIFF_UPDATE_INDEX) && ((git_diff_generated *)diff)->index_updated)
    0          
1419 0 0         if ((error = git_index_write(index)) < 0)
1420 0           goto out;
1421              
1422 116           *out = diff;
1423 116           diff = NULL;
1424             out:
1425 116           git_iterator_free(a);
1426 116           git_iterator_free(b);
1427 116           git_diff_free(diff);
1428 116           git__free(prefix);
1429              
1430 116           return error;
1431             }
1432              
1433 2           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 2           git_iterator_options a_opts = GIT_ITERATOR_OPTIONS_INIT,
1440 2           b_opts = GIT_ITERATOR_OPTIONS_INIT;
1441 2           git_iterator *a = NULL, *b = NULL;
1442 2           git_diff *diff = NULL;
1443 2           char *prefix = NULL;
1444             git_index *index;
1445             int error;
1446              
1447 2 50         assert(out && repo);
    50          
1448              
1449 2           *out = NULL;
1450              
1451 2 50         if ((error = diff_prepare_iterator_opts(&prefix, &a_opts, 0,
1452 2 50         &b_opts, GIT_ITERATOR_DONT_AUTOEXPAND, opts) < 0) ||
1453 2 50         (error = git_repository_index__weakptr(&index, repo)) < 0 ||
1454 2 50         (error = git_iterator_for_tree(&a, old_tree, &a_opts)) < 0 ||
1455 2 50         (error = git_iterator_for_workdir(&b, repo, index, old_tree, &b_opts)) < 0 ||
1456 2           (error = git_diff__from_iterators(&diff, repo, a, b, opts)) < 0)
1457             goto out;
1458              
1459 2           *out = diff;
1460 2           diff = NULL;
1461             out:
1462 2           git_iterator_free(a);
1463 2           git_iterator_free(b);
1464 2           git_diff_free(diff);
1465 2           git__free(prefix);
1466              
1467 2           return error;
1468             }
1469              
1470 0           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 0           git_diff *d1 = NULL, *d2 = NULL;
1477 0           git_index *index = NULL;
1478 0           int error = 0;
1479              
1480 0 0         assert(out && repo);
    0          
1481              
1482 0           *out = NULL;
1483              
1484 0 0         if ((error = diff_load_index(&index, repo)) < 0)
1485 0           return error;
1486              
1487 0 0         if (!(error = git_diff_tree_to_index(&d1, repo, tree, index, opts)) &&
    0          
1488 0           !(error = git_diff_index_to_workdir(&d2, repo, index, opts)))
1489 0           error = git_diff_merge(d1, d2);
1490              
1491 0           git_diff_free(d2);
1492              
1493 0 0         if (error) {
1494 0           git_diff_free(d1);
1495 0           d1 = NULL;
1496             }
1497              
1498 0           *out = d1;
1499 0           return error;
1500             }
1501              
1502 0           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 0           git_iterator_options a_opts = GIT_ITERATOR_OPTIONS_INIT,
1510 0           b_opts = GIT_ITERATOR_OPTIONS_INIT;
1511 0           git_iterator *a = NULL, *b = NULL;
1512 0           git_diff *diff = NULL;
1513 0           char *prefix = NULL;
1514             int error;
1515              
1516 0 0         assert(out && old_index && new_index);
    0          
    0          
1517              
1518 0           *out = NULL;
1519              
1520 0 0         if ((error = diff_prepare_iterator_opts(&prefix, &a_opts, GIT_ITERATOR_DONT_IGNORE_CASE,
1521 0 0         &b_opts, GIT_ITERATOR_DONT_IGNORE_CASE, opts) < 0) ||
1522 0 0         (error = git_iterator_for_index(&a, repo, old_index, &a_opts)) < 0 ||
1523 0 0         (error = git_iterator_for_index(&b, repo, new_index, &b_opts)) < 0 ||
1524 0           (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 0 0         if (old_index->ignore_case || new_index->ignore_case)
    0          
1529 0           diff_set_ignore_case(diff, true);
1530              
1531 0           *out = diff;
1532 0           diff = NULL;
1533             out:
1534 0           git_iterator_free(a);
1535 0           git_iterator_free(b);
1536 0           git_diff_free(diff);
1537 0           git__free(prefix);
1538              
1539 0           return error;
1540             }
1541              
1542 63           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 63           int cmp, error = 0;
1549             git_diff_delta *h2i, *i2w;
1550             size_t i, j, i_max, j_max;
1551 63           int (*strcomp)(const char *, const char *) = git__strcmp;
1552             bool h2i_icase, i2w_icase, icase_mismatch;
1553              
1554 63 100         i_max = head2idx ? head2idx->deltas.length : 0;
1555 63 100         j_max = idx2wd ? idx2wd->deltas.length : 0;
1556 63 100         if (!i_max && !j_max)
    100          
1557 21           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 42 100         h2i_icase = head2idx != NULL && git_diff_is_sorted_icase(head2idx);
    50          
1573 42 50         i2w_icase = idx2wd != NULL && git_diff_is_sorted_icase(idx2wd);
    50          
1574              
1575 42           icase_mismatch =
1576 42 100         (head2idx != NULL && idx2wd != NULL && h2i_icase != i2w_icase);
    50          
    50          
1577              
1578 42 50         if (icase_mismatch && h2i_icase) {
    0          
1579 0           git_vector_set_cmp(&head2idx->deltas, git_diff_delta__cmp);
1580 0           git_vector_sort(&head2idx->deltas);
1581             }
1582              
1583 42 50         if (i2w_icase && !icase_mismatch) {
    0          
1584 0           strcomp = git__strcasecmp;
1585              
1586 0           git_vector_set_cmp(&idx2wd->deltas, diff_delta_i2w_casecmp);
1587 0           git_vector_sort(&idx2wd->deltas);
1588 42 50         } else if (idx2wd != NULL) {
1589 42           git_vector_set_cmp(&idx2wd->deltas, diff_delta_i2w_cmp);
1590 42           git_vector_sort(&idx2wd->deltas);
1591             }
1592              
1593 108 100         for (i = 0, j = 0; i < i_max || j < j_max; ) {
    100          
1594 66 100         h2i = head2idx ? GIT_VECTOR_GET(&head2idx->deltas, i) : NULL;
    100          
1595 66 50         i2w = idx2wd ? GIT_VECTOR_GET(&idx2wd->deltas, j) : NULL;
    100          
1596              
1597 66 100         cmp = !i2w ? -1 : !h2i ? 1 :
    100          
1598 21           strcomp(h2i->new_file.path, i2w->old_file.path);
1599              
1600 66 100         if (cmp < 0) {
1601 20           i++; i2w = NULL;
1602 46 100         } else if (cmp > 0) {
1603 36           j++; h2i = NULL;
1604             } else {
1605 10           i++; j++;
1606             }
1607              
1608 66 50         if ((error = cb(h2i, i2w, payload)) != 0) {
1609 0           git_error_set_after_callback(error);
1610 0           break;
1611             }
1612             }
1613              
1614             /* restore case-insensitive delta sort */
1615 42 50         if (icase_mismatch && h2i_icase) {
    0          
1616 0           git_vector_set_cmp(&head2idx->deltas, git_diff_delta__casecmp);
1617 0           git_vector_sort(&head2idx->deltas);
1618             }
1619              
1620             /* restore idx2wd sort by new path */
1621 42 50         if (idx2wd != NULL) {
1622 42 50         git_vector_set_cmp(&idx2wd->deltas,
1623             i2w_icase ? git_diff_delta__casecmp : git_diff_delta__cmp);
1624 42           git_vector_sort(&idx2wd->deltas);
1625             }
1626              
1627 42           return error;
1628             }
1629              
1630 3           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 3           git_commit *parent = NULL;
1637 3           git_diff *commit_diff = NULL;
1638 3           git_tree *old_tree = NULL, *new_tree = NULL;
1639             size_t parents;
1640 3           int error = 0;
1641              
1642 3           *out = NULL;
1643              
1644 3 50         if ((parents = git_commit_parentcount(commit)) > 1) {
1645             char commit_oidstr[GIT_OID_HEXSZ + 1];
1646              
1647 0           error = -1;
1648 0           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 0           goto on_error;
1651             }
1652              
1653 3 50         if (parents > 0)
1654 3 50         if ((error = git_commit_parent(&parent, commit, 0)) < 0 ||
    50          
1655 3           (error = git_commit_tree(&old_tree, parent)) < 0)
1656             goto on_error;
1657              
1658 3 50         if ((error = git_commit_tree(&new_tree, commit)) < 0 ||
    50          
1659 3           (error = git_diff_tree_to_tree(&commit_diff, repo, old_tree, new_tree, opts)) < 0)
1660             goto on_error;
1661              
1662 3           *out = commit_diff;
1663              
1664             on_error:
1665 3           git_tree_free(new_tree);
1666 3           git_tree_free(old_tree);
1667 3           git_commit_free(parent);
1668              
1669 3           return error;
1670             }
1671