2 * Copyright (C) 2012 the libgit2 contributors
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.
13 #include "attr_file.h"
16 static char *diff_prefix_from_pathspec(const git_strarray
*pathspec
)
18 git_buf prefix
= GIT_BUF_INIT
;
21 if (git_buf_common_prefix(&prefix
, pathspec
) < 0)
24 /* diff prefix will only be leading non-wildcards */
25 for (scan
= prefix
.ptr
; *scan
; ++scan
) {
26 if (git__iswildcard(*scan
) &&
27 (scan
== prefix
.ptr
|| (*(scan
- 1) != '\\')))
30 git_buf_truncate(&prefix
, scan
- prefix
.ptr
);
32 if (prefix
.size
<= 0) {
33 git_buf_free(&prefix
);
37 git_buf_unescape(&prefix
);
39 return git_buf_detach(&prefix
);
42 static bool diff_pathspec_is_interesting(const git_strarray
*pathspec
)
46 if (pathspec
== NULL
|| pathspec
->count
== 0)
48 if (pathspec
->count
> 1)
51 str
= pathspec
->strings
[0];
52 if (!str
|| !str
[0] || (!str
[1] && (str
[0] == '*' || str
[0] == '.')))
57 static bool diff_path_matches_pathspec(git_diff_list
*diff
, const char *path
)
60 git_attr_fnmatch
*match
;
62 if (!diff
->pathspec
.length
)
65 git_vector_foreach(&diff
->pathspec
, i
, match
) {
66 int result
= strcmp(match
->pattern
, path
) ? FNM_NOMATCH
: 0;
68 if (((diff
->opts
.flags
& GIT_DIFF_DISABLE_PATHSPEC_MATCH
) == 0) &&
69 result
== FNM_NOMATCH
)
70 result
= p_fnmatch(match
->pattern
, path
, 0);
72 /* if we didn't match, look for exact dirname prefix match */
73 if (result
== FNM_NOMATCH
&&
74 (match
->flags
& GIT_ATTR_FNMATCH_HASWILD
) == 0 &&
75 strncmp(path
, match
->pattern
, match
->length
) == 0 &&
76 path
[match
->length
] == '/')
80 return (match
->flags
& GIT_ATTR_FNMATCH_NEGATIVE
) ? false : true;
86 static git_diff_delta
*diff_delta__alloc(
91 git_diff_delta
*delta
= git__calloc(1, sizeof(git_diff_delta
));
95 delta
->old_file
.path
= git_pool_strdup(&diff
->pool
, path
);
96 if (delta
->old_file
.path
== NULL
) {
101 delta
->new_file
.path
= delta
->old_file
.path
;
103 if (diff
->opts
.flags
& GIT_DIFF_REVERSE
) {
105 case GIT_DELTA_ADDED
: status
= GIT_DELTA_DELETED
; break;
106 case GIT_DELTA_DELETED
: status
= GIT_DELTA_ADDED
; break;
107 default: break; /* leave other status values alone */
110 delta
->status
= status
;
115 static git_diff_delta
*diff_delta__dup(
116 const git_diff_delta
*d
, git_pool
*pool
)
118 git_diff_delta
*delta
= git__malloc(sizeof(git_diff_delta
));
122 memcpy(delta
, d
, sizeof(git_diff_delta
));
124 delta
->old_file
.path
= git_pool_strdup(pool
, d
->old_file
.path
);
125 if (delta
->old_file
.path
== NULL
)
128 if (d
->new_file
.path
!= d
->old_file
.path
) {
129 delta
->new_file
.path
= git_pool_strdup(pool
, d
->new_file
.path
);
130 if (delta
->new_file
.path
== NULL
)
133 delta
->new_file
.path
= delta
->old_file
.path
;
143 static git_diff_delta
*diff_delta__merge_like_cgit(
144 const git_diff_delta
*a
, const git_diff_delta
*b
, git_pool
*pool
)
148 /* Emulate C git for merging two diffs (a la 'git diff <sha>').
150 * When C git does a diff between the work dir and a tree, it actually
151 * diffs with the index but uses the workdir contents. This emulates
152 * those choices so we can emulate the type of diff.
154 * We have three file descriptions here, let's call them:
156 * f2 = a->new_file AND b->old_file
160 /* if f2 == f3 or f2 is deleted, then just dup the 'a' diff */
161 if (b
->status
== GIT_DELTA_UNMODIFIED
|| a
->status
== GIT_DELTA_DELETED
)
162 return diff_delta__dup(a
, pool
);
164 /* otherwise, base this diff on the 'b' diff */
165 if ((dup
= diff_delta__dup(b
, pool
)) == NULL
)
168 /* If 'a' status is uninteresting, then we're done */
169 if (a
->status
== GIT_DELTA_UNMODIFIED
)
172 assert(a
->status
!= GIT_DELTA_UNMODIFIED
);
173 assert(b
->status
!= GIT_DELTA_UNMODIFIED
);
175 /* A cgit exception is that the diff of a file that is only in the
176 * index (i.e. not in HEAD nor workdir) is given as empty.
178 if (dup
->status
== GIT_DELTA_DELETED
) {
179 if (a
->status
== GIT_DELTA_ADDED
)
180 dup
->status
= GIT_DELTA_UNMODIFIED
;
181 /* else don't overwrite DELETE status */
183 dup
->status
= a
->status
;
186 git_oid_cpy(&dup
->old_file
.oid
, &a
->old_file
.oid
);
187 dup
->old_file
.mode
= a
->old_file
.mode
;
188 dup
->old_file
.size
= a
->old_file
.size
;
189 dup
->old_file
.flags
= a
->old_file
.flags
;
194 static int diff_delta__from_one(
197 const git_index_entry
*entry
)
199 git_diff_delta
*delta
;
201 if (status
== GIT_DELTA_IGNORED
&&
202 (diff
->opts
.flags
& GIT_DIFF_INCLUDE_IGNORED
) == 0)
205 if (status
== GIT_DELTA_UNTRACKED
&&
206 (diff
->opts
.flags
& GIT_DIFF_INCLUDE_UNTRACKED
) == 0)
209 if (!diff_path_matches_pathspec(diff
, entry
->path
))
212 delta
= diff_delta__alloc(diff
, status
, entry
->path
);
213 GITERR_CHECK_ALLOC(delta
);
215 /* This fn is just for single-sided diffs */
216 assert(status
!= GIT_DELTA_MODIFIED
);
218 if (delta
->status
== GIT_DELTA_DELETED
) {
219 delta
->old_file
.mode
= entry
->mode
;
220 delta
->old_file
.size
= entry
->file_size
;
221 git_oid_cpy(&delta
->old_file
.oid
, &entry
->oid
);
222 } else /* ADDED, IGNORED, UNTRACKED */ {
223 delta
->new_file
.mode
= entry
->mode
;
224 delta
->new_file
.size
= entry
->file_size
;
225 git_oid_cpy(&delta
->new_file
.oid
, &entry
->oid
);
228 delta
->old_file
.flags
|= GIT_DIFF_FILE_VALID_OID
;
229 delta
->new_file
.flags
|= GIT_DIFF_FILE_VALID_OID
;
231 if (git_vector_insert(&diff
->deltas
, delta
) < 0) {
239 static int diff_delta__from_two(
242 const git_index_entry
*old_entry
,
244 const git_index_entry
*new_entry
,
248 git_diff_delta
*delta
;
250 if (status
== GIT_DELTA_UNMODIFIED
&&
251 (diff
->opts
.flags
& GIT_DIFF_INCLUDE_UNMODIFIED
) == 0)
254 if ((diff
->opts
.flags
& GIT_DIFF_REVERSE
) != 0) {
255 uint32_t temp_mode
= old_mode
;
256 const git_index_entry
*temp_entry
= old_entry
;
257 old_entry
= new_entry
;
258 new_entry
= temp_entry
;
260 new_mode
= temp_mode
;
263 delta
= diff_delta__alloc(diff
, status
, old_entry
->path
);
264 GITERR_CHECK_ALLOC(delta
);
266 git_oid_cpy(&delta
->old_file
.oid
, &old_entry
->oid
);
267 delta
->old_file
.size
= old_entry
->file_size
;
268 delta
->old_file
.mode
= old_mode
;
269 delta
->old_file
.flags
|= GIT_DIFF_FILE_VALID_OID
;
271 git_oid_cpy(&delta
->new_file
.oid
, new_oid
? new_oid
: &new_entry
->oid
);
272 delta
->new_file
.size
= new_entry
->file_size
;
273 delta
->new_file
.mode
= new_mode
;
274 if (new_oid
|| !git_oid_iszero(&new_entry
->oid
))
275 delta
->new_file
.flags
|= GIT_DIFF_FILE_VALID_OID
;
277 if (git_vector_insert(&diff
->deltas
, delta
) < 0) {
285 static char *diff_strdup_prefix(git_pool
*pool
, const char *prefix
)
287 size_t len
= strlen(prefix
);
289 /* append '/' at end if needed */
290 if (len
> 0 && prefix
[len
- 1] != '/')
291 return git_pool_strcat(pool
, prefix
, "/");
293 return git_pool_strndup(pool
, prefix
, len
+ 1);
296 static int diff_delta__cmp(const void *a
, const void *b
)
298 const git_diff_delta
*da
= a
, *db
= b
;
299 int val
= strcmp(da
->old_file
.path
, db
->old_file
.path
);
300 return val
? val
: ((int)da
->status
- (int)db
->status
);
303 static int config_bool(git_config
*cfg
, const char *name
, int defvalue
)
307 if (git_config_get_bool(&val
, cfg
, name
) < 0)
313 static git_diff_list
*git_diff_list_alloc(
314 git_repository
*repo
, const git_diff_options
*opts
)
318 git_diff_list
*diff
= git__calloc(1, sizeof(git_diff_list
));
322 GIT_REFCOUNT_INC(diff
);
325 if (git_vector_init(&diff
->deltas
, 0, diff_delta__cmp
) < 0 ||
326 git_pool_init(&diff
->pool
, 1, 0) < 0)
329 /* load config values that affect diff behavior */
330 if (git_repository_config__weakptr(&cfg
, repo
) < 0)
332 if (config_bool(cfg
, "core.symlinks", 1))
333 diff
->diffcaps
= diff
->diffcaps
| GIT_DIFFCAPS_HAS_SYMLINKS
;
334 if (config_bool(cfg
, "core.ignorestat", 0))
335 diff
->diffcaps
= diff
->diffcaps
| GIT_DIFFCAPS_ASSUME_UNCHANGED
;
336 if (config_bool(cfg
, "core.filemode", 1))
337 diff
->diffcaps
= diff
->diffcaps
| GIT_DIFFCAPS_TRUST_MODE_BITS
;
338 if (config_bool(cfg
, "core.trustctime", 1))
339 diff
->diffcaps
= diff
->diffcaps
| GIT_DIFFCAPS_TRUST_CTIME
;
340 /* Don't set GIT_DIFFCAPS_USE_DEV - compile time option in core git */
345 memcpy(&diff
->opts
, opts
, sizeof(git_diff_options
));
346 memset(&diff
->opts
.pathspec
, 0, sizeof(diff
->opts
.pathspec
));
348 diff
->opts
.old_prefix
= diff_strdup_prefix(&diff
->pool
,
349 opts
->old_prefix
? opts
->old_prefix
: DIFF_OLD_PREFIX_DEFAULT
);
350 diff
->opts
.new_prefix
= diff_strdup_prefix(&diff
->pool
,
351 opts
->new_prefix
? opts
->new_prefix
: DIFF_NEW_PREFIX_DEFAULT
);
353 if (!diff
->opts
.old_prefix
|| !diff
->opts
.new_prefix
)
356 if (diff
->opts
.flags
& GIT_DIFF_REVERSE
) {
357 char *swap
= diff
->opts
.old_prefix
;
358 diff
->opts
.old_prefix
= diff
->opts
.new_prefix
;
359 diff
->opts
.new_prefix
= swap
;
362 /* only copy pathspec if it is "interesting" so we can test
363 * diff->pathspec.length > 0 to know if it is worth calling
364 * fnmatch as we iterate.
366 if (!diff_pathspec_is_interesting(&opts
->pathspec
))
370 &diff
->pathspec
, (unsigned int)opts
->pathspec
.count
, NULL
) < 0)
373 for (i
= 0; i
< opts
->pathspec
.count
; ++i
) {
375 const char *pattern
= opts
->pathspec
.strings
[i
];
376 git_attr_fnmatch
*match
= git__calloc(1, sizeof(git_attr_fnmatch
));
379 match
->flags
= GIT_ATTR_FNMATCH_ALLOWSPACE
;
380 ret
= git_attr_fnmatch__parse(match
, &diff
->pool
, NULL
, &pattern
);
381 if (ret
== GIT_ENOTFOUND
) {
387 if (git_vector_insert(&diff
->pathspec
, match
) < 0)
394 git_diff_list_free(diff
);
398 static void diff_list_free(git_diff_list
*diff
)
400 git_diff_delta
*delta
;
401 git_attr_fnmatch
*match
;
404 git_vector_foreach(&diff
->deltas
, i
, delta
) {
406 diff
->deltas
.contents
[i
] = NULL
;
408 git_vector_free(&diff
->deltas
);
410 git_vector_foreach(&diff
->pathspec
, i
, match
) {
412 diff
->pathspec
.contents
[i
] = NULL
;
414 git_vector_free(&diff
->pathspec
);
416 git_pool_clear(&diff
->pool
);
420 void git_diff_list_free(git_diff_list
*diff
)
425 GIT_REFCOUNT_DEC(diff
, diff_list_free
);
428 static int oid_for_workdir_item(
429 git_repository
*repo
,
430 const git_index_entry
*item
,
434 git_buf full_path
= GIT_BUF_INIT
;
436 if (git_buf_joinpath(&full_path
, git_repository_workdir(repo
), item
->path
) < 0)
439 /* calculate OID for file if possible*/
440 if (S_ISLNK(item
->mode
))
441 result
= git_odb__hashlink(oid
, full_path
.ptr
);
442 else if (!git__is_sizet(item
->file_size
)) {
443 giterr_set(GITERR_OS
, "File size overflow for 32-bit systems");
446 git_vector filters
= GIT_VECTOR_INIT
;
448 result
= git_filters_load(
449 &filters
, repo
, item
->path
, GIT_FILTER_TO_ODB
);
451 int fd
= git_futils_open_ro(full_path
.ptr
);
455 result
= git_odb__hashfd_filtered(
456 oid
, fd
, (size_t)item
->file_size
, GIT_OBJ_BLOB
, &filters
);
461 git_filters_free(&filters
);
464 git_buf_free(&full_path
);
469 #define MODE_BITS_MASK 0000777
471 static int maybe_modified(
472 git_iterator
*old_iter
,
473 const git_index_entry
*oitem
,
474 git_iterator
*new_iter
,
475 const git_index_entry
*nitem
,
478 git_oid noid
, *use_noid
= NULL
;
479 git_delta_t status
= GIT_DELTA_MODIFIED
;
480 unsigned int omode
= oitem
->mode
;
481 unsigned int nmode
= nitem
->mode
;
483 GIT_UNUSED(old_iter
);
485 if (!diff_path_matches_pathspec(diff
, oitem
->path
))
488 /* on platforms with no symlinks, preserve mode of existing symlinks */
489 if (S_ISLNK(omode
) && S_ISREG(nmode
) &&
490 !(diff
->diffcaps
& GIT_DIFFCAPS_HAS_SYMLINKS
) &&
491 new_iter
->type
== GIT_ITERATOR_WORKDIR
)
494 /* on platforms with no execmode, just preserve old mode */
495 if (!(diff
->diffcaps
& GIT_DIFFCAPS_TRUST_MODE_BITS
) &&
496 (nmode
& MODE_BITS_MASK
) != (omode
& MODE_BITS_MASK
) &&
497 new_iter
->type
== GIT_ITERATOR_WORKDIR
)
498 nmode
= (nmode
& ~MODE_BITS_MASK
) | (omode
& MODE_BITS_MASK
);
500 /* support "assume unchanged" (poorly, b/c we still stat everything) */
501 if ((diff
->diffcaps
& GIT_DIFFCAPS_ASSUME_UNCHANGED
) != 0)
502 status
= (oitem
->flags_extended
& GIT_IDXENTRY_INTENT_TO_ADD
) ?
503 GIT_DELTA_MODIFIED
: GIT_DELTA_UNMODIFIED
;
505 /* support "skip worktree" index bit */
506 else if ((oitem
->flags_extended
& GIT_IDXENTRY_SKIP_WORKTREE
) != 0)
507 status
= GIT_DELTA_UNMODIFIED
;
509 /* if basic type of file changed, then split into delete and add */
510 else if (GIT_MODE_TYPE(omode
) != GIT_MODE_TYPE(nmode
)) {
511 if (diff_delta__from_one(diff
, GIT_DELTA_DELETED
, oitem
) < 0 ||
512 diff_delta__from_one(diff
, GIT_DELTA_ADDED
, nitem
) < 0)
517 /* if oids and modes match, then file is unmodified */
518 else if (git_oid_cmp(&oitem
->oid
, &nitem
->oid
) == 0 &&
520 status
= GIT_DELTA_UNMODIFIED
;
522 /* if modes match and we have an unknown OID and a workdir iterator,
523 * then check deeper for matching
525 else if (omode
== nmode
&&
526 git_oid_iszero(&nitem
->oid
) &&
527 new_iter
->type
== GIT_ITERATOR_WORKDIR
)
529 /* TODO: add check against index file st_mtime to avoid racy-git */
531 /* if they files look exactly alike, then we'll assume the same */
532 if (oitem
->file_size
== nitem
->file_size
&&
533 (!(diff
->diffcaps
& GIT_DIFFCAPS_TRUST_CTIME
) ||
534 (oitem
->ctime
.seconds
== nitem
->ctime
.seconds
)) &&
535 oitem
->mtime
.seconds
== nitem
->mtime
.seconds
&&
536 (!(diff
->diffcaps
& GIT_DIFFCAPS_USE_DEV
) ||
537 (oitem
->dev
== nitem
->dev
)) &&
538 oitem
->ino
== nitem
->ino
&&
539 oitem
->uid
== nitem
->uid
&&
540 oitem
->gid
== nitem
->gid
)
541 status
= GIT_DELTA_UNMODIFIED
;
543 else if (S_ISGITLINK(nmode
)) {
546 if ((diff
->opts
.flags
& GIT_DIFF_IGNORE_SUBMODULES
) != 0)
547 status
= GIT_DELTA_UNMODIFIED
;
548 else if (git_submodule_lookup(&sub
, diff
->repo
, nitem
->path
) < 0)
550 else if (git_submodule_ignore(sub
) == GIT_SUBMODULE_IGNORE_ALL
)
551 status
= GIT_DELTA_UNMODIFIED
;
553 /* TODO: support other GIT_SUBMODULE_IGNORE values */
554 status
= GIT_DELTA_UNMODIFIED
;
558 /* TODO: check git attributes so we will not have to read the file
559 * in if it is marked binary.
562 else if (oid_for_workdir_item(diff
->repo
, nitem
, &noid
) < 0)
565 else if (git_oid_cmp(&oitem
->oid
, &noid
) == 0 &&
567 status
= GIT_DELTA_UNMODIFIED
;
569 /* store calculated oid so we don't have to recalc later */
573 return diff_delta__from_two(
574 diff
, status
, oitem
, omode
, nitem
, nmode
, use_noid
);
577 static int git_index_entry_cmp_case(const void *a
, const void *b
)
579 const git_index_entry
*entry_a
= a
;
580 const git_index_entry
*entry_b
= b
;
582 return strcmp(entry_a
->path
, entry_b
->path
);
585 static int git_index_entry_cmp_icase(const void *a
, const void *b
)
587 const git_index_entry
*entry_a
= a
;
588 const git_index_entry
*entry_b
= b
;
590 return strcasecmp(entry_a
->path
, entry_b
->path
);
593 static int diff_from_iterators(
594 git_repository
*repo
,
595 const git_diff_options
*opts
, /**< can be NULL for defaults */
596 git_iterator
*old_iter
,
597 git_iterator
*new_iter
,
598 git_diff_list
**diff_ptr
)
600 const git_index_entry
*oitem
, *nitem
;
601 git_buf ignore_prefix
= GIT_BUF_INIT
;
602 git_diff_list
*diff
= git_diff_list_alloc(repo
, opts
);
603 git_vector_cmp entry_compare
;
608 diff
->old_src
= old_iter
->type
;
609 diff
->new_src
= new_iter
->type
;
611 /* Use case-insensitive compare if either iterator has
612 * the ignore_case bit set */
613 if (!old_iter
->ignore_case
&& !new_iter
->ignore_case
) {
614 entry_compare
= git_index_entry_cmp_case
;
615 diff
->opts
.flags
&= ~GIT_DIFF_DELTAS_ARE_ICASE
;
617 entry_compare
= git_index_entry_cmp_icase
;
618 diff
->opts
.flags
|= GIT_DIFF_DELTAS_ARE_ICASE
;
620 /* If one of the iterators doesn't have ignore_case set,
621 * then that's unfortunate because we'll have to spool
622 * its data, sort it icase, and then use that for our
623 * merge join to the other iterator that is icase sorted */
624 if (!old_iter
->ignore_case
) {
625 if (git_iterator_spoolandsort(&old_iter
, old_iter
, git_index_entry_cmp_icase
, true) < 0)
627 } else if (!new_iter
->ignore_case
) {
628 if (git_iterator_spoolandsort(&new_iter
, new_iter
, git_index_entry_cmp_icase
, true) < 0)
633 if (git_iterator_current(old_iter
, &oitem
) < 0 ||
634 git_iterator_current(new_iter
, &nitem
) < 0)
637 /* run iterators building diffs */
638 while (oitem
|| nitem
) {
640 /* create DELETED records for old items not matched in new */
641 if (oitem
&& (!nitem
|| entry_compare(oitem
, nitem
) < 0)) {
642 if (diff_delta__from_one(diff
, GIT_DELTA_DELETED
, oitem
) < 0 ||
643 git_iterator_advance(old_iter
, &oitem
) < 0)
647 /* create ADDED, TRACKED, or IGNORED records for new items not
648 * matched in old (and/or descend into directories as needed)
650 else if (nitem
&& (!oitem
|| entry_compare(oitem
, nitem
) > 0)) {
651 git_delta_t delta_type
= GIT_DELTA_UNTRACKED
;
653 /* check if contained in ignored parent directory */
654 if (git_buf_len(&ignore_prefix
) &&
655 ITERATOR_PREFIXCMP(*old_iter
, nitem
->path
, git_buf_cstr(&ignore_prefix
)) == 0)
656 delta_type
= GIT_DELTA_IGNORED
;
658 if (S_ISDIR(nitem
->mode
)) {
659 /* recurse into directory only if there are tracked items in
660 * it or if the user requested the contents of untracked
661 * directories and it is not under an ignored directory.
663 if ((oitem
&& ITERATOR_PREFIXCMP(*old_iter
, oitem
->path
, nitem
->path
) == 0) ||
664 (delta_type
== GIT_DELTA_UNTRACKED
&&
665 (diff
->opts
.flags
& GIT_DIFF_RECURSE_UNTRACKED_DIRS
) != 0))
667 /* if this directory is ignored, remember it as the
668 * "ignore_prefix" for processing contained items
670 if (delta_type
== GIT_DELTA_UNTRACKED
&&
671 git_iterator_current_is_ignored(new_iter
))
672 git_buf_sets(&ignore_prefix
, nitem
->path
);
674 if (git_iterator_advance_into_directory(new_iter
, &nitem
) < 0)
681 /* In core git, the next two "else if" clauses are effectively
682 * reversed -- i.e. when an untracked file contained in an
683 * ignored directory is individually ignored, it shows up as an
684 * ignored file in the diff list, even though other untracked
685 * files in the same directory are skipped completely.
687 * To me, this is odd. If the directory is ignored and the file
688 * is untracked, we should skip it consistently, regardless of
689 * whether it happens to match a pattern in the ignore file.
691 * To match the core git behavior, just reverse the following
692 * two "else if" cases so that individual file ignores are
693 * checked before container directory exclusions are used to
696 else if (delta_type
== GIT_DELTA_IGNORED
) {
697 if (git_iterator_advance(new_iter
, &nitem
) < 0)
699 continue; /* ignored parent directory, so skip completely */
702 else if (git_iterator_current_is_ignored(new_iter
))
703 delta_type
= GIT_DELTA_IGNORED
;
705 else if (new_iter
->type
!= GIT_ITERATOR_WORKDIR
)
706 delta_type
= GIT_DELTA_ADDED
;
708 if (diff_delta__from_one(diff
, delta_type
, nitem
) < 0 ||
709 git_iterator_advance(new_iter
, &nitem
) < 0)
713 /* otherwise item paths match, so create MODIFIED record
714 * (or ADDED and DELETED pair if type changed)
717 assert(oitem
&& nitem
&& entry_compare(oitem
->path
, nitem
->path
) == 0);
719 if (maybe_modified(old_iter
, oitem
, new_iter
, nitem
, diff
) < 0 ||
720 git_iterator_advance(old_iter
, &oitem
) < 0 ||
721 git_iterator_advance(new_iter
, &nitem
) < 0)
726 git_iterator_free(old_iter
);
727 git_iterator_free(new_iter
);
728 git_buf_free(&ignore_prefix
);
734 git_iterator_free(old_iter
);
735 git_iterator_free(new_iter
);
736 git_buf_free(&ignore_prefix
);
738 git_diff_list_free(diff
);
744 int git_diff_tree_to_tree(
745 git_repository
*repo
,
746 const git_diff_options
*opts
, /**< can be NULL for defaults */
749 git_diff_list
**diff
)
751 git_iterator
*a
= NULL
, *b
= NULL
;
752 char *prefix
= opts
? diff_prefix_from_pathspec(&opts
->pathspec
) : NULL
;
754 assert(repo
&& old_tree
&& new_tree
&& diff
);
756 if (git_iterator_for_tree_range(&a
, repo
, old_tree
, prefix
, prefix
) < 0 ||
757 git_iterator_for_tree_range(&b
, repo
, new_tree
, prefix
, prefix
) < 0)
762 return diff_from_iterators(repo
, opts
, a
, b
, diff
);
765 int git_diff_index_to_tree(
766 git_repository
*repo
,
767 const git_diff_options
*opts
,
769 git_diff_list
**diff
)
771 git_iterator
*a
= NULL
, *b
= NULL
;
772 char *prefix
= opts
? diff_prefix_from_pathspec(&opts
->pathspec
) : NULL
;
774 assert(repo
&& diff
);
776 if (git_iterator_for_tree_range(&a
, repo
, old_tree
, prefix
, prefix
) < 0 ||
777 git_iterator_for_index_range(&b
, repo
, prefix
, prefix
) < 0)
782 return diff_from_iterators(repo
, opts
, a
, b
, diff
);
786 git_iterator_free(a
);
790 int git_diff_workdir_to_index(
791 git_repository
*repo
,
792 const git_diff_options
*opts
,
793 git_diff_list
**diff
)
795 git_iterator
*a
= NULL
, *b
= NULL
;
796 char *prefix
= opts
? diff_prefix_from_pathspec(&opts
->pathspec
) : NULL
;
798 assert(repo
&& diff
);
800 if (git_iterator_for_index_range(&a
, repo
, prefix
, prefix
) < 0 ||
801 git_iterator_for_workdir_range(&b
, repo
, prefix
, prefix
) < 0)
806 return diff_from_iterators(repo
, opts
, a
, b
, diff
);
810 git_iterator_free(a
);
815 int git_diff_workdir_to_tree(
816 git_repository
*repo
,
817 const git_diff_options
*opts
,
819 git_diff_list
**diff
)
821 git_iterator
*a
= NULL
, *b
= NULL
;
822 char *prefix
= opts
? diff_prefix_from_pathspec(&opts
->pathspec
) : NULL
;
824 assert(repo
&& old_tree
&& diff
);
826 if (git_iterator_for_tree_range(&a
, repo
, old_tree
, prefix
, prefix
) < 0 ||
827 git_iterator_for_workdir_range(&b
, repo
, prefix
, prefix
) < 0)
832 return diff_from_iterators(repo
, opts
, a
, b
, diff
);
836 git_iterator_free(a
);
842 const git_diff_list
*from
)
847 git_diff_delta
*delta
;
848 bool ignore_case
= false;
851 assert(onto
&& from
);
853 if (!from
->deltas
.length
)
856 if (git_vector_init(&onto_new
, onto
->deltas
.length
, diff_delta__cmp
) < 0 ||
857 git_pool_init(&onto_pool
, 1, 0) < 0)
860 if ((onto
->opts
.flags
& GIT_DIFF_DELTAS_ARE_ICASE
) != 0 ||
861 (from
->opts
.flags
& GIT_DIFF_DELTAS_ARE_ICASE
) != 0)
865 /* This function currently only supports merging diff lists that
866 * are sorted identically. */
867 assert((onto
->opts
.flags
& GIT_DIFF_DELTAS_ARE_ICASE
) != 0 &&
868 (from
->opts
.flags
& GIT_DIFF_DELTAS_ARE_ICASE
) != 0);
871 for (i
= 0, j
= 0; i
< onto
->deltas
.length
|| j
< from
->deltas
.length
; ) {
872 git_diff_delta
*o
= GIT_VECTOR_GET(&onto
->deltas
, i
);
873 const git_diff_delta
*f
= GIT_VECTOR_GET(&from
->deltas
, j
);
874 int cmp
= !f
? -1 : !o
? 1 : STRCMP_CASESELECT(ignore_case
, o
->old_file
.path
, f
->old_file
.path
);
877 delta
= diff_delta__dup(o
, &onto_pool
);
879 } else if (cmp
> 0) {
880 delta
= diff_delta__dup(f
, &onto_pool
);
883 delta
= diff_delta__merge_like_cgit(o
, f
, &onto_pool
);
888 if ((error
= !delta
? -1 : git_vector_insert(&onto_new
, delta
)) < 0)
893 git_vector_swap(&onto
->deltas
, &onto_new
);
894 git_pool_swap(&onto
->pool
, &onto_pool
);
895 onto
->new_src
= from
->new_src
;
897 /* prefix strings also come from old pool, so recreate those.*/
898 onto
->opts
.old_prefix
=
899 git_pool_strdup_safe(&onto
->pool
, onto
->opts
.old_prefix
);
900 onto
->opts
.new_prefix
=
901 git_pool_strdup_safe(&onto
->pool
, onto
->opts
.new_prefix
);
904 git_vector_foreach(&onto_new
, i
, delta
)
906 git_vector_free(&onto_new
);
907 git_pool_clear(&onto_pool
);