]> git.proxmox.com Git - libgit2.git/blame - src/diff.c
Merge remote-tracking branch 'carlosmn/remaining-errors' into new-error-handling
[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
RB
7#include "common.h"
8#include "git2/diff.h"
65b09b1d 9#include "diff.h"
74fa4bfa 10#include "fileops.h"
95dfb031 11#include "config.h"
14a513e0
RB
12#include "attr_file.h"
13
14static bool diff_pathspec_is_interesting(const git_strarray *pathspec)
15{
16 const char *str;
17
18 if (pathspec == NULL || pathspec->count == 0)
19 return false;
20 if (pathspec->count > 1)
21 return true;
22
23 str = pathspec->strings[0];
24 if (!str || !str[0] || (!str[1] && (str[0] == '*' || str[0] == '.')))
25 return false;
26 return true;
27}
28
29static bool diff_path_matches_pathspec(git_diff_list *diff, const char *path)
30{
31 unsigned int i;
32 git_attr_fnmatch *match;
33
34 if (!diff->pathspec.length)
35 return true;
36
37 git_vector_foreach(&diff->pathspec, i, match) {
38 int result = git__fnmatch(match->pattern, path, 0);
39
40 /* if we didn't match, look for exact dirname prefix match */
41 if (result == GIT_ENOMATCH &&
42 (match->flags & GIT_ATTR_FNMATCH_HASWILD) == 0 &&
43 strncmp(path, match->pattern, match->length) == 0 &&
44 path[match->length] == '/')
45 result = 0;
46
47 if (result == 0)
48 return (match->flags & GIT_ATTR_FNMATCH_NEGATIVE) ? false : true;
49
50 if (result != GIT_ENOMATCH)
51 giterr_clear();
52 }
53
54 return false;
55}
cd33323b 56
74fa4bfa 57static git_diff_delta *diff_delta__alloc(
a2e895be 58 git_diff_list *diff,
e1bcc191 59 git_delta_t status,
74fa4bfa 60 const char *path)
3a437590 61{
a2e895be 62 git_diff_delta *delta = git__calloc(1, sizeof(git_diff_delta));
a2e895be 63 if (!delta)
74fa4bfa 64 return NULL;
3a437590 65
19fa2bc1 66 delta->old.path = git_pool_strdup(&diff->pool, path);
74fa4bfa 67 if (delta->old.path == NULL) {
a2e895be 68 git__free(delta);
74fa4bfa 69 return NULL;
a2e895be 70 }
74fa4bfa 71 delta->new.path = delta->old.path;
a2e895be 72
74fa4bfa
RB
73 if (diff->opts.flags & GIT_DIFF_REVERSE) {
74 switch (status) {
e1bcc191
RB
75 case GIT_DELTA_ADDED: status = GIT_DELTA_DELETED; break;
76 case GIT_DELTA_DELETED: status = GIT_DELTA_ADDED; break;
74fa4bfa
RB
77 default: break; /* leave other status values alone */
78 }
79 }
a2e895be
RB
80 delta->status = status;
81
74fa4bfa
RB
82 return delta;
83}
84
19fa2bc1
RB
85static git_diff_delta *diff_delta__dup(
86 const git_diff_delta *d, git_pool *pool)
74fa4bfa
RB
87{
88 git_diff_delta *delta = git__malloc(sizeof(git_diff_delta));
89 if (!delta)
90 return NULL;
91
92 memcpy(delta, d, sizeof(git_diff_delta));
93
19fa2bc1
RB
94 delta->old.path = git_pool_strdup(pool, d->old.path);
95 if (delta->old.path == NULL)
96 goto fail;
a2e895be 97
74fa4bfa 98 if (d->new.path != d->old.path) {
19fa2bc1
RB
99 delta->new.path = git_pool_strdup(pool, d->new.path);
100 if (delta->new.path == NULL)
101 goto fail;
74fa4bfa
RB
102 } else {
103 delta->new.path = delta->old.path;
74fa4bfa 104 }
3a437590 105
74fa4bfa 106 return delta;
19fa2bc1
RB
107
108fail:
109 git__free(delta);
110 return NULL;
a2e895be
RB
111}
112
74fa4bfa 113static git_diff_delta *diff_delta__merge_like_cgit(
19fa2bc1 114 const git_diff_delta *a, const git_diff_delta *b, git_pool *pool)
a2e895be 115{
19fa2bc1 116 git_diff_delta *dup = diff_delta__dup(a, pool);
74fa4bfa
RB
117 if (!dup)
118 return NULL;
a2e895be 119
74fa4bfa
RB
120 if (git_oid_cmp(&dup->new.oid, &b->new.oid) == 0)
121 return dup;
a2e895be 122
74fa4bfa 123 git_oid_cpy(&dup->new.oid, &b->new.oid);
a2e895be 124
74fa4bfa
RB
125 dup->new.mode = b->new.mode;
126 dup->new.size = b->new.size;
19fa2bc1 127 dup->new.flags = b->new.flags;
a2e895be 128
74fa4bfa
RB
129 /* Emulate C git for merging two diffs (a la 'git diff <sha>').
130 *
131 * When C git does a diff between the work dir and a tree, it actually
132 * diffs with the index but uses the workdir contents. This emulates
133 * those choices so we can emulate the type of diff.
134 */
135 if (git_oid_cmp(&dup->old.oid, &dup->new.oid) == 0) {
e1bcc191 136 if (dup->status == GIT_DELTA_DELETED)
74fa4bfa 137 /* preserve pending delete info */;
e1bcc191
RB
138 else if (b->status == GIT_DELTA_UNTRACKED ||
139 b->status == GIT_DELTA_IGNORED)
74fa4bfa
RB
140 dup->status = b->status;
141 else
e1bcc191 142 dup->status = GIT_DELTA_UNMODIFIED;
74fa4bfa 143 }
e1bcc191
RB
144 else if (dup->status == GIT_DELTA_UNMODIFIED ||
145 b->status == GIT_DELTA_DELETED)
74fa4bfa 146 dup->status = b->status;
3a437590 147
74fa4bfa 148 return dup;
3a437590
RB
149}
150
74fa4bfa
RB
151static int diff_delta__from_one(
152 git_diff_list *diff,
e1bcc191 153 git_delta_t status,
74fa4bfa 154 const git_index_entry *entry)
3a437590 155{
66142ae0
RB
156 git_diff_delta *delta;
157
158 if (status == GIT_DELTA_IGNORED &&
159 (diff->opts.flags & GIT_DIFF_INCLUDE_IGNORED) == 0)
160 return 0;
161
162 if (status == GIT_DELTA_UNTRACKED &&
163 (diff->opts.flags & GIT_DIFF_INCLUDE_UNTRACKED) == 0)
164 return 0;
165
14a513e0
RB
166 if (!diff_path_matches_pathspec(diff, entry->path))
167 return 0;
168
66142ae0 169 delta = diff_delta__alloc(diff, status, entry->path);
ae9e29fd 170 GITERR_CHECK_ALLOC(delta);
3a437590 171
74fa4bfa 172 /* This fn is just for single-sided diffs */
e1bcc191 173 assert(status != GIT_DELTA_MODIFIED);
3a437590 174
e1bcc191 175 if (delta->status == GIT_DELTA_DELETED) {
74fa4bfa
RB
176 delta->old.mode = entry->mode;
177 delta->old.size = entry->file_size;
178 git_oid_cpy(&delta->old.oid, &entry->oid);
179 } else /* ADDED, IGNORED, UNTRACKED */ {
180 delta->new.mode = entry->mode;
181 delta->new.size = entry->file_size;
182 git_oid_cpy(&delta->new.oid, &entry->oid);
183 }
3a437590 184
74fa4bfa
RB
185 delta->old.flags |= GIT_DIFF_FILE_VALID_OID;
186 delta->new.flags |= GIT_DIFF_FILE_VALID_OID;
3a437590 187
ae9e29fd 188 if (git_vector_insert(&diff->deltas, delta) < 0) {
19fa2bc1 189 git__free(delta);
ae9e29fd
RB
190 return -1;
191 }
3a437590 192
ae9e29fd 193 return 0;
3a437590
RB
194}
195
74fa4bfa
RB
196static int diff_delta__from_two(
197 git_diff_list *diff,
e1bcc191 198 git_delta_t status,
74fa4bfa
RB
199 const git_index_entry *old,
200 const git_index_entry *new,
201 git_oid *new_oid)
65b09b1d 202{
74fa4bfa 203 git_diff_delta *delta;
65b09b1d 204
66142ae0
RB
205 if (status == GIT_DELTA_UNMODIFIED &&
206 (diff->opts.flags & GIT_DIFF_INCLUDE_UNMODIFIED) == 0)
207 return 0;
208
74fa4bfa
RB
209 if ((diff->opts.flags & GIT_DIFF_REVERSE) != 0) {
210 const git_index_entry *temp = old;
211 old = new;
212 new = temp;
213 }
65b09b1d 214
74fa4bfa 215 delta = diff_delta__alloc(diff, status, old->path);
ae9e29fd 216 GITERR_CHECK_ALLOC(delta);
74fa4bfa
RB
217
218 delta->old.mode = old->mode;
219 git_oid_cpy(&delta->old.oid, &old->oid);
220 delta->old.flags |= GIT_DIFF_FILE_VALID_OID;
221
222 delta->new.mode = new->mode;
223 git_oid_cpy(&delta->new.oid, new_oid ? new_oid : &new->oid);
224 if (new_oid || !git_oid_iszero(&new->oid))
225 delta->new.flags |= GIT_DIFF_FILE_VALID_OID;
226
ae9e29fd 227 if (git_vector_insert(&diff->deltas, delta) < 0) {
19fa2bc1 228 git__free(delta);
ae9e29fd
RB
229 return -1;
230 }
3a437590 231
ae9e29fd 232 return 0;
65b09b1d
RB
233}
234
19fa2bc1 235static char *diff_strdup_prefix(git_pool *pool, const char *prefix)
a2e895be
RB
236{
237 size_t len = strlen(prefix);
19fa2bc1
RB
238
239 /* append '/' at end if needed */
240 if (len > 0 && prefix[len - 1] != '/')
241 return git_pool_strcat(pool, prefix, "/");
242 else
243 return git_pool_strndup(pool, prefix, len + 1);
a2e895be
RB
244}
245
74fa4bfa
RB
246static int diff_delta__cmp(const void *a, const void *b)
247{
248 const git_diff_delta *da = a, *db = b;
249 int val = strcmp(da->old.path, db->old.path);
250 return val ? val : ((int)da->status - (int)db->status);
251}
252
95dfb031
RB
253static int config_bool(git_config *cfg, const char *name, int defvalue)
254{
255 int val = defvalue;
256 if (git_config_get_bool(cfg, name, &val) < 0)
257 giterr_clear();
258 return val;
259}
260
65b09b1d
RB
261static git_diff_list *git_diff_list_alloc(
262 git_repository *repo, const git_diff_options *opts)
263{
95dfb031 264 git_config *cfg;
14a513e0 265 size_t i;
65b09b1d 266 git_diff_list *diff = git__calloc(1, sizeof(git_diff_list));
a2e895be
RB
267 if (diff == NULL)
268 return NULL;
269
270 diff->repo = repo;
a2e895be 271
19fa2bc1
RB
272 if (git_vector_init(&diff->deltas, 0, diff_delta__cmp) < 0 ||
273 git_pool_init(&diff->pool, 1, 0) < 0)
274 goto fail;
275
95dfb031 276 /* load config values that affect diff behavior */
7784bcbb 277 if (git_repository_config__weakptr(&cfg, repo) < 0)
95dfb031
RB
278 goto fail;
279 if (config_bool(cfg, "core.symlinks", 1))
280 diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_HAS_SYMLINKS;
281 if (config_bool(cfg, "core.ignorestat", 0))
282 diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_ASSUME_UNCHANGED;
283 if (config_bool(cfg, "core.filemode", 1))
284 diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_TRUST_EXEC_BIT;
285 if (config_bool(cfg, "core.trustctime", 1))
286 diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_TRUST_CTIME;
287 /* Don't set GIT_DIFFCAPS_USE_DEV - compile time option in core git */
95dfb031 288
a2e895be
RB
289 if (opts == NULL)
290 return diff;
291
292 memcpy(&diff->opts, opts, sizeof(git_diff_options));
14a513e0 293 memset(&diff->opts.pathspec, 0, sizeof(diff->opts.pathspec));
a2e895be 294
19fa2bc1 295 diff->opts.src_prefix = diff_strdup_prefix(&diff->pool,
74fa4bfa 296 opts->src_prefix ? opts->src_prefix : DIFF_SRC_PREFIX_DEFAULT);
19fa2bc1 297 diff->opts.dst_prefix = diff_strdup_prefix(&diff->pool,
74fa4bfa
RB
298 opts->dst_prefix ? opts->dst_prefix : DIFF_DST_PREFIX_DEFAULT);
299
95dfb031
RB
300 if (!diff->opts.src_prefix || !diff->opts.dst_prefix)
301 goto fail;
74fa4bfa 302
a2e895be
RB
303 if (diff->opts.flags & GIT_DIFF_REVERSE) {
304 char *swap = diff->opts.src_prefix;
305 diff->opts.src_prefix = diff->opts.dst_prefix;
306 diff->opts.dst_prefix = swap;
65b09b1d 307 }
a2e895be 308
14a513e0
RB
309 /* only copy pathspec if it is "interesting" so we can test
310 * diff->pathspec.length > 0 to know if it is worth calling
311 * fnmatch as we iterate.
312 */
313 if (!diff_pathspec_is_interesting(&opts->pathspec))
314 return diff;
315
821f6bc7
RB
316 if (git_vector_init(
317 &diff->pathspec, (unsigned int)opts->pathspec.count, NULL) < 0)
14a513e0
RB
318 goto fail;
319
320 for (i = 0; i < opts->pathspec.count; ++i) {
321 int ret;
322 const char *pattern = opts->pathspec.strings[i];
19fa2bc1 323 git_attr_fnmatch *match = git__calloc(1, sizeof(git_attr_fnmatch));
14a513e0
RB
324 if (!match)
325 goto fail;
19fa2bc1 326 ret = git_attr_fnmatch__parse(match, &diff->pool, NULL, &pattern);
14a513e0
RB
327 if (ret == GIT_ENOTFOUND) {
328 git__free(match);
329 continue;
330 } else if (ret < 0)
331 goto fail;
332
333 if (git_vector_insert(&diff->pathspec, match) < 0)
334 goto fail;
335 }
a2e895be 336
65b09b1d 337 return diff;
95dfb031
RB
338
339fail:
14a513e0 340 git_diff_list_free(diff);
95dfb031 341 return NULL;
65b09b1d
RB
342}
343
344void git_diff_list_free(git_diff_list *diff)
345{
3a437590 346 git_diff_delta *delta;
14a513e0 347 git_attr_fnmatch *match;
3a437590
RB
348 unsigned int i;
349
65b09b1d
RB
350 if (!diff)
351 return;
3a437590 352
74fa4bfa 353 git_vector_foreach(&diff->deltas, i, delta) {
19fa2bc1 354 git__free(delta);
74fa4bfa 355 diff->deltas.contents[i] = NULL;
a2e895be 356 }
74fa4bfa 357 git_vector_free(&diff->deltas);
14a513e0
RB
358
359 git_vector_foreach(&diff->pathspec, i, match) {
19fa2bc1
RB
360 git__free(match);
361 diff->pathspec.contents[i] = NULL;
14a513e0
RB
362 }
363 git_vector_free(&diff->pathspec);
364
19fa2bc1 365 git_pool_clear(&diff->pool);
65b09b1d
RB
366 git__free(diff);
367}
368
74fa4bfa 369static int oid_for_workdir_item(
65b09b1d 370 git_repository *repo,
74fa4bfa
RB
371 const git_index_entry *item,
372 git_oid *oid)
65b09b1d 373{
ae9e29fd 374 int result;
74fa4bfa 375 git_buf full_path = GIT_BUF_INIT;
a2e895be 376
ae9e29fd
RB
377 if (git_buf_joinpath(&full_path, git_repository_workdir(repo), item->path) < 0)
378 return -1;
a2e895be 379
ae9e29fd 380 /* calculate OID for file if possible*/
74fa4bfa 381 if (S_ISLNK(item->mode))
ae9e29fd
RB
382 result = git_odb__hashlink(oid, full_path.ptr);
383 else if (!git__is_sizet(item->file_size)) {
384 giterr_set(GITERR_OS, "File size overflow for 32-bit systems");
385 result = -1;
386 } else {
387 int fd = git_futils_open_ro(full_path.ptr);
388 if (fd < 0)
389 result = fd;
74fa4bfa 390 else {
ae9e29fd 391 result = git_odb__hashfd(
74fa4bfa
RB
392 oid, fd, (size_t)item->file_size, GIT_OBJ_BLOB);
393 p_close(fd);
394 }
a2e895be 395 }
a2e895be 396
74fa4bfa 397 git_buf_free(&full_path);
a2e895be 398
ae9e29fd 399 return result;
a2e895be
RB
400}
401
95dfb031
RB
402#define EXEC_BIT_MASK 0000111
403
74fa4bfa
RB
404static int maybe_modified(
405 git_iterator *old,
406 const git_index_entry *oitem,
407 git_iterator *new,
408 const git_index_entry *nitem,
409 git_diff_list *diff)
e47329b6 410{
74fa4bfa 411 git_oid noid, *use_noid = NULL;
66142ae0 412 git_delta_t status = GIT_DELTA_MODIFIED;
95dfb031
RB
413 unsigned int omode = oitem->mode;
414 unsigned int nmode = nitem->mode;
e47329b6 415
854eccbb 416 GIT_UNUSED(old);
e47329b6 417
14a513e0
RB
418 if (!diff_path_matches_pathspec(diff, oitem->path))
419 return 0;
420
95dfb031
RB
421 /* on platforms with no symlinks, promote plain files to symlinks */
422 if (S_ISLNK(omode) && S_ISREG(nmode) &&
423 !(diff->diffcaps & GIT_DIFFCAPS_HAS_SYMLINKS))
424 nmode = GIT_MODE_TYPE(omode) | (nmode & GIT_MODE_PERMS_MASK);
425
426 /* on platforms with no execmode, clear exec bit from comparisons */
427 if (!(diff->diffcaps & GIT_DIFFCAPS_TRUST_EXEC_BIT)) {
428 omode = omode & ~EXEC_BIT_MASK;
429 nmode = nmode & ~EXEC_BIT_MASK;
430 }
431
432 /* support "assume unchanged" (badly, b/c we still stat everything) */
433 if ((diff->diffcaps & GIT_DIFFCAPS_ASSUME_UNCHANGED) != 0)
434 status = (oitem->flags_extended & GIT_IDXENTRY_INTENT_TO_ADD) ?
435 GIT_DELTA_MODIFIED : GIT_DELTA_UNMODIFIED;
436
437 /* support "skip worktree" index bit */
438 else if ((oitem->flags_extended & GIT_IDXENTRY_SKIP_WORKTREE) != 0)
66142ae0 439 status = GIT_DELTA_UNMODIFIED;
e47329b6 440
66142ae0 441 /* if basic type of file changed, then split into delete and add */
95dfb031 442 else if (GIT_MODE_TYPE(omode) != GIT_MODE_TYPE(nmode)) {
ae9e29fd
RB
443 if (diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem) < 0 ||
444 diff_delta__from_one(diff, GIT_DELTA_ADDED, nitem) < 0)
445 return -1;
446 return 0;
e47329b6
RB
447 }
448
66142ae0
RB
449 /* if oids and modes match, then file is unmodified */
450 else if (git_oid_cmp(&oitem->oid, &nitem->oid) == 0 &&
95dfb031 451 omode == nmode)
66142ae0 452 status = GIT_DELTA_UNMODIFIED;
e47329b6 453
66142ae0
RB
454 /* if we have a workdir item with an unknown oid, check deeper */
455 else if (git_oid_iszero(&nitem->oid) && new->type == GIT_ITERATOR_WORKDIR) {
95dfb031
RB
456 /* TODO: add check against index file st_mtime to avoid racy-git */
457
74fa4bfa
RB
458 /* if they files look exactly alike, then we'll assume the same */
459 if (oitem->file_size == nitem->file_size &&
95dfb031
RB
460 (!(diff->diffcaps & GIT_DIFFCAPS_TRUST_CTIME) ||
461 (oitem->ctime.seconds == nitem->ctime.seconds)) &&
74fa4bfa 462 oitem->mtime.seconds == nitem->mtime.seconds &&
95dfb031
RB
463 (!(diff->diffcaps & GIT_DIFFCAPS_USE_DEV) ||
464 (oitem->dev == nitem->dev)) &&
74fa4bfa
RB
465 oitem->ino == nitem->ino &&
466 oitem->uid == nitem->uid &&
467 oitem->gid == nitem->gid)
66142ae0
RB
468 status = GIT_DELTA_UNMODIFIED;
469
95dfb031
RB
470 else if (S_ISGITLINK(nmode)) {
471 git_submodule *sub;
472
473 if ((diff->opts.flags & GIT_DIFF_IGNORE_SUBMODULES) != 0)
474 status = GIT_DELTA_UNMODIFIED;
475 else if (git_submodule_lookup(&sub, diff->repo, nitem->path) < 0)
476 return -1;
477 else if (sub->ignore == GIT_SUBMODULE_IGNORE_ALL)
478 status = GIT_DELTA_UNMODIFIED;
479 else {
480 /* TODO: support other GIT_SUBMODULE_IGNORE values */
481 status = GIT_DELTA_UNMODIFIED;
482 }
483 }
e47329b6 484
74fa4bfa
RB
485 /* TODO: check git attributes so we will not have to read the file
486 * in if it is marked binary.
487 */
ae9e29fd 488
66142ae0 489 else if (oid_for_workdir_item(diff->repo, nitem, &noid) < 0)
ae9e29fd 490 return -1;
e47329b6 491
66142ae0 492 else if (git_oid_cmp(&oitem->oid, &noid) == 0 &&
95dfb031 493 omode == nmode)
66142ae0 494 status = GIT_DELTA_UNMODIFIED;
e47329b6 495
74fa4bfa
RB
496 /* store calculated oid so we don't have to recalc later */
497 use_noid = &noid;
e47329b6
RB
498 }
499
66142ae0 500 return diff_delta__from_two(diff, status, oitem, nitem, use_noid);
e47329b6
RB
501}
502
74fa4bfa 503static int diff_from_iterators(
e47329b6 504 git_repository *repo,
74fa4bfa
RB
505 const git_diff_options *opts, /**< can be NULL for defaults */
506 git_iterator *old,
507 git_iterator *new,
508 git_diff_list **diff_ptr)
e47329b6 509{
74fa4bfa
RB
510 const git_index_entry *oitem, *nitem;
511 char *ignore_prefix = NULL;
512 git_diff_list *diff = git_diff_list_alloc(repo, opts);
ae9e29fd
RB
513 if (!diff)
514 goto fail;
e47329b6 515
74fa4bfa
RB
516 diff->old_src = old->type;
517 diff->new_src = new->type;
e47329b6 518
ae9e29fd
RB
519 if (git_iterator_current(old, &oitem) < 0 ||
520 git_iterator_current(new, &nitem) < 0)
521 goto fail;
65b09b1d 522
74fa4bfa 523 /* run iterators building diffs */
ae9e29fd 524 while (oitem || nitem) {
cd33323b 525
74fa4bfa
RB
526 /* create DELETED records for old items not matched in new */
527 if (oitem && (!nitem || strcmp(oitem->path, nitem->path) < 0)) {
ae9e29fd
RB
528 if (diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem) < 0 ||
529 git_iterator_advance(old, &oitem) < 0)
530 goto fail;
65b09b1d 531 }
a2e895be 532
74fa4bfa
RB
533 /* create ADDED, TRACKED, or IGNORED records for new items not
534 * matched in old (and/or descend into directories as needed)
535 */
ae9e29fd 536 else if (nitem && (!oitem || strcmp(oitem->path, nitem->path) > 0)) {
74fa4bfa 537 int is_ignored;
e1bcc191 538 git_delta_t delta_type = GIT_DELTA_ADDED;
a2e895be 539
74fa4bfa
RB
540 /* contained in ignored parent directory, so this can be skipped. */
541 if (ignore_prefix != NULL &&
542 git__prefixcmp(nitem->path, ignore_prefix) == 0)
65b09b1d 543 {
ae9e29fd
RB
544 if (git_iterator_advance(new, &nitem) < 0)
545 goto fail;
74fa4bfa 546 continue;
65b09b1d
RB
547 }
548
74fa4bfa
RB
549 is_ignored = git_iterator_current_is_ignored(new);
550
551 if (S_ISDIR(nitem->mode)) {
4b136a94
RB
552 /* recurse into directory if explicitly requested or
553 * if there are tracked items inside the directory
554 */
555 if ((diff->opts.flags & GIT_DIFF_RECURSE_UNTRACKED_DIRS) ||
556 (oitem && git__prefixcmp(oitem->path, nitem->path) == 0))
557 {
74fa4bfa
RB
558 if (is_ignored)
559 ignore_prefix = nitem->path;
ae9e29fd
RB
560 if (git_iterator_advance_into_directory(new, &nitem) < 0)
561 goto fail;
74fa4bfa
RB
562 continue;
563 }
e1bcc191 564 delta_type = GIT_DELTA_UNTRACKED;
65b09b1d 565 }
74fa4bfa 566 else if (is_ignored)
e1bcc191 567 delta_type = GIT_DELTA_IGNORED;
74fa4bfa 568 else if (new->type == GIT_ITERATOR_WORKDIR)
e1bcc191 569 delta_type = GIT_DELTA_UNTRACKED;
74fa4bfa 570
ae9e29fd
RB
571 if (diff_delta__from_one(diff, delta_type, nitem) < 0 ||
572 git_iterator_advance(new, &nitem) < 0)
573 goto fail;
65b09b1d
RB
574 }
575
74fa4bfa
RB
576 /* otherwise item paths match, so create MODIFIED record
577 * (or ADDED and DELETED pair if type changed)
a2e895be 578 */
ae9e29fd
RB
579 else {
580 assert(oitem && nitem && strcmp(oitem->path, nitem->path) == 0);
a2e895be 581
ae9e29fd
RB
582 if (maybe_modified(old, oitem, new, nitem, diff) < 0 ||
583 git_iterator_advance(old, &oitem) < 0 ||
584 git_iterator_advance(new, &nitem) < 0)
585 goto fail;
586 }
65b09b1d
RB
587 }
588
74fa4bfa
RB
589 git_iterator_free(old);
590 git_iterator_free(new);
74fa4bfa 591 *diff_ptr = diff;
ae9e29fd 592 return 0;
65b09b1d 593
ae9e29fd
RB
594fail:
595 git_iterator_free(old);
596 git_iterator_free(new);
597 git_diff_list_free(diff);
598 *diff_ptr = NULL;
599 return -1;
65b09b1d
RB
600}
601
74fa4bfa
RB
602
603int git_diff_tree_to_tree(
604 git_repository *repo,
605 const git_diff_options *opts, /**< can be NULL for defaults */
606 git_tree *old,
607 git_tree *new,
608 git_diff_list **diff)
65b09b1d 609{
74fa4bfa 610 git_iterator *a = NULL, *b = NULL;
3a437590 611
74fa4bfa 612 assert(repo && old && new && diff);
3a437590 613
ae9e29fd
RB
614 if (git_iterator_for_tree(repo, old, &a) < 0 ||
615 git_iterator_for_tree(repo, new, &b) < 0)
616 return -1;
3a437590 617
74fa4bfa 618 return diff_from_iterators(repo, opts, a, b, diff);
65b09b1d
RB
619}
620
74fa4bfa
RB
621int git_diff_index_to_tree(
622 git_repository *repo,
623 const git_diff_options *opts,
624 git_tree *old,
625 git_diff_list **diff)
65b09b1d 626{
74fa4bfa 627 git_iterator *a = NULL, *b = NULL;
3a437590 628
74fa4bfa 629 assert(repo && old && diff);
3a437590 630
ae9e29fd
RB
631 if (git_iterator_for_tree(repo, old, &a) < 0 ||
632 git_iterator_for_index(repo, &b) < 0)
633 return -1;
3a437590 634
74fa4bfa 635 return diff_from_iterators(repo, opts, a, b, diff);
65b09b1d
RB
636}
637
74fa4bfa
RB
638int git_diff_workdir_to_index(
639 git_repository *repo,
640 const git_diff_options *opts,
641 git_diff_list **diff)
65b09b1d 642{
74fa4bfa 643 git_iterator *a = NULL, *b = NULL;
3a437590 644
74fa4bfa 645 assert(repo && diff);
3a437590 646
ae9e29fd
RB
647 if (git_iterator_for_index(repo, &a) < 0 ||
648 git_iterator_for_workdir(repo, &b) < 0)
649 return -1;
3a437590 650
74fa4bfa 651 return diff_from_iterators(repo, opts, a, b, diff);
65b09b1d
RB
652}
653
65b09b1d 654
74fa4bfa
RB
655int git_diff_workdir_to_tree(
656 git_repository *repo,
657 const git_diff_options *opts,
658 git_tree *old,
659 git_diff_list **diff)
65b09b1d 660{
74fa4bfa 661 git_iterator *a = NULL, *b = NULL;
3a437590 662
74fa4bfa 663 assert(repo && old && diff);
3a437590 664
ae9e29fd
RB
665 if (git_iterator_for_tree(repo, old, &a) < 0 ||
666 git_iterator_for_workdir(repo, &b) < 0)
667 return -1;
3a437590 668
74fa4bfa 669 return diff_from_iterators(repo, opts, a, b, diff);
65b09b1d
RB
670}
671
74fa4bfa
RB
672int git_diff_merge(
673 git_diff_list *onto,
674 const git_diff_list *from)
65b09b1d 675{
ae9e29fd 676 int error = 0;
19fa2bc1 677 git_pool onto_pool;
74fa4bfa 678 git_vector onto_new;
1db12b00
RB
679 git_diff_delta *delta;
680 unsigned int i, j;
681
682 assert(onto && from);
683
684 if (!from->deltas.length)
685 return 0;
3a437590 686
19fa2bc1
RB
687 if (git_vector_init(&onto_new, onto->deltas.length, diff_delta__cmp) < 0 ||
688 git_pool_init(&onto_pool, 1, 0) < 0)
ae9e29fd 689 return -1;
3a437590 690
1db12b00
RB
691 for (i = 0, j = 0; i < onto->deltas.length || j < from->deltas.length; ) {
692 git_diff_delta *o = GIT_VECTOR_GET(&onto->deltas, i);
693 const git_diff_delta *f = GIT_VECTOR_GET(&from->deltas, j);
694 int cmp = !f ? -1 : !o ? 1 : strcmp(o->old.path, f->old.path);
695
696 if (cmp < 0) {
19fa2bc1 697 delta = diff_delta__dup(o, &onto_pool);
1db12b00
RB
698 i++;
699 } else if (cmp > 0) {
19fa2bc1 700 delta = diff_delta__dup(f, &onto_pool);
1db12b00
RB
701 j++;
702 } else {
19fa2bc1 703 delta = diff_delta__merge_like_cgit(o, f, &onto_pool);
1db12b00
RB
704 i++;
705 j++;
706 }
707
a48ea31d
RB
708 if ((error = !delta ? -1 : git_vector_insert(&onto_new, delta)) < 0)
709 break;
1db12b00 710 }
65b09b1d 711
1db12b00 712 if (!error) {
74fa4bfa 713 git_vector_swap(&onto->deltas, &onto_new);
19fa2bc1 714 git_pool_swap(&onto->pool, &onto_pool);
74fa4bfa 715 onto->new_src = from->new_src;
65b09b1d 716 }
cd33323b 717
74fa4bfa 718 git_vector_foreach(&onto_new, i, delta)
19fa2bc1 719 git__free(delta);
74fa4bfa 720 git_vector_free(&onto_new);
19fa2bc1 721 git_pool_clear(&onto_pool);
cd33323b 722
74fa4bfa 723 return error;
cd33323b 724}
a48ea31d 725