File Coverage

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