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