]> git.proxmox.com Git - libgit2.git/blame - src/diff.c
Diff: teach get_workdir_content to show a submodule as text
[libgit2.git] / src / diff.c
CommitLineData
cd33323b 1/*
74fa4bfa 2 * Copyright (C) 2012 the libgit2 contributors
cd33323b
RB
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 */
cd33323b 7#include "common.h"
65b09b1d 8#include "diff.h"
74fa4bfa 9#include "fileops.h"
95dfb031 10#include "config.h"
14a513e0 11#include "attr_file.h"
60b9d3fc 12#include "filter.h"
cd33323b 13
41a82592
RB
14static char *diff_prefix_from_pathspec(const git_strarray *pathspec)
15{
16 git_buf prefix = GIT_BUF_INIT;
17 const char *scan;
18
19 if (git_buf_common_prefix(&prefix, pathspec) < 0)
20 return NULL;
21
22 /* diff prefix will only be leading non-wildcards */
ffbc689c 23 for (scan = prefix.ptr; *scan; ++scan) {
24 if (git__iswildcard(*scan) &&
25 (scan == prefix.ptr || (*(scan - 1) != '\\')))
26 break;
27 }
41a82592
RB
28 git_buf_truncate(&prefix, scan - prefix.ptr);
29
ffbc689c 30 if (prefix.size <= 0) {
31 git_buf_free(&prefix);
32 return NULL;
33 }
41a82592 34
ffbc689c 35 git_buf_unescape(&prefix);
36
37 return git_buf_detach(&prefix);
41a82592
RB
38}
39
14a513e0 40static bool diff_pathspec_is_interesting(const git_strarray *pathspec)
3a437590 41{
14a513e0 42 const char *str;
3a437590 43
14a513e0
RB
44 if (pathspec == NULL || pathspec->count == 0)
45 return false;
46 if (pathspec->count > 1)
47 return true;
48
49 str = pathspec->strings[0];
50 if (!str || !str[0] || (!str[1] && (str[0] == '*' || str[0] == '.')))
51 return false;
52 return true;
53}
54
55static bool diff_path_matches_pathspec(git_diff_list *diff, const char *path)
56{
57 unsigned int i;
58 git_attr_fnmatch *match;
59
60 if (!diff->pathspec.length)
61 return true;
62
63 git_vector_foreach(&diff->pathspec, i, match) {
a1773f9d 64 int result = strcmp(match->pattern, path) ? FNM_NOMATCH : 0;
60b9d3fc
RB
65
66 if (((diff->opts.flags & GIT_DIFF_DISABLE_PATHSPEC_MATCH) == 0) &&
a1773f9d 67 result == FNM_NOMATCH)
ffbc689c 68 result = p_fnmatch(match->pattern, path, 0);
14a513e0
RB
69
70 /* if we didn't match, look for exact dirname prefix match */
3fbcac89 71 if (result == FNM_NOMATCH &&
14a513e0
RB
72 (match->flags & GIT_ATTR_FNMATCH_HASWILD) == 0 &&
73 strncmp(path, match->pattern, match->length) == 0 &&
74 path[match->length] == '/')
75 result = 0;
3a437590 76
14a513e0
RB
77 if (result == 0)
78 return (match->flags & GIT_ATTR_FNMATCH_NEGATIVE) ? false : true;
74fa4bfa 79 }
3a437590 80
14a513e0 81 return false;
3a437590
RB
82}
83
74fa4bfa 84static git_diff_delta *diff_delta__alloc(
a2e895be 85 git_diff_list *diff,
e1bcc191 86 git_delta_t status,
74fa4bfa 87 const char *path)
3a437590 88{
a2e895be 89 git_diff_delta *delta = git__calloc(1, sizeof(git_diff_delta));
a2e895be 90 if (!delta)
74fa4bfa 91 return NULL;
3a437590 92
16b83019 93 delta->old_file.path = git_pool_strdup(&diff->pool, path);
2de60205 94 if (delta->old_file.path == NULL) {
a2e895be 95 git__free(delta);
74fa4bfa 96 return NULL;
a2e895be 97 }
40879fac 98
2de60205 99 delta->new_file.path = delta->old_file.path;
a2e895be 100
74fa4bfa
RB
101 if (diff->opts.flags & GIT_DIFF_REVERSE) {
102 switch (status) {
e1bcc191
RB
103 case GIT_DELTA_ADDED: status = GIT_DELTA_DELETED; break;
104 case GIT_DELTA_DELETED: status = GIT_DELTA_ADDED; break;
74fa4bfa
RB
105 default: break; /* leave other status values alone */
106 }
107 }
a2e895be
RB
108 delta->status = status;
109
74fa4bfa
RB
110 return delta;
111}
112
19fa2bc1
RB
113static git_diff_delta *diff_delta__dup(
114 const git_diff_delta *d, git_pool *pool)
74fa4bfa
RB
115{
116 git_diff_delta *delta = git__malloc(sizeof(git_diff_delta));
117 if (!delta)
118 return NULL;
119
120 memcpy(delta, d, sizeof(git_diff_delta));
121
16b83019
RB
122 delta->old_file.path = git_pool_strdup(pool, d->old_file.path);
123 if (delta->old_file.path == NULL)
19fa2bc1 124 goto fail;
a2e895be 125
2de60205 126 if (d->new_file.path != d->old_file.path) {
16b83019
RB
127 delta->new_file.path = git_pool_strdup(pool, d->new_file.path);
128 if (delta->new_file.path == NULL)
19fa2bc1 129 goto fail;
74fa4bfa 130 } else {
2de60205 131 delta->new_file.path = delta->old_file.path;
74fa4bfa 132 }
3a437590 133
74fa4bfa 134 return delta;
19fa2bc1
RB
135
136fail:
137 git__free(delta);
138 return NULL;
a2e895be
RB
139}
140
74fa4bfa 141static git_diff_delta *diff_delta__merge_like_cgit(
19fa2bc1 142 const git_diff_delta *a, const git_diff_delta *b, git_pool *pool)
a2e895be 143{
145e696b 144 git_diff_delta *dup;
a2e895be 145
74fa4bfa
RB
146 /* Emulate C git for merging two diffs (a la 'git diff <sha>').
147 *
148 * When C git does a diff between the work dir and a tree, it actually
149 * diffs with the index but uses the workdir contents. This emulates
150 * those choices so we can emulate the type of diff.
145e696b
RB
151 *
152 * We have three file descriptions here, let's call them:
153 * f1 = a->old_file
154 * f2 = a->new_file AND b->old_file
155 * f3 = b->new_file
74fa4bfa 156 */
145e696b
RB
157
158 /* if f2 == f3 or f2 is deleted, then just dup the 'a' diff */
159 if (b->status == GIT_DELTA_UNMODIFIED || a->status == GIT_DELTA_DELETED)
160 return diff_delta__dup(a, pool);
161
162 /* otherwise, base this diff on the 'b' diff */
163 if ((dup = diff_delta__dup(b, pool)) == NULL)
164 return NULL;
165
166 /* If 'a' status is uninteresting, then we're done */
167 if (a->status == GIT_DELTA_UNMODIFIED)
168 return dup;
169
170 assert(a->status != GIT_DELTA_UNMODIFIED);
171 assert(b->status != GIT_DELTA_UNMODIFIED);
172
173 /* A cgit exception is that the diff of a file that is only in the
174 * index (i.e. not in HEAD nor workdir) is given as empty.
175 */
176 if (dup->status == GIT_DELTA_DELETED) {
177 if (a->status == GIT_DELTA_ADDED)
e1bcc191 178 dup->status = GIT_DELTA_UNMODIFIED;
145e696b
RB
179 /* else don't overwrite DELETE status */
180 } else {
181 dup->status = a->status;
74fa4bfa 182 }
145e696b
RB
183
184 git_oid_cpy(&dup->old_file.oid, &a->old_file.oid);
185 dup->old_file.mode = a->old_file.mode;
186 dup->old_file.size = a->old_file.size;
187 dup->old_file.flags = a->old_file.flags;
3a437590 188
74fa4bfa 189 return dup;
3a437590
RB
190}
191
74fa4bfa
RB
192static int diff_delta__from_one(
193 git_diff_list *diff,
e1bcc191 194 git_delta_t status,
74fa4bfa 195 const git_index_entry *entry)
3a437590 196{
66142ae0
RB
197 git_diff_delta *delta;
198
199 if (status == GIT_DELTA_IGNORED &&
200 (diff->opts.flags & GIT_DIFF_INCLUDE_IGNORED) == 0)
201 return 0;
202
203 if (status == GIT_DELTA_UNTRACKED &&
204 (diff->opts.flags & GIT_DIFF_INCLUDE_UNTRACKED) == 0)
205 return 0;
206
14a513e0
RB
207 if (!diff_path_matches_pathspec(diff, entry->path))
208 return 0;
209
66142ae0 210 delta = diff_delta__alloc(diff, status, entry->path);
ae9e29fd 211 GITERR_CHECK_ALLOC(delta);
3a437590 212
74fa4bfa 213 /* This fn is just for single-sided diffs */
e1bcc191 214 assert(status != GIT_DELTA_MODIFIED);
3a437590 215
e1bcc191 216 if (delta->status == GIT_DELTA_DELETED) {
2de60205
RB
217 delta->old_file.mode = entry->mode;
218 delta->old_file.size = entry->file_size;
219 git_oid_cpy(&delta->old_file.oid, &entry->oid);
74fa4bfa 220 } else /* ADDED, IGNORED, UNTRACKED */ {
2de60205
RB
221 delta->new_file.mode = entry->mode;
222 delta->new_file.size = entry->file_size;
223 git_oid_cpy(&delta->new_file.oid, &entry->oid);
74fa4bfa 224 }
3a437590 225
2de60205
RB
226 delta->old_file.flags |= GIT_DIFF_FILE_VALID_OID;
227 delta->new_file.flags |= GIT_DIFF_FILE_VALID_OID;
3a437590 228
ae9e29fd 229 if (git_vector_insert(&diff->deltas, delta) < 0) {
19fa2bc1 230 git__free(delta);
ae9e29fd
RB
231 return -1;
232 }
3a437590 233
ae9e29fd 234 return 0;
3a437590
RB
235}
236
74fa4bfa
RB
237static int diff_delta__from_two(
238 git_diff_list *diff,
e1bcc191 239 git_delta_t status,
2de60205 240 const git_index_entry *old_entry,
0abd7244 241 uint32_t old_mode,
2de60205 242 const git_index_entry *new_entry,
0abd7244 243 uint32_t new_mode,
74fa4bfa 244 git_oid *new_oid)
65b09b1d 245{
74fa4bfa 246 git_diff_delta *delta;
65b09b1d 247
66142ae0
RB
248 if (status == GIT_DELTA_UNMODIFIED &&
249 (diff->opts.flags & GIT_DIFF_INCLUDE_UNMODIFIED) == 0)
250 return 0;
251
74fa4bfa 252 if ((diff->opts.flags & GIT_DIFF_REVERSE) != 0) {
0abd7244
RB
253 uint32_t temp_mode = old_mode;
254 const git_index_entry *temp_entry = old_entry;
2de60205 255 old_entry = new_entry;
0abd7244
RB
256 new_entry = temp_entry;
257 old_mode = new_mode;
258 new_mode = temp_mode;
74fa4bfa 259 }
65b09b1d 260
2de60205 261 delta = diff_delta__alloc(diff, status, old_entry->path);
ae9e29fd 262 GITERR_CHECK_ALLOC(delta);
74fa4bfa 263
2de60205 264 git_oid_cpy(&delta->old_file.oid, &old_entry->oid);
60b9d3fc
RB
265 delta->old_file.size = old_entry->file_size;
266 delta->old_file.mode = old_mode;
2de60205 267 delta->old_file.flags |= GIT_DIFF_FILE_VALID_OID;
74fa4bfa 268
5f69a31f 269 git_oid_cpy(&delta->new_file.oid, &new_entry->oid);
60b9d3fc
RB
270 delta->new_file.size = new_entry->file_size;
271 delta->new_file.mode = new_mode;
5f69a31f
RB
272
273 if (new_oid) {
274 if ((diff->opts.flags & GIT_DIFF_REVERSE) != 0)
275 git_oid_cpy(&delta->old_file.oid, new_oid);
276 else
277 git_oid_cpy(&delta->new_file.oid, new_oid);
278 }
279
2de60205
RB
280 if (new_oid || !git_oid_iszero(&new_entry->oid))
281 delta->new_file.flags |= GIT_DIFF_FILE_VALID_OID;
74fa4bfa 282
ae9e29fd 283 if (git_vector_insert(&diff->deltas, delta) < 0) {
19fa2bc1 284 git__free(delta);
ae9e29fd
RB
285 return -1;
286 }
3a437590 287
ae9e29fd 288 return 0;
65b09b1d
RB
289}
290
19fa2bc1 291static char *diff_strdup_prefix(git_pool *pool, const char *prefix)
a2e895be
RB
292{
293 size_t len = strlen(prefix);
19fa2bc1
RB
294
295 /* append '/' at end if needed */
296 if (len > 0 && prefix[len - 1] != '/')
297 return git_pool_strcat(pool, prefix, "/");
298 else
299 return git_pool_strndup(pool, prefix, len + 1);
a2e895be
RB
300}
301
74fa4bfa
RB
302static int diff_delta__cmp(const void *a, const void *b)
303{
304 const git_diff_delta *da = a, *db = b;
2de60205 305 int val = strcmp(da->old_file.path, db->old_file.path);
74fa4bfa
RB
306 return val ? val : ((int)da->status - (int)db->status);
307}
308
95dfb031
RB
309static int config_bool(git_config *cfg, const char *name, int defvalue)
310{
311 int val = defvalue;
29e948de
VM
312
313 if (git_config_get_bool(&val, cfg, name) < 0)
95dfb031 314 giterr_clear();
29e948de 315
95dfb031
RB
316 return val;
317}
318
65b09b1d
RB
319static git_diff_list *git_diff_list_alloc(
320 git_repository *repo, const git_diff_options *opts)
321{
95dfb031 322 git_config *cfg;
14a513e0 323 size_t i;
65b09b1d 324 git_diff_list *diff = git__calloc(1, sizeof(git_diff_list));
a2e895be
RB
325 if (diff == NULL)
326 return NULL;
327
f335ecd6 328 GIT_REFCOUNT_INC(diff);
a2e895be 329 diff->repo = repo;
a2e895be 330
19fa2bc1
RB
331 if (git_vector_init(&diff->deltas, 0, diff_delta__cmp) < 0 ||
332 git_pool_init(&diff->pool, 1, 0) < 0)
333 goto fail;
334
95dfb031 335 /* load config values that affect diff behavior */
7784bcbb 336 if (git_repository_config__weakptr(&cfg, repo) < 0)
95dfb031
RB
337 goto fail;
338 if (config_bool(cfg, "core.symlinks", 1))
339 diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_HAS_SYMLINKS;
340 if (config_bool(cfg, "core.ignorestat", 0))
341 diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_ASSUME_UNCHANGED;
342 if (config_bool(cfg, "core.filemode", 1))
0abd7244 343 diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_TRUST_MODE_BITS;
95dfb031
RB
344 if (config_bool(cfg, "core.trustctime", 1))
345 diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_TRUST_CTIME;
346 /* Don't set GIT_DIFFCAPS_USE_DEV - compile time option in core git */
95dfb031 347
a2e895be
RB
348 if (opts == NULL)
349 return diff;
350
351 memcpy(&diff->opts, opts, sizeof(git_diff_options));
14a513e0 352 memset(&diff->opts.pathspec, 0, sizeof(diff->opts.pathspec));
a2e895be 353
16b83019 354 diff->opts.old_prefix = diff_strdup_prefix(&diff->pool,
2de60205 355 opts->old_prefix ? opts->old_prefix : DIFF_OLD_PREFIX_DEFAULT);
16b83019 356 diff->opts.new_prefix = diff_strdup_prefix(&diff->pool,
2de60205 357 opts->new_prefix ? opts->new_prefix : DIFF_NEW_PREFIX_DEFAULT);
74fa4bfa 358
16b83019 359 if (!diff->opts.old_prefix || !diff->opts.new_prefix)
95dfb031 360 goto fail;
74fa4bfa 361
a2e895be 362 if (diff->opts.flags & GIT_DIFF_REVERSE) {
2de60205
RB
363 char *swap = diff->opts.old_prefix;
364 diff->opts.old_prefix = diff->opts.new_prefix;
365 diff->opts.new_prefix = swap;
65b09b1d 366 }
a2e895be 367
14a513e0
RB
368 /* only copy pathspec if it is "interesting" so we can test
369 * diff->pathspec.length > 0 to know if it is worth calling
370 * fnmatch as we iterate.
371 */
372 if (!diff_pathspec_is_interesting(&opts->pathspec))
373 return diff;
74fa4bfa 374
821f6bc7
RB
375 if (git_vector_init(
376 &diff->pathspec, (unsigned int)opts->pathspec.count, NULL) < 0)
14a513e0
RB
377 goto fail;
378
379 for (i = 0; i < opts->pathspec.count; ++i) {
380 int ret;
381 const char *pattern = opts->pathspec.strings[i];
19fa2bc1 382 git_attr_fnmatch *match = git__calloc(1, sizeof(git_attr_fnmatch));
14a513e0
RB
383 if (!match)
384 goto fail;
2a99df69 385 match->flags = GIT_ATTR_FNMATCH_ALLOWSPACE;
19fa2bc1 386 ret = git_attr_fnmatch__parse(match, &diff->pool, NULL, &pattern);
904b67e6 387 if (ret == GIT_ENOTFOUND) {
14a513e0
RB
388 git__free(match);
389 continue;
390 } else if (ret < 0)
391 goto fail;
392
393 if (git_vector_insert(&diff->pathspec, match) < 0)
394 goto fail;
395 }
a2e895be 396
65b09b1d 397 return diff;
95dfb031
RB
398
399fail:
14a513e0 400 git_diff_list_free(diff);
95dfb031 401 return NULL;
65b09b1d
RB
402}
403
f335ecd6 404static void diff_list_free(git_diff_list *diff)
65b09b1d 405{
3a437590 406 git_diff_delta *delta;
14a513e0 407 git_attr_fnmatch *match;
3a437590
RB
408 unsigned int i;
409
74fa4bfa 410 git_vector_foreach(&diff->deltas, i, delta) {
19fa2bc1 411 git__free(delta);
74fa4bfa 412 diff->deltas.contents[i] = NULL;
a2e895be 413 }
74fa4bfa 414 git_vector_free(&diff->deltas);
14a513e0
RB
415
416 git_vector_foreach(&diff->pathspec, i, match) {
19fa2bc1
RB
417 git__free(match);
418 diff->pathspec.contents[i] = NULL;
14a513e0
RB
419 }
420 git_vector_free(&diff->pathspec);
421
19fa2bc1 422 git_pool_clear(&diff->pool);
65b09b1d
RB
423 git__free(diff);
424}
425
f335ecd6
RB
426void git_diff_list_free(git_diff_list *diff)
427{
428 if (!diff)
429 return;
430
431 GIT_REFCOUNT_DEC(diff, diff_list_free);
432}
433
5f69a31f
RB
434void git_diff_list_addref(git_diff_list *diff)
435{
436 GIT_REFCOUNT_INC(diff);
437}
438
74fa4bfa 439static int oid_for_workdir_item(
65b09b1d 440 git_repository *repo,
74fa4bfa
RB
441 const git_index_entry *item,
442 git_oid *oid)
65b09b1d 443{
ae9e29fd 444 int result;
74fa4bfa 445 git_buf full_path = GIT_BUF_INIT;
a2e895be 446
ae9e29fd
RB
447 if (git_buf_joinpath(&full_path, git_repository_workdir(repo), item->path) < 0)
448 return -1;
a2e895be 449
ae9e29fd 450 /* calculate OID for file if possible*/
74fa4bfa 451 if (S_ISLNK(item->mode))
ae9e29fd
RB
452 result = git_odb__hashlink(oid, full_path.ptr);
453 else if (!git__is_sizet(item->file_size)) {
454 giterr_set(GITERR_OS, "File size overflow for 32-bit systems");
455 result = -1;
456 } else {
60b9d3fc
RB
457 git_vector filters = GIT_VECTOR_INIT;
458
459 result = git_filters_load(
460 &filters, repo, item->path, GIT_FILTER_TO_ODB);
461 if (result >= 0) {
462 int fd = git_futils_open_ro(full_path.ptr);
463 if (fd < 0)
464 result = fd;
465 else {
466 result = git_odb__hashfd_filtered(
467 oid, fd, (size_t)item->file_size, GIT_OBJ_BLOB, &filters);
468 p_close(fd);
469 }
74fa4bfa 470 }
60b9d3fc
RB
471
472 git_filters_free(&filters);
a2e895be 473 }
a2e895be 474
74fa4bfa 475 git_buf_free(&full_path);
a2e895be 476
ae9e29fd 477 return result;
a2e895be
RB
478}
479
0abd7244 480#define MODE_BITS_MASK 0000777
95dfb031 481
74fa4bfa 482static int maybe_modified(
2de60205 483 git_iterator *old_iter,
74fa4bfa 484 const git_index_entry *oitem,
2de60205 485 git_iterator *new_iter,
74fa4bfa
RB
486 const git_index_entry *nitem,
487 git_diff_list *diff)
e47329b6 488{
74fa4bfa 489 git_oid noid, *use_noid = NULL;
66142ae0 490 git_delta_t status = GIT_DELTA_MODIFIED;
95dfb031
RB
491 unsigned int omode = oitem->mode;
492 unsigned int nmode = nitem->mode;
e47329b6 493
2de60205 494 GIT_UNUSED(old_iter);
e47329b6 495
14a513e0
RB
496 if (!diff_path_matches_pathspec(diff, oitem->path))
497 return 0;
498
da825c92 499 /* on platforms with no symlinks, preserve mode of existing symlinks */
95dfb031 500 if (S_ISLNK(omode) && S_ISREG(nmode) &&
5fdc41e7
RB
501 !(diff->diffcaps & GIT_DIFFCAPS_HAS_SYMLINKS) &&
502 new_iter->type == GIT_ITERATOR_WORKDIR)
da825c92 503 nmode = omode;
e47329b6 504
0abd7244
RB
505 /* on platforms with no execmode, just preserve old mode */
506 if (!(diff->diffcaps & GIT_DIFFCAPS_TRUST_MODE_BITS) &&
507 (nmode & MODE_BITS_MASK) != (omode & MODE_BITS_MASK) &&
508 new_iter->type == GIT_ITERATOR_WORKDIR)
509 nmode = (nmode & ~MODE_BITS_MASK) | (omode & MODE_BITS_MASK);
e47329b6 510
0abd7244 511 /* support "assume unchanged" (poorly, b/c we still stat everything) */
95dfb031
RB
512 if ((diff->diffcaps & GIT_DIFFCAPS_ASSUME_UNCHANGED) != 0)
513 status = (oitem->flags_extended & GIT_IDXENTRY_INTENT_TO_ADD) ?
514 GIT_DELTA_MODIFIED : GIT_DELTA_UNMODIFIED;
515
516 /* support "skip worktree" index bit */
517 else if ((oitem->flags_extended & GIT_IDXENTRY_SKIP_WORKTREE) != 0)
66142ae0 518 status = GIT_DELTA_UNMODIFIED;
e47329b6 519
66142ae0 520 /* if basic type of file changed, then split into delete and add */
95dfb031 521 else if (GIT_MODE_TYPE(omode) != GIT_MODE_TYPE(nmode)) {
ae9e29fd
RB
522 if (diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem) < 0 ||
523 diff_delta__from_one(diff, GIT_DELTA_ADDED, nitem) < 0)
524 return -1;
525 return 0;
e47329b6
RB
526 }
527
66142ae0
RB
528 /* if oids and modes match, then file is unmodified */
529 else if (git_oid_cmp(&oitem->oid, &nitem->oid) == 0 &&
95dfb031 530 omode == nmode)
66142ae0 531 status = GIT_DELTA_UNMODIFIED;
e47329b6 532
5f69a31f
RB
533 /* if we have an unknown OID and a workdir iterator, then check some
534 * circumstances that can accelerate things or need special handling
0abd7244 535 */
5f69a31f
RB
536 else if (git_oid_iszero(&nitem->oid) &&
537 new_iter->type == GIT_ITERATOR_WORKDIR)
0abd7244 538 {
95dfb031 539 /* TODO: add check against index file st_mtime to avoid racy-git */
e47329b6 540
5f69a31f
RB
541 /* if the stat data looks exactly alike, then assume the same */
542 if (omode == nmode &&
543 oitem->file_size == nitem->file_size &&
95dfb031
RB
544 (!(diff->diffcaps & GIT_DIFFCAPS_TRUST_CTIME) ||
545 (oitem->ctime.seconds == nitem->ctime.seconds)) &&
74fa4bfa 546 oitem->mtime.seconds == nitem->mtime.seconds &&
95dfb031
RB
547 (!(diff->diffcaps & GIT_DIFFCAPS_USE_DEV) ||
548 (oitem->dev == nitem->dev)) &&
74fa4bfa
RB
549 oitem->ino == nitem->ino &&
550 oitem->uid == nitem->uid &&
551 oitem->gid == nitem->gid)
66142ae0
RB
552 status = GIT_DELTA_UNMODIFIED;
553
95dfb031
RB
554 else if (S_ISGITLINK(nmode)) {
555 git_submodule *sub;
556
557 if ((diff->opts.flags & GIT_DIFF_IGNORE_SUBMODULES) != 0)
558 status = GIT_DELTA_UNMODIFIED;
559 else if (git_submodule_lookup(&sub, diff->repo, nitem->path) < 0)
560 return -1;
aa13bf05 561 else if (git_submodule_ignore(sub) == GIT_SUBMODULE_IGNORE_ALL)
95dfb031
RB
562 status = GIT_DELTA_UNMODIFIED;
563 else {
7e57d250
SC
564 unsigned int sm_status = 0;
565 if (git_submodule_status(&sm_status, sub) < 0)
566 return -1;
567 status = GIT_SUBMODULE_STATUS_IS_UNMODIFIED(sm_status)
568 ? GIT_DELTA_UNMODIFIED : GIT_DELTA_MODIFIED;
95dfb031
RB
569 }
570 }
5f69a31f 571 }
e47329b6 572
5f69a31f
RB
573 /* if we got here and decided that the files are modified, but we
574 * haven't calculated the OID of the new item, then calculate it now
575 */
576 if (status == GIT_DELTA_MODIFIED && git_oid_iszero(&nitem->oid)) {
577 if (oid_for_workdir_item(diff->repo, nitem, &noid) < 0)
ae9e29fd 578 return -1;
5f69a31f 579 else if (omode == nmode && git_oid_equal(&oitem->oid, &noid))
66142ae0 580 status = GIT_DELTA_UNMODIFIED;
e47329b6 581
74fa4bfa
RB
582 /* store calculated oid so we don't have to recalc later */
583 use_noid = &noid;
e47329b6
RB
584 }
585
0abd7244
RB
586 return diff_delta__from_two(
587 diff, status, oitem, omode, nitem, nmode, use_noid);
e47329b6
RB
588}
589
ec40b7f9
PK
590static int git_index_entry_cmp_case(const void *a, const void *b)
591{
592 const git_index_entry *entry_a = a;
593 const git_index_entry *entry_b = b;
594
595 return strcmp(entry_a->path, entry_b->path);
596}
597
598static int git_index_entry_cmp_icase(const void *a, const void *b)
599{
600 const git_index_entry *entry_a = a;
601 const git_index_entry *entry_b = b;
602
603 return strcasecmp(entry_a->path, entry_b->path);
604}
605
74fa4bfa 606static int diff_from_iterators(
e47329b6 607 git_repository *repo,
74fa4bfa 608 const git_diff_options *opts, /**< can be NULL for defaults */
2de60205
RB
609 git_iterator *old_iter,
610 git_iterator *new_iter,
74fa4bfa 611 git_diff_list **diff_ptr)
e47329b6 612{
74fa4bfa 613 const git_index_entry *oitem, *nitem;
b709e951 614 git_buf ignore_prefix = GIT_BUF_INIT;
74fa4bfa 615 git_diff_list *diff = git_diff_list_alloc(repo, opts);
ec40b7f9
PK
616 git_vector_cmp entry_compare;
617
ae9e29fd
RB
618 if (!diff)
619 goto fail;
e47329b6 620
2de60205
RB
621 diff->old_src = old_iter->type;
622 diff->new_src = new_iter->type;
e47329b6 623
ec40b7f9
PK
624 /* Use case-insensitive compare if either iterator has
625 * the ignore_case bit set */
626 if (!old_iter->ignore_case && !new_iter->ignore_case) {
627 entry_compare = git_index_entry_cmp_case;
628 diff->opts.flags &= ~GIT_DIFF_DELTAS_ARE_ICASE;
629 } else {
630 entry_compare = git_index_entry_cmp_icase;
631 diff->opts.flags |= GIT_DIFF_DELTAS_ARE_ICASE;
632
633 /* If one of the iterators doesn't have ignore_case set,
634 * then that's unfortunate because we'll have to spool
635 * its data, sort it icase, and then use that for our
636 * merge join to the other iterator that is icase sorted */
637 if (!old_iter->ignore_case) {
638 if (git_iterator_spoolandsort(&old_iter, old_iter, git_index_entry_cmp_icase, true) < 0)
639 goto fail;
640 } else if (!new_iter->ignore_case) {
641 if (git_iterator_spoolandsort(&new_iter, new_iter, git_index_entry_cmp_icase, true) < 0)
642 goto fail;
643 }
644 }
645
16b83019
RB
646 if (git_iterator_current(old_iter, &oitem) < 0 ||
647 git_iterator_current(new_iter, &nitem) < 0)
ae9e29fd 648 goto fail;
65b09b1d 649
74fa4bfa 650 /* run iterators building diffs */
ae9e29fd 651 while (oitem || nitem) {
cd33323b 652
74fa4bfa 653 /* create DELETED records for old items not matched in new */
ec40b7f9 654 if (oitem && (!nitem || entry_compare(oitem, nitem) < 0)) {
ae9e29fd 655 if (diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem) < 0 ||
16b83019 656 git_iterator_advance(old_iter, &oitem) < 0)
ae9e29fd 657 goto fail;
65b09b1d 658 }
a2e895be 659
74fa4bfa
RB
660 /* create ADDED, TRACKED, or IGNORED records for new items not
661 * matched in old (and/or descend into directories as needed)
662 */
ec40b7f9 663 else if (nitem && (!oitem || entry_compare(oitem, nitem) > 0)) {
bd4ca902 664 git_delta_t delta_type = GIT_DELTA_UNTRACKED;
a2e895be 665
bd4ca902 666 /* check if contained in ignored parent directory */
b709e951 667 if (git_buf_len(&ignore_prefix) &&
ec40b7f9 668 ITERATOR_PREFIXCMP(*old_iter, nitem->path, git_buf_cstr(&ignore_prefix)) == 0)
bd4ca902 669 delta_type = GIT_DELTA_IGNORED;
74fa4bfa
RB
670
671 if (S_ISDIR(nitem->mode)) {
bd4ca902
RB
672 /* recurse into directory only if there are tracked items in
673 * it or if the user requested the contents of untracked
674 * directories and it is not under an ignored directory.
4b136a94 675 */
ec40b7f9 676 if ((oitem && ITERATOR_PREFIXCMP(*old_iter, oitem->path, nitem->path) == 0) ||
bd4ca902
RB
677 (delta_type == GIT_DELTA_UNTRACKED &&
678 (diff->opts.flags & GIT_DIFF_RECURSE_UNTRACKED_DIRS) != 0))
4b136a94 679 {
bd4ca902
RB
680 /* if this directory is ignored, remember it as the
681 * "ignore_prefix" for processing contained items
682 */
683 if (delta_type == GIT_DELTA_UNTRACKED &&
684 git_iterator_current_is_ignored(new_iter))
b709e951 685 git_buf_sets(&ignore_prefix, nitem->path);
40879fac 686
16b83019 687 if (git_iterator_advance_into_directory(new_iter, &nitem) < 0)
ae9e29fd 688 goto fail;
40879fac 689
74fa4bfa
RB
690 continue;
691 }
65b09b1d 692 }
bd4ca902
RB
693
694 /* In core git, the next two "else if" clauses are effectively
695 * reversed -- i.e. when an untracked file contained in an
696 * ignored directory is individually ignored, it shows up as an
697 * ignored file in the diff list, even though other untracked
698 * files in the same directory are skipped completely.
699 *
700 * To me, this is odd. If the directory is ignored and the file
701 * is untracked, we should skip it consistently, regardless of
702 * whether it happens to match a pattern in the ignore file.
703 *
704 * To match the core git behavior, just reverse the following
705 * two "else if" cases so that individual file ignores are
706 * checked before container directory exclusions are used to
707 * skip the file.
708 */
709 else if (delta_type == GIT_DELTA_IGNORED) {
710 if (git_iterator_advance(new_iter, &nitem) < 0)
711 goto fail;
712 continue; /* ignored parent directory, so skip completely */
713 }
714
715 else if (git_iterator_current_is_ignored(new_iter))
e1bcc191 716 delta_type = GIT_DELTA_IGNORED;
bd4ca902
RB
717
718 else if (new_iter->type != GIT_ITERATOR_WORKDIR)
719 delta_type = GIT_DELTA_ADDED;
74fa4bfa 720
ae9e29fd 721 if (diff_delta__from_one(diff, delta_type, nitem) < 0 ||
16b83019 722 git_iterator_advance(new_iter, &nitem) < 0)
ae9e29fd 723 goto fail;
65b09b1d
RB
724 }
725
74fa4bfa
RB
726 /* otherwise item paths match, so create MODIFIED record
727 * (or ADDED and DELETED pair if type changed)
a2e895be 728 */
ae9e29fd 729 else {
f08c60a5 730 assert(oitem && nitem && entry_compare(oitem, nitem) == 0);
a2e895be 731
16b83019
RB
732 if (maybe_modified(old_iter, oitem, new_iter, nitem, diff) < 0 ||
733 git_iterator_advance(old_iter, &oitem) < 0 ||
734 git_iterator_advance(new_iter, &nitem) < 0)
ae9e29fd
RB
735 goto fail;
736 }
65b09b1d
RB
737 }
738
2de60205
RB
739 git_iterator_free(old_iter);
740 git_iterator_free(new_iter);
b709e951
RB
741 git_buf_free(&ignore_prefix);
742
74fa4bfa 743 *diff_ptr = diff;
ae9e29fd 744 return 0;
65b09b1d 745
ae9e29fd 746fail:
16b83019
RB
747 git_iterator_free(old_iter);
748 git_iterator_free(new_iter);
b709e951
RB
749 git_buf_free(&ignore_prefix);
750
ae9e29fd
RB
751 git_diff_list_free(diff);
752 *diff_ptr = NULL;
753 return -1;
65b09b1d
RB
754}
755
74fa4bfa
RB
756
757int git_diff_tree_to_tree(
758 git_repository *repo,
759 const git_diff_options *opts, /**< can be NULL for defaults */
2de60205
RB
760 git_tree *old_tree,
761 git_tree *new_tree,
74fa4bfa 762 git_diff_list **diff)
65b09b1d 763{
74fa4bfa 764 git_iterator *a = NULL, *b = NULL;
41a82592 765 char *prefix = opts ? diff_prefix_from_pathspec(&opts->pathspec) : NULL;
3a437590 766
2de60205 767 assert(repo && old_tree && new_tree && diff);
3a437590 768
41a82592
RB
769 if (git_iterator_for_tree_range(&a, repo, old_tree, prefix, prefix) < 0 ||
770 git_iterator_for_tree_range(&b, repo, new_tree, prefix, prefix) < 0)
ae9e29fd 771 return -1;
3a437590 772
41a82592
RB
773 git__free(prefix);
774
74fa4bfa 775 return diff_from_iterators(repo, opts, a, b, diff);
65b09b1d
RB
776}
777
74fa4bfa
RB
778int git_diff_index_to_tree(
779 git_repository *repo,
780 const git_diff_options *opts,
2de60205 781 git_tree *old_tree,
74fa4bfa 782 git_diff_list **diff)
65b09b1d 783{
74fa4bfa 784 git_iterator *a = NULL, *b = NULL;
41a82592 785 char *prefix = opts ? diff_prefix_from_pathspec(&opts->pathspec) : NULL;
3a437590 786
7e000ab2 787 assert(repo && diff);
3a437590 788
41a82592 789 if (git_iterator_for_tree_range(&a, repo, old_tree, prefix, prefix) < 0 ||
1d94a7d0
CMN
790 git_iterator_for_index_range(&b, repo, prefix, prefix) < 0)
791 goto on_error;
3a437590 792
41a82592
RB
793 git__free(prefix);
794
74fa4bfa 795 return diff_from_iterators(repo, opts, a, b, diff);
1d94a7d0
CMN
796
797on_error:
798 git__free(prefix);
799 git_iterator_free(a);
800 return -1;
65b09b1d
RB
801}
802
74fa4bfa
RB
803int git_diff_workdir_to_index(
804 git_repository *repo,
805 const git_diff_options *opts,
806 git_diff_list **diff)
65b09b1d 807{
74fa4bfa 808 git_iterator *a = NULL, *b = NULL;
41a82592 809 char *prefix = opts ? diff_prefix_from_pathspec(&opts->pathspec) : NULL;
3a437590 810
74fa4bfa 811 assert(repo && diff);
3a437590 812
41a82592 813 if (git_iterator_for_index_range(&a, repo, prefix, prefix) < 0 ||
1d94a7d0
CMN
814 git_iterator_for_workdir_range(&b, repo, prefix, prefix) < 0)
815 goto on_error;
3a437590 816
41a82592
RB
817 git__free(prefix);
818
74fa4bfa 819 return diff_from_iterators(repo, opts, a, b, diff);
1d94a7d0
CMN
820
821on_error:
822 git__free(prefix);
823 git_iterator_free(a);
824 return -1;
65b09b1d
RB
825}
826
65b09b1d 827
74fa4bfa
RB
828int git_diff_workdir_to_tree(
829 git_repository *repo,
830 const git_diff_options *opts,
2de60205 831 git_tree *old_tree,
74fa4bfa 832 git_diff_list **diff)
65b09b1d 833{
74fa4bfa 834 git_iterator *a = NULL, *b = NULL;
41a82592 835 char *prefix = opts ? diff_prefix_from_pathspec(&opts->pathspec) : NULL;
3a437590 836
2de60205 837 assert(repo && old_tree && diff);
3a437590 838
41a82592 839 if (git_iterator_for_tree_range(&a, repo, old_tree, prefix, prefix) < 0 ||
1d94a7d0
CMN
840 git_iterator_for_workdir_range(&b, repo, prefix, prefix) < 0)
841 goto on_error;
3a437590 842
41a82592
RB
843 git__free(prefix);
844
74fa4bfa 845 return diff_from_iterators(repo, opts, a, b, diff);
1d94a7d0
CMN
846
847on_error:
848 git__free(prefix);
849 git_iterator_free(a);
850 return -1;
65b09b1d
RB
851}
852
64286308
RB
853
854bool git_diff_delta__should_skip(
bae957b9 855 const git_diff_options *opts, const git_diff_delta *delta)
64286308
RB
856{
857 uint32_t flags = opts ? opts->flags : 0;
858
859 if (delta->status == GIT_DELTA_UNMODIFIED &&
860 (flags & GIT_DIFF_INCLUDE_UNMODIFIED) == 0)
861 return true;
862
863 if (delta->status == GIT_DELTA_IGNORED &&
864 (flags & GIT_DIFF_INCLUDE_IGNORED) == 0)
865 return true;
866
867 if (delta->status == GIT_DELTA_UNTRACKED &&
868 (flags & GIT_DIFF_INCLUDE_UNTRACKED) == 0)
869 return true;
870
871 return false;
872}
873
874
74fa4bfa
RB
875int git_diff_merge(
876 git_diff_list *onto,
877 const git_diff_list *from)
65b09b1d 878{
ae9e29fd 879 int error = 0;
19fa2bc1 880 git_pool onto_pool;
74fa4bfa
RB
881 git_vector onto_new;
882 git_diff_delta *delta;
ec40b7f9 883 bool ignore_case = false;
1db12b00
RB
884 unsigned int i, j;
885
886 assert(onto && from);
3a437590 887
1db12b00
RB
888 if (!from->deltas.length)
889 return 0;
3a437590 890
19fa2bc1
RB
891 if (git_vector_init(&onto_new, onto->deltas.length, diff_delta__cmp) < 0 ||
892 git_pool_init(&onto_pool, 1, 0) < 0)
ae9e29fd 893 return -1;
74fa4bfa 894
ec40b7f9
PK
895 if ((onto->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0 ||
896 (from->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0)
897 {
898 ignore_case = true;
899
900 /* This function currently only supports merging diff lists that
901 * are sorted identically. */
902 assert((onto->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0 &&
903 (from->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0);
904 }
905
1db12b00
RB
906 for (i = 0, j = 0; i < onto->deltas.length || j < from->deltas.length; ) {
907 git_diff_delta *o = GIT_VECTOR_GET(&onto->deltas, i);
908 const git_diff_delta *f = GIT_VECTOR_GET(&from->deltas, j);
ec40b7f9 909 int cmp = !f ? -1 : !o ? 1 : STRCMP_CASESELECT(ignore_case, o->old_file.path, f->old_file.path);
1db12b00
RB
910
911 if (cmp < 0) {
19fa2bc1 912 delta = diff_delta__dup(o, &onto_pool);
74fa4bfa 913 i++;
1db12b00 914 } else if (cmp > 0) {
19fa2bc1 915 delta = diff_delta__dup(f, &onto_pool);
74fa4bfa
RB
916 j++;
917 } else {
19fa2bc1 918 delta = diff_delta__merge_like_cgit(o, f, &onto_pool);
74fa4bfa
RB
919 i++;
920 j++;
921 }
3a437590 922
64286308
RB
923 /* the ignore rules for the target may not match the source
924 * or the result of a merged delta could be skippable...
925 */
926 if (git_diff_delta__should_skip(&onto->opts, delta)) {
927 git__free(delta);
928 continue;
929 }
930
a48ea31d 931 if ((error = !delta ? -1 : git_vector_insert(&onto_new, delta)) < 0)
74fa4bfa 932 break;
a2e895be 933 }
65b09b1d 934
1db12b00 935 if (!error) {
74fa4bfa 936 git_vector_swap(&onto->deltas, &onto_new);
19fa2bc1 937 git_pool_swap(&onto->pool, &onto_pool);
74fa4bfa 938 onto->new_src = from->new_src;
145e696b
RB
939
940 /* prefix strings also come from old pool, so recreate those.*/
941 onto->opts.old_prefix =
71d27358 942 git_pool_strdup_safe(&onto->pool, onto->opts.old_prefix);
145e696b 943 onto->opts.new_prefix =
71d27358 944 git_pool_strdup_safe(&onto->pool, onto->opts.new_prefix);
65b09b1d 945 }
cd33323b 946
74fa4bfa 947 git_vector_foreach(&onto_new, i, delta)
19fa2bc1 948 git__free(delta);
74fa4bfa 949 git_vector_free(&onto_new);
19fa2bc1 950 git_pool_clear(&onto_pool);
cd33323b 951
74fa4bfa 952 return error;
cd33323b 953}