2 * Copyright (C) the libgit2 contributors. All rights reserved.
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.
8 #include "diff_generate.h"
11 #include "patch_generate.h"
14 #include "attr_file.h"
19 #include "submodule.h"
21 #define DIFF_FLAG_IS_SET(DIFF,FLAG) \
22 (((DIFF)->base.opts.flags & (FLAG)) != 0)
23 #define DIFF_FLAG_ISNT_SET(DIFF,FLAG) \
24 (((DIFF)->base.opts.flags & (FLAG)) == 0)
25 #define DIFF_FLAG_SET(DIFF,FLAG,VAL) (DIFF)->base.opts.flags = \
26 (VAL) ? ((DIFF)->base.opts.flags | (FLAG)) : \
27 ((DIFF)->base.opts.flags & ~(FLAG))
38 static git_diff_delta
*diff_delta__alloc(
39 git_diff_generated
*diff
,
43 git_diff_delta
*delta
= git__calloc(1, sizeof(git_diff_delta
));
47 delta
->old_file
.path
= git_pool_strdup(&diff
->base
.pool
, path
);
48 if (delta
->old_file
.path
== NULL
) {
53 delta
->new_file
.path
= delta
->old_file
.path
;
55 if (DIFF_FLAG_IS_SET(diff
, GIT_DIFF_REVERSE
)) {
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 */
62 delta
->status
= status
;
67 static int diff_insert_delta(
68 git_diff_generated
*diff
,
69 git_diff_delta
*delta
,
70 const char *matched_pathspec
)
74 if (diff
->base
.opts
.notify_cb
) {
75 error
= diff
->base
.opts
.notify_cb(
76 &diff
->base
, delta
, matched_pathspec
, diff
->base
.opts
.payload
);
81 if (error
> 0) /* positive value means to skip this delta */
83 else /* negative value means to cancel diff */
84 return git_error_set_after_callback_function(error
, "git_diff");
88 if ((error
= git_vector_insert(&diff
->base
.deltas
, delta
)) < 0)
94 static bool diff_pathspec_match(
95 const char **matched_pathspec
,
96 git_diff_generated
*diff
,
97 const git_index_entry
*entry
)
99 bool disable_pathspec_match
=
100 DIFF_FLAG_IS_SET(diff
, GIT_DIFF_DISABLE_PATHSPEC_MATCH
);
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.
108 if ((S_ISLNK(entry
->mode
) || S_ISREG(entry
->mode
)) &&
109 disable_pathspec_match
) {
110 *matched_pathspec
= entry
->path
;
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
);
120 static int diff_delta__from_one(
121 git_diff_generated
*diff
,
123 const git_index_entry
*oitem
,
124 const git_index_entry
*nitem
)
126 const git_index_entry
*entry
= nitem
;
127 bool has_old
= false;
128 git_diff_delta
*delta
;
129 const char *matched_pathspec
;
131 assert((oitem
!= NULL
) ^ (nitem
!= NULL
));
138 if (DIFF_FLAG_IS_SET(diff
, GIT_DIFF_REVERSE
))
141 if ((entry
->flags
& GIT_INDEX_ENTRY_VALID
) != 0)
144 if (status
== GIT_DELTA_IGNORED
&&
145 DIFF_FLAG_ISNT_SET(diff
, GIT_DIFF_INCLUDE_IGNORED
))
148 if (status
== GIT_DELTA_UNTRACKED
&&
149 DIFF_FLAG_ISNT_SET(diff
, GIT_DIFF_INCLUDE_UNTRACKED
))
152 if (status
== GIT_DELTA_UNREADABLE
&&
153 DIFF_FLAG_ISNT_SET(diff
, GIT_DIFF_INCLUDE_UNREADABLE
))
156 if (!diff_pathspec_match(&matched_pathspec
, diff
, entry
))
159 delta
= diff_delta__alloc(diff
, status
, entry
->path
);
160 GIT_ERROR_CHECK_ALLOC(delta
);
162 /* This fn is just for single-sided diffs */
163 assert(status
!= GIT_DELTA_MODIFIED
);
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
;
180 delta
->old_file
.flags
|= GIT_DIFF_FLAG_VALID_ID
;
182 if (has_old
|| !git_oid_is_zero(&delta
->new_file
.id
))
183 delta
->new_file
.flags
|= GIT_DIFF_FLAG_VALID_ID
;
185 return diff_insert_delta(diff
, delta
, matched_pathspec
);
188 static int diff_delta__from_two(
189 git_diff_generated
*diff
,
191 const git_index_entry
*old_entry
,
193 const git_index_entry
*new_entry
,
195 const git_oid
*new_id
,
196 const char *matched_pathspec
)
198 const git_oid
*old_id
= &old_entry
->id
;
199 git_diff_delta
*delta
;
200 const char *canonical_path
= old_entry
->path
;
202 if (status
== GIT_DELTA_UNMODIFIED
&&
203 DIFF_FLAG_ISNT_SET(diff
, GIT_DIFF_INCLUDE_UNMODIFIED
))
207 new_id
= &new_entry
->id
;
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
;
214 old_entry
= new_entry
;
215 new_entry
= temp_entry
;
217 new_mode
= temp_mode
;
222 delta
= diff_delta__alloc(diff
, status
, canonical_path
);
223 GIT_ERROR_CHECK_ALLOC(delta
);
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
;
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
;
243 if (!git_oid_is_zero(&new_entry
->id
))
244 delta
->new_file
.flags
|= GIT_DIFF_FLAG_VALID_ID
;
247 return diff_insert_delta(diff
, delta
, matched_pathspec
);
250 static git_diff_delta
*diff_delta__last_for_item(
251 git_diff_generated
*diff
,
252 const git_index_entry
*item
)
254 git_diff_delta
*delta
= git_vector_last(&diff
->base
.deltas
);
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)
264 case GIT_DELTA_ADDED
:
265 if (git_oid__cmp(&delta
->new_file
.id
, &item
->id
) == 0)
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)
274 case GIT_DELTA_MODIFIED
:
275 if (git_oid__cmp(&delta
->old_file
.id
, &item
->id
) == 0 ||
276 (delta
->new_file
.mode
== item
->mode
&&
277 git_oid__cmp(&delta
->new_file
.id
, &item
->id
) == 0))
287 static char *diff_strdup_prefix(git_pool
*pool
, const char *prefix
)
289 size_t len
= strlen(prefix
);
291 /* append '/' at end if needed */
292 if (len
> 0 && prefix
[len
- 1] != '/')
293 return git_pool_strcat(pool
, prefix
, "/");
295 return git_pool_strndup(pool
, prefix
, len
+ 1);
298 GIT_INLINE(const char *) diff_delta__i2w_path(const git_diff_delta
*delta
)
300 return delta
->old_file
.path
?
301 delta
->old_file
.path
: delta
->new_file
.path
;
304 static int diff_delta_i2w_cmp(const void *a
, const void *b
)
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
);
311 static int diff_delta_i2w_casecmp(const void *a
, const void *b
)
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
);
318 bool git_diff_delta__should_skip(
319 const git_diff_options
*opts
, const git_diff_delta
*delta
)
321 uint32_t flags
= opts
? opts
->flags
: 0;
323 if (delta
->status
== GIT_DELTA_UNMODIFIED
&&
324 (flags
& GIT_DIFF_INCLUDE_UNMODIFIED
) == 0)
327 if (delta
->status
== GIT_DELTA_IGNORED
&&
328 (flags
& GIT_DIFF_INCLUDE_IGNORED
) == 0)
331 if (delta
->status
== GIT_DELTA_UNTRACKED
&&
332 (flags
& GIT_DIFF_INCLUDE_UNTRACKED
) == 0)
335 if (delta
->status
== GIT_DELTA_UNREADABLE
&&
336 (flags
& GIT_DIFF_INCLUDE_UNREADABLE
) == 0)
343 static const char *diff_mnemonic_prefix(
344 git_iterator_t type
, bool left_side
)
346 const char *pfx
= "";
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;
357 /* note: without a deeper look at pathspecs, there is no easy way
358 * to get the (o)bject / (w)ork tree mnemonics working...
364 static void diff_set_ignore_case(git_diff
*diff
, bool ignore_case
)
367 diff
->opts
.flags
&= ~GIT_DIFF_IGNORE_CASE
;
369 diff
->strcomp
= git__strcmp
;
370 diff
->strncomp
= git__strncmp
;
371 diff
->pfxcomp
= git__prefixcmp
;
372 diff
->entrycomp
= git_diff__entry_cmp
;
374 git_vector_set_cmp(&diff
->deltas
, git_diff_delta__cmp
);
376 diff
->opts
.flags
|= GIT_DIFF_IGNORE_CASE
;
378 diff
->strcomp
= git__strcasecmp
;
379 diff
->strncomp
= git__strncasecmp
;
380 diff
->pfxcomp
= git__prefixcmp_icase
;
381 diff
->entrycomp
= git_diff__entry_icmp
;
383 git_vector_set_cmp(&diff
->deltas
, git_diff_delta__casecmp
);
386 git_vector_sort(&diff
->deltas
);
389 static void diff_generated_free(git_diff
*d
)
391 git_diff_generated
*diff
= (git_diff_generated
*)d
;
393 git_attr_session__free(&diff
->base
.attrsession
);
394 git_vector_free_deep(&diff
->base
.deltas
);
396 git_pathspec__vfree(&diff
->pathspec
);
397 git_pool_clear(&diff
->base
.pool
);
399 git__memzero(diff
, sizeof(*diff
));
403 static git_diff_generated
*diff_generated_alloc(
404 git_repository
*repo
,
405 git_iterator
*old_iter
,
406 git_iterator
*new_iter
)
408 git_diff_generated
*diff
;
409 git_diff_options dflt
= GIT_DIFF_OPTIONS_INIT
;
411 assert(repo
&& old_iter
&& new_iter
);
413 if ((diff
= git__calloc(1, sizeof(git_diff_generated
))) == NULL
)
416 GIT_REFCOUNT_INC(&diff
->base
);
417 diff
->base
.type
= GIT_DIFF_TYPE_GENERATED
;
418 diff
->base
.repo
= repo
;
419 diff
->base
.old_src
= old_iter
->type
;
420 diff
->base
.new_src
= new_iter
->type
;
421 diff
->base
.patch_fn
= git_patch_generated_from_diff
;
422 diff
->base
.free_fn
= diff_generated_free
;
423 git_attr_session__init(&diff
->base
.attrsession
, repo
);
424 memcpy(&diff
->base
.opts
, &dflt
, sizeof(git_diff_options
));
426 if (git_pool_init(&diff
->base
.pool
, 1) < 0 ||
427 git_vector_init(&diff
->base
.deltas
, 0, git_diff_delta__cmp
) < 0) {
428 git_diff_free(&diff
->base
);
432 /* Use case-insensitive compare if either iterator has
433 * the ignore_case bit set */
434 diff_set_ignore_case(
436 git_iterator_ignore_case(old_iter
) ||
437 git_iterator_ignore_case(new_iter
));
442 static int diff_generated_apply_options(
443 git_diff_generated
*diff
,
444 const git_diff_options
*opts
)
446 git_config
*cfg
= NULL
;
447 git_repository
*repo
= diff
->base
.repo
;
448 git_pool
*pool
= &diff
->base
.pool
;
452 /* copy user options (except case sensitivity info from iterators) */
453 bool icase
= DIFF_FLAG_IS_SET(diff
, GIT_DIFF_IGNORE_CASE
);
454 memcpy(&diff
->base
.opts
, opts
, sizeof(diff
->base
.opts
));
455 DIFF_FLAG_SET(diff
, GIT_DIFF_IGNORE_CASE
, icase
);
457 /* initialize pathspec from options */
458 if (git_pathspec__vinit(&diff
->pathspec
, &opts
->pathspec
, pool
) < 0)
462 /* flag INCLUDE_TYPECHANGE_TREES implies INCLUDE_TYPECHANGE */
463 if (DIFF_FLAG_IS_SET(diff
, GIT_DIFF_INCLUDE_TYPECHANGE_TREES
))
464 diff
->base
.opts
.flags
|= GIT_DIFF_INCLUDE_TYPECHANGE
;
466 /* flag INCLUDE_UNTRACKED_CONTENT implies INCLUDE_UNTRACKED */
467 if (DIFF_FLAG_IS_SET(diff
, GIT_DIFF_SHOW_UNTRACKED_CONTENT
))
468 diff
->base
.opts
.flags
|= GIT_DIFF_INCLUDE_UNTRACKED
;
470 /* load config values that affect diff behavior */
471 if ((val
= git_repository_config_snapshot(&cfg
, repo
)) < 0)
474 if (!git_config__configmap_lookup(&val
, cfg
, GIT_CONFIGMAP_SYMLINKS
) && val
)
475 diff
->diffcaps
|= GIT_DIFFCAPS_HAS_SYMLINKS
;
477 if (!git_config__configmap_lookup(&val
, cfg
, GIT_CONFIGMAP_IGNORESTAT
) && val
)
478 diff
->diffcaps
|= GIT_DIFFCAPS_IGNORE_STAT
;
480 if ((diff
->base
.opts
.flags
& GIT_DIFF_IGNORE_FILEMODE
) == 0 &&
481 !git_config__configmap_lookup(&val
, cfg
, GIT_CONFIGMAP_FILEMODE
) && val
)
482 diff
->diffcaps
|= GIT_DIFFCAPS_TRUST_MODE_BITS
;
484 if (!git_config__configmap_lookup(&val
, cfg
, GIT_CONFIGMAP_TRUSTCTIME
) && val
)
485 diff
->diffcaps
|= GIT_DIFFCAPS_TRUST_CTIME
;
487 /* Don't set GIT_DIFFCAPS_USE_DEV - compile time option in core git */
489 /* If not given explicit `opts`, check `diff.xyz` configs */
491 int context
= git_config__get_int_force(cfg
, "diff.context", 3);
492 diff
->base
.opts
.context_lines
= context
>= 0 ? (uint32_t)context
: 3;
494 /* add other defaults here */
497 /* Reverse src info if diff is reversed */
498 if (DIFF_FLAG_IS_SET(diff
, GIT_DIFF_REVERSE
)) {
499 git_iterator_t tmp_src
= diff
->base
.old_src
;
500 diff
->base
.old_src
= diff
->base
.new_src
;
501 diff
->base
.new_src
= tmp_src
;
504 /* Unset UPDATE_INDEX unless diffing workdir and index */
505 if (DIFF_FLAG_IS_SET(diff
, GIT_DIFF_UPDATE_INDEX
) &&
506 (!(diff
->base
.old_src
== GIT_ITERATOR_WORKDIR
||
507 diff
->base
.new_src
== GIT_ITERATOR_WORKDIR
) ||
508 !(diff
->base
.old_src
== GIT_ITERATOR_INDEX
||
509 diff
->base
.new_src
== GIT_ITERATOR_INDEX
)))
510 diff
->base
.opts
.flags
&= ~GIT_DIFF_UPDATE_INDEX
;
512 /* if ignore_submodules not explicitly set, check diff config */
513 if (diff
->base
.opts
.ignore_submodules
<= 0) {
514 git_config_entry
*entry
;
515 git_config__lookup_entry(&entry
, cfg
, "diff.ignoresubmodules", true);
517 if (entry
&& git_submodule_parse_ignore(
518 &diff
->base
.opts
.ignore_submodules
, entry
->value
) < 0)
520 git_config_entry_free(entry
);
523 /* if either prefix is not set, figure out appropriate value */
524 if (!diff
->base
.opts
.old_prefix
|| !diff
->base
.opts
.new_prefix
) {
525 const char *use_old
= DIFF_OLD_PREFIX_DEFAULT
;
526 const char *use_new
= DIFF_NEW_PREFIX_DEFAULT
;
528 if (git_config__get_bool_force(cfg
, "diff.noprefix", 0))
529 use_old
= use_new
= "";
530 else if (git_config__get_bool_force(cfg
, "diff.mnemonicprefix", 0)) {
531 use_old
= diff_mnemonic_prefix(diff
->base
.old_src
, true);
532 use_new
= diff_mnemonic_prefix(diff
->base
.new_src
, false);
535 if (!diff
->base
.opts
.old_prefix
)
536 diff
->base
.opts
.old_prefix
= use_old
;
537 if (!diff
->base
.opts
.new_prefix
)
538 diff
->base
.opts
.new_prefix
= use_new
;
541 /* strdup prefix from pool so we're not dependent on external data */
542 diff
->base
.opts
.old_prefix
= diff_strdup_prefix(pool
, diff
->base
.opts
.old_prefix
);
543 diff
->base
.opts
.new_prefix
= diff_strdup_prefix(pool
, diff
->base
.opts
.new_prefix
);
545 if (DIFF_FLAG_IS_SET(diff
, GIT_DIFF_REVERSE
)) {
546 const char *tmp_prefix
= diff
->base
.opts
.old_prefix
;
547 diff
->base
.opts
.old_prefix
= diff
->base
.opts
.new_prefix
;
548 diff
->base
.opts
.new_prefix
= tmp_prefix
;
551 git_config_free(cfg
);
553 /* check strdup results for error */
554 return (!diff
->base
.opts
.old_prefix
|| !diff
->base
.opts
.new_prefix
) ? -1 : 0;
557 int git_diff__oid_for_file(
562 git_object_size_t size
)
564 git_index_entry entry
;
566 if (size
> UINT32_MAX
) {
567 git_error_set(GIT_ERROR_NOMEMORY
, "file size overflow (for 32-bits) on '%s'", path
);
571 memset(&entry
, 0, sizeof(entry
));
573 entry
.file_size
= (uint32_t)size
;
574 entry
.path
= (char *)path
;
576 return git_diff__oid_for_entry(out
, diff
, &entry
, mode
, NULL
);
579 int git_diff__oid_for_entry(
582 const git_index_entry
*src
,
584 const git_oid
*update_match
)
586 git_diff_generated
*diff
;
587 git_buf full_path
= GIT_BUF_INIT
;
588 git_index_entry entry
= *src
;
589 git_filter_list
*fl
= NULL
;
592 assert(d
->type
== GIT_DIFF_TYPE_GENERATED
);
593 diff
= (git_diff_generated
*)d
;
595 memset(out
, 0, sizeof(*out
));
597 if (git_buf_joinpath(&full_path
,
598 git_repository_workdir(diff
->base
.repo
), entry
.path
) < 0)
604 diff
->base
.perf
.stat_calls
++;
606 if (p_stat(full_path
.ptr
, &st
) < 0) {
607 error
= git_path_set_error(errno
, entry
.path
, "stat");
608 git_buf_dispose(&full_path
);
612 git_index_entry__init_from_stat(&entry
,
613 &st
, (diff
->diffcaps
& GIT_DIFFCAPS_TRUST_MODE_BITS
) != 0);
616 /* calculate OID for file if possible */
617 if (S_ISGITLINK(mode
)) {
620 if (!git_submodule_lookup(&sm
, diff
->base
.repo
, entry
.path
)) {
621 const git_oid
*sm_oid
= git_submodule_wd_id(sm
);
623 git_oid_cpy(out
, sm_oid
);
624 git_submodule_free(sm
);
626 /* if submodule lookup failed probably just in an intermediate
627 * state where some init hasn't happened, so ignore the error
631 } else if (S_ISLNK(mode
)) {
632 error
= git_odb__hashlink(out
, full_path
.ptr
);
633 diff
->base
.perf
.oid_calculations
++;
634 } else if (!git__is_sizet(entry
.file_size
)) {
635 git_error_set(GIT_ERROR_NOMEMORY
, "file size overflow (for 32-bits) on '%s'",
638 } else if (!(error
= git_filter_list_load(&fl
,
639 diff
->base
.repo
, NULL
, entry
.path
,
640 GIT_FILTER_TO_ODB
, GIT_FILTER_ALLOW_UNSAFE
)))
642 int fd
= git_futils_open_ro(full_path
.ptr
);
646 error
= git_odb__hashfd_filtered(
647 out
, fd
, (size_t)entry
.file_size
, GIT_OBJECT_BLOB
, fl
);
649 diff
->base
.perf
.oid_calculations
++;
652 git_filter_list_free(fl
);
655 /* update index for entry if requested */
656 if (!error
&& update_match
&& git_oid_equal(out
, update_match
)) {
658 git_index_entry updated_entry
;
660 memcpy(&updated_entry
, &entry
, sizeof(git_index_entry
));
661 updated_entry
.mode
= mode
;
662 git_oid_cpy(&updated_entry
.id
, out
);
664 if (!(error
= git_repository_index__weakptr(&idx
,
666 error
= git_index_add(idx
, &updated_entry
);
667 diff
->index_updated
= true;
671 git_buf_dispose(&full_path
);
676 git_repository
*repo
;
677 git_iterator
*old_iter
;
678 git_iterator
*new_iter
;
679 const git_index_entry
*oitem
;
680 const git_index_entry
*nitem
;
683 #define MODE_BITS_MASK 0000777
685 static int maybe_modified_submodule(
688 git_diff_generated
*diff
,
689 diff_in_progress
*info
)
693 unsigned int sm_status
= 0;
694 git_submodule_ignore_t ign
= diff
->base
.opts
.ignore_submodules
;
696 *status
= GIT_DELTA_UNMODIFIED
;
698 if (DIFF_FLAG_IS_SET(diff
, GIT_DIFF_IGNORE_SUBMODULES
) ||
699 ign
== GIT_SUBMODULE_IGNORE_ALL
)
702 if ((error
= git_submodule_lookup(
703 &sub
, diff
->base
.repo
, info
->nitem
->path
)) < 0) {
705 /* GIT_EEXISTS means dir with .git in it was found - ignore it */
706 if (error
== GIT_EEXISTS
) {
713 if (ign
<= 0 && git_submodule_ignore(sub
) == GIT_SUBMODULE_IGNORE_ALL
)
715 else if ((error
= git_submodule__status(
716 &sm_status
, NULL
, NULL
, found_oid
, sub
, ign
)) < 0)
717 /* return error below */;
719 /* check IS_WD_UNMODIFIED because this case is only used
720 * when the new side of the diff is the working directory
722 else if (!GIT_SUBMODULE_STATUS_IS_WD_UNMODIFIED(sm_status
))
723 *status
= GIT_DELTA_MODIFIED
;
725 /* now that we have a HEAD OID, check if HEAD moved */
726 else if ((sm_status
& GIT_SUBMODULE_STATUS_IN_WD
) != 0 &&
727 !git_oid_equal(&info
->oitem
->id
, found_oid
))
728 *status
= GIT_DELTA_MODIFIED
;
730 git_submodule_free(sub
);
734 static int maybe_modified(
735 git_diff_generated
*diff
,
736 diff_in_progress
*info
)
739 git_delta_t status
= GIT_DELTA_MODIFIED
;
740 const git_index_entry
*oitem
= info
->oitem
;
741 const git_index_entry
*nitem
= info
->nitem
;
742 unsigned int omode
= oitem
->mode
;
743 unsigned int nmode
= nitem
->mode
;
744 bool new_is_workdir
= (info
->new_iter
->type
== GIT_ITERATOR_WORKDIR
);
745 bool modified_uncertain
= false;
746 const char *matched_pathspec
;
749 if (!diff_pathspec_match(&matched_pathspec
, diff
, oitem
))
752 memset(&noid
, 0, sizeof(noid
));
754 /* on platforms with no symlinks, preserve mode of existing symlinks */
755 if (S_ISLNK(omode
) && S_ISREG(nmode
) && new_is_workdir
&&
756 !(diff
->diffcaps
& GIT_DIFFCAPS_HAS_SYMLINKS
))
759 /* on platforms with no execmode, just preserve old mode */
760 if (!(diff
->diffcaps
& GIT_DIFFCAPS_TRUST_MODE_BITS
) &&
761 (nmode
& MODE_BITS_MASK
) != (omode
& MODE_BITS_MASK
) &&
763 nmode
= (nmode
& ~MODE_BITS_MASK
) | (omode
& MODE_BITS_MASK
);
765 /* if one side is a conflict, mark the whole delta as conflicted */
766 if (git_index_entry_is_conflict(oitem
) ||
767 git_index_entry_is_conflict(nitem
)) {
768 status
= GIT_DELTA_CONFLICTED
;
770 /* support "assume unchanged" (poorly, b/c we still stat everything) */
771 } else if ((oitem
->flags
& GIT_INDEX_ENTRY_VALID
) != 0) {
772 status
= GIT_DELTA_UNMODIFIED
;
774 /* support "skip worktree" index bit */
775 } else if ((oitem
->flags_extended
& GIT_INDEX_ENTRY_SKIP_WORKTREE
) != 0) {
776 status
= GIT_DELTA_UNMODIFIED
;
778 /* if basic type of file changed, then split into delete and add */
779 } else if (GIT_MODE_TYPE(omode
) != GIT_MODE_TYPE(nmode
)) {
780 if (DIFF_FLAG_IS_SET(diff
, GIT_DIFF_INCLUDE_TYPECHANGE
)) {
781 status
= GIT_DELTA_TYPECHANGE
;
784 else if (nmode
== GIT_FILEMODE_UNREADABLE
) {
785 if (!(error
= diff_delta__from_one(diff
, GIT_DELTA_DELETED
, oitem
, NULL
)))
786 error
= diff_delta__from_one(diff
, GIT_DELTA_UNREADABLE
, NULL
, nitem
);
791 if (!(error
= diff_delta__from_one(diff
, GIT_DELTA_DELETED
, oitem
, NULL
)))
792 error
= diff_delta__from_one(diff
, GIT_DELTA_ADDED
, NULL
, nitem
);
796 /* if oids and modes match (and are valid), then file is unmodified */
797 } else if (git_oid_equal(&oitem
->id
, &nitem
->id
) &&
799 !git_oid_is_zero(&oitem
->id
)) {
800 status
= GIT_DELTA_UNMODIFIED
;
802 /* if we have an unknown OID and a workdir iterator, then check some
803 * circumstances that can accelerate things or need special handling
805 } else if (git_oid_is_zero(&nitem
->id
) && new_is_workdir
) {
807 ((diff
->diffcaps
& GIT_DIFFCAPS_TRUST_CTIME
) != 0);
808 git_index
*index
= git_iterator_index(info
->new_iter
);
810 status
= GIT_DELTA_UNMODIFIED
;
812 if (S_ISGITLINK(nmode
)) {
813 if ((error
= maybe_modified_submodule(&status
, &noid
, diff
, info
)) < 0)
817 /* if the stat data looks different, then mark modified - this just
818 * means that the OID will be recalculated below to confirm change
820 else if (omode
!= nmode
|| oitem
->file_size
!= nitem
->file_size
) {
821 status
= GIT_DELTA_MODIFIED
;
823 (oitem
->file_size
<= 0 && nitem
->file_size
> 0);
825 else if (!git_index_time_eq(&oitem
->mtime
, &nitem
->mtime
) ||
826 (use_ctime
&& !git_index_time_eq(&oitem
->ctime
, &nitem
->ctime
)) ||
827 oitem
->ino
!= nitem
->ino
||
828 oitem
->uid
!= nitem
->uid
||
829 oitem
->gid
!= nitem
->gid
||
830 git_index_entry_newer_than_index(nitem
, index
))
832 status
= GIT_DELTA_MODIFIED
;
833 modified_uncertain
= true;
836 /* if mode is GITLINK and submodules are ignored, then skip */
837 } else if (S_ISGITLINK(nmode
) &&
838 DIFF_FLAG_IS_SET(diff
, GIT_DIFF_IGNORE_SUBMODULES
)) {
839 status
= GIT_DELTA_UNMODIFIED
;
842 /* if we got here and decided that the files are modified, but we
843 * haven't calculated the OID of the new item, then calculate it now
845 if (modified_uncertain
&& git_oid_is_zero(&nitem
->id
)) {
846 const git_oid
*update_check
=
847 DIFF_FLAG_IS_SET(diff
, GIT_DIFF_UPDATE_INDEX
) && omode
== nmode
?
850 if ((error
= git_diff__oid_for_entry(
851 &noid
, &diff
->base
, nitem
, nmode
, update_check
)) < 0)
854 /* if oid matches, then mark unmodified (except submodules, where
855 * the filesystem content may be modified even if the oid still
856 * matches between the index and the workdir HEAD)
858 if (omode
== nmode
&& !S_ISGITLINK(omode
) &&
859 git_oid_equal(&oitem
->id
, &noid
))
860 status
= GIT_DELTA_UNMODIFIED
;
863 /* If we want case changes, then break this into a delete of the old
864 * and an add of the new so that consumers can act accordingly (eg,
865 * checkout will update the case on disk.)
867 if (DIFF_FLAG_IS_SET(diff
, GIT_DIFF_IGNORE_CASE
) &&
868 DIFF_FLAG_IS_SET(diff
, GIT_DIFF_INCLUDE_CASECHANGE
) &&
869 strcmp(oitem
->path
, nitem
->path
) != 0) {
871 if (!(error
= diff_delta__from_one(diff
, GIT_DELTA_DELETED
, oitem
, NULL
)))
872 error
= diff_delta__from_one(diff
, GIT_DELTA_ADDED
, NULL
, nitem
);
877 return diff_delta__from_two(
878 diff
, status
, oitem
, omode
, nitem
, nmode
,
879 git_oid_is_zero(&noid
) ? NULL
: &noid
, matched_pathspec
);
882 static bool entry_is_prefixed(
883 git_diff_generated
*diff
,
884 const git_index_entry
*item
,
885 const git_index_entry
*prefix_item
)
889 if (!item
|| diff
->base
.pfxcomp(item
->path
, prefix_item
->path
) != 0)
892 pathlen
= strlen(prefix_item
->path
);
894 return (prefix_item
->path
[pathlen
- 1] == '/' ||
895 item
->path
[pathlen
] == '\0' ||
896 item
->path
[pathlen
] == '/');
899 static int iterator_current(
900 const git_index_entry
**entry
,
901 git_iterator
*iterator
)
905 if ((error
= git_iterator_current(entry
, iterator
)) == GIT_ITEROVER
) {
913 static int iterator_advance(
914 const git_index_entry
**entry
,
915 git_iterator
*iterator
)
917 const git_index_entry
*prev_entry
= *entry
;
920 /* if we're looking for conflicts, we only want to report
921 * one conflict for each file, instead of all three sides.
922 * so if this entry is a conflict for this file, and the
923 * previous one was a conflict for the same file, skip it.
925 while ((error
= git_iterator_advance(entry
, iterator
)) == 0) {
926 if (!(iterator
->flags
& GIT_ITERATOR_INCLUDE_CONFLICTS
) ||
927 !git_index_entry_is_conflict(prev_entry
) ||
928 !git_index_entry_is_conflict(*entry
))
931 cmp
= (iterator
->flags
& GIT_ITERATOR_IGNORE_CASE
) ?
932 strcasecmp(prev_entry
->path
, (*entry
)->path
) :
933 strcmp(prev_entry
->path
, (*entry
)->path
);
939 if (error
== GIT_ITEROVER
) {
947 static int iterator_advance_into(
948 const git_index_entry
**entry
,
949 git_iterator
*iterator
)
953 if ((error
= git_iterator_advance_into(entry
, iterator
)) == GIT_ITEROVER
) {
961 static int iterator_advance_over(
962 const git_index_entry
**entry
,
963 git_iterator_status_t
*status
,
964 git_iterator
*iterator
)
966 int error
= git_iterator_advance_over(entry
, status
, iterator
);
968 if (error
== GIT_ITEROVER
) {
976 static int handle_unmatched_new_item(
977 git_diff_generated
*diff
, diff_in_progress
*info
)
980 const git_index_entry
*nitem
= info
->nitem
;
981 git_delta_t delta_type
= GIT_DELTA_UNTRACKED
;
984 /* check if this is a prefix of the other side */
985 contains_oitem
= entry_is_prefixed(diff
, info
->oitem
, nitem
);
987 /* update delta_type if this item is conflicted */
988 if (git_index_entry_is_conflict(nitem
))
989 delta_type
= GIT_DELTA_CONFLICTED
;
991 /* update delta_type if this item is ignored */
992 else if (git_iterator_current_is_ignored(info
->new_iter
))
993 delta_type
= GIT_DELTA_IGNORED
;
995 if (nitem
->mode
== GIT_FILEMODE_TREE
) {
996 bool recurse_into_dir
= contains_oitem
;
998 /* check if user requests recursion into this type of dir */
999 recurse_into_dir
= contains_oitem
||
1000 (delta_type
== GIT_DELTA_UNTRACKED
&&
1001 DIFF_FLAG_IS_SET(diff
, GIT_DIFF_RECURSE_UNTRACKED_DIRS
)) ||
1002 (delta_type
== GIT_DELTA_IGNORED
&&
1003 DIFF_FLAG_IS_SET(diff
, GIT_DIFF_RECURSE_IGNORED_DIRS
));
1005 /* do not advance into directories that contain a .git file */
1006 if (recurse_into_dir
&& !contains_oitem
) {
1007 git_buf
*full
= NULL
;
1008 if (git_iterator_current_workdir_path(&full
, info
->new_iter
) < 0)
1010 if (full
&& git_path_contains(full
, DOT_GIT
)) {
1011 /* TODO: warning if not a valid git repository */
1012 recurse_into_dir
= false;
1016 /* still have to look into untracked directories to match core git -
1017 * with no untracked files, directory is treated as ignored
1019 if (!recurse_into_dir
&&
1020 delta_type
== GIT_DELTA_UNTRACKED
&&
1021 DIFF_FLAG_ISNT_SET(diff
, GIT_DIFF_ENABLE_FAST_UNTRACKED_DIRS
))
1023 git_diff_delta
*last
;
1024 git_iterator_status_t untracked_state
;
1026 /* attempt to insert record for this directory */
1027 if ((error
= diff_delta__from_one(diff
, delta_type
, NULL
, nitem
)) != 0)
1030 /* if delta wasn't created (because of rules), just skip ahead */
1031 last
= diff_delta__last_for_item(diff
, nitem
);
1033 return iterator_advance(&info
->nitem
, info
->new_iter
);
1035 /* iterate into dir looking for an actual untracked file */
1036 if ((error
= iterator_advance_over(
1037 &info
->nitem
, &untracked_state
, info
->new_iter
)) < 0)
1040 /* if we found nothing that matched our pathlist filter, exclude */
1041 if (untracked_state
== GIT_ITERATOR_STATUS_FILTERED
) {
1042 git_vector_pop(&diff
->base
.deltas
);
1046 /* if we found nothing or just ignored items, update the record */
1047 if (untracked_state
== GIT_ITERATOR_STATUS_IGNORED
||
1048 untracked_state
== GIT_ITERATOR_STATUS_EMPTY
) {
1049 last
->status
= GIT_DELTA_IGNORED
;
1051 /* remove the record if we don't want ignored records */
1052 if (DIFF_FLAG_ISNT_SET(diff
, GIT_DIFF_INCLUDE_IGNORED
)) {
1053 git_vector_pop(&diff
->base
.deltas
);
1061 /* try to advance into directory if necessary */
1062 if (recurse_into_dir
) {
1063 error
= iterator_advance_into(&info
->nitem
, info
->new_iter
);
1065 /* if directory is empty, can't advance into it, so skip it */
1066 if (error
== GIT_ENOTFOUND
) {
1068 error
= iterator_advance(&info
->nitem
, info
->new_iter
);
1075 else if (delta_type
== GIT_DELTA_IGNORED
&&
1076 DIFF_FLAG_ISNT_SET(diff
, GIT_DIFF_RECURSE_IGNORED_DIRS
) &&
1077 git_iterator_current_tree_is_ignored(info
->new_iter
))
1078 /* item contained in ignored directory, so skip over it */
1079 return iterator_advance(&info
->nitem
, info
->new_iter
);
1081 else if (info
->new_iter
->type
!= GIT_ITERATOR_WORKDIR
) {
1082 if (delta_type
!= GIT_DELTA_CONFLICTED
)
1083 delta_type
= GIT_DELTA_ADDED
;
1086 else if (nitem
->mode
== GIT_FILEMODE_COMMIT
) {
1087 /* ignore things that are not actual submodules */
1088 if (git_submodule_lookup(NULL
, info
->repo
, nitem
->path
) != 0) {
1090 delta_type
= GIT_DELTA_IGNORED
;
1092 /* if this contains a tracked item, treat as normal TREE */
1093 if (contains_oitem
) {
1094 error
= iterator_advance_into(&info
->nitem
, info
->new_iter
);
1095 if (error
!= GIT_ENOTFOUND
)
1099 return iterator_advance(&info
->nitem
, info
->new_iter
);
1104 else if (nitem
->mode
== GIT_FILEMODE_UNREADABLE
) {
1105 if (DIFF_FLAG_IS_SET(diff
, GIT_DIFF_INCLUDE_UNREADABLE_AS_UNTRACKED
))
1106 delta_type
= GIT_DELTA_UNTRACKED
;
1108 delta_type
= GIT_DELTA_UNREADABLE
;
1111 /* Actually create the record for this item if necessary */
1112 if ((error
= diff_delta__from_one(diff
, delta_type
, NULL
, nitem
)) != 0)
1115 /* If user requested TYPECHANGE records, then check for that instead of
1116 * just generating an ADDED/UNTRACKED record
1118 if (delta_type
!= GIT_DELTA_IGNORED
&&
1119 DIFF_FLAG_IS_SET(diff
, GIT_DIFF_INCLUDE_TYPECHANGE_TREES
) &&
1122 /* this entry was prefixed with a tree - make TYPECHANGE */
1123 git_diff_delta
*last
= diff_delta__last_for_item(diff
, nitem
);
1125 last
->status
= GIT_DELTA_TYPECHANGE
;
1126 last
->old_file
.mode
= GIT_FILEMODE_TREE
;
1130 return iterator_advance(&info
->nitem
, info
->new_iter
);
1133 static int handle_unmatched_old_item(
1134 git_diff_generated
*diff
, diff_in_progress
*info
)
1136 git_delta_t delta_type
= GIT_DELTA_DELETED
;
1139 /* update delta_type if this item is conflicted */
1140 if (git_index_entry_is_conflict(info
->oitem
))
1141 delta_type
= GIT_DELTA_CONFLICTED
;
1143 if ((error
= diff_delta__from_one(diff
, delta_type
, info
->oitem
, NULL
)) < 0)
1146 /* if we are generating TYPECHANGE records then check for that
1147 * instead of just generating a DELETE record
1149 if (DIFF_FLAG_IS_SET(diff
, GIT_DIFF_INCLUDE_TYPECHANGE_TREES
) &&
1150 entry_is_prefixed(diff
, info
->nitem
, info
->oitem
))
1152 /* this entry has become a tree! convert to TYPECHANGE */
1153 git_diff_delta
*last
= diff_delta__last_for_item(diff
, info
->oitem
);
1155 last
->status
= GIT_DELTA_TYPECHANGE
;
1156 last
->new_file
.mode
= GIT_FILEMODE_TREE
;
1159 /* If new_iter is a workdir iterator, then this situation
1160 * will certainly be followed by a series of untracked items.
1161 * Unless RECURSE_UNTRACKED_DIRS is set, skip over them...
1163 if (S_ISDIR(info
->nitem
->mode
) &&
1164 DIFF_FLAG_ISNT_SET(diff
, GIT_DIFF_RECURSE_UNTRACKED_DIRS
))
1165 return iterator_advance(&info
->nitem
, info
->new_iter
);
1168 return iterator_advance(&info
->oitem
, info
->old_iter
);
1171 static int handle_matched_item(
1172 git_diff_generated
*diff
, diff_in_progress
*info
)
1176 if ((error
= maybe_modified(diff
, info
)) < 0)
1179 if (!(error
= iterator_advance(&info
->oitem
, info
->old_iter
)))
1180 error
= iterator_advance(&info
->nitem
, info
->new_iter
);
1185 int git_diff__from_iterators(
1187 git_repository
*repo
,
1188 git_iterator
*old_iter
,
1189 git_iterator
*new_iter
,
1190 const git_diff_options
*opts
)
1192 git_diff_generated
*diff
;
1193 diff_in_progress info
;
1198 diff
= diff_generated_alloc(repo
, old_iter
, new_iter
);
1199 GIT_ERROR_CHECK_ALLOC(diff
);
1202 info
.old_iter
= old_iter
;
1203 info
.new_iter
= new_iter
;
1205 /* make iterators have matching icase behavior */
1206 if (DIFF_FLAG_IS_SET(diff
, GIT_DIFF_IGNORE_CASE
)) {
1207 git_iterator_set_ignore_case(old_iter
, true);
1208 git_iterator_set_ignore_case(new_iter
, true);
1211 /* finish initialization */
1212 if ((error
= diff_generated_apply_options(diff
, opts
)) < 0)
1215 if ((error
= iterator_current(&info
.oitem
, old_iter
)) < 0 ||
1216 (error
= iterator_current(&info
.nitem
, new_iter
)) < 0)
1219 /* run iterators building diffs */
1220 while (!error
&& (info
.oitem
|| info
.nitem
)) {
1223 /* report progress */
1224 if (opts
&& opts
->progress_cb
) {
1225 if ((error
= opts
->progress_cb(&diff
->base
,
1226 info
.oitem
? info
.oitem
->path
: NULL
,
1227 info
.nitem
? info
.nitem
->path
: NULL
,
1233 (info
.nitem
? diff
->base
.entrycomp(info
.oitem
, info
.nitem
) : -1) : 1;
1235 /* create DELETED records for old items not matched in new */
1237 error
= handle_unmatched_old_item(diff
, &info
);
1239 /* create ADDED, TRACKED, or IGNORED records for new items not
1240 * matched in old (and/or descend into directories as needed)
1243 error
= handle_unmatched_new_item(diff
, &info
);
1245 /* otherwise item paths match, so create MODIFIED record
1246 * (or ADDED and DELETED pair if type changed)
1249 error
= handle_matched_item(diff
, &info
);
1252 diff
->base
.perf
.stat_calls
+=
1253 old_iter
->stat_calls
+ new_iter
->stat_calls
;
1259 git_diff_free(&diff
->base
);
1264 static int diff_prepare_iterator_opts(char **prefix
, git_iterator_options
*a
, int aflags
,
1265 git_iterator_options
*b
, int bflags
,
1266 const git_diff_options
*opts
)
1268 GIT_ERROR_CHECK_VERSION(opts
, GIT_DIFF_OPTIONS_VERSION
, "git_diff_options");
1272 if (opts
&& (opts
->flags
& GIT_DIFF_DISABLE_PATHSPEC_MATCH
)) {
1273 a
->pathlist
.strings
= opts
->pathspec
.strings
;
1274 a
->pathlist
.count
= opts
->pathspec
.count
;
1275 b
->pathlist
.strings
= opts
->pathspec
.strings
;
1276 b
->pathlist
.count
= opts
->pathspec
.count
;
1278 *prefix
= git_pathspec_prefix(&opts
->pathspec
);
1279 GIT_ERROR_CHECK_ALLOC(prefix
);
1284 a
->start
= b
->start
= *prefix
;
1285 a
->end
= b
->end
= *prefix
;
1290 int git_diff_tree_to_tree(
1292 git_repository
*repo
,
1295 const git_diff_options
*opts
)
1297 git_iterator_flag_t iflag
= GIT_ITERATOR_DONT_IGNORE_CASE
;
1298 git_iterator_options a_opts
= GIT_ITERATOR_OPTIONS_INIT
,
1299 b_opts
= GIT_ITERATOR_OPTIONS_INIT
;
1300 git_iterator
*a
= NULL
, *b
= NULL
;
1301 git_diff
*diff
= NULL
;
1302 char *prefix
= NULL
;
1305 assert(out
&& repo
);
1309 /* for tree to tree diff, be case sensitive even if the index is
1310 * currently case insensitive, unless the user explicitly asked
1311 * for case insensitivity
1313 if (opts
&& (opts
->flags
& GIT_DIFF_IGNORE_CASE
) != 0)
1314 iflag
= GIT_ITERATOR_IGNORE_CASE
;
1316 if ((error
= diff_prepare_iterator_opts(&prefix
, &a_opts
, iflag
, &b_opts
, iflag
, opts
)) < 0 ||
1317 (error
= git_iterator_for_tree(&a
, old_tree
, &a_opts
)) < 0 ||
1318 (error
= git_iterator_for_tree(&b
, new_tree
, &b_opts
)) < 0 ||
1319 (error
= git_diff__from_iterators(&diff
, repo
, a
, b
, opts
)) < 0)
1325 git_iterator_free(a
);
1326 git_iterator_free(b
);
1327 git_diff_free(diff
);
1333 static int diff_load_index(git_index
**index
, git_repository
*repo
)
1335 int error
= git_repository_index__weakptr(index
, repo
);
1337 /* reload the repository index when user did not pass one in */
1338 if (!error
&& git_index_read(*index
, false) < 0)
1344 int git_diff_tree_to_index(
1346 git_repository
*repo
,
1349 const git_diff_options
*opts
)
1351 git_iterator_flag_t iflag
= GIT_ITERATOR_DONT_IGNORE_CASE
|
1352 GIT_ITERATOR_INCLUDE_CONFLICTS
;
1353 git_iterator_options a_opts
= GIT_ITERATOR_OPTIONS_INIT
,
1354 b_opts
= GIT_ITERATOR_OPTIONS_INIT
;
1355 git_iterator
*a
= NULL
, *b
= NULL
;
1356 git_diff
*diff
= NULL
;
1357 char *prefix
= NULL
;
1358 bool index_ignore_case
= false;
1361 assert(out
&& repo
);
1365 if (!index
&& (error
= diff_load_index(&index
, repo
)) < 0)
1368 index_ignore_case
= index
->ignore_case
;
1370 if ((error
= diff_prepare_iterator_opts(&prefix
, &a_opts
, iflag
, &b_opts
, iflag
, opts
)) < 0 ||
1371 (error
= git_iterator_for_tree(&a
, old_tree
, &a_opts
)) < 0 ||
1372 (error
= git_iterator_for_index(&b
, repo
, index
, &b_opts
)) < 0 ||
1373 (error
= git_diff__from_iterators(&diff
, repo
, a
, b
, opts
)) < 0)
1376 /* if index is in case-insensitive order, re-sort deltas to match */
1377 if (index_ignore_case
)
1378 diff_set_ignore_case(diff
, true);
1383 git_iterator_free(a
);
1384 git_iterator_free(b
);
1385 git_diff_free(diff
);
1391 int git_diff_index_to_workdir(
1393 git_repository
*repo
,
1395 const git_diff_options
*opts
)
1397 git_iterator_options a_opts
= GIT_ITERATOR_OPTIONS_INIT
,
1398 b_opts
= GIT_ITERATOR_OPTIONS_INIT
;
1399 git_iterator
*a
= NULL
, *b
= NULL
;
1400 git_diff
*diff
= NULL
;
1401 char *prefix
= NULL
;
1404 assert(out
&& repo
);
1408 if (!index
&& (error
= diff_load_index(&index
, repo
)) < 0)
1411 if ((error
= diff_prepare_iterator_opts(&prefix
, &a_opts
, GIT_ITERATOR_INCLUDE_CONFLICTS
,
1412 &b_opts
, GIT_ITERATOR_DONT_AUTOEXPAND
, opts
)) < 0 ||
1413 (error
= git_iterator_for_index(&a
, repo
, index
, &a_opts
)) < 0 ||
1414 (error
= git_iterator_for_workdir(&b
, repo
, index
, NULL
, &b_opts
)) < 0 ||
1415 (error
= git_diff__from_iterators(&diff
, repo
, a
, b
, opts
)) < 0)
1418 if ((diff
->opts
.flags
& GIT_DIFF_UPDATE_INDEX
) && ((git_diff_generated
*)diff
)->index_updated
)
1419 if ((error
= git_index_write(index
)) < 0)
1425 git_iterator_free(a
);
1426 git_iterator_free(b
);
1427 git_diff_free(diff
);
1433 int git_diff_tree_to_workdir(
1435 git_repository
*repo
,
1437 const git_diff_options
*opts
)
1439 git_iterator_options a_opts
= GIT_ITERATOR_OPTIONS_INIT
,
1440 b_opts
= GIT_ITERATOR_OPTIONS_INIT
;
1441 git_iterator
*a
= NULL
, *b
= NULL
;
1442 git_diff
*diff
= NULL
;
1443 char *prefix
= NULL
;
1447 assert(out
&& repo
);
1451 if ((error
= diff_prepare_iterator_opts(&prefix
, &a_opts
, 0,
1452 &b_opts
, GIT_ITERATOR_DONT_AUTOEXPAND
, opts
) < 0) ||
1453 (error
= git_repository_index__weakptr(&index
, repo
)) < 0 ||
1454 (error
= git_iterator_for_tree(&a
, old_tree
, &a_opts
)) < 0 ||
1455 (error
= git_iterator_for_workdir(&b
, repo
, index
, old_tree
, &b_opts
)) < 0 ||
1456 (error
= git_diff__from_iterators(&diff
, repo
, a
, b
, opts
)) < 0)
1462 git_iterator_free(a
);
1463 git_iterator_free(b
);
1464 git_diff_free(diff
);
1470 int git_diff_tree_to_workdir_with_index(
1472 git_repository
*repo
,
1474 const git_diff_options
*opts
)
1476 git_diff
*d1
= NULL
, *d2
= NULL
;
1477 git_index
*index
= NULL
;
1480 assert(out
&& repo
);
1484 if ((error
= diff_load_index(&index
, repo
)) < 0)
1487 if (!(error
= git_diff_tree_to_index(&d1
, repo
, tree
, index
, opts
)) &&
1488 !(error
= git_diff_index_to_workdir(&d2
, repo
, index
, opts
)))
1489 error
= git_diff_merge(d1
, d2
);
1502 int git_diff_index_to_index(
1504 git_repository
*repo
,
1505 git_index
*old_index
,
1506 git_index
*new_index
,
1507 const git_diff_options
*opts
)
1509 git_iterator_options a_opts
= GIT_ITERATOR_OPTIONS_INIT
,
1510 b_opts
= GIT_ITERATOR_OPTIONS_INIT
;
1511 git_iterator
*a
= NULL
, *b
= NULL
;
1512 git_diff
*diff
= NULL
;
1513 char *prefix
= NULL
;
1516 assert(out
&& old_index
&& new_index
);
1520 if ((error
= diff_prepare_iterator_opts(&prefix
, &a_opts
, GIT_ITERATOR_DONT_IGNORE_CASE
,
1521 &b_opts
, GIT_ITERATOR_DONT_IGNORE_CASE
, opts
) < 0) ||
1522 (error
= git_iterator_for_index(&a
, repo
, old_index
, &a_opts
)) < 0 ||
1523 (error
= git_iterator_for_index(&b
, repo
, new_index
, &b_opts
)) < 0 ||
1524 (error
= git_diff__from_iterators(&diff
, repo
, a
, b
, opts
)) < 0)
1527 /* if index is in case-insensitive order, re-sort deltas to match */
1528 if (old_index
->ignore_case
|| new_index
->ignore_case
)
1529 diff_set_ignore_case(diff
, true);
1534 git_iterator_free(a
);
1535 git_iterator_free(b
);
1536 git_diff_free(diff
);
1542 int git_diff__paired_foreach(
1545 int (*cb
)(git_diff_delta
*h2i
, git_diff_delta
*i2w
, void *payload
),
1549 git_diff_delta
*h2i
, *i2w
;
1550 size_t i
, j
, i_max
, j_max
;
1551 int (*strcomp
)(const char *, const char *) = git__strcmp
;
1552 bool h2i_icase
, i2w_icase
, icase_mismatch
;
1554 i_max
= head2idx
? head2idx
->deltas
.length
: 0;
1555 j_max
= idx2wd
? idx2wd
->deltas
.length
: 0;
1556 if (!i_max
&& !j_max
)
1559 /* At some point, tree-to-index diffs will probably never ignore case,
1560 * even if that isn't true now. Index-to-workdir diffs may or may not
1561 * ignore case, but the index filename for the idx2wd diff should
1562 * still be using the canonical case-preserving name.
1564 * Therefore the main thing we need to do here is make sure the diffs
1565 * are traversed in a compatible order. To do this, we temporarily
1566 * resort a mismatched diff to get the order correct.
1568 * In order to traverse renames in the index->workdir, we need to
1569 * ensure that we compare the index name on both sides, so we
1570 * always sort by the old name in the i2w list.
1572 h2i_icase
= head2idx
!= NULL
&& git_diff_is_sorted_icase(head2idx
);
1573 i2w_icase
= idx2wd
!= NULL
&& git_diff_is_sorted_icase(idx2wd
);
1576 (head2idx
!= NULL
&& idx2wd
!= NULL
&& h2i_icase
!= i2w_icase
);
1578 if (icase_mismatch
&& h2i_icase
) {
1579 git_vector_set_cmp(&head2idx
->deltas
, git_diff_delta__cmp
);
1580 git_vector_sort(&head2idx
->deltas
);
1583 if (i2w_icase
&& !icase_mismatch
) {
1584 strcomp
= git__strcasecmp
;
1586 git_vector_set_cmp(&idx2wd
->deltas
, diff_delta_i2w_casecmp
);
1587 git_vector_sort(&idx2wd
->deltas
);
1588 } else if (idx2wd
!= NULL
) {
1589 git_vector_set_cmp(&idx2wd
->deltas
, diff_delta_i2w_cmp
);
1590 git_vector_sort(&idx2wd
->deltas
);
1593 for (i
= 0, j
= 0; i
< i_max
|| j
< j_max
; ) {
1594 h2i
= head2idx
? GIT_VECTOR_GET(&head2idx
->deltas
, i
) : NULL
;
1595 i2w
= idx2wd
? GIT_VECTOR_GET(&idx2wd
->deltas
, j
) : NULL
;
1597 cmp
= !i2w
? -1 : !h2i
? 1 :
1598 strcomp(h2i
->new_file
.path
, i2w
->old_file
.path
);
1602 } else if (cmp
> 0) {
1608 if ((error
= cb(h2i
, i2w
, payload
)) != 0) {
1609 git_error_set_after_callback(error
);
1614 /* restore case-insensitive delta sort */
1615 if (icase_mismatch
&& h2i_icase
) {
1616 git_vector_set_cmp(&head2idx
->deltas
, git_diff_delta__casecmp
);
1617 git_vector_sort(&head2idx
->deltas
);
1620 /* restore idx2wd sort by new path */
1621 if (idx2wd
!= NULL
) {
1622 git_vector_set_cmp(&idx2wd
->deltas
,
1623 i2w_icase
? git_diff_delta__casecmp
: git_diff_delta__cmp
);
1624 git_vector_sort(&idx2wd
->deltas
);
1630 int git_diff__commit(
1632 git_repository
*repo
,
1633 const git_commit
*commit
,
1634 const git_diff_options
*opts
)
1636 git_commit
*parent
= NULL
;
1637 git_diff
*commit_diff
= NULL
;
1638 git_tree
*old_tree
= NULL
, *new_tree
= NULL
;
1644 if ((parents
= git_commit_parentcount(commit
)) > 1) {
1645 char commit_oidstr
[GIT_OID_HEXSZ
+ 1];
1648 git_error_set(GIT_ERROR_INVALID
, "commit %s is a merge commit",
1649 git_oid_tostr(commit_oidstr
, GIT_OID_HEXSZ
+ 1, git_commit_id(commit
)));
1654 if ((error
= git_commit_parent(&parent
, commit
, 0)) < 0 ||
1655 (error
= git_commit_tree(&old_tree
, parent
)) < 0)
1658 if ((error
= git_commit_tree(&new_tree
, commit
)) < 0 ||
1659 (error
= git_diff_tree_to_tree(&commit_diff
, repo
, old_tree
, new_tree
, opts
)) < 0)
1665 git_tree_free(new_tree
);
1666 git_tree_free(old_tree
);
1667 git_commit_free(parent
);