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.
11 #include "attr_file.h"
16 #include "submodule.h"
18 #define DIFF_FLAG_IS_SET(DIFF,FLAG) (((DIFF)->opts.flags & (FLAG)) != 0)
19 #define DIFF_FLAG_ISNT_SET(DIFF,FLAG) (((DIFF)->opts.flags & (FLAG)) == 0)
20 #define DIFF_FLAG_SET(DIFF,FLAG,VAL) (DIFF)->opts.flags = \
21 (VAL) ? ((DIFF)->opts.flags | (FLAG)) : ((DIFF)->opts.flags & ~(VAL))
23 static git_diff_delta
*diff_delta__alloc(
28 git_diff_delta
*delta
= git__calloc(1, sizeof(git_diff_delta
));
32 delta
->old_file
.path
= git_pool_strdup(&diff
->pool
, path
);
33 if (delta
->old_file
.path
== NULL
) {
38 delta
->new_file
.path
= delta
->old_file
.path
;
40 if (DIFF_FLAG_IS_SET(diff
, GIT_DIFF_REVERSE
)) {
42 case GIT_DELTA_ADDED
: status
= GIT_DELTA_DELETED
; break;
43 case GIT_DELTA_DELETED
: status
= GIT_DELTA_ADDED
; break;
44 default: break; /* leave other status values alone */
47 delta
->status
= status
;
52 static int diff_insert_delta(
53 git_diff
*diff
, git_diff_delta
*delta
, const char *matched_pathspec
)
57 if (diff
->opts
.notify_cb
) {
58 error
= diff
->opts
.notify_cb(
59 diff
, delta
, matched_pathspec
, diff
->opts
.notify_payload
);
64 if (error
> 0) /* positive value means to skip this delta */
66 else /* negative value means to cancel diff */
67 return giterr_set_after_callback_function(error
, "git_diff");
71 if ((error
= git_vector_insert(&diff
->deltas
, delta
)) < 0)
77 static bool diff_pathspec_match(
78 const char **matched_pathspec
,
80 const git_index_entry
*entry
)
82 bool disable_pathspec_match
=
83 DIFF_FLAG_IS_SET(diff
, GIT_DIFF_DISABLE_PATHSPEC_MATCH
);
85 /* If we're disabling fnmatch, then the iterator has already applied
86 * the filters to the files for us and we don't have to do anything.
87 * However, this only applies to *files* - the iterator will include
88 * directories that we need to recurse into when not autoexpanding,
89 * so we still need to apply the pathspec match to directories.
91 if ((S_ISLNK(entry
->mode
) || S_ISREG(entry
->mode
)) &&
92 disable_pathspec_match
) {
93 *matched_pathspec
= entry
->path
;
97 return git_pathspec__match(
98 &diff
->pathspec
, entry
->path
, disable_pathspec_match
,
99 DIFF_FLAG_IS_SET(diff
, GIT_DIFF_IGNORE_CASE
),
100 matched_pathspec
, NULL
);
103 static int diff_delta__from_one(
106 const git_index_entry
*oitem
,
107 const git_index_entry
*nitem
)
109 const git_index_entry
*entry
= nitem
;
110 bool has_old
= false;
111 git_diff_delta
*delta
;
112 const char *matched_pathspec
;
114 assert((oitem
!= NULL
) ^ (nitem
!= NULL
));
121 if (DIFF_FLAG_IS_SET(diff
, GIT_DIFF_REVERSE
))
124 if ((entry
->flags
& GIT_IDXENTRY_VALID
) != 0)
127 if (status
== GIT_DELTA_IGNORED
&&
128 DIFF_FLAG_ISNT_SET(diff
, GIT_DIFF_INCLUDE_IGNORED
))
131 if (status
== GIT_DELTA_UNTRACKED
&&
132 DIFF_FLAG_ISNT_SET(diff
, GIT_DIFF_INCLUDE_UNTRACKED
))
135 if (status
== GIT_DELTA_UNREADABLE
&&
136 DIFF_FLAG_ISNT_SET(diff
, GIT_DIFF_INCLUDE_UNREADABLE
))
139 if (!diff_pathspec_match(&matched_pathspec
, diff
, entry
))
142 delta
= diff_delta__alloc(diff
, status
, entry
->path
);
143 GITERR_CHECK_ALLOC(delta
);
145 /* This fn is just for single-sided diffs */
146 assert(status
!= GIT_DELTA_MODIFIED
);
150 delta
->old_file
.mode
= entry
->mode
;
151 delta
->old_file
.size
= entry
->file_size
;
152 delta
->old_file
.flags
|= GIT_DIFF_FLAG_EXISTS
;
153 git_oid_cpy(&delta
->old_file
.id
, &entry
->id
);
154 } else /* ADDED, IGNORED, UNTRACKED */ {
155 delta
->new_file
.mode
= entry
->mode
;
156 delta
->new_file
.size
= entry
->file_size
;
157 delta
->new_file
.flags
|= GIT_DIFF_FLAG_EXISTS
;
158 git_oid_cpy(&delta
->new_file
.id
, &entry
->id
);
161 delta
->old_file
.flags
|= GIT_DIFF_FLAG_VALID_ID
;
163 if (has_old
|| !git_oid_iszero(&delta
->new_file
.id
))
164 delta
->new_file
.flags
|= GIT_DIFF_FLAG_VALID_ID
;
166 return diff_insert_delta(diff
, delta
, matched_pathspec
);
169 static int diff_delta__from_two(
172 const git_index_entry
*old_entry
,
174 const git_index_entry
*new_entry
,
176 const git_oid
*new_id
,
177 const char *matched_pathspec
)
179 const git_oid
*old_id
= &old_entry
->id
;
180 git_diff_delta
*delta
;
181 const char *canonical_path
= old_entry
->path
;
183 if (status
== GIT_DELTA_UNMODIFIED
&&
184 DIFF_FLAG_ISNT_SET(diff
, GIT_DIFF_INCLUDE_UNMODIFIED
))
188 new_id
= &new_entry
->id
;
190 if (DIFF_FLAG_IS_SET(diff
, GIT_DIFF_REVERSE
)) {
191 uint32_t temp_mode
= old_mode
;
192 const git_index_entry
*temp_entry
= old_entry
;
193 const git_oid
*temp_id
= old_id
;
195 old_entry
= new_entry
;
196 new_entry
= temp_entry
;
198 new_mode
= temp_mode
;
203 delta
= diff_delta__alloc(diff
, status
, canonical_path
);
204 GITERR_CHECK_ALLOC(delta
);
207 if (!git_index_entry_is_conflict(old_entry
)) {
208 delta
->old_file
.size
= old_entry
->file_size
;
209 delta
->old_file
.mode
= old_mode
;
210 git_oid_cpy(&delta
->old_file
.id
, old_id
);
211 delta
->old_file
.flags
|= GIT_DIFF_FLAG_VALID_ID
|
212 GIT_DIFF_FLAG_EXISTS
;
215 if (!git_index_entry_is_conflict(new_entry
)) {
216 git_oid_cpy(&delta
->new_file
.id
, new_id
);
217 delta
->new_file
.size
= new_entry
->file_size
;
218 delta
->new_file
.mode
= new_mode
;
219 delta
->old_file
.flags
|= GIT_DIFF_FLAG_EXISTS
;
220 delta
->new_file
.flags
|= GIT_DIFF_FLAG_EXISTS
;
222 if (!git_oid_iszero(&new_entry
->id
))
223 delta
->new_file
.flags
|= GIT_DIFF_FLAG_VALID_ID
;
226 return diff_insert_delta(diff
, delta
, matched_pathspec
);
229 static git_diff_delta
*diff_delta__last_for_item(
231 const git_index_entry
*item
)
233 git_diff_delta
*delta
= git_vector_last(&diff
->deltas
);
237 switch (delta
->status
) {
238 case GIT_DELTA_UNMODIFIED
:
239 case GIT_DELTA_DELETED
:
240 if (git_oid__cmp(&delta
->old_file
.id
, &item
->id
) == 0)
243 case GIT_DELTA_ADDED
:
244 if (git_oid__cmp(&delta
->new_file
.id
, &item
->id
) == 0)
247 case GIT_DELTA_UNREADABLE
:
248 case GIT_DELTA_UNTRACKED
:
249 if (diff
->strcomp(delta
->new_file
.path
, item
->path
) == 0 &&
250 git_oid__cmp(&delta
->new_file
.id
, &item
->id
) == 0)
253 case GIT_DELTA_MODIFIED
:
254 if (git_oid__cmp(&delta
->old_file
.id
, &item
->id
) == 0 ||
255 git_oid__cmp(&delta
->new_file
.id
, &item
->id
) == 0)
265 static char *diff_strdup_prefix(git_pool
*pool
, const char *prefix
)
267 size_t len
= strlen(prefix
);
269 /* append '/' at end if needed */
270 if (len
> 0 && prefix
[len
- 1] != '/')
271 return git_pool_strcat(pool
, prefix
, "/");
273 return git_pool_strndup(pool
, prefix
, len
+ 1);
276 GIT_INLINE(const char *) diff_delta__path(const git_diff_delta
*delta
)
278 const char *str
= delta
->old_file
.path
;
281 delta
->status
== GIT_DELTA_ADDED
||
282 delta
->status
== GIT_DELTA_RENAMED
||
283 delta
->status
== GIT_DELTA_COPIED
)
284 str
= delta
->new_file
.path
;
289 const char *git_diff_delta__path(const git_diff_delta
*delta
)
291 return diff_delta__path(delta
);
294 int git_diff_delta__cmp(const void *a
, const void *b
)
296 const git_diff_delta
*da
= a
, *db
= b
;
297 int val
= strcmp(diff_delta__path(da
), diff_delta__path(db
));
298 return val
? val
: ((int)da
->status
- (int)db
->status
);
301 int git_diff_delta__casecmp(const void *a
, const void *b
)
303 const git_diff_delta
*da
= a
, *db
= b
;
304 int val
= strcasecmp(diff_delta__path(da
), diff_delta__path(db
));
305 return val
? val
: ((int)da
->status
- (int)db
->status
);
308 GIT_INLINE(const char *) diff_delta__i2w_path(const git_diff_delta
*delta
)
310 return delta
->old_file
.path
?
311 delta
->old_file
.path
: delta
->new_file
.path
;
314 int git_diff_delta__i2w_cmp(const void *a
, const void *b
)
316 const git_diff_delta
*da
= a
, *db
= b
;
317 int val
= strcmp(diff_delta__i2w_path(da
), diff_delta__i2w_path(db
));
318 return val
? val
: ((int)da
->status
- (int)db
->status
);
321 int git_diff_delta__i2w_casecmp(const void *a
, const void *b
)
323 const git_diff_delta
*da
= a
, *db
= b
;
324 int val
= strcasecmp(diff_delta__i2w_path(da
), diff_delta__i2w_path(db
));
325 return val
? val
: ((int)da
->status
- (int)db
->status
);
328 bool git_diff_delta__should_skip(
329 const git_diff_options
*opts
, const git_diff_delta
*delta
)
331 uint32_t flags
= opts
? opts
->flags
: 0;
333 if (delta
->status
== GIT_DELTA_UNMODIFIED
&&
334 (flags
& GIT_DIFF_INCLUDE_UNMODIFIED
) == 0)
337 if (delta
->status
== GIT_DELTA_IGNORED
&&
338 (flags
& GIT_DIFF_INCLUDE_IGNORED
) == 0)
341 if (delta
->status
== GIT_DELTA_UNTRACKED
&&
342 (flags
& GIT_DIFF_INCLUDE_UNTRACKED
) == 0)
345 if (delta
->status
== GIT_DELTA_UNREADABLE
&&
346 (flags
& GIT_DIFF_INCLUDE_UNREADABLE
) == 0)
353 static const char *diff_mnemonic_prefix(
354 git_iterator_type_t type
, bool left_side
)
356 const char *pfx
= "";
359 case GIT_ITERATOR_TYPE_EMPTY
: pfx
= "c"; break;
360 case GIT_ITERATOR_TYPE_TREE
: pfx
= "c"; break;
361 case GIT_ITERATOR_TYPE_INDEX
: pfx
= "i"; break;
362 case GIT_ITERATOR_TYPE_WORKDIR
: pfx
= "w"; break;
363 case GIT_ITERATOR_TYPE_FS
: pfx
= left_side
? "1" : "2"; break;
367 /* note: without a deeper look at pathspecs, there is no easy way
368 * to get the (o)bject / (w)ork tree mnemonics working...
374 static int diff_entry_cmp(const void *a
, const void *b
)
376 const git_index_entry
*entry_a
= a
;
377 const git_index_entry
*entry_b
= b
;
379 return strcmp(entry_a
->path
, entry_b
->path
);
382 static int diff_entry_icmp(const void *a
, const void *b
)
384 const git_index_entry
*entry_a
= a
;
385 const git_index_entry
*entry_b
= b
;
387 return strcasecmp(entry_a
->path
, entry_b
->path
);
390 static void diff_set_ignore_case(git_diff
*diff
, bool ignore_case
)
393 diff
->opts
.flags
&= ~GIT_DIFF_IGNORE_CASE
;
395 diff
->strcomp
= git__strcmp
;
396 diff
->strncomp
= git__strncmp
;
397 diff
->pfxcomp
= git__prefixcmp
;
398 diff
->entrycomp
= diff_entry_cmp
;
400 git_vector_set_cmp(&diff
->deltas
, git_diff_delta__cmp
);
402 diff
->opts
.flags
|= GIT_DIFF_IGNORE_CASE
;
404 diff
->strcomp
= git__strcasecmp
;
405 diff
->strncomp
= git__strncasecmp
;
406 diff
->pfxcomp
= git__prefixcmp_icase
;
407 diff
->entrycomp
= diff_entry_icmp
;
409 git_vector_set_cmp(&diff
->deltas
, git_diff_delta__casecmp
);
412 git_vector_sort(&diff
->deltas
);
415 static git_diff
*diff_list_alloc(
416 git_repository
*repo
,
417 git_iterator
*old_iter
,
418 git_iterator
*new_iter
)
420 git_diff_options dflt
= GIT_DIFF_OPTIONS_INIT
;
421 git_diff
*diff
= git__calloc(1, sizeof(git_diff
));
425 assert(repo
&& old_iter
&& new_iter
);
427 GIT_REFCOUNT_INC(diff
);
429 diff
->old_src
= old_iter
->type
;
430 diff
->new_src
= new_iter
->type
;
431 memcpy(&diff
->opts
, &dflt
, sizeof(diff
->opts
));
433 if (git_vector_init(&diff
->deltas
, 0, git_diff_delta__cmp
) < 0 ||
434 git_pool_init(&diff
->pool
, 1, 0) < 0) {
439 /* Use case-insensitive compare if either iterator has
440 * the ignore_case bit set */
441 diff_set_ignore_case(
443 git_iterator_ignore_case(old_iter
) ||
444 git_iterator_ignore_case(new_iter
));
449 static int diff_list_apply_options(
451 const git_diff_options
*opts
)
453 git_config
*cfg
= NULL
;
454 git_repository
*repo
= diff
->repo
;
455 git_pool
*pool
= &diff
->pool
;
459 /* copy user options (except case sensitivity info from iterators) */
460 bool icase
= DIFF_FLAG_IS_SET(diff
, GIT_DIFF_IGNORE_CASE
);
461 memcpy(&diff
->opts
, opts
, sizeof(diff
->opts
));
462 DIFF_FLAG_SET(diff
, GIT_DIFF_IGNORE_CASE
, icase
);
464 /* initialize pathspec from options */
465 if (git_pathspec__vinit(&diff
->pathspec
, &opts
->pathspec
, pool
) < 0)
469 /* flag INCLUDE_TYPECHANGE_TREES implies INCLUDE_TYPECHANGE */
470 if (DIFF_FLAG_IS_SET(diff
, GIT_DIFF_INCLUDE_TYPECHANGE_TREES
))
471 diff
->opts
.flags
|= GIT_DIFF_INCLUDE_TYPECHANGE
;
473 /* flag INCLUDE_UNTRACKED_CONTENT implies INCLUDE_UNTRACKED */
474 if (DIFF_FLAG_IS_SET(diff
, GIT_DIFF_SHOW_UNTRACKED_CONTENT
))
475 diff
->opts
.flags
|= GIT_DIFF_INCLUDE_UNTRACKED
;
477 /* load config values that affect diff behavior */
478 if ((val
= git_repository_config_snapshot(&cfg
, repo
)) < 0)
481 if (!git_config__cvar(&val
, cfg
, GIT_CVAR_SYMLINKS
) && val
)
482 diff
->diffcaps
= diff
->diffcaps
| GIT_DIFFCAPS_HAS_SYMLINKS
;
484 if (!git_config__cvar(&val
, cfg
, GIT_CVAR_IGNORESTAT
) && val
)
485 diff
->diffcaps
= diff
->diffcaps
| GIT_DIFFCAPS_IGNORE_STAT
;
487 if ((diff
->opts
.flags
& GIT_DIFF_IGNORE_FILEMODE
) == 0 &&
488 !git_config__cvar(&val
, cfg
, GIT_CVAR_FILEMODE
) && val
)
489 diff
->diffcaps
= diff
->diffcaps
| GIT_DIFFCAPS_TRUST_MODE_BITS
;
491 if (!git_config__cvar(&val
, cfg
, GIT_CVAR_TRUSTCTIME
) && val
)
492 diff
->diffcaps
= diff
->diffcaps
| GIT_DIFFCAPS_TRUST_CTIME
;
494 /* Don't set GIT_DIFFCAPS_USE_DEV - compile time option in core git */
496 /* Set GIT_DIFFCAPS_TRUST_NANOSECS on a platform basis */
497 diff
->diffcaps
= diff
->diffcaps
| GIT_DIFFCAPS_TRUST_NANOSECS
;
499 /* If not given explicit `opts`, check `diff.xyz` configs */
501 int context
= git_config__get_int_force(cfg
, "diff.context", 3);
502 diff
->opts
.context_lines
= context
>= 0 ? (uint32_t)context
: 3;
504 /* add other defaults here */
507 /* Reverse src info if diff is reversed */
508 if (DIFF_FLAG_IS_SET(diff
, GIT_DIFF_REVERSE
)) {
509 git_iterator_type_t tmp_src
= diff
->old_src
;
510 diff
->old_src
= diff
->new_src
;
511 diff
->new_src
= tmp_src
;
514 /* Unset UPDATE_INDEX unless diffing workdir and index */
515 if (DIFF_FLAG_IS_SET(diff
, GIT_DIFF_UPDATE_INDEX
) &&
516 (!(diff
->old_src
== GIT_ITERATOR_TYPE_WORKDIR
||
517 diff
->new_src
== GIT_ITERATOR_TYPE_WORKDIR
) ||
518 !(diff
->old_src
== GIT_ITERATOR_TYPE_INDEX
||
519 diff
->new_src
== GIT_ITERATOR_TYPE_INDEX
)))
520 diff
->opts
.flags
&= ~GIT_DIFF_UPDATE_INDEX
;
522 /* if ignore_submodules not explicitly set, check diff config */
523 if (diff
->opts
.ignore_submodules
<= 0) {
524 git_config_entry
*entry
;
525 git_config__lookup_entry(&entry
, cfg
, "diff.ignoresubmodules", true);
527 if (entry
&& git_submodule_parse_ignore(
528 &diff
->opts
.ignore_submodules
, entry
->value
) < 0)
530 git_config_entry_free(entry
);
533 /* if either prefix is not set, figure out appropriate value */
534 if (!diff
->opts
.old_prefix
|| !diff
->opts
.new_prefix
) {
535 const char *use_old
= DIFF_OLD_PREFIX_DEFAULT
;
536 const char *use_new
= DIFF_NEW_PREFIX_DEFAULT
;
538 if (git_config__get_bool_force(cfg
, "diff.noprefix", 0))
539 use_old
= use_new
= "";
540 else if (git_config__get_bool_force(cfg
, "diff.mnemonicprefix", 0)) {
541 use_old
= diff_mnemonic_prefix(diff
->old_src
, true);
542 use_new
= diff_mnemonic_prefix(diff
->new_src
, false);
545 if (!diff
->opts
.old_prefix
)
546 diff
->opts
.old_prefix
= use_old
;
547 if (!diff
->opts
.new_prefix
)
548 diff
->opts
.new_prefix
= use_new
;
551 /* strdup prefix from pool so we're not dependent on external data */
552 diff
->opts
.old_prefix
= diff_strdup_prefix(pool
, diff
->opts
.old_prefix
);
553 diff
->opts
.new_prefix
= diff_strdup_prefix(pool
, diff
->opts
.new_prefix
);
555 if (DIFF_FLAG_IS_SET(diff
, GIT_DIFF_REVERSE
)) {
556 const char *tmp_prefix
= diff
->opts
.old_prefix
;
557 diff
->opts
.old_prefix
= diff
->opts
.new_prefix
;
558 diff
->opts
.new_prefix
= tmp_prefix
;
561 git_config_free(cfg
);
563 /* check strdup results for error */
564 return (!diff
->opts
.old_prefix
|| !diff
->opts
.new_prefix
) ? -1 : 0;
567 static void diff_list_free(git_diff
*diff
)
569 git_vector_free_deep(&diff
->deltas
);
571 git_pathspec__vfree(&diff
->pathspec
);
572 git_pool_clear(&diff
->pool
);
574 git__memzero(diff
, sizeof(*diff
));
578 void git_diff_free(git_diff
*diff
)
583 GIT_REFCOUNT_DEC(diff
, diff_list_free
);
586 void git_diff_addref(git_diff
*diff
)
588 GIT_REFCOUNT_INC(diff
);
591 int git_diff__oid_for_file(
598 git_index_entry entry
;
600 memset(&entry
, 0, sizeof(entry
));
602 entry
.file_size
= size
;
603 entry
.path
= (char *)path
;
605 return git_diff__oid_for_entry(out
, diff
, &entry
, mode
, NULL
);
608 int git_diff__oid_for_entry(
611 const git_index_entry
*src
,
613 const git_oid
*update_match
)
616 git_buf full_path
= GIT_BUF_INIT
;
617 git_index_entry entry
= *src
;
618 git_filter_list
*fl
= NULL
;
620 memset(out
, 0, sizeof(*out
));
622 if (git_buf_joinpath(
623 &full_path
, git_repository_workdir(diff
->repo
), entry
.path
) < 0)
629 diff
->perf
.stat_calls
++;
631 if (p_stat(full_path
.ptr
, &st
) < 0) {
632 error
= git_path_set_error(errno
, entry
.path
, "stat");
633 git_buf_free(&full_path
);
637 git_index_entry__init_from_stat(
638 &entry
, &st
, (diff
->diffcaps
& GIT_DIFFCAPS_TRUST_MODE_BITS
) != 0);
641 /* calculate OID for file if possible */
642 if (S_ISGITLINK(mode
)) {
645 if (!git_submodule_lookup(&sm
, diff
->repo
, entry
.path
)) {
646 const git_oid
*sm_oid
= git_submodule_wd_id(sm
);
648 git_oid_cpy(out
, sm_oid
);
649 git_submodule_free(sm
);
651 /* if submodule lookup failed probably just in an intermediate
652 * state where some init hasn't happened, so ignore the error
656 } else if (S_ISLNK(mode
)) {
657 error
= git_odb__hashlink(out
, full_path
.ptr
);
658 diff
->perf
.oid_calculations
++;
659 } else if (!git__is_sizet(entry
.file_size
)) {
660 giterr_set(GITERR_OS
, "File size overflow (for 32-bits) on '%s'",
663 } else if (!(error
= git_filter_list_load(
664 &fl
, diff
->repo
, NULL
, entry
.path
,
665 GIT_FILTER_TO_ODB
, GIT_FILTER_ALLOW_UNSAFE
)))
667 int fd
= git_futils_open_ro(full_path
.ptr
);
671 error
= git_odb__hashfd_filtered(
672 out
, fd
, (size_t)entry
.file_size
, GIT_OBJ_BLOB
, fl
);
674 diff
->perf
.oid_calculations
++;
677 git_filter_list_free(fl
);
680 /* update index for entry if requested */
681 if (!error
&& update_match
&& git_oid_equal(out
, update_match
)) {
683 git_index_entry updated_entry
;
685 memcpy(&updated_entry
, &entry
, sizeof(git_index_entry
));
686 updated_entry
.mode
= mode
;
687 git_oid_cpy(&updated_entry
.id
, out
);
689 if (!(error
= git_repository_index__weakptr(&idx
, diff
->repo
))) {
690 error
= git_index_add(idx
, &updated_entry
);
691 diff
->index_updated
= true;
695 git_buf_free(&full_path
);
699 static bool diff_time_eq(
700 const git_index_time
*a
, const git_index_time
*b
, bool use_nanos
)
702 return a
->seconds
== b
->seconds
&&
703 (!use_nanos
|| a
->nanoseconds
== b
->nanoseconds
);
707 git_repository
*repo
;
708 git_iterator
*old_iter
;
709 git_iterator
*new_iter
;
710 const git_index_entry
*oitem
;
711 const git_index_entry
*nitem
;
714 #define MODE_BITS_MASK 0000777
716 static int maybe_modified_submodule(
720 diff_in_progress
*info
)
724 unsigned int sm_status
= 0;
725 git_submodule_ignore_t ign
= diff
->opts
.ignore_submodules
;
727 *status
= GIT_DELTA_UNMODIFIED
;
729 if (DIFF_FLAG_IS_SET(diff
, GIT_DIFF_IGNORE_SUBMODULES
) ||
730 ign
== GIT_SUBMODULE_IGNORE_ALL
)
733 if ((error
= git_submodule_lookup(
734 &sub
, diff
->repo
, info
->nitem
->path
)) < 0) {
736 /* GIT_EEXISTS means dir with .git in it was found - ignore it */
737 if (error
== GIT_EEXISTS
) {
744 if (ign
<= 0 && git_submodule_ignore(sub
) == GIT_SUBMODULE_IGNORE_ALL
)
746 else if ((error
= git_submodule__status(
747 &sm_status
, NULL
, NULL
, found_oid
, sub
, ign
)) < 0)
748 /* return error below */;
750 /* check IS_WD_UNMODIFIED because this case is only used
751 * when the new side of the diff is the working directory
753 else if (!GIT_SUBMODULE_STATUS_IS_WD_UNMODIFIED(sm_status
))
754 *status
= GIT_DELTA_MODIFIED
;
756 /* now that we have a HEAD OID, check if HEAD moved */
757 else if ((sm_status
& GIT_SUBMODULE_STATUS_IN_WD
) != 0 &&
758 !git_oid_equal(&info
->oitem
->id
, found_oid
))
759 *status
= GIT_DELTA_MODIFIED
;
761 git_submodule_free(sub
);
765 static int maybe_modified(
767 diff_in_progress
*info
)
770 git_delta_t status
= GIT_DELTA_MODIFIED
;
771 const git_index_entry
*oitem
= info
->oitem
;
772 const git_index_entry
*nitem
= info
->nitem
;
773 unsigned int omode
= oitem
->mode
;
774 unsigned int nmode
= nitem
->mode
;
775 bool new_is_workdir
= (info
->new_iter
->type
== GIT_ITERATOR_TYPE_WORKDIR
);
776 bool modified_uncertain
= false;
777 const char *matched_pathspec
;
780 if (!diff_pathspec_match(&matched_pathspec
, diff
, oitem
))
783 memset(&noid
, 0, sizeof(noid
));
785 /* on platforms with no symlinks, preserve mode of existing symlinks */
786 if (S_ISLNK(omode
) && S_ISREG(nmode
) && new_is_workdir
&&
787 !(diff
->diffcaps
& GIT_DIFFCAPS_HAS_SYMLINKS
))
790 /* on platforms with no execmode, just preserve old mode */
791 if (!(diff
->diffcaps
& GIT_DIFFCAPS_TRUST_MODE_BITS
) &&
792 (nmode
& MODE_BITS_MASK
) != (omode
& MODE_BITS_MASK
) &&
794 nmode
= (nmode
& ~MODE_BITS_MASK
) | (omode
& MODE_BITS_MASK
);
796 /* if one side is a conflict, mark the whole delta as conflicted */
797 if (git_index_entry_is_conflict(oitem
) ||
798 git_index_entry_is_conflict(nitem
)) {
799 status
= GIT_DELTA_CONFLICTED
;
801 /* support "assume unchanged" (poorly, b/c we still stat everything) */
802 } else if ((oitem
->flags
& GIT_IDXENTRY_VALID
) != 0) {
803 status
= GIT_DELTA_UNMODIFIED
;
805 /* support "skip worktree" index bit */
806 } else if ((oitem
->flags_extended
& GIT_IDXENTRY_SKIP_WORKTREE
) != 0) {
807 status
= GIT_DELTA_UNMODIFIED
;
809 /* if basic type of file changed, then split into delete and add */
810 } else if (GIT_MODE_TYPE(omode
) != GIT_MODE_TYPE(nmode
)) {
811 if (DIFF_FLAG_IS_SET(diff
, GIT_DIFF_INCLUDE_TYPECHANGE
)) {
812 status
= GIT_DELTA_TYPECHANGE
;
815 else if (nmode
== GIT_FILEMODE_UNREADABLE
) {
816 if (!(error
= diff_delta__from_one(diff
, GIT_DELTA_DELETED
, oitem
, NULL
)))
817 error
= diff_delta__from_one(diff
, GIT_DELTA_UNREADABLE
, NULL
, nitem
);
822 if (!(error
= diff_delta__from_one(diff
, GIT_DELTA_DELETED
, oitem
, NULL
)))
823 error
= diff_delta__from_one(diff
, GIT_DELTA_ADDED
, NULL
, nitem
);
827 /* if oids and modes match (and are valid), then file is unmodified */
828 } else if (git_oid_equal(&oitem
->id
, &nitem
->id
) &&
830 !git_oid_iszero(&oitem
->id
)) {
831 status
= GIT_DELTA_UNMODIFIED
;
833 /* if we have an unknown OID and a workdir iterator, then check some
834 * circumstances that can accelerate things or need special handling
836 } else if (git_oid_iszero(&nitem
->id
) && new_is_workdir
) {
837 bool use_ctime
= ((diff
->diffcaps
& GIT_DIFFCAPS_TRUST_CTIME
) != 0);
838 bool use_nanos
= ((diff
->diffcaps
& GIT_DIFFCAPS_TRUST_NANOSECS
) != 0);
840 git_iterator_index(&index
, info
->new_iter
);
842 status
= GIT_DELTA_UNMODIFIED
;
844 if (S_ISGITLINK(nmode
)) {
845 if ((error
= maybe_modified_submodule(&status
, &noid
, diff
, info
)) < 0)
849 /* if the stat data looks different, then mark modified - this just
850 * means that the OID will be recalculated below to confirm change
852 else if (omode
!= nmode
|| oitem
->file_size
!= nitem
->file_size
) {
853 status
= GIT_DELTA_MODIFIED
;
855 (oitem
->file_size
<= 0 && nitem
->file_size
> 0);
857 else if (!diff_time_eq(&oitem
->mtime
, &nitem
->mtime
, use_nanos
) ||
859 !diff_time_eq(&oitem
->ctime
, &nitem
->ctime
, use_nanos
)) ||
860 oitem
->ino
!= nitem
->ino
||
861 oitem
->uid
!= nitem
->uid
||
862 oitem
->gid
!= nitem
->gid
||
863 (index
&& nitem
->mtime
.seconds
>= index
->stamp
.mtime
))
865 status
= GIT_DELTA_MODIFIED
;
866 modified_uncertain
= true;
869 /* if mode is GITLINK and submodules are ignored, then skip */
870 } else if (S_ISGITLINK(nmode
) &&
871 DIFF_FLAG_IS_SET(diff
, GIT_DIFF_IGNORE_SUBMODULES
)) {
872 status
= GIT_DELTA_UNMODIFIED
;
875 /* if we got here and decided that the files are modified, but we
876 * haven't calculated the OID of the new item, then calculate it now
878 if (modified_uncertain
&& git_oid_iszero(&nitem
->id
)) {
879 const git_oid
*update_check
=
880 DIFF_FLAG_IS_SET(diff
, GIT_DIFF_UPDATE_INDEX
) && omode
== nmode
?
883 if ((error
= git_diff__oid_for_entry(
884 &noid
, diff
, nitem
, nmode
, update_check
)) < 0)
887 /* if oid matches, then mark unmodified (except submodules, where
888 * the filesystem content may be modified even if the oid still
889 * matches between the index and the workdir HEAD)
891 if (omode
== nmode
&& !S_ISGITLINK(omode
) &&
892 git_oid_equal(&oitem
->id
, &noid
))
893 status
= GIT_DELTA_UNMODIFIED
;
896 /* If we want case changes, then break this into a delete of the old
897 * and an add of the new so that consumers can act accordingly (eg,
898 * checkout will update the case on disk.)
900 if (DIFF_FLAG_IS_SET(diff
, GIT_DIFF_IGNORE_CASE
) &&
901 DIFF_FLAG_IS_SET(diff
, GIT_DIFF_INCLUDE_CASECHANGE
) &&
902 strcmp(oitem
->path
, nitem
->path
) != 0) {
904 if (!(error
= diff_delta__from_one(diff
, GIT_DELTA_DELETED
, oitem
, NULL
)))
905 error
= diff_delta__from_one(diff
, GIT_DELTA_ADDED
, NULL
, nitem
);
910 return diff_delta__from_two(
911 diff
, status
, oitem
, omode
, nitem
, nmode
,
912 git_oid_iszero(&noid
) ? NULL
: &noid
, matched_pathspec
);
915 static bool entry_is_prefixed(
917 const git_index_entry
*item
,
918 const git_index_entry
*prefix_item
)
922 if (!item
|| diff
->pfxcomp(item
->path
, prefix_item
->path
) != 0)
925 pathlen
= strlen(prefix_item
->path
);
927 return (prefix_item
->path
[pathlen
- 1] == '/' ||
928 item
->path
[pathlen
] == '\0' ||
929 item
->path
[pathlen
] == '/');
932 static int iterator_current(
933 const git_index_entry
**entry
,
934 git_iterator
*iterator
)
938 if ((error
= git_iterator_current(entry
, iterator
)) == GIT_ITEROVER
) {
946 static int iterator_advance(
947 const git_index_entry
**entry
,
948 git_iterator
*iterator
)
950 const git_index_entry
*prev_entry
= *entry
;
953 /* if we're looking for conflicts, we only want to report
954 * one conflict for each file, instead of all three sides.
955 * so if this entry is a conflict for this file, and the
956 * previous one was a conflict for the same file, skip it.
958 while ((error
= git_iterator_advance(entry
, iterator
)) == 0) {
959 if (!(iterator
->flags
& GIT_ITERATOR_INCLUDE_CONFLICTS
) ||
960 !git_index_entry_is_conflict(prev_entry
) ||
961 !git_index_entry_is_conflict(*entry
))
964 cmp
= (iterator
->flags
& GIT_ITERATOR_IGNORE_CASE
) ?
965 strcasecmp(prev_entry
->path
, (*entry
)->path
) :
966 strcmp(prev_entry
->path
, (*entry
)->path
);
972 if (error
== GIT_ITEROVER
) {
980 static int iterator_advance_into(
981 const git_index_entry
**entry
,
982 git_iterator
*iterator
)
986 if ((error
= git_iterator_advance_into(entry
, iterator
)) == GIT_ITEROVER
) {
994 static int iterator_advance_over_with_status(
995 const git_index_entry
**entry
,
996 git_iterator_status_t
*status
,
997 git_iterator
*iterator
)
1001 if ((error
= git_iterator_advance_over_with_status(
1002 entry
, status
, iterator
)) == GIT_ITEROVER
) {
1010 static int handle_unmatched_new_item(
1011 git_diff
*diff
, diff_in_progress
*info
)
1014 const git_index_entry
*nitem
= info
->nitem
;
1015 git_delta_t delta_type
= GIT_DELTA_UNTRACKED
;
1016 bool contains_oitem
;
1018 /* check if this is a prefix of the other side */
1019 contains_oitem
= entry_is_prefixed(diff
, info
->oitem
, nitem
);
1021 /* update delta_type if this item is conflicted */
1022 if (git_index_entry_is_conflict(nitem
))
1023 delta_type
= GIT_DELTA_CONFLICTED
;
1025 /* update delta_type if this item is ignored */
1026 else if (git_iterator_current_is_ignored(info
->new_iter
))
1027 delta_type
= GIT_DELTA_IGNORED
;
1029 if (nitem
->mode
== GIT_FILEMODE_TREE
) {
1030 bool recurse_into_dir
= contains_oitem
;
1032 /* check if user requests recursion into this type of dir */
1033 recurse_into_dir
= contains_oitem
||
1034 (delta_type
== GIT_DELTA_UNTRACKED
&&
1035 DIFF_FLAG_IS_SET(diff
, GIT_DIFF_RECURSE_UNTRACKED_DIRS
)) ||
1036 (delta_type
== GIT_DELTA_IGNORED
&&
1037 DIFF_FLAG_IS_SET(diff
, GIT_DIFF_RECURSE_IGNORED_DIRS
));
1039 /* do not advance into directories that contain a .git file */
1040 if (recurse_into_dir
&& !contains_oitem
) {
1041 git_buf
*full
= NULL
;
1042 if (git_iterator_current_workdir_path(&full
, info
->new_iter
) < 0)
1044 if (full
&& git_path_contains(full
, DOT_GIT
)) {
1045 /* TODO: warning if not a valid git repository */
1046 recurse_into_dir
= false;
1050 /* still have to look into untracked directories to match core git -
1051 * with no untracked files, directory is treated as ignored
1053 if (!recurse_into_dir
&&
1054 delta_type
== GIT_DELTA_UNTRACKED
&&
1055 DIFF_FLAG_ISNT_SET(diff
, GIT_DIFF_ENABLE_FAST_UNTRACKED_DIRS
))
1057 git_diff_delta
*last
;
1058 git_iterator_status_t untracked_state
;
1060 /* attempt to insert record for this directory */
1061 if ((error
= diff_delta__from_one(diff
, delta_type
, NULL
, nitem
)) != 0)
1064 /* if delta wasn't created (because of rules), just skip ahead */
1065 last
= diff_delta__last_for_item(diff
, nitem
);
1067 return iterator_advance(&info
->nitem
, info
->new_iter
);
1069 /* iterate into dir looking for an actual untracked file */
1070 if ((error
= iterator_advance_over_with_status(
1071 &info
->nitem
, &untracked_state
, info
->new_iter
)) < 0)
1074 /* if we found nothing that matched our pathlist filter, exclude */
1075 if (untracked_state
== GIT_ITERATOR_STATUS_FILTERED
) {
1076 git_vector_pop(&diff
->deltas
);
1080 /* if we found nothing or just ignored items, update the record */
1081 if (untracked_state
== GIT_ITERATOR_STATUS_IGNORED
||
1082 untracked_state
== GIT_ITERATOR_STATUS_EMPTY
) {
1083 last
->status
= GIT_DELTA_IGNORED
;
1085 /* remove the record if we don't want ignored records */
1086 if (DIFF_FLAG_ISNT_SET(diff
, GIT_DIFF_INCLUDE_IGNORED
)) {
1087 git_vector_pop(&diff
->deltas
);
1095 /* try to advance into directory if necessary */
1096 if (recurse_into_dir
) {
1097 error
= iterator_advance_into(&info
->nitem
, info
->new_iter
);
1099 /* if real error or no error, proceed with iteration */
1100 if (error
!= GIT_ENOTFOUND
)
1104 /* if directory is empty, can't advance into it, so either skip
1108 return iterator_advance(&info
->nitem
, info
->new_iter
);
1109 delta_type
= GIT_DELTA_IGNORED
;
1113 else if (delta_type
== GIT_DELTA_IGNORED
&&
1114 DIFF_FLAG_ISNT_SET(diff
, GIT_DIFF_RECURSE_IGNORED_DIRS
) &&
1115 git_iterator_current_tree_is_ignored(info
->new_iter
))
1116 /* item contained in ignored directory, so skip over it */
1117 return iterator_advance(&info
->nitem
, info
->new_iter
);
1119 else if (info
->new_iter
->type
!= GIT_ITERATOR_TYPE_WORKDIR
) {
1120 if (delta_type
!= GIT_DELTA_CONFLICTED
)
1121 delta_type
= GIT_DELTA_ADDED
;
1124 else if (nitem
->mode
== GIT_FILEMODE_COMMIT
) {
1125 /* ignore things that are not actual submodules */
1126 if (git_submodule_lookup(NULL
, info
->repo
, nitem
->path
) != 0) {
1128 delta_type
= GIT_DELTA_IGNORED
;
1130 /* if this contains a tracked item, treat as normal TREE */
1131 if (contains_oitem
) {
1132 error
= iterator_advance_into(&info
->nitem
, info
->new_iter
);
1133 if (error
!= GIT_ENOTFOUND
)
1137 return iterator_advance(&info
->nitem
, info
->new_iter
);
1142 else if (nitem
->mode
== GIT_FILEMODE_UNREADABLE
) {
1143 if (DIFF_FLAG_IS_SET(diff
, GIT_DIFF_INCLUDE_UNREADABLE_AS_UNTRACKED
))
1144 delta_type
= GIT_DELTA_UNTRACKED
;
1146 delta_type
= GIT_DELTA_UNREADABLE
;
1149 /* Actually create the record for this item if necessary */
1150 if ((error
= diff_delta__from_one(diff
, delta_type
, NULL
, nitem
)) != 0)
1153 /* If user requested TYPECHANGE records, then check for that instead of
1154 * just generating an ADDED/UNTRACKED record
1156 if (delta_type
!= GIT_DELTA_IGNORED
&&
1157 DIFF_FLAG_IS_SET(diff
, GIT_DIFF_INCLUDE_TYPECHANGE_TREES
) &&
1160 /* this entry was prefixed with a tree - make TYPECHANGE */
1161 git_diff_delta
*last
= diff_delta__last_for_item(diff
, nitem
);
1163 last
->status
= GIT_DELTA_TYPECHANGE
;
1164 last
->old_file
.mode
= GIT_FILEMODE_TREE
;
1168 return iterator_advance(&info
->nitem
, info
->new_iter
);
1171 static int handle_unmatched_old_item(
1172 git_diff
*diff
, diff_in_progress
*info
)
1174 git_delta_t delta_type
= GIT_DELTA_DELETED
;
1177 /* update delta_type if this item is conflicted */
1178 if (git_index_entry_is_conflict(info
->oitem
))
1179 delta_type
= GIT_DELTA_CONFLICTED
;
1181 if ((error
= diff_delta__from_one(diff
, delta_type
, info
->oitem
, NULL
)) < 0)
1184 /* if we are generating TYPECHANGE records then check for that
1185 * instead of just generating a DELETE record
1187 if (DIFF_FLAG_IS_SET(diff
, GIT_DIFF_INCLUDE_TYPECHANGE_TREES
) &&
1188 entry_is_prefixed(diff
, info
->nitem
, info
->oitem
))
1190 /* this entry has become a tree! convert to TYPECHANGE */
1191 git_diff_delta
*last
= diff_delta__last_for_item(diff
, info
->oitem
);
1193 last
->status
= GIT_DELTA_TYPECHANGE
;
1194 last
->new_file
.mode
= GIT_FILEMODE_TREE
;
1197 /* If new_iter is a workdir iterator, then this situation
1198 * will certainly be followed by a series of untracked items.
1199 * Unless RECURSE_UNTRACKED_DIRS is set, skip over them...
1201 if (S_ISDIR(info
->nitem
->mode
) &&
1202 DIFF_FLAG_ISNT_SET(diff
, GIT_DIFF_RECURSE_UNTRACKED_DIRS
))
1203 return iterator_advance(&info
->nitem
, info
->new_iter
);
1206 return iterator_advance(&info
->oitem
, info
->old_iter
);
1209 static int handle_matched_item(
1210 git_diff
*diff
, diff_in_progress
*info
)
1214 if ((error
= maybe_modified(diff
, info
)) < 0)
1217 if (!(error
= iterator_advance(&info
->oitem
, info
->old_iter
)))
1218 error
= iterator_advance(&info
->nitem
, info
->new_iter
);
1223 int git_diff__from_iterators(
1224 git_diff
**diff_ptr
,
1225 git_repository
*repo
,
1226 git_iterator
*old_iter
,
1227 git_iterator
*new_iter
,
1228 const git_diff_options
*opts
)
1231 diff_in_progress info
;
1236 diff
= diff_list_alloc(repo
, old_iter
, new_iter
);
1237 GITERR_CHECK_ALLOC(diff
);
1240 info
.old_iter
= old_iter
;
1241 info
.new_iter
= new_iter
;
1243 /* make iterators have matching icase behavior */
1244 if (DIFF_FLAG_IS_SET(diff
, GIT_DIFF_IGNORE_CASE
)) {
1245 if ((error
= git_iterator_set_ignore_case(old_iter
, true)) < 0 ||
1246 (error
= git_iterator_set_ignore_case(new_iter
, true)) < 0)
1250 /* finish initialization */
1251 if ((error
= diff_list_apply_options(diff
, opts
)) < 0)
1254 if ((error
= iterator_current(&info
.oitem
, old_iter
)) < 0 ||
1255 (error
= iterator_current(&info
.nitem
, new_iter
)) < 0)
1258 /* run iterators building diffs */
1259 while (!error
&& (info
.oitem
|| info
.nitem
)) {
1260 int cmp
= info
.oitem
?
1261 (info
.nitem
? diff
->entrycomp(info
.oitem
, info
.nitem
) : -1) : 1;
1263 /* create DELETED records for old items not matched in new */
1265 error
= handle_unmatched_old_item(diff
, &info
);
1267 /* create ADDED, TRACKED, or IGNORED records for new items not
1268 * matched in old (and/or descend into directories as needed)
1271 error
= handle_unmatched_new_item(diff
, &info
);
1273 /* otherwise item paths match, so create MODIFIED record
1274 * (or ADDED and DELETED pair if type changed)
1277 error
= handle_matched_item(diff
, &info
);
1280 diff
->perf
.stat_calls
+= old_iter
->stat_calls
+ new_iter
->stat_calls
;
1286 git_diff_free(diff
);
1291 #define DIFF_FROM_ITERATORS(MAKE_FIRST, FLAGS_FIRST, MAKE_SECOND, FLAGS_SECOND) do { \
1292 git_iterator *a = NULL, *b = NULL; \
1293 char *pfx = (opts && !(opts->flags & GIT_DIFF_DISABLE_PATHSPEC_MATCH)) ? \
1294 git_pathspec_prefix(&opts->pathspec) : NULL; \
1295 git_iterator_options a_opts = GIT_ITERATOR_OPTIONS_INIT, \
1296 b_opts = GIT_ITERATOR_OPTIONS_INIT; \
1297 a_opts.flags = FLAGS_FIRST; \
1298 a_opts.start = pfx; \
1300 b_opts.flags = FLAGS_SECOND; \
1301 b_opts.start = pfx; \
1303 GITERR_CHECK_VERSION(opts, GIT_DIFF_OPTIONS_VERSION, "git_diff_options"); \
1304 if (opts && (opts->flags & GIT_DIFF_DISABLE_PATHSPEC_MATCH)) { \
1305 a_opts.pathlist.strings = opts->pathspec.strings; \
1306 a_opts.pathlist.count = opts->pathspec.count; \
1307 b_opts.pathlist.strings = opts->pathspec.strings; \
1308 b_opts.pathlist.count = opts->pathspec.count; \
1310 if (!error && !(error = MAKE_FIRST) && !(error = MAKE_SECOND)) \
1311 error = git_diff__from_iterators(diff, repo, a, b, opts); \
1312 git__free(pfx); git_iterator_free(a); git_iterator_free(b); \
1315 int git_diff_tree_to_tree(
1317 git_repository
*repo
,
1320 const git_diff_options
*opts
)
1322 git_iterator_flag_t iflag
= GIT_ITERATOR_DONT_IGNORE_CASE
;
1325 assert(diff
&& repo
);
1327 /* for tree to tree diff, be case sensitive even if the index is
1328 * currently case insensitive, unless the user explicitly asked
1329 * for case insensitivity
1331 if (opts
&& (opts
->flags
& GIT_DIFF_IGNORE_CASE
) != 0)
1332 iflag
= GIT_ITERATOR_IGNORE_CASE
;
1334 DIFF_FROM_ITERATORS(
1335 git_iterator_for_tree(&a
, old_tree
, &a_opts
), iflag
,
1336 git_iterator_for_tree(&b
, new_tree
, &b_opts
), iflag
1342 static int diff_load_index(git_index
**index
, git_repository
*repo
)
1344 int error
= git_repository_index__weakptr(index
, repo
);
1346 /* reload the repository index when user did not pass one in */
1347 if (!error
&& git_index_read(*index
, false) < 0)
1353 int git_diff_tree_to_index(
1355 git_repository
*repo
,
1358 const git_diff_options
*opts
)
1360 git_iterator_flag_t iflag
= GIT_ITERATOR_DONT_IGNORE_CASE
|
1361 GIT_ITERATOR_INCLUDE_CONFLICTS
;
1362 bool index_ignore_case
= false;
1365 assert(diff
&& repo
);
1367 if (!index
&& (error
= diff_load_index(&index
, repo
)) < 0)
1370 index_ignore_case
= index
->ignore_case
;
1372 DIFF_FROM_ITERATORS(
1373 git_iterator_for_tree(&a
, old_tree
, &a_opts
), iflag
,
1374 git_iterator_for_index(&b
, index
, &b_opts
), iflag
1377 /* if index is in case-insensitive order, re-sort deltas to match */
1378 if (!error
&& index_ignore_case
)
1379 diff_set_ignore_case(*diff
, true);
1384 int git_diff_index_to_workdir(
1386 git_repository
*repo
,
1388 const git_diff_options
*opts
)
1392 assert(diff
&& repo
);
1394 if (!index
&& (error
= diff_load_index(&index
, repo
)) < 0)
1397 DIFF_FROM_ITERATORS(
1398 git_iterator_for_index(&a
, index
, &a_opts
),
1399 GIT_ITERATOR_INCLUDE_CONFLICTS
,
1401 git_iterator_for_workdir(&b
, repo
, index
, NULL
, &b_opts
),
1402 GIT_ITERATOR_DONT_AUTOEXPAND
1405 if (!error
&& DIFF_FLAG_IS_SET(*diff
, GIT_DIFF_UPDATE_INDEX
) && (*diff
)->index_updated
)
1406 error
= git_index_write(index
);
1411 int git_diff_tree_to_workdir(
1413 git_repository
*repo
,
1415 const git_diff_options
*opts
)
1420 assert(diff
&& repo
);
1422 if ((error
= git_repository_index__weakptr(&index
, repo
)))
1425 DIFF_FROM_ITERATORS(
1426 git_iterator_for_tree(&a
, old_tree
, &a_opts
), 0,
1427 git_iterator_for_workdir(&b
, repo
, index
, old_tree
, &b_opts
), GIT_ITERATOR_DONT_AUTOEXPAND
1433 int git_diff_tree_to_workdir_with_index(
1435 git_repository
*repo
,
1437 const git_diff_options
*opts
)
1440 git_diff
*d1
= NULL
, *d2
= NULL
;
1441 git_index
*index
= NULL
;
1443 assert(diff
&& repo
);
1445 if ((error
= diff_load_index(&index
, repo
)) < 0)
1448 if (!(error
= git_diff_tree_to_index(&d1
, repo
, old_tree
, index
, opts
)) &&
1449 !(error
= git_diff_index_to_workdir(&d2
, repo
, index
, opts
)))
1450 error
= git_diff_merge(d1
, d2
);
1463 int git_diff_index_to_index(
1465 git_repository
*repo
,
1466 git_index
*old_index
,
1467 git_index
*new_index
,
1468 const git_diff_options
*opts
)
1472 assert(diff
&& old_index
&& new_index
);
1474 DIFF_FROM_ITERATORS(
1475 git_iterator_for_index(&a
, old_index
, &a_opts
), GIT_ITERATOR_DONT_IGNORE_CASE
,
1476 git_iterator_for_index(&b
, new_index
, &b_opts
), GIT_ITERATOR_DONT_IGNORE_CASE
1479 /* if index is in case-insensitive order, re-sort deltas to match */
1480 if (!error
&& (old_index
->ignore_case
|| new_index
->ignore_case
))
1481 diff_set_ignore_case(*diff
, true);
1486 size_t git_diff_num_deltas(const git_diff
*diff
)
1489 return diff
->deltas
.length
;
1492 size_t git_diff_num_deltas_of_type(const git_diff
*diff
, git_delta_t type
)
1494 size_t i
, count
= 0;
1495 const git_diff_delta
*delta
;
1499 git_vector_foreach(&diff
->deltas
, i
, delta
) {
1500 count
+= (delta
->status
== type
);
1506 const git_diff_delta
*git_diff_get_delta(const git_diff
*diff
, size_t idx
)
1509 return git_vector_get(&diff
->deltas
, idx
);
1512 int git_diff_is_sorted_icase(const git_diff
*diff
)
1514 return (diff
->opts
.flags
& GIT_DIFF_IGNORE_CASE
) != 0;
1517 int git_diff_get_perfdata(git_diff_perfdata
*out
, const git_diff
*diff
)
1520 GITERR_CHECK_VERSION(out
, GIT_DIFF_PERFDATA_VERSION
, "git_diff_perfdata");
1521 out
->stat_calls
= diff
->perf
.stat_calls
;
1522 out
->oid_calculations
= diff
->perf
.oid_calculations
;
1526 int git_diff__paired_foreach(
1529 int (*cb
)(git_diff_delta
*h2i
, git_diff_delta
*i2w
, void *payload
),
1533 git_diff_delta
*h2i
, *i2w
;
1534 size_t i
, j
, i_max
, j_max
;
1535 int (*strcomp
)(const char *, const char *) = git__strcmp
;
1536 bool h2i_icase
, i2w_icase
, icase_mismatch
;
1538 i_max
= head2idx
? head2idx
->deltas
.length
: 0;
1539 j_max
= idx2wd
? idx2wd
->deltas
.length
: 0;
1540 if (!i_max
&& !j_max
)
1543 /* At some point, tree-to-index diffs will probably never ignore case,
1544 * even if that isn't true now. Index-to-workdir diffs may or may not
1545 * ignore case, but the index filename for the idx2wd diff should
1546 * still be using the canonical case-preserving name.
1548 * Therefore the main thing we need to do here is make sure the diffs
1549 * are traversed in a compatible order. To do this, we temporarily
1550 * resort a mismatched diff to get the order correct.
1552 * In order to traverse renames in the index->workdir, we need to
1553 * ensure that we compare the index name on both sides, so we
1554 * always sort by the old name in the i2w list.
1556 h2i_icase
= head2idx
!= NULL
&&
1557 (head2idx
->opts
.flags
& GIT_DIFF_IGNORE_CASE
) != 0;
1559 i2w_icase
= idx2wd
!= NULL
&&
1560 (idx2wd
->opts
.flags
& GIT_DIFF_IGNORE_CASE
) != 0;
1563 (head2idx
!= NULL
&& idx2wd
!= NULL
&& h2i_icase
!= i2w_icase
);
1565 if (icase_mismatch
&& h2i_icase
) {
1566 git_vector_set_cmp(&head2idx
->deltas
, git_diff_delta__cmp
);
1567 git_vector_sort(&head2idx
->deltas
);
1570 if (i2w_icase
&& !icase_mismatch
) {
1571 strcomp
= git__strcasecmp
;
1573 git_vector_set_cmp(&idx2wd
->deltas
, git_diff_delta__i2w_casecmp
);
1574 git_vector_sort(&idx2wd
->deltas
);
1575 } else if (idx2wd
!= NULL
) {
1576 git_vector_set_cmp(&idx2wd
->deltas
, git_diff_delta__i2w_cmp
);
1577 git_vector_sort(&idx2wd
->deltas
);
1580 for (i
= 0, j
= 0; i
< i_max
|| j
< j_max
; ) {
1581 h2i
= head2idx
? GIT_VECTOR_GET(&head2idx
->deltas
, i
) : NULL
;
1582 i2w
= idx2wd
? GIT_VECTOR_GET(&idx2wd
->deltas
, j
) : NULL
;
1584 cmp
= !i2w
? -1 : !h2i
? 1 :
1585 strcomp(h2i
->new_file
.path
, i2w
->old_file
.path
);
1589 } else if (cmp
> 0) {
1595 if ((error
= cb(h2i
, i2w
, payload
)) != 0) {
1596 giterr_set_after_callback(error
);
1601 /* restore case-insensitive delta sort */
1602 if (icase_mismatch
&& h2i_icase
) {
1603 git_vector_set_cmp(&head2idx
->deltas
, git_diff_delta__casecmp
);
1604 git_vector_sort(&head2idx
->deltas
);
1607 /* restore idx2wd sort by new path */
1608 if (idx2wd
!= NULL
) {
1609 git_vector_set_cmp(&idx2wd
->deltas
,
1610 i2w_icase
? git_diff_delta__casecmp
: git_diff_delta__cmp
);
1611 git_vector_sort(&idx2wd
->deltas
);
1617 int git_diff__commit(
1619 git_repository
*repo
,
1620 const git_commit
*commit
,
1621 const git_diff_options
*opts
)
1623 git_commit
*parent
= NULL
;
1624 git_diff
*commit_diff
= NULL
;
1625 git_tree
*old_tree
= NULL
, *new_tree
= NULL
;
1629 if ((parents
= git_commit_parentcount(commit
)) > 1) {
1630 char commit_oidstr
[GIT_OID_HEXSZ
+ 1];
1633 giterr_set(GITERR_INVALID
, "Commit %s is a merge commit",
1634 git_oid_tostr(commit_oidstr
, GIT_OID_HEXSZ
+ 1, git_commit_id(commit
)));
1639 if ((error
= git_commit_parent(&parent
, commit
, 0)) < 0 ||
1640 (error
= git_commit_tree(&old_tree
, parent
)) < 0)
1643 if ((error
= git_commit_tree(&new_tree
, commit
)) < 0 ||
1644 (error
= git_diff_tree_to_tree(&commit_diff
, repo
, old_tree
, new_tree
, opts
)) < 0)
1647 *diff
= commit_diff
;
1650 git_tree_free(new_tree
);
1651 git_tree_free(old_tree
);
1652 git_commit_free(parent
);
1657 int git_diff_format_email__append_header_tobuf(
1660 const git_signature
*author
,
1661 const char *summary
,
1663 size_t total_patches
,
1664 bool exclude_patchno_marker
)
1666 char idstr
[GIT_OID_HEXSZ
+ 1];
1667 char date_str
[GIT_DATE_RFC2822_SZ
];
1670 git_oid_fmt(idstr
, id
);
1671 idstr
[GIT_OID_HEXSZ
] = '\0';
1673 if ((error
= git__date_rfc2822_fmt(date_str
, sizeof(date_str
), &author
->when
)) < 0)
1676 error
= git_buf_printf(out
,
1677 "From %s Mon Sep 17 00:00:00 2001\n" \
1682 author
->name
, author
->email
,
1688 if (!exclude_patchno_marker
) {
1689 if (total_patches
== 1) {
1690 error
= git_buf_puts(out
, "[PATCH] ");
1692 error
= git_buf_printf(out
, "[PATCH %"PRIuZ
"/%"PRIuZ
"] ", patch_no
, total_patches
);
1699 error
= git_buf_printf(out
, "%s\n\n", summary
);
1704 int git_diff_format_email__append_patches_tobuf(
1711 deltas
= git_diff_num_deltas(diff
);
1713 for (i
= 0; i
< deltas
; ++i
) {
1714 git_patch
*patch
= NULL
;
1716 if ((error
= git_patch_from_diff(&patch
, diff
, i
)) >= 0)
1717 error
= git_patch_to_buf(out
, patch
);
1719 git_patch_free(patch
);
1728 int git_diff_format_email(
1731 const git_diff_format_email_options
*opts
)
1733 git_diff_stats
*stats
= NULL
;
1734 char *summary
= NULL
, *loc
= NULL
;
1736 unsigned int format_flags
= 0;
1740 assert(out
&& diff
&& opts
);
1741 assert(opts
->summary
&& opts
->id
&& opts
->author
);
1743 GITERR_CHECK_VERSION(opts
, GIT_DIFF_FORMAT_EMAIL_OPTIONS_VERSION
, "git_format_email_options");
1745 if ((ignore_marker
= opts
->flags
& GIT_DIFF_FORMAT_EMAIL_EXCLUDE_SUBJECT_PATCH_MARKER
) == false) {
1746 if (opts
->patch_no
> opts
->total_patches
) {
1747 giterr_set(GITERR_INVALID
, "patch %"PRIuZ
" out of range. max %"PRIuZ
, opts
->patch_no
, opts
->total_patches
);
1751 if (opts
->patch_no
== 0) {
1752 giterr_set(GITERR_INVALID
, "invalid patch no %"PRIuZ
". should be >0", opts
->patch_no
);
1757 /* the summary we receive may not be clean.
1758 * it could potentially contain new line characters
1759 * or not be set, sanitize, */
1760 if ((loc
= strpbrk(opts
->summary
, "\r\n")) != NULL
) {
1763 if ((offset
= (loc
- opts
->summary
)) == 0) {
1764 giterr_set(GITERR_INVALID
, "summary is empty");
1769 GITERR_CHECK_ALLOC_ADD(&allocsize
, offset
, 1);
1770 summary
= git__calloc(allocsize
, sizeof(char));
1771 GITERR_CHECK_ALLOC(summary
);
1773 strncpy(summary
, opts
->summary
, offset
);
1776 error
= git_diff_format_email__append_header_tobuf(out
,
1777 opts
->id
, opts
->author
, summary
== NULL
? opts
->summary
: summary
,
1778 opts
->patch_no
, opts
->total_patches
, ignore_marker
);
1783 format_flags
= GIT_DIFF_STATS_FULL
| GIT_DIFF_STATS_INCLUDE_SUMMARY
;
1785 if ((error
= git_buf_puts(out
, "---\n")) < 0 ||
1786 (error
= git_diff_get_stats(&stats
, diff
)) < 0 ||
1787 (error
= git_diff_stats_to_buf(out
, stats
, format_flags
, 0)) < 0 ||
1788 (error
= git_buf_putc(out
, '\n')) < 0 ||
1789 (error
= git_diff_format_email__append_patches_tobuf(out
, diff
)) < 0)
1792 error
= git_buf_puts(out
, "--\nlibgit2 " LIBGIT2_VERSION
"\n\n");
1796 git_diff_stats_free(stats
);
1801 int git_diff_commit_as_email(
1803 git_repository
*repo
,
1806 size_t total_patches
,
1807 git_diff_format_email_flags_t flags
,
1808 const git_diff_options
*diff_opts
)
1810 git_diff
*diff
= NULL
;
1811 git_diff_format_email_options opts
= GIT_DIFF_FORMAT_EMAIL_OPTIONS_INIT
;
1814 assert (out
&& repo
&& commit
);
1817 opts
.patch_no
= patch_no
;
1818 opts
.total_patches
= total_patches
;
1819 opts
.id
= git_commit_id(commit
);
1820 opts
.summary
= git_commit_summary(commit
);
1821 opts
.author
= git_commit_author(commit
);
1823 if ((error
= git_diff__commit(&diff
, repo
, commit
, diff_opts
)) < 0)
1826 error
= git_diff_format_email(out
, diff
, &opts
);
1828 git_diff_free(diff
);
1832 int git_diff_init_options(git_diff_options
*opts
, unsigned int version
)
1834 GIT_INIT_STRUCTURE_FROM_TEMPLATE(
1835 opts
, version
, git_diff_options
, GIT_DIFF_OPTIONS_INIT
);
1839 int git_diff_find_init_options(
1840 git_diff_find_options
*opts
, unsigned int version
)
1842 GIT_INIT_STRUCTURE_FROM_TEMPLATE(
1843 opts
, version
, git_diff_find_options
, GIT_DIFF_FIND_OPTIONS_INIT
);
1847 int git_diff_format_email_init_options(
1848 git_diff_format_email_options
*opts
, unsigned int version
)
1850 GIT_INIT_STRUCTURE_FROM_TEMPLATE(
1851 opts
, version
, git_diff_format_email_options
,
1852 GIT_DIFF_FORMAT_EMAIL_OPTIONS_INIT
);