From aa3af01db0ab89d6ae56e9656dc28590142fbcc3 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Wed, 13 May 2015 15:52:21 -0400 Subject: [PATCH 01/17] index iterator: optionally include conflicts --- src/iterator.c | 18 +++++++++++------- src/iterator.h | 2 ++ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/iterator.c b/src/iterator.c index c5c5fd7ce..1e946fadc 100644 --- a/src/iterator.c +++ b/src/iterator.c @@ -46,6 +46,7 @@ #define iterator__include_trees(I) iterator__flag(I,INCLUDE_TREES) #define iterator__dont_autoexpand(I) iterator__flag(I,DONT_AUTOEXPAND) #define iterator__do_autoexpand(I) !iterator__flag(I,DONT_AUTOEXPAND) +#define iterator__include_conflicts(I) iterator__flag(I, INCLUDE_CONFLICTS) #define GIT_ITERATOR_FIRST_ACCESS (1 << 15) #define iterator__has_been_accessed(I) iterator__flag(I,FIRST_ACCESS) @@ -668,13 +669,16 @@ static const git_index_entry *index_iterator__index_entry(index_iterator *ii) return ie; } -static const git_index_entry *index_iterator__skip_conflicts(index_iterator *ii) +static const git_index_entry *index_iterator__advance_over_conflicts(index_iterator *ii) { - const git_index_entry *ie; + const git_index_entry *ie = index_iterator__index_entry(ii); - while ((ie = index_iterator__index_entry(ii)) != NULL && - git_index_entry_stage(ie) != 0) - ii->current++; + if (!iterator__include_conflicts(ii)) { + while (ie && git_index_entry_stage(ie) != 0) { + ii->current++; + ie = index_iterator__index_entry(ii); + } + } return ie; } @@ -702,7 +706,7 @@ static void index_iterator__next_prefix_tree(index_iterator *ii) static int index_iterator__first_prefix_tree(index_iterator *ii) { - const git_index_entry *ie = index_iterator__skip_conflicts(ii); + const git_index_entry *ie = index_iterator__advance_over_conflicts(ii); const char *scan, *prior, *slash; if (!ie || !iterator__include_trees(ii)) @@ -825,7 +829,7 @@ static int index_iterator__reset( git_index_snapshot_find( &ii->current, &ii->entries, ii->entry_srch, ii->base.start, 0, 0); - if ((ie = index_iterator__skip_conflicts(ii)) == NULL) + if ((ie = index_iterator__advance_over_conflicts(ii)) == NULL) return 0; if (git_buf_sets(&ii->partial, ie->path) < 0) diff --git a/src/iterator.h b/src/iterator.h index 1520bffc2..db1f325a7 100644 --- a/src/iterator.h +++ b/src/iterator.h @@ -33,6 +33,8 @@ typedef enum { GIT_ITERATOR_DONT_AUTOEXPAND = (1u << 3), /** convert precomposed unicode to decomposed unicode */ GIT_ITERATOR_PRECOMPOSE_UNICODE = (1u << 4), + /** include conflicts */ + GIT_ITERATOR_INCLUDE_CONFLICTS = (1u << 5), } git_iterator_flag_t; typedef struct { From 1b6c26db976596e74ddaf5da8d0f43a63ccee063 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Wed, 13 May 2015 17:47:26 -0400 Subject: [PATCH 02/17] diff: wrap the iterator functions Wrap the iterator current / advance functions so that we can extend them, but also handle GIT_ITEROVER cases in the iterator funcs instead of the callers. --- src/diff.c | 98 ++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 73 insertions(+), 25 deletions(-) diff --git a/src/diff.c b/src/diff.c index f7e1c8ee4..f829fb07c 100644 --- a/src/diff.c +++ b/src/diff.c @@ -857,6 +857,64 @@ static bool entry_is_prefixed( item->path[pathlen] == '/'); } +static int iterator_current( + const git_index_entry **entry, + git_iterator *iterator) +{ + int error; + + if ((error = git_iterator_current(entry, iterator)) == GIT_ITEROVER) { + *entry = NULL; + error = 0; + } + + return error; +} + +static int iterator_advance( + const git_index_entry **entry, + git_iterator *iterator) +{ + int error; + + if ((error = git_iterator_advance(entry, iterator)) == GIT_ITEROVER) { + *entry = NULL; + error = 0; + } + + return error; +} + +static int iterator_advance_into( + const git_index_entry **entry, + git_iterator *iterator) +{ + int error; + + if ((error = git_iterator_advance_into(entry, iterator)) == GIT_ITEROVER) { + *entry = NULL; + error = 0; + } + + return error; +} + +static int iterator_advance_over_with_status( + const git_index_entry **entry, + git_iterator_status_t *status, + git_iterator *iterator) +{ + int error; + + if ((error = git_iterator_advance_over_with_status( + entry, status, iterator)) == GIT_ITEROVER) { + *entry = NULL; + error = 0; + } + + return error; +} + static int handle_unmatched_new_item( git_diff *diff, diff_in_progress *info) { @@ -910,12 +968,11 @@ static int handle_unmatched_new_item( /* if delta wasn't created (because of rules), just skip ahead */ last = diff_delta__last_for_item(diff, nitem); if (!last) - return git_iterator_advance(&info->nitem, info->new_iter); + return iterator_advance(&info->nitem, info->new_iter); /* iterate into dir looking for an actual untracked file */ - if ((error = git_iterator_advance_over_with_status( - &info->nitem, &untracked_state, info->new_iter)) < 0 && - error != GIT_ITEROVER) + if ((error = iterator_advance_over_with_status( + &info->nitem, &untracked_state, info->new_iter)) < 0) return error; /* if we found nothing or just ignored items, update the record */ @@ -935,7 +992,7 @@ static int handle_unmatched_new_item( /* try to advance into directory if necessary */ if (recurse_into_dir) { - error = git_iterator_advance_into(&info->nitem, info->new_iter); + error = iterator_advance_into(&info->nitem, info->new_iter); /* if real error or no error, proceed with iteration */ if (error != GIT_ENOTFOUND) @@ -946,7 +1003,7 @@ static int handle_unmatched_new_item( * it or ignore it */ if (contains_oitem) - return git_iterator_advance(&info->nitem, info->new_iter); + return iterator_advance(&info->nitem, info->new_iter); delta_type = GIT_DELTA_IGNORED; } } @@ -955,7 +1012,7 @@ static int handle_unmatched_new_item( DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_RECURSE_IGNORED_DIRS) && git_iterator_current_tree_is_ignored(info->new_iter)) /* item contained in ignored directory, so skip over it */ - return git_iterator_advance(&info->nitem, info->new_iter); + return iterator_advance(&info->nitem, info->new_iter); else if (info->new_iter->type != GIT_ITERATOR_TYPE_WORKDIR) delta_type = GIT_DELTA_ADDED; @@ -968,12 +1025,12 @@ static int handle_unmatched_new_item( /* if this contains a tracked item, treat as normal TREE */ if (contains_oitem) { - error = git_iterator_advance_into(&info->nitem, info->new_iter); + error = iterator_advance_into(&info->nitem, info->new_iter); if (error != GIT_ENOTFOUND) return error; giterr_clear(); - return git_iterator_advance(&info->nitem, info->new_iter); + return iterator_advance(&info->nitem, info->new_iter); } } } @@ -1004,7 +1061,7 @@ static int handle_unmatched_new_item( } } - return git_iterator_advance(&info->nitem, info->new_iter); + return iterator_advance(&info->nitem, info->new_iter); } static int handle_unmatched_old_item( @@ -1033,10 +1090,10 @@ static int handle_unmatched_old_item( */ if (S_ISDIR(info->nitem->mode) && DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_RECURSE_UNTRACKED_DIRS)) - return git_iterator_advance(&info->nitem, info->new_iter); + return iterator_advance(&info->nitem, info->new_iter); } - return git_iterator_advance(&info->oitem, info->old_iter); + return iterator_advance(&info->oitem, info->old_iter); } static int handle_matched_item( @@ -1047,9 +1104,8 @@ static int handle_matched_item( if ((error = maybe_modified(diff, info)) < 0) return error; - if (!(error = git_iterator_advance(&info->oitem, info->old_iter)) || - error == GIT_ITEROVER) - error = git_iterator_advance(&info->nitem, info->new_iter); + if (!(error = iterator_advance(&info->oitem, info->old_iter))) + error = iterator_advance(&info->nitem, info->new_iter); return error; } @@ -1085,13 +1141,9 @@ int git_diff__from_iterators( if ((error = diff_list_apply_options(diff, opts)) < 0) goto cleanup; - if ((error = git_iterator_current(&info.oitem, old_iter)) < 0 && - error != GIT_ITEROVER) + if ((error = iterator_current(&info.oitem, old_iter)) < 0 || + (error = iterator_current(&info.nitem, new_iter)) < 0) goto cleanup; - if ((error = git_iterator_current(&info.nitem, new_iter)) < 0 && - error != GIT_ITEROVER) - goto cleanup; - error = 0; /* run iterators building diffs */ while (!error && (info.oitem || info.nitem)) { @@ -1113,10 +1165,6 @@ int git_diff__from_iterators( */ else error = handle_matched_item(diff, &info); - - /* because we are iterating over two lists, ignore ITEROVER */ - if (error == GIT_ITEROVER) - error = 0; } diff->perf.stat_calls += old_iter->stat_calls + new_iter->stat_calls; From ecd60a56ebd6bdc347a486f53da7de0febef58cf Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Thu, 14 May 2015 11:52:48 -0400 Subject: [PATCH 03/17] conflicts: when adding conflicts, remove staged When adding a conflict for some path, remove the staged entry. Otherwise, an illegal index (with both stage 0 and high-stage entries) would result. --- CHANGELOG.md | 2 ++ include/git2/index.h | 3 ++- src/index.c | 17 +++++++++++++- tests/index/conflicts.c | 50 +++++++++++++++++++++++++++++++++++++++++ tests/status/worktree.c | 2 +- 5 files changed, 71 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a0ae0e03..8e4bdf919 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,8 @@ support for HTTPS connections insead of OpenSSL. the error message, which allows you to get the "repository not found" messages. +* `git_index_conflict_add()` will remove staged entries that exist for + conflicted paths. ### API additions diff --git a/include/git2/index.h b/include/git2/index.h index 52032f7fd..1e1d5e748 100644 --- a/include/git2/index.h +++ b/include/git2/index.h @@ -631,7 +631,8 @@ GIT_EXTERN(int) git_index_find(size_t *at_pos, git_index *index, const char *pat /**@{*/ /** - * Add or update index entries to represent a conflict + * Add or update index entries to represent a conflict. Any staged + * entries that exist at the given paths will be removed. * * The entries are the entries from the tree included in the merge. Any * entry may be null to indicate that that file was not present in the diff --git a/src/index.c b/src/index.c index 561fe73c4..19432b18c 100644 --- a/src/index.c +++ b/src/index.c @@ -1314,6 +1314,21 @@ int git_index_conflict_add(git_index *index, (ret = index_entry_dup(&entries[2], INDEX_OWNER(index), their_entry)) < 0) goto on_error; + /* Remove existing index entries for each path */ + for (i = 0; i < 3; i++) { + if (entries[i] == NULL) + continue; + + if ((ret = git_index_remove(index, entries[i]->path, 0)) != 0) { + if (ret != GIT_ENOTFOUND) + goto on_error; + + giterr_clear(); + ret = 0; + } + } + + /* Add the conflict entries */ for (i = 0; i < 3; i++) { if (entries[i] == NULL) continue; @@ -1321,7 +1336,7 @@ int git_index_conflict_add(git_index *index, /* Make sure stage is correct */ GIT_IDXENTRY_STAGE_SET(entries[i], i + 1); - if ((ret = index_insert(index, &entries[i], 1, true)) < 0) + if ((ret = index_insert(index, &entries[i], 0, true)) < 0) goto on_error; entries[i] = NULL; /* don't free if later entry fails */ diff --git a/tests/index/conflicts.c b/tests/index/conflicts.c index 427351693..aed5dd1a9 100644 --- a/tests/index/conflicts.c +++ b/tests/index/conflicts.c @@ -16,6 +16,7 @@ static git_index *repo_index; #define CONFLICTS_TWO_OUR_OID "8b3f43d2402825c200f835ca1762413e386fd0b2" #define CONFLICTS_TWO_THEIR_OID "220bd62631c8cf7a83ef39c6b94595f00517211e" +#define TEST_STAGED_OID "beefdadafeedabedcafedeedbabedeadbeaddeaf" #define TEST_ANCESTOR_OID "f00ff00ff00ff00ff00ff00ff00ff00ff00ff00f" #define TEST_OUR_OID "b44bb44bb44bb44bb44bb44bb44bb44bb44bb44b" #define TEST_THEIR_OID "0123456789abcdef0123456789abcdef01234567" @@ -96,6 +97,55 @@ void test_index_conflicts__add_fixes_incorrect_stage(void) cl_assert(git_index_entry_stage(conflict_entry[2]) == 3); } +void test_index_conflicts__add_removes_stage_zero(void) +{ + git_index_entry staged, ancestor_entry, our_entry, their_entry; + const git_index_entry *conflict_entry[3]; + + cl_assert(git_index_entrycount(repo_index) == 8); + + memset(&staged, 0x0, sizeof(git_index_entry)); + memset(&ancestor_entry, 0x0, sizeof(git_index_entry)); + memset(&our_entry, 0x0, sizeof(git_index_entry)); + memset(&their_entry, 0x0, sizeof(git_index_entry)); + + staged.mode = 0100644; + staged.path = "test-one.txt"; + git_oid_fromstr(&staged.id, TEST_STAGED_OID); + cl_git_pass(git_index_add(repo_index, &staged)); + cl_assert(git_index_entrycount(repo_index) == 9); + + ancestor_entry.path = "test-one.txt"; + ancestor_entry.mode = 0100644; + ancestor_entry.flags |= (3 << GIT_IDXENTRY_STAGESHIFT); + git_oid_fromstr(&ancestor_entry.id, TEST_ANCESTOR_OID); + + our_entry.path = "test-one.txt"; + our_entry.mode = 0100644; + our_entry.flags |= (1 << GIT_IDXENTRY_STAGESHIFT); + git_oid_fromstr(&our_entry.id, TEST_OUR_OID); + + their_entry.path = "test-one.txt"; + their_entry.mode = 0100644; + their_entry.flags |= (2 << GIT_IDXENTRY_STAGESHIFT); + git_oid_fromstr(&their_entry.id, TEST_THEIR_OID); + + cl_git_pass(git_index_conflict_add(repo_index, &ancestor_entry, &our_entry, &their_entry)); + + cl_assert(git_index_entrycount(repo_index) == 11); + + cl_assert_equal_p(NULL, git_index_get_bypath(repo_index, "test-one.txt", 0)); + + cl_git_pass(git_index_conflict_get(&conflict_entry[0], &conflict_entry[1], &conflict_entry[2], repo_index, "test-one.txt")); + + cl_assert_equal_oid(&ancestor_entry.id, &conflict_entry[0]->id); + cl_assert_equal_i(1, git_index_entry_stage(conflict_entry[0])); + cl_assert_equal_oid(&our_entry.id, &conflict_entry[1]->id); + cl_assert_equal_i(2, git_index_entry_stage(conflict_entry[1])); + cl_assert_equal_oid(&their_entry.id, &conflict_entry[2]->id); + cl_assert_equal_i(3, git_index_entry_stage(conflict_entry[2])); +} + void test_index_conflicts__get(void) { const git_index_entry *conflict_entry[3]; diff --git a/tests/status/worktree.c b/tests/status/worktree.c index 8897bf9b5..a8d71477e 100644 --- a/tests/status/worktree.c +++ b/tests/status/worktree.c @@ -634,7 +634,7 @@ void test_status_worktree__conflicted_item(void) &our_entry, &their_entry)); cl_git_pass(git_status_file(&status, repo, "modified_file")); - cl_assert_equal_i(GIT_STATUS_WT_MODIFIED, status); + cl_assert_equal_i(GIT_STATUS_INDEX_DELETED|GIT_STATUS_WT_NEW, status); git_index_free(index); } From 3ab5a65967b2007e200be4925781583880e5b938 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Thu, 14 May 2015 12:54:39 -0400 Subject: [PATCH 04/17] index: remove error message in non-error remove If `git_index_remove_bypath` does no work, and returns an OK error code, it should not set an error message. --- src/index.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/index.c b/src/index.c index 19432b18c..b39c0893d 100644 --- a/src/index.c +++ b/src/index.c @@ -1173,6 +1173,9 @@ int git_index_remove_bypath(git_index *index, const char *path) ret != GIT_ENOTFOUND)) return ret; + if (ret == GIT_ENOTFOUND) + giterr_clear(); + return 0; } From d67f270e58942fed5871eca6802d3fa4f290e4f1 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Thu, 14 May 2015 13:30:29 -0400 Subject: [PATCH 05/17] index: validate mode of new conflicts --- src/index.c | 9 +++++++++ tests/index/conflicts.c | 9 ++++++++- tests/status/worktree.c | 6 ++++++ 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/index.c b/src/index.c index b39c0893d..e37cb1f4d 100644 --- a/src/index.c +++ b/src/index.c @@ -1317,6 +1317,15 @@ int git_index_conflict_add(git_index *index, (ret = index_entry_dup(&entries[2], INDEX_OWNER(index), their_entry)) < 0) goto on_error; + /* Validate entries */ + for (i = 0; i < 3; i++) { + if (entries[i] && !valid_filemode(entries[i]->mode)) { + giterr_set(GITERR_INDEX, "invalid filemode for stage %d entry", + i); + return -1; + } + } + /* Remove existing index entries for each path */ for (i = 0; i < 3; i++) { if (entries[i] == NULL) diff --git a/tests/index/conflicts.c b/tests/index/conflicts.c index aed5dd1a9..3b38723bd 100644 --- a/tests/index/conflicts.c +++ b/tests/index/conflicts.c @@ -47,14 +47,17 @@ void test_index_conflicts__add(void) memset(&their_entry, 0x0, sizeof(git_index_entry)); ancestor_entry.path = "test-one.txt"; + ancestor_entry.mode = 0100644; ancestor_entry.flags |= (1 << GIT_IDXENTRY_STAGESHIFT); git_oid_fromstr(&ancestor_entry.id, TEST_ANCESTOR_OID); our_entry.path = "test-one.txt"; + our_entry.mode = 0100644; ancestor_entry.flags |= (2 << GIT_IDXENTRY_STAGESHIFT); git_oid_fromstr(&our_entry.id, TEST_OUR_OID); their_entry.path = "test-one.txt"; + their_entry.mode = 0100644; ancestor_entry.flags |= (3 << GIT_IDXENTRY_STAGESHIFT); git_oid_fromstr(&their_entry.id, TEST_THEIR_OID); @@ -75,14 +78,17 @@ void test_index_conflicts__add_fixes_incorrect_stage(void) memset(&their_entry, 0x0, sizeof(git_index_entry)); ancestor_entry.path = "test-one.txt"; + ancestor_entry.mode = 0100644; ancestor_entry.flags |= (3 << GIT_IDXENTRY_STAGESHIFT); git_oid_fromstr(&ancestor_entry.id, TEST_ANCESTOR_OID); our_entry.path = "test-one.txt"; + our_entry.mode = 0100644; ancestor_entry.flags |= (1 << GIT_IDXENTRY_STAGESHIFT); git_oid_fromstr(&our_entry.id, TEST_OUR_OID); their_entry.path = "test-one.txt"; + their_entry.mode = 0100644; ancestor_entry.flags |= (2 << GIT_IDXENTRY_STAGESHIFT); git_oid_fromstr(&their_entry.id, TEST_THEIR_OID); @@ -109,8 +115,8 @@ void test_index_conflicts__add_removes_stage_zero(void) memset(&our_entry, 0x0, sizeof(git_index_entry)); memset(&their_entry, 0x0, sizeof(git_index_entry)); - staged.mode = 0100644; staged.path = "test-one.txt"; + staged.mode = 0100644; git_oid_fromstr(&staged.id, TEST_STAGED_OID); cl_git_pass(git_index_add(repo_index, &staged)); cl_assert(git_index_entrycount(repo_index) == 9); @@ -322,6 +328,7 @@ void test_index_conflicts__partial(void) memset(&their_entry, 0x0, sizeof(git_index_entry)); ancestor_entry.path = "test-one.txt"; + ancestor_entry.mode = 0100644; ancestor_entry.flags |= (1 << GIT_IDXENTRY_STAGESHIFT); git_oid_fromstr(&ancestor_entry.id, TEST_ANCESTOR_OID); diff --git a/tests/status/worktree.c b/tests/status/worktree.c index a8d71477e..de21a1c31 100644 --- a/tests/status/worktree.c +++ b/tests/status/worktree.c @@ -461,14 +461,17 @@ void test_status_worktree__conflict_with_diff3(void) memset(&their_entry, 0x0, sizeof(git_index_entry)); ancestor_entry.path = "modified_file"; + ancestor_entry.mode = 0100644; git_oid_fromstr(&ancestor_entry.id, "452e4244b5d083ddf0460acf1ecc74db9dcfa11a"); our_entry.path = "modified_file"; + our_entry.mode = 0100644; git_oid_fromstr(&our_entry.id, "452e4244b5d083ddf0460acf1ecc74db9dcfa11a"); their_entry.path = "modified_file"; + their_entry.mode = 0100644; git_oid_fromstr(&their_entry.id, "452e4244b5d083ddf0460acf1ecc74db9dcfa11a"); @@ -614,14 +617,17 @@ void test_status_worktree__conflicted_item(void) memset(&our_entry, 0x0, sizeof(git_index_entry)); memset(&their_entry, 0x0, sizeof(git_index_entry)); + ancestor_entry.mode = 0100644; ancestor_entry.path = "modified_file"; git_oid_fromstr(&ancestor_entry.id, "452e4244b5d083ddf0460acf1ecc74db9dcfa11a"); + our_entry.mode = 0100644; our_entry.path = "modified_file"; git_oid_fromstr(&our_entry.id, "452e4244b5d083ddf0460acf1ecc74db9dcfa11a"); + their_entry.mode = 0100644; their_entry.path = "modified_file"; git_oid_fromstr(&their_entry.id, "452e4244b5d083ddf0460acf1ecc74db9dcfa11a"); From 7c948014006e86beaced3e77b61cb0a8ddb0958c Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Thu, 14 May 2015 14:00:29 -0400 Subject: [PATCH 06/17] diff/status: introduce conflicts When diffing against an index, return a new `GIT_DELTA_CONFLICTED` delta type for items that are conflicted. For a single file path, only one delta will be produced (despite the fact that there are multiple entries in the index). Index iterators now have the (optional) ability to return conflicts in the index. Prior to this change, they would be omitted, and callers (like diff) would omit conflicted index entries entirely. --- CHANGELOG.md | 8 +++++ include/git2/diff.h | 21 +++++------ include/git2/status.h | 1 + src/diff.c | 77 ++++++++++++++++++++++++++++++++++------- src/reset.c | 1 + src/status.c | 6 ++++ tests/status/worktree.c | 4 +-- 7 files changed, 93 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e4bdf919..03e5273ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -100,6 +100,14 @@ support for HTTPS connections insead of OpenSSL. configuration of the server, and tools can use this to show messages about failing to communicate with the server. +* `git_diff_index_to_workdir()` and `git_diff_tree_to_index()` will now + produce deltas of type `GIT_DELTA_CONFLICTED` to indicate that the index + side of the delta is a conflict. + +* The `git_status` family of functions will now produce status of type + `GIT_STATUS_CONFLICTED` to indicate that a conflict exists for that file + in the index. + ### API removals * `git_remote_save()` and `git_remote_clear_refspecs()` has been diff --git a/include/git2/diff.h b/include/git2/diff.h index cac3b268a..7505c08c2 100644 --- a/include/git2/diff.h +++ b/include/git2/diff.h @@ -239,16 +239,17 @@ typedef enum { * DELETED pairs). */ typedef enum { - GIT_DELTA_UNMODIFIED = 0, /**< no changes */ - GIT_DELTA_ADDED = 1, /**< entry does not exist in old version */ - GIT_DELTA_DELETED = 2, /**< entry does not exist in new version */ - GIT_DELTA_MODIFIED = 3, /**< entry content changed between old and new */ - GIT_DELTA_RENAMED = 4, /**< entry was renamed between old and new */ - GIT_DELTA_COPIED = 5, /**< entry was copied from another old entry */ - GIT_DELTA_IGNORED = 6, /**< entry is ignored item in workdir */ - GIT_DELTA_UNTRACKED = 7, /**< entry is untracked item in workdir */ - GIT_DELTA_TYPECHANGE = 8, /**< type of entry changed between old and new */ - GIT_DELTA_UNREADABLE = 9, /**< entry is unreadable */ + GIT_DELTA_UNMODIFIED = 0, /**< no changes */ + GIT_DELTA_ADDED = 1, /**< entry does not exist in old version */ + GIT_DELTA_DELETED = 2, /**< entry does not exist in new version */ + GIT_DELTA_MODIFIED = 3, /**< entry content changed between old and new */ + GIT_DELTA_RENAMED = 4, /**< entry was renamed between old and new */ + GIT_DELTA_COPIED = 5, /**< entry was copied from another old entry */ + GIT_DELTA_IGNORED = 6, /**< entry is ignored item in workdir */ + GIT_DELTA_UNTRACKED = 7, /**< entry is untracked item in workdir */ + GIT_DELTA_TYPECHANGE = 8, /**< type of entry changed between old and new */ + GIT_DELTA_UNREADABLE = 9, /**< entry is unreadable */ + GIT_DELTA_CONFLICTED = 10, /**< entry in the index is conflicted */ } git_delta_t; /** diff --git a/include/git2/status.h b/include/git2/status.h index 5f211810d..671113955 100644 --- a/include/git2/status.h +++ b/include/git2/status.h @@ -46,6 +46,7 @@ typedef enum { GIT_STATUS_WT_UNREADABLE = (1u << 12), GIT_STATUS_IGNORED = (1u << 14), + GIT_STATUS_CONFLICTED = (1u << 15), } git_status_t; /** diff --git a/src/diff.c b/src/diff.c index f829fb07c..765956a29 100644 --- a/src/diff.c +++ b/src/diff.c @@ -327,6 +327,22 @@ static const char *diff_mnemonic_prefix( return pfx; } +static int diff_entry_cmp(const void *a, const void *b) +{ + const git_index_entry *entry_a = a; + const git_index_entry *entry_b = b; + + return strcmp(entry_a->path, entry_b->path); +} + +static int diff_entry_icmp(const void *a, const void *b) +{ + const git_index_entry *entry_a = a; + const git_index_entry *entry_b = b; + + return strcasecmp(entry_a->path, entry_b->path); +} + static void diff_set_ignore_case(git_diff *diff, bool ignore_case) { if (!ignore_case) { @@ -335,7 +351,7 @@ static void diff_set_ignore_case(git_diff *diff, bool ignore_case) diff->strcomp = git__strcmp; diff->strncomp = git__strncmp; diff->pfxcomp = git__prefixcmp; - diff->entrycomp = git_index_entry_cmp; + diff->entrycomp = diff_entry_cmp; git_vector_set_cmp(&diff->deltas, git_diff_delta__cmp); } else { @@ -344,7 +360,7 @@ static void diff_set_ignore_case(git_diff *diff, bool ignore_case) diff->strcomp = git__strcasecmp; diff->strncomp = git__strncasecmp; diff->pfxcomp = git__prefixcmp_icase; - diff->entrycomp = git_index_entry_icmp; + diff->entrycomp = diff_entry_icmp; git_vector_set_cmp(&diff->deltas, git_diff_delta__casecmp); } @@ -731,8 +747,12 @@ static int maybe_modified( new_is_workdir) nmode = (nmode & ~MODE_BITS_MASK) | (omode & MODE_BITS_MASK); + /* if one side is a conflict, mark the whole delta as conflicted */ + if (git_index_entry_stage(oitem) > 0 || git_index_entry_stage(nitem) > 0) + status = GIT_DELTA_CONFLICTED; + /* support "assume unchanged" (poorly, b/c we still stat everything) */ - if ((oitem->flags & GIT_IDXENTRY_VALID) != 0) + else if ((oitem->flags & GIT_IDXENTRY_VALID) != 0) status = GIT_DELTA_UNMODIFIED; /* support "skip worktree" index bit */ @@ -875,9 +895,29 @@ static int iterator_advance( const git_index_entry **entry, git_iterator *iterator) { - int error; + const git_index_entry *prev_entry = *entry; + int cmp, error; - if ((error = git_iterator_advance(entry, iterator)) == GIT_ITEROVER) { + /* if we're looking for conflicts, we only want to report + * one conflict for each file, instead of all three sides. + * so if this entry is a conflict for this file, and the + * previous one was a conflict for the same file, skip it. + */ + while ((error = git_iterator_advance(entry, iterator)) == 0) { + if (!(iterator->flags & GIT_ITERATOR_INCLUDE_CONFLICTS) || + git_index_entry_stage(prev_entry) == 0 || + git_index_entry_stage(*entry) == 0) + break; + + cmp = (iterator->flags & GIT_ITERATOR_IGNORE_CASE) ? + strcasecmp(prev_entry->path, (*entry)->path) : + strcmp(prev_entry->path, (*entry)->path); + + if (cmp) + break; + } + + if (error == GIT_ITEROVER) { *entry = NULL; error = 0; } @@ -926,8 +966,12 @@ static int handle_unmatched_new_item( /* check if this is a prefix of the other side */ contains_oitem = entry_is_prefixed(diff, info->oitem, nitem); + /* update delta_type if this item is conflicted */ + if (git_index_entry_stage(nitem)) + delta_type = GIT_DELTA_CONFLICTED; + /* update delta_type if this item is ignored */ - if (git_iterator_current_is_ignored(info->new_iter)) + else if (git_iterator_current_is_ignored(info->new_iter)) delta_type = GIT_DELTA_IGNORED; if (nitem->mode == GIT_FILEMODE_TREE) { @@ -1067,8 +1111,14 @@ static int handle_unmatched_new_item( static int handle_unmatched_old_item( git_diff *diff, diff_in_progress *info) { - int error = diff_delta__from_one(diff, GIT_DELTA_DELETED, info->oitem); - if (error != 0) + git_delta_t delta_type = GIT_DELTA_DELETED; + int error; + + /* update delta_type if this item is conflicted */ + if (git_index_entry_stage(info->oitem)) + delta_type = GIT_DELTA_CONFLICTED; + + if ((error = diff_delta__from_one(diff, delta_type, info->oitem)) < 0) return error; /* if we are generating TYPECHANGE records then check for that @@ -1234,6 +1284,8 @@ int git_diff_tree_to_index( { int error = 0; bool index_ignore_case = false; + git_iterator_flag_t iflag = GIT_ITERATOR_DONT_IGNORE_CASE | + GIT_ITERATOR_INCLUDE_CONFLICTS; assert(diff && repo); @@ -1243,10 +1295,8 @@ int git_diff_tree_to_index( index_ignore_case = index->ignore_case; DIFF_FROM_ITERATORS( - git_iterator_for_tree( - &a, old_tree, GIT_ITERATOR_DONT_IGNORE_CASE, pfx, pfx), - git_iterator_for_index( - &b, index, GIT_ITERATOR_DONT_IGNORE_CASE, pfx, pfx) + git_iterator_for_tree(&a, old_tree, iflag, pfx, pfx), + git_iterator_for_index(&b, index, iflag, pfx, pfx) ); /* if index is in case-insensitive order, re-sort deltas to match */ @@ -1270,7 +1320,8 @@ int git_diff_index_to_workdir( return error; DIFF_FROM_ITERATORS( - git_iterator_for_index(&a, index, 0, pfx, pfx), + git_iterator_for_index( + &a, index, GIT_ITERATOR_INCLUDE_CONFLICTS, pfx, pfx), git_iterator_for_workdir( &b, repo, index, NULL, GIT_ITERATOR_DONT_AUTOEXPAND, pfx, pfx) ); diff --git a/src/reset.c b/src/reset.c index d08e48cd7..0ffa51b66 100644 --- a/src/reset.c +++ b/src/reset.c @@ -63,6 +63,7 @@ int git_reset_default( assert(delta->status == GIT_DELTA_ADDED || delta->status == GIT_DELTA_MODIFIED || + delta->status == GIT_DELTA_CONFLICTED || delta->status == GIT_DELTA_DELETED); error = git_index_conflict_remove(index, delta->old_file.path); diff --git a/src/status.c b/src/status.c index 720ccb7eb..b206b0e2f 100644 --- a/src/status.c +++ b/src/status.c @@ -44,6 +44,9 @@ static unsigned int index_delta2status(const git_diff_delta *head2idx) case GIT_DELTA_TYPECHANGE: st = GIT_STATUS_INDEX_TYPECHANGE; break; + case GIT_DELTA_CONFLICTED: + st = GIT_STATUS_CONFLICTED; + break; default: break; } @@ -102,6 +105,9 @@ static unsigned int workdir_delta2status( case GIT_DELTA_TYPECHANGE: st = GIT_STATUS_WT_TYPECHANGE; break; + case GIT_DELTA_CONFLICTED: + st = GIT_STATUS_CONFLICTED; + break; default: break; } diff --git a/tests/status/worktree.c b/tests/status/worktree.c index de21a1c31..9be0a4172 100644 --- a/tests/status/worktree.c +++ b/tests/status/worktree.c @@ -487,7 +487,7 @@ void test_status_worktree__conflict_with_diff3(void) cl_git_pass(git_status_file(&status, repo, "modified_file")); - cl_assert_equal_i(GIT_STATUS_INDEX_DELETED | GIT_STATUS_WT_NEW, status); + cl_assert_equal_i(GIT_STATUS_CONFLICTED, status); } static const char *filemode_paths[] = { @@ -640,7 +640,7 @@ void test_status_worktree__conflicted_item(void) &our_entry, &their_entry)); cl_git_pass(git_status_file(&status, repo, "modified_file")); - cl_assert_equal_i(GIT_STATUS_INDEX_DELETED|GIT_STATUS_WT_NEW, status); + cl_assert_equal_i(GIT_STATUS_CONFLICTED, status); git_index_free(index); } From 7877146fc2b2a25ce1d8c3be5f15186acf292a27 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Mon, 18 May 2015 15:13:43 -0400 Subject: [PATCH 07/17] diff: for conflicts w/o workdir, blank nitem side Make sure that we provide a blanked nitem side when the item does not exist in the working directory. --- src/diff.c | 39 ++++++++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/src/diff.c b/src/diff.c index 765956a29..c061a903b 100644 --- a/src/diff.c +++ b/src/diff.c @@ -77,11 +77,24 @@ static int diff_insert_delta( static int diff_delta__from_one( git_diff *diff, git_delta_t status, - const git_index_entry *entry) + const git_index_entry *oitem, + const git_index_entry *nitem) { + const git_index_entry *entry = nitem; + bool has_old = false; git_diff_delta *delta; const char *matched_pathspec; + assert((oitem != NULL) ^ (nitem != NULL)); + + if (oitem) { + entry = oitem; + has_old = true; + } + + if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) + has_old = !has_old; + if ((entry->flags & GIT_IDXENTRY_VALID) != 0) return 0; @@ -111,7 +124,7 @@ static int diff_delta__from_one( assert(status != GIT_DELTA_MODIFIED); delta->nfiles = 1; - if (delta->status == GIT_DELTA_DELETED) { + if (has_old) { delta->old_file.mode = entry->mode; delta->old_file.size = entry->file_size; git_oid_cpy(&delta->old_file.id, &entry->id); @@ -123,8 +136,7 @@ static int diff_delta__from_one( delta->old_file.flags |= GIT_DIFF_FLAG_VALID_ID; - if (delta->status == GIT_DELTA_DELETED || - !git_oid_iszero(&delta->new_file.id)) + if (has_old || !git_oid_iszero(&delta->new_file.id)) delta->new_file.flags |= GIT_DIFF_FLAG_VALID_ID; return diff_insert_delta(diff, delta, matched_pathspec); @@ -764,13 +776,13 @@ static int maybe_modified( if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE)) status = GIT_DELTA_TYPECHANGE; else if (nmode == GIT_FILEMODE_UNREADABLE) { - if (!(error = diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem))) - error = diff_delta__from_one(diff, GIT_DELTA_UNREADABLE, nitem); + if (!(error = diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem, NULL))) + error = diff_delta__from_one(diff, GIT_DELTA_UNREADABLE, NULL, nitem); return error; } else { - if (!(error = diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem))) - error = diff_delta__from_one(diff, GIT_DELTA_ADDED, nitem); + if (!(error = diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem, NULL))) + error = diff_delta__from_one(diff, GIT_DELTA_ADDED, NULL, nitem); return error; } } @@ -850,8 +862,9 @@ static int maybe_modified( DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_CASECHANGE) && strcmp(oitem->path, nitem->path) != 0) { - if (!(error = diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem))) - error = diff_delta__from_one(diff, GIT_DELTA_ADDED, nitem); + if (!(error = diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem, NULL))) + error = diff_delta__from_one(diff, GIT_DELTA_ADDED, NULL, nitem); + return error; } @@ -1006,7 +1019,7 @@ static int handle_unmatched_new_item( git_iterator_status_t untracked_state; /* attempt to insert record for this directory */ - if ((error = diff_delta__from_one(diff, delta_type, nitem)) != 0) + if ((error = diff_delta__from_one(diff, delta_type, NULL, nitem)) != 0) return error; /* if delta wasn't created (because of rules), just skip ahead */ @@ -1087,7 +1100,7 @@ static int handle_unmatched_new_item( } /* Actually create the record for this item if necessary */ - if ((error = diff_delta__from_one(diff, delta_type, nitem)) != 0) + if ((error = diff_delta__from_one(diff, delta_type, NULL, nitem)) != 0) return error; /* If user requested TYPECHANGE records, then check for that instead of @@ -1118,7 +1131,7 @@ static int handle_unmatched_old_item( if (git_index_entry_stage(info->oitem)) delta_type = GIT_DELTA_CONFLICTED; - if ((error = diff_delta__from_one(diff, delta_type, info->oitem)) < 0) + if ((error = diff_delta__from_one(diff, delta_type, info->oitem, NULL)) < 0) return error; /* if we are generating TYPECHANGE records then check for that From bb815157da1b3545d475a678e4db5292f6a723b2 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Mon, 18 May 2015 16:23:13 -0400 Subject: [PATCH 08/17] diff conflicts: add tests for tree to index --- tests/diff/diff_helpers.c | 2 +- tests/diff/diff_helpers.h | 2 +- tests/diff/index.c | 76 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 78 insertions(+), 2 deletions(-) diff --git a/tests/diff/diff_helpers.c b/tests/diff/diff_helpers.c index 47e06f0d8..03862d6b4 100644 --- a/tests/diff/diff_helpers.c +++ b/tests/diff/diff_helpers.c @@ -68,7 +68,7 @@ int diff_file_cb( if ((delta->flags & GIT_DIFF_FLAG_BINARY) != 0) e->files_binary++; - cl_assert(delta->status <= GIT_DELTA_TYPECHANGE); + cl_assert(delta->status <= GIT_DELTA_CONFLICTED); e->file_status[delta->status] += 1; diff --git a/tests/diff/diff_helpers.h b/tests/diff/diff_helpers.h index bf21f4b1f..6bcd1e671 100644 --- a/tests/diff/diff_helpers.h +++ b/tests/diff/diff_helpers.h @@ -8,7 +8,7 @@ typedef struct { int files; int files_binary; - int file_status[10]; /* indexed by git_delta_t value */ + int file_status[11]; /* indexed by git_delta_t value */ int hunks; int hunk_new_lines; diff --git a/tests/diff/index.c b/tests/diff/index.c index 21afe8da2..a544b83c7 100644 --- a/tests/diff/index.c +++ b/tests/diff/index.c @@ -163,3 +163,79 @@ void test_diff_index__checks_options_version(void) git_tree_free(a); } +static void do_conflicted_diff(diff_expects *exp, unsigned long flags) +{ + const char *a_commit = "26a125ee1bf"; /* the current HEAD */ + git_tree *a = resolve_commit_oid_to_tree(g_repo, a_commit); + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_index_entry ancestor = {0}, ours = {0}, theirs = {0}; + git_diff *diff = NULL; + git_index *index; + + cl_assert(a); + + opts.context_lines = 1; + opts.interhunk_lines = 1; + opts.flags |= flags; + + memset(exp, 0, sizeof(diff_expects)); + + cl_git_pass(git_repository_index(&index, g_repo)); + + ancestor.path = ours.path = theirs.path = "staged_changes"; + ancestor.mode = ours.mode = theirs.mode = 0100644; + + git_oid_fromstr(&ancestor.id, "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"); + git_oid_fromstr(&ours.id, "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"); + git_oid_fromstr(&theirs.id, "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"); + + cl_git_pass(git_index_conflict_add(index, &ancestor, &ours, &theirs)); + cl_git_pass(git_diff_tree_to_index(&diff, g_repo, a, index, &opts)); + + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_hunk_cb, diff_line_cb, exp)); + + git_diff_free(diff); + git_tree_free(a); + git_index_free(index); +} + +void test_diff_index__reports_conflicts(void) +{ + diff_expects exp; + + do_conflicted_diff(&exp, 0); + + cl_assert_equal_i(8, exp.files); + cl_assert_equal_i(3, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(2, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(2, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_CONFLICTED]); + + cl_assert_equal_i(7, exp.hunks); + + cl_assert_equal_i(9, exp.lines); + cl_assert_equal_i(2, exp.line_ctxt); + cl_assert_equal_i(5, exp.line_adds); + cl_assert_equal_i(2, exp.line_dels); +} + +void test_diff_index__reports_conflicts_when_reversed(void) +{ + diff_expects exp; + + do_conflicted_diff(&exp, GIT_DIFF_REVERSE); + + cl_assert_equal_i(8, exp.files); + cl_assert_equal_i(2, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(3, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(2, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_CONFLICTED]); + + cl_assert_equal_i(7, exp.hunks); + + cl_assert_equal_i(9, exp.lines); + cl_assert_equal_i(2, exp.line_ctxt); + cl_assert_equal_i(2, exp.line_adds); + cl_assert_equal_i(5, exp.line_dels); +} From b22369efa243022ff42582b559ddb6c6e05af2b1 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Mon, 18 May 2015 17:01:37 -0400 Subject: [PATCH 09/17] diff conflicts: test index to workdir w/ conflicts --- tests/diff/workdir.c | 45 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/tests/diff/workdir.c b/tests/diff/workdir.c index 963be9481..460f1b592 100644 --- a/tests/diff/workdir.c +++ b/tests/diff/workdir.c @@ -68,6 +68,51 @@ void test_diff_workdir__to_index(void) git_diff_free(diff); } +void test_diff_workdir__to_index_with_conflicts(void) +{ + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_diff *diff = NULL; + git_index *index; + git_index_entry our_entry = {0}, their_entry = {0}; + diff_expects exp = {0}; + + g_repo = cl_git_sandbox_init("status"); + + opts.context_lines = 3; + opts.interhunk_lines = 1; + + /* Adding an entry that represents a rename gets two files in conflict */ + our_entry.path = "subdir/modified_file"; + our_entry.mode = 0100644; + + their_entry.path = "subdir/rename_conflict"; + their_entry.mode = 0100644; + + cl_git_pass(git_repository_index(&index, g_repo)); + cl_git_pass(git_index_conflict_add(index, NULL, &our_entry, &their_entry)); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, index, &opts)); + + cl_git_pass(diff_foreach_via_iterator( + diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp)); + + cl_assert_equal_i(9, exp.files); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(4, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(3, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(2, exp.file_status[GIT_DELTA_CONFLICTED]); + + cl_assert_equal_i(7, exp.hunks); + + cl_assert_equal_i(12, exp.lines); + cl_assert_equal_i(4, exp.line_ctxt); + cl_assert_equal_i(3, exp.line_adds); + cl_assert_equal_i(5, exp.line_dels); + + git_diff_free(diff); + git_index_free(index); +} + void test_diff_workdir__to_index_with_assume_unchanged(void) { git_diff_options opts = GIT_DIFF_OPTIONS_INIT; From 191e97a02b3360820177ed7642116796551ce1f6 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Mon, 18 May 2015 18:15:17 -0400 Subject: [PATCH 10/17] diff conflicts: don't include incorrect ID Since a diff entry only concerns a single entry, zero the information for the index side of a conflict. (The index entry would otherwise erroneously include the lowest-stage index entry - generally the ancestor of a conflict.) Test that during status, the index side of the conflict is empty. --- src/diff.c | 39 +++++++++++++++++++--------------- tests/status/worktree.c | 46 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 17 deletions(-) diff --git a/src/diff.c b/src/diff.c index c061a903b..abe0de0d4 100644 --- a/src/diff.c +++ b/src/diff.c @@ -149,9 +149,10 @@ static int diff_delta__from_two( uint32_t old_mode, const git_index_entry *new_entry, uint32_t new_mode, - git_oid *new_oid, + const git_oid *new_id, const char *matched_pathspec) { + const git_oid *old_id = &old_entry->id; git_diff_delta *delta; const char *canonical_path = old_entry->path; @@ -159,37 +160,41 @@ static int diff_delta__from_two( DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_UNMODIFIED)) return 0; + if (!new_id) + new_id = &new_entry->id; + if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) { uint32_t temp_mode = old_mode; const git_index_entry *temp_entry = old_entry; + const git_oid *temp_id = old_id; + old_entry = new_entry; new_entry = temp_entry; old_mode = new_mode; new_mode = temp_mode; + old_id = new_id; + new_id = temp_id; } delta = diff_delta__alloc(diff, status, canonical_path); GITERR_CHECK_ALLOC(delta); delta->nfiles = 2; - git_oid_cpy(&delta->old_file.id, &old_entry->id); - delta->old_file.size = old_entry->file_size; - delta->old_file.mode = old_mode; - delta->old_file.flags |= GIT_DIFF_FLAG_VALID_ID; - - git_oid_cpy(&delta->new_file.id, &new_entry->id); - delta->new_file.size = new_entry->file_size; - delta->new_file.mode = new_mode; - - if (new_oid) { - if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) - git_oid_cpy(&delta->old_file.id, new_oid); - else - git_oid_cpy(&delta->new_file.id, new_oid); + if (!git_index_entry_stage(old_entry)) { + delta->old_file.size = old_entry->file_size; + delta->old_file.mode = old_mode; + git_oid_cpy(&delta->old_file.id, old_id); + delta->old_file.flags |= GIT_DIFF_FLAG_VALID_ID; } - if (new_oid || !git_oid_iszero(&new_entry->id)) - delta->new_file.flags |= GIT_DIFF_FLAG_VALID_ID; + if (!git_index_entry_stage(new_entry)) { + git_oid_cpy(&delta->new_file.id, new_id); + delta->new_file.size = new_entry->file_size; + delta->new_file.mode = new_mode; + + if (!git_oid_iszero(&new_entry->id)) + delta->new_file.flags |= GIT_DIFF_FLAG_VALID_ID; + } return diff_insert_delta(diff, delta, matched_pathspec); } diff --git a/tests/status/worktree.c b/tests/status/worktree.c index 9be0a4172..e272c0a10 100644 --- a/tests/status/worktree.c +++ b/tests/status/worktree.c @@ -645,6 +645,52 @@ void test_status_worktree__conflicted_item(void) git_index_free(index); } +void test_status_worktree__conflict_has_no_oid(void) +{ + git_repository *repo = cl_git_sandbox_init("status"); + git_index *index; + git_index_entry entry = {0}; + git_status_list *statuslist; + const git_status_entry *status; + git_oid zero_id = {0}; + + entry.mode = 0100644; + entry.path = "modified_file"; + git_oid_fromstr(&entry.id, "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"); + + cl_git_pass(git_repository_index(&index, repo)); + cl_git_pass(git_index_conflict_add(index, &entry, &entry, &entry)); + + git_status_list_new(&statuslist, repo, NULL); + + cl_assert_equal_i(16, git_status_list_entrycount(statuslist)); + + status = git_status_byindex(statuslist, 2); + + cl_assert_equal_i(GIT_STATUS_CONFLICTED, status->status); + cl_assert_equal_s("modified_file", status->head_to_index->old_file.path); + cl_assert(!git_oid_equal(&zero_id, &status->head_to_index->old_file.id)); + cl_assert(0 != status->head_to_index->old_file.mode); + cl_assert_equal_s("modified_file", status->head_to_index->new_file.path); + cl_assert_equal_oid(&zero_id, &status->head_to_index->new_file.id); + cl_assert_equal_i(0, status->head_to_index->new_file.mode); + cl_assert_equal_i(0, status->head_to_index->new_file.size); + + cl_assert_equal_s("modified_file", status->index_to_workdir->old_file.path); + cl_assert_equal_oid(&zero_id, &status->index_to_workdir->old_file.id); + cl_assert_equal_i(0, status->index_to_workdir->old_file.mode); + cl_assert_equal_i(0, status->index_to_workdir->old_file.size); + cl_assert_equal_s("modified_file", status->index_to_workdir->new_file.path); + cl_assert( + !git_oid_equal(&zero_id, &status->index_to_workdir->new_file.id) || + !(status->index_to_workdir->new_file.flags & GIT_DIFF_FLAG_VALID_ID)); + cl_assert(0 != status->index_to_workdir->new_file.mode); + cl_assert(0 != status->index_to_workdir->new_file.size); + + git_index_free(index); + git_status_list_free(statuslist); +} + static void stage_and_commit(git_repository *repo, const char *path) { git_index *index; From 2f1080ea04ad1235efcd4b213dbe3a1b847644f7 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Tue, 19 May 2015 11:17:07 -0400 Subject: [PATCH 11/17] conflict tests: use GIT_IDXENTRY_STAGE_SET --- tests/checkout/conflict.c | 2 +- tests/checkout/index.c | 6 +++--- tests/checkout/tree.c | 6 +++--- tests/index/collision.c | 10 +++++----- tests/index/conflicts.c | 20 ++++++++++---------- tests/object/tree/duplicateentries.c | 6 +++--- tests/reset/hard.c | 2 +- 7 files changed, 26 insertions(+), 26 deletions(-) diff --git a/tests/checkout/conflict.c b/tests/checkout/conflict.c index 3d763af12..006dc6522 100644 --- a/tests/checkout/conflict.c +++ b/tests/checkout/conflict.c @@ -102,7 +102,7 @@ static void create_index(struct checkout_index_entry *entries, size_t entries_le memset(&entry, 0x0, sizeof(git_index_entry)); entry.mode = entries[i].mode; - entry.flags = entries[i].stage << GIT_IDXENTRY_STAGESHIFT; + GIT_IDXENTRY_STAGE_SET(&entry, entries[i].stage); git_oid_fromstr(&entry.id, entries[i].oid_str); entry.path = entries[i].path; diff --git a/tests/checkout/index.c b/tests/checkout/index.c index de0770dba..89b014586 100644 --- a/tests/checkout/index.c +++ b/tests/checkout/index.c @@ -685,15 +685,15 @@ static void add_conflict(git_index *index, const char *path) entry.path = path; git_oid_fromstr(&entry.id, "d427e0b2e138501a3d15cc376077a3631e15bd46"); - entry.flags = (1 << GIT_IDXENTRY_STAGESHIFT); + GIT_IDXENTRY_STAGE_SET(&entry, 1); cl_git_pass(git_index_add(index, &entry)); git_oid_fromstr(&entry.id, "4e886e602529caa9ab11d71f86634bd1b6e0de10"); - entry.flags = (2 << GIT_IDXENTRY_STAGESHIFT); + GIT_IDXENTRY_STAGE_SET(&entry, 2); cl_git_pass(git_index_add(index, &entry)); git_oid_fromstr(&entry.id, "2bd0a343aeef7a2cf0d158478966a6e587ff3863"); - entry.flags = (3 << GIT_IDXENTRY_STAGESHIFT); + GIT_IDXENTRY_STAGE_SET(&entry, 3); cl_git_pass(git_index_add(index, &entry)); } diff --git a/tests/checkout/tree.c b/tests/checkout/tree.c index 50541a703..462323186 100644 --- a/tests/checkout/tree.c +++ b/tests/checkout/tree.c @@ -893,16 +893,16 @@ static void create_conflict(const char *path) memset(&entry, 0x0, sizeof(git_index_entry)); entry.mode = 0100644; - entry.flags = 1 << GIT_IDXENTRY_STAGESHIFT; + GIT_IDXENTRY_STAGE_SET(&entry, 1); git_oid_fromstr(&entry.id, "d427e0b2e138501a3d15cc376077a3631e15bd46"); entry.path = path; cl_git_pass(git_index_add(index, &entry)); - entry.flags = 2 << GIT_IDXENTRY_STAGESHIFT; + GIT_IDXENTRY_STAGE_SET(&entry, 2); git_oid_fromstr(&entry.id, "ee3fa1b8c00aff7fe02065fdb50864bb0d932ccf"); cl_git_pass(git_index_add(index, &entry)); - entry.flags = 3 << GIT_IDXENTRY_STAGESHIFT; + GIT_IDXENTRY_STAGE_SET(&entry, 3); git_oid_fromstr(&entry.id, "2bd0a343aeef7a2cf0d158478966a6e587ff3863"); cl_git_pass(git_index_add(index, &entry)); diff --git a/tests/index/collision.c b/tests/index/collision.c index 1f002e8d3..19c1548e9 100644 --- a/tests/index/collision.c +++ b/tests/index/collision.c @@ -57,7 +57,7 @@ void test_index_collision__add_with_highstage_1(void) git_oid_fromstr(&entry.id, "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391"); entry.path = "a/b"; - entry.flags = (2 << GIT_IDXENTRY_STAGESHIFT); + GIT_IDXENTRY_STAGE_SET(&entry, 2); cl_git_pass(git_index_add(index, &entry)); /* create a blob beneath the previous tree entry */ @@ -67,7 +67,7 @@ void test_index_collision__add_with_highstage_1(void) /* create another tree entry above the blob */ entry.path = "a/b"; - entry.flags = (1 << GIT_IDXENTRY_STAGESHIFT); + GIT_IDXENTRY_STAGE_SET(&entry, 1); cl_git_pass(git_index_add(index, &entry)); git_index_free(index); @@ -89,17 +89,17 @@ void test_index_collision__add_with_highstage_2(void) git_oid_fromstr(&entry.id, "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391"); entry.path = "a/b/c"; - entry.flags = (1 << GIT_IDXENTRY_STAGESHIFT); + GIT_IDXENTRY_STAGE_SET(&entry, 1); cl_git_pass(git_index_add(index, &entry)); /* create a blob beneath the previous tree entry */ entry.path = "a/b/c"; - entry.flags = (2 << GIT_IDXENTRY_STAGESHIFT); + GIT_IDXENTRY_STAGE_SET(&entry, 2); cl_git_pass(git_index_add(index, &entry)); /* create another tree entry above the blob */ entry.path = "a/b"; - entry.flags = (3 << GIT_IDXENTRY_STAGESHIFT); + GIT_IDXENTRY_STAGE_SET(&entry, 3); cl_git_pass(git_index_add(index, &entry)); git_index_free(index); diff --git a/tests/index/conflicts.c b/tests/index/conflicts.c index 3b38723bd..65f8ed599 100644 --- a/tests/index/conflicts.c +++ b/tests/index/conflicts.c @@ -48,17 +48,17 @@ void test_index_conflicts__add(void) ancestor_entry.path = "test-one.txt"; ancestor_entry.mode = 0100644; - ancestor_entry.flags |= (1 << GIT_IDXENTRY_STAGESHIFT); + GIT_IDXENTRY_STAGE_SET(&ancestor_entry, 1); git_oid_fromstr(&ancestor_entry.id, TEST_ANCESTOR_OID); our_entry.path = "test-one.txt"; our_entry.mode = 0100644; - ancestor_entry.flags |= (2 << GIT_IDXENTRY_STAGESHIFT); + GIT_IDXENTRY_STAGE_SET(&our_entry, 2); git_oid_fromstr(&our_entry.id, TEST_OUR_OID); their_entry.path = "test-one.txt"; their_entry.mode = 0100644; - ancestor_entry.flags |= (3 << GIT_IDXENTRY_STAGESHIFT); + GIT_IDXENTRY_STAGE_SET(&ancestor_entry, 2); git_oid_fromstr(&their_entry.id, TEST_THEIR_OID); cl_git_pass(git_index_conflict_add(repo_index, &ancestor_entry, &our_entry, &their_entry)); @@ -79,17 +79,17 @@ void test_index_conflicts__add_fixes_incorrect_stage(void) ancestor_entry.path = "test-one.txt"; ancestor_entry.mode = 0100644; - ancestor_entry.flags |= (3 << GIT_IDXENTRY_STAGESHIFT); + GIT_IDXENTRY_STAGE_SET(&ancestor_entry, 3); git_oid_fromstr(&ancestor_entry.id, TEST_ANCESTOR_OID); our_entry.path = "test-one.txt"; our_entry.mode = 0100644; - ancestor_entry.flags |= (1 << GIT_IDXENTRY_STAGESHIFT); + GIT_IDXENTRY_STAGE_SET(&our_entry, 1); git_oid_fromstr(&our_entry.id, TEST_OUR_OID); their_entry.path = "test-one.txt"; their_entry.mode = 0100644; - ancestor_entry.flags |= (2 << GIT_IDXENTRY_STAGESHIFT); + GIT_IDXENTRY_STAGE_SET(&their_entry, 2); git_oid_fromstr(&their_entry.id, TEST_THEIR_OID); cl_git_pass(git_index_conflict_add(repo_index, &ancestor_entry, &our_entry, &their_entry)); @@ -123,17 +123,17 @@ void test_index_conflicts__add_removes_stage_zero(void) ancestor_entry.path = "test-one.txt"; ancestor_entry.mode = 0100644; - ancestor_entry.flags |= (3 << GIT_IDXENTRY_STAGESHIFT); + GIT_IDXENTRY_STAGE_SET(&ancestor_entry, 3); git_oid_fromstr(&ancestor_entry.id, TEST_ANCESTOR_OID); our_entry.path = "test-one.txt"; our_entry.mode = 0100644; - our_entry.flags |= (1 << GIT_IDXENTRY_STAGESHIFT); + GIT_IDXENTRY_STAGE_SET(&our_entry, 1); git_oid_fromstr(&our_entry.id, TEST_OUR_OID); their_entry.path = "test-one.txt"; their_entry.mode = 0100644; - their_entry.flags |= (2 << GIT_IDXENTRY_STAGESHIFT); + GIT_IDXENTRY_STAGE_SET(&their_entry, 2); git_oid_fromstr(&their_entry.id, TEST_THEIR_OID); cl_git_pass(git_index_conflict_add(repo_index, &ancestor_entry, &our_entry, &their_entry)); @@ -329,7 +329,7 @@ void test_index_conflicts__partial(void) ancestor_entry.path = "test-one.txt"; ancestor_entry.mode = 0100644; - ancestor_entry.flags |= (1 << GIT_IDXENTRY_STAGESHIFT); + GIT_IDXENTRY_STAGE_SET(&ancestor_entry, 1); git_oid_fromstr(&ancestor_entry.id, TEST_ANCESTOR_OID); cl_git_pass(git_index_conflict_add(repo_index, &ancestor_entry, NULL, NULL)); diff --git a/tests/object/tree/duplicateentries.c b/tests/object/tree/duplicateentries.c index 65be12e9b..35dd383ec 100644 --- a/tests/object/tree/duplicateentries.c +++ b/tests/object/tree/duplicateentries.c @@ -126,17 +126,17 @@ static void add_fake_conflicts(git_index *index) ancestor_entry.path = "duplicate"; ancestor_entry.mode = GIT_FILEMODE_BLOB; - ancestor_entry.flags |= (1 << GIT_IDXENTRY_STAGESHIFT); + GIT_IDXENTRY_STAGE_SET(&ancestor_entry, 1); git_oid_fromstr(&ancestor_entry.id, "a8233120f6ad708f843d861ce2b7228ec4e3dec6"); our_entry.path = "duplicate"; our_entry.mode = GIT_FILEMODE_BLOB; - ancestor_entry.flags |= (2 << GIT_IDXENTRY_STAGESHIFT); + GIT_IDXENTRY_STAGE_SET(&our_entry, 2); git_oid_fromstr(&our_entry.id, "45b983be36b73c0788dc9cbcb76cbb80fc7bb057"); their_entry.path = "duplicate"; their_entry.mode = GIT_FILEMODE_BLOB; - ancestor_entry.flags |= (3 << GIT_IDXENTRY_STAGESHIFT); + GIT_IDXENTRY_STAGE_SET(&their_entry, 3); git_oid_fromstr(&their_entry.id, "a71586c1dfe8a71c6cbf6c129f404c5642ff31bd"); cl_git_pass(git_index_conflict_add(index, &ancestor_entry, &our_entry, &their_entry)); diff --git a/tests/reset/hard.c b/tests/reset/hard.c index 86d4be2ed..88055adda 100644 --- a/tests/reset/hard.c +++ b/tests/reset/hard.c @@ -108,7 +108,7 @@ static void index_entry_init(git_index *index, int side, git_oid *oid) memset(&entry, 0x0, sizeof(git_index_entry)); entry.path = "conflicting_file"; - entry.flags = (side << GIT_IDXENTRY_STAGESHIFT); + GIT_IDXENTRY_STAGE_SET(&entry, side); entry.mode = 0100644; git_oid_cpy(&entry.id, oid); From 9f545b9d71c7bd316be80e5fe8b47135e9deb97e Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Tue, 19 May 2015 11:23:59 -0400 Subject: [PATCH 12/17] introduce `git_index_entry_is_conflict` It's not always obvious the mapping between stage level and conflict-ness. More importantly, this can lead otherwise sane people to write constructs like `if (!git_index_entry_stage(entry))`, which (while technically correct) is unreadable. Provide a nice method to help avoid such messy thinking. --- CHANGELOG.md | 4 ++++ include/git2/index.h | 9 +++++++++ src/diff.c | 15 ++++++++------- src/index.c | 7 ++++++- src/iterator.c | 2 +- src/merge.c | 4 ++-- tests/index/conflicts.c | 4 ++-- tests/merge/trees/trivial.c | 2 +- tests/merge/workdir/trivial.c | 2 +- 9 files changed, 34 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 03e5273ed..cdd1a5d27 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -108,6 +108,10 @@ support for HTTPS connections insead of OpenSSL. `GIT_STATUS_CONFLICTED` to indicate that a conflict exists for that file in the index. +* `git_index_entry_is_conflict()` is a utility function to determine if + a given index entry has a non-zero stage entry, indicating that it is + one side of a conflict. + ### API removals * `git_remote_save()` and `git_remote_clear_refspecs()` has been diff --git a/include/git2/index.h b/include/git2/index.h index 1e1d5e748..49bbe1614 100644 --- a/include/git2/index.h +++ b/include/git2/index.h @@ -430,6 +430,15 @@ GIT_EXTERN(int) git_index_add(git_index *index, const git_index_entry *source_en */ GIT_EXTERN(int) git_index_entry_stage(const git_index_entry *entry); +/** + * Return whether the given index entry is a conflict (has a high stage + * entry). This is simply shorthand for `git_index_entry_stage > 0`. + * + * @param entry The entry + * @return 1 if the entry is a conflict entry, 0 otherwise + */ +GIT_EXTERN(int) git_index_entry_is_conflict(const git_index_entry *entry); + /**@}*/ /** @name Workdir Index Entry Functions diff --git a/src/diff.c b/src/diff.c index abe0de0d4..46f339692 100644 --- a/src/diff.c +++ b/src/diff.c @@ -180,14 +180,14 @@ static int diff_delta__from_two( GITERR_CHECK_ALLOC(delta); delta->nfiles = 2; - if (!git_index_entry_stage(old_entry)) { + if (!git_index_entry_is_conflict(old_entry)) { delta->old_file.size = old_entry->file_size; delta->old_file.mode = old_mode; git_oid_cpy(&delta->old_file.id, old_id); delta->old_file.flags |= GIT_DIFF_FLAG_VALID_ID; } - if (!git_index_entry_stage(new_entry)) { + if (!git_index_entry_is_conflict(new_entry)) { git_oid_cpy(&delta->new_file.id, new_id); delta->new_file.size = new_entry->file_size; delta->new_file.mode = new_mode; @@ -765,7 +765,8 @@ static int maybe_modified( nmode = (nmode & ~MODE_BITS_MASK) | (omode & MODE_BITS_MASK); /* if one side is a conflict, mark the whole delta as conflicted */ - if (git_index_entry_stage(oitem) > 0 || git_index_entry_stage(nitem) > 0) + if (git_index_entry_is_conflict(oitem) || + git_index_entry_is_conflict(nitem)) status = GIT_DELTA_CONFLICTED; /* support "assume unchanged" (poorly, b/c we still stat everything) */ @@ -923,8 +924,8 @@ static int iterator_advance( */ while ((error = git_iterator_advance(entry, iterator)) == 0) { if (!(iterator->flags & GIT_ITERATOR_INCLUDE_CONFLICTS) || - git_index_entry_stage(prev_entry) == 0 || - git_index_entry_stage(*entry) == 0) + !git_index_entry_is_conflict(prev_entry) || + !git_index_entry_is_conflict(*entry)) break; cmp = (iterator->flags & GIT_ITERATOR_IGNORE_CASE) ? @@ -985,7 +986,7 @@ static int handle_unmatched_new_item( contains_oitem = entry_is_prefixed(diff, info->oitem, nitem); /* update delta_type if this item is conflicted */ - if (git_index_entry_stage(nitem)) + if (git_index_entry_is_conflict(nitem)) delta_type = GIT_DELTA_CONFLICTED; /* update delta_type if this item is ignored */ @@ -1133,7 +1134,7 @@ static int handle_unmatched_old_item( int error; /* update delta_type if this item is conflicted */ - if (git_index_entry_stage(info->oitem)) + if (git_index_entry_is_conflict(info->oitem)) delta_type = GIT_DELTA_CONFLICTED; if ((error = diff_delta__from_one(diff, delta_type, info->oitem, NULL)) < 0) diff --git a/src/index.c b/src/index.c index e37cb1f4d..bd65d924b 100644 --- a/src/index.c +++ b/src/index.c @@ -1537,7 +1537,7 @@ int git_index_conflict_next( while (iterator->cur < iterator->index->entries.length) { entry = git_index_get_byindex(iterator->index, iterator->cur); - if (git_index_entry_stage(entry) > 0) { + if (git_index_entry_is_conflict(entry)) { if ((len = index_conflict__get_byindex( ancestor_out, our_out, @@ -2383,6 +2383,11 @@ int git_index_entry_stage(const git_index_entry *entry) return GIT_IDXENTRY_STAGE(entry); } +int git_index_entry_is_conflict(const git_index_entry *entry) +{ + return (GIT_IDXENTRY_STAGE(entry) > 0); +} + typedef struct read_tree_data { git_index *index; git_vector *old_entries; diff --git a/src/iterator.c b/src/iterator.c index 1e946fadc..93303a87d 100644 --- a/src/iterator.c +++ b/src/iterator.c @@ -674,7 +674,7 @@ static const git_index_entry *index_iterator__advance_over_conflicts(index_itera const git_index_entry *ie = index_iterator__index_entry(ii); if (!iterator__include_conflicts(ii)) { - while (ie && git_index_entry_stage(ie) != 0) { + while (ie && git_index_entry_is_conflict(ie)) { ii->current++; ie = index_iterator__index_entry(ii); } diff --git a/src/merge.c b/src/merge.c index 5e7727429..eaf7eee1f 100644 --- a/src/merge.c +++ b/src/merge.c @@ -2492,7 +2492,7 @@ int git_merge__check_result(git_repository *repo, git_index *index_new) for (i = 0; i < git_index_entrycount(index_new); i++) { e = git_index_get_byindex(index_new, i); - if (git_index_entry_stage(e) != 0 && + if (git_index_entry_is_conflict(e) && (git_vector_last(&paths) == NULL || strcmp(git_vector_last(&paths), e->path) != 0)) { @@ -2544,7 +2544,7 @@ int git_merge__append_conflicts_to_merge_msg( for (i = 0; i < git_index_entrycount(index); i++) { const git_index_entry *e = git_index_get_byindex(index, i); - if (git_index_entry_stage(e) == 0) + if (!git_index_entry_is_conflict(e)) continue; if (last == NULL || strcmp(e->path, last) != 0) diff --git a/tests/index/conflicts.c b/tests/index/conflicts.c index 65f8ed599..b7a2456eb 100644 --- a/tests/index/conflicts.c +++ b/tests/index/conflicts.c @@ -272,7 +272,7 @@ void test_index_conflicts__moved_to_reuc_on_add(void) cl_assert(entry = git_index_get_byindex(repo_index, i)); if (strcmp(entry->path, "conflicts-one.txt") == 0) - cl_assert(git_index_entry_stage(entry) == 0); + cl_assert(!git_index_entry_is_conflict(entry)); } } @@ -312,7 +312,7 @@ void test_index_conflicts__remove_all_conflicts(void) for (i = 0; i < git_index_entrycount(repo_index); i++) { cl_assert(entry = git_index_get_byindex(repo_index, i)); - cl_assert(git_index_entry_stage(entry) == 0); + cl_assert(!git_index_entry_is_conflict(entry)); } } diff --git a/tests/merge/trees/trivial.c b/tests/merge/trees/trivial.c index 55f38248f..2262edda6 100644 --- a/tests/merge/trees/trivial.c +++ b/tests/merge/trees/trivial.c @@ -71,7 +71,7 @@ static int merge_trivial_conflict_entrycount(git_index *index) for (i = 0; i < git_index_entrycount(index); i++) { cl_assert(entry = git_index_get_byindex(index, i)); - if (git_index_entry_stage(entry) > 0) + if (git_index_entry_is_conflict(entry)) count++; } diff --git a/tests/merge/workdir/trivial.c b/tests/merge/workdir/trivial.c index 5cc20f746..4ddaf233d 100644 --- a/tests/merge/workdir/trivial.c +++ b/tests/merge/workdir/trivial.c @@ -66,7 +66,7 @@ static size_t merge_trivial_conflict_entrycount(void) for (i = 0; i < git_index_entrycount(repo_index); i++) { cl_assert(entry = git_index_get_byindex(repo_index, i)); - if (git_index_entry_stage(entry) > 0) + if (git_index_entry_is_conflict(entry)) count++; } From 253a05f76b356c624fbb0b81235a30f897b5e24f Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Tue, 19 May 2015 11:31:15 -0400 Subject: [PATCH 13/17] diff: prettify `maybe_modified` a little --- src/diff.c | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/src/diff.c b/src/diff.c index 46f339692..d4f7d260c 100644 --- a/src/diff.c +++ b/src/diff.c @@ -766,43 +766,45 @@ static int maybe_modified( /* if one side is a conflict, mark the whole delta as conflicted */ if (git_index_entry_is_conflict(oitem) || - git_index_entry_is_conflict(nitem)) + git_index_entry_is_conflict(nitem)) { status = GIT_DELTA_CONFLICTED; /* support "assume unchanged" (poorly, b/c we still stat everything) */ - else if ((oitem->flags & GIT_IDXENTRY_VALID) != 0) + } else if ((oitem->flags & GIT_IDXENTRY_VALID) != 0) { status = GIT_DELTA_UNMODIFIED; /* support "skip worktree" index bit */ - else if ((oitem->flags_extended & GIT_IDXENTRY_SKIP_WORKTREE) != 0) + } else if ((oitem->flags_extended & GIT_IDXENTRY_SKIP_WORKTREE) != 0) { status = GIT_DELTA_UNMODIFIED; /* if basic type of file changed, then split into delete and add */ - else if (GIT_MODE_TYPE(omode) != GIT_MODE_TYPE(nmode)) { - if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE)) + } else if (GIT_MODE_TYPE(omode) != GIT_MODE_TYPE(nmode)) { + if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE)) { status = GIT_DELTA_TYPECHANGE; + } + else if (nmode == GIT_FILEMODE_UNREADABLE) { if (!(error = diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem, NULL))) error = diff_delta__from_one(diff, GIT_DELTA_UNREADABLE, NULL, nitem); return error; } + else { if (!(error = diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem, NULL))) error = diff_delta__from_one(diff, GIT_DELTA_ADDED, NULL, nitem); return error; } - } /* if oids and modes match (and are valid), then file is unmodified */ - else if (git_oid_equal(&oitem->id, &nitem->id) && + } else if (git_oid_equal(&oitem->id, &nitem->id) && omode == nmode && - !git_oid_iszero(&oitem->id)) + !git_oid_iszero(&oitem->id)) { status = GIT_DELTA_UNMODIFIED; /* if we have an unknown OID and a workdir iterator, then check some * circumstances that can accelerate things or need special handling */ - else if (git_oid_iszero(&nitem->id) && new_is_workdir) { + } else if (git_oid_iszero(&nitem->id) && new_is_workdir) { bool use_ctime = ((diff->diffcaps & GIT_DIFFCAPS_TRUST_CTIME) != 0); bool use_nanos = ((diff->diffcaps & GIT_DIFFCAPS_TRUST_NANOSECS) != 0); @@ -833,12 +835,12 @@ static int maybe_modified( status = GIT_DELTA_MODIFIED; modified_uncertain = true; } - } /* if mode is GITLINK and submodules are ignored, then skip */ - else if (S_ISGITLINK(nmode) && - DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_SUBMODULES)) + } else if (S_ISGITLINK(nmode) && + DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_SUBMODULES)) { status = GIT_DELTA_UNMODIFIED; + } /* if we got here and decided that the files are modified, but we * haven't calculated the OID of the new item, then calculate it now @@ -847,6 +849,7 @@ static int maybe_modified( const git_oid *update_check = DIFF_FLAG_IS_SET(diff, GIT_DIFF_UPDATE_INDEX) && omode == nmode ? &oitem->id : NULL; + if ((error = git_diff__oid_for_entry( &noid, diff, nitem, update_check)) < 0) return error; From 1c4b5cee0079f61c9fad77f8954a873d91f6d81a Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Tue, 19 May 2015 17:51:13 -0400 Subject: [PATCH 14/17] Introduce cl_git_sandbox_init_new() cl_git_sandbox_init_new() will create a clar temp directory and initialize a new repository at that location. --- tests/clar_libgit2.c | 8 ++++++++ tests/clar_libgit2.h | 1 + 2 files changed, 9 insertions(+) diff --git a/tests/clar_libgit2.c b/tests/clar_libgit2.c index 6087c2a67..dabc47a09 100644 --- a/tests/clar_libgit2.c +++ b/tests/clar_libgit2.c @@ -197,6 +197,14 @@ git_repository *cl_git_sandbox_init(const char *sandbox) return _cl_repo; } +git_repository *cl_git_sandbox_init_new(const char *sandbox) +{ + cl_git_pass(git_repository_init(&_cl_repo, sandbox, false)); + _cl_sandbox = sandbox; + + return _cl_repo; +} + git_repository *cl_git_sandbox_reopen(void) { if (_cl_repo) { diff --git a/tests/clar_libgit2.h b/tests/clar_libgit2.h index 86c90b049..9ab0da4f6 100644 --- a/tests/clar_libgit2.h +++ b/tests/clar_libgit2.h @@ -127,6 +127,7 @@ int cl_rename(const char *source, const char *dest); /* Git sandbox setup helpers */ git_repository *cl_git_sandbox_init(const char *sandbox); +git_repository *cl_git_sandbox_init_new(const char *name); void cl_git_sandbox_cleanup(void); git_repository *cl_git_sandbox_reopen(void); From 666ae188215557fb440b74f6af6e5cfbec27d459 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Tue, 19 May 2015 17:52:13 -0400 Subject: [PATCH 15/17] git_index_add_all: test that conflicts are handled When confronted with a conflict in the index, `git_index_add_all` should stage the working directory copy. If there is no file in the working directory, the conflict should simply be removed. --- tests/index/addall.c | 153 +++++++++++++++++++++++++++++-------------- 1 file changed, 104 insertions(+), 49 deletions(-) diff --git a/tests/index/addall.c b/tests/index/addall.c index 377d2079e..211e76228 100644 --- a/tests/index/addall.c +++ b/tests/index/addall.c @@ -12,10 +12,7 @@ void test_index_addall__initialize(void) void test_index_addall__cleanup(void) { - git_repository_free(g_repo); - g_repo = NULL; - - cl_fixture_cleanup(TEST_DIR); + cl_git_sandbox_cleanup(); } #define STATUS_INDEX_FLAGS \ @@ -36,6 +33,7 @@ typedef struct { size_t wt_dels; size_t wt_mods; size_t ignores; + size_t conflicts; } index_status_counts; static int index_status_cb( @@ -67,6 +65,8 @@ static int index_status_cb( if (status_flags & GIT_STATUS_IGNORED) vals->ignores++; + if (status_flags & GIT_STATUS_CONFLICTED) + vals->conflicts++; return 0; } @@ -75,7 +75,7 @@ static void check_status_at_line( git_repository *repo, size_t index_adds, size_t index_dels, size_t index_mods, size_t wt_adds, size_t wt_dels, size_t wt_mods, size_t ignores, - const char *file, int line) + size_t conflicts, const char *file, int line) { index_status_counts vals; @@ -97,10 +97,12 @@ static void check_status_at_line( file,line,"wrong workdir mods", 1, "%"PRIuZ, wt_mods, vals.wt_mods); clar__assert_equal( file,line,"wrong ignores", 1, "%"PRIuZ, ignores, vals.ignores); + clar__assert_equal( + file,line,"wrong conflicts", 1, "%"PRIuZ, conflicts, vals.conflicts); } -#define check_status(R,IA,ID,IM,WA,WD,WM,IG) \ - check_status_at_line(R,IA,ID,IM,WA,WD,WM,IG,__FILE__,__LINE__) +#define check_status(R,IA,ID,IM,WA,WD,WM,IG,C) \ + check_status_at_line(R,IA,ID,IM,WA,WD,WM,IG,C,__FILE__,__LINE__) static void check_stat_data(git_index *index, const char *path, bool match) { @@ -137,21 +139,22 @@ static void check_stat_data(git_index *index, const char *path, bool match) static void addall_create_test_repo(bool check_every_step) { - cl_git_pass(git_repository_init(&g_repo, TEST_DIR, false)); + g_repo = cl_git_sandbox_init_new(TEST_DIR); + if (check_every_step) - check_status(g_repo, 0, 0, 0, 0, 0, 0, 0); + check_status(g_repo, 0, 0, 0, 0, 0, 0, 0, 0); cl_git_mkfile(TEST_DIR "/file.foo", "a file"); if (check_every_step) - check_status(g_repo, 0, 0, 0, 1, 0, 0, 0); + check_status(g_repo, 0, 0, 0, 1, 0, 0, 0, 0); cl_git_mkfile(TEST_DIR "/.gitignore", "*.foo\n"); if (check_every_step) - check_status(g_repo, 0, 0, 0, 1, 0, 0, 1); + check_status(g_repo, 0, 0, 0, 1, 0, 0, 1, 0); cl_git_mkfile(TEST_DIR "/file.bar", "another file"); if (check_every_step) - check_status(g_repo, 0, 0, 0, 2, 0, 0, 1); + check_status(g_repo, 0, 0, 0, 2, 0, 0, 1, 0); } void test_index_addall__repo_lifecycle(void) @@ -171,107 +174,107 @@ void test_index_addall__repo_lifecycle(void) cl_git_pass(git_index_add_all(index, &paths, 0, NULL, NULL)); check_stat_data(index, TEST_DIR "/file.bar", true); - check_status(g_repo, 1, 0, 0, 1, 0, 0, 1); + check_status(g_repo, 1, 0, 0, 1, 0, 0, 1, 0); cl_git_rewritefile(TEST_DIR "/file.bar", "new content for file"); check_stat_data(index, TEST_DIR "/file.bar", false); - check_status(g_repo, 1, 0, 0, 1, 0, 1, 1); + check_status(g_repo, 1, 0, 0, 1, 0, 1, 1, 0); cl_git_mkfile(TEST_DIR "/file.zzz", "yet another one"); cl_git_mkfile(TEST_DIR "/other.zzz", "yet another one"); cl_git_mkfile(TEST_DIR "/more.zzz", "yet another one"); - check_status(g_repo, 1, 0, 0, 4, 0, 1, 1); + check_status(g_repo, 1, 0, 0, 4, 0, 1, 1, 0); cl_git_pass(git_index_update_all(index, NULL, NULL, NULL)); check_stat_data(index, TEST_DIR "/file.bar", true); - check_status(g_repo, 1, 0, 0, 4, 0, 0, 1); + check_status(g_repo, 1, 0, 0, 4, 0, 0, 1, 0); cl_git_pass(git_index_add_all(index, &paths, 0, NULL, NULL)); check_stat_data(index, TEST_DIR "/file.zzz", true); - check_status(g_repo, 2, 0, 0, 3, 0, 0, 1); + check_status(g_repo, 2, 0, 0, 3, 0, 0, 1, 0); cl_repo_commit_from_index(NULL, g_repo, NULL, 0, "first commit"); - check_status(g_repo, 0, 0, 0, 3, 0, 0, 1); + check_status(g_repo, 0, 0, 0, 3, 0, 0, 1, 0); if (cl_repo_get_bool(g_repo, "core.filemode")) { cl_git_pass(git_index_update_all(index, NULL, NULL, NULL)); cl_must_pass(p_chmod(TEST_DIR "/file.zzz", 0777)); cl_git_pass(git_index_update_all(index, NULL, NULL, NULL)); - check_status(g_repo, 0, 0, 1, 3, 0, 0, 1); + check_status(g_repo, 0, 0, 1, 3, 0, 0, 1, 0); /* go back to what we had before */ cl_must_pass(p_chmod(TEST_DIR "/file.zzz", 0666)); cl_git_pass(git_index_update_all(index, NULL, NULL, NULL)); - check_status(g_repo, 0, 0, 0, 3, 0, 0, 1); + check_status(g_repo, 0, 0, 0, 3, 0, 0, 1, 0); } /* attempt to add an ignored file - does nothing */ strs[0] = "file.foo"; cl_git_pass(git_index_add_all(index, &paths, 0, NULL, NULL)); - check_status(g_repo, 0, 0, 0, 3, 0, 0, 1); + check_status(g_repo, 0, 0, 0, 3, 0, 0, 1, 0); /* add with check - should generate error */ error = git_index_add_all( index, &paths, GIT_INDEX_ADD_CHECK_PATHSPEC, NULL, NULL); cl_assert_equal_i(GIT_EINVALIDSPEC, error); - check_status(g_repo, 0, 0, 0, 3, 0, 0, 1); + check_status(g_repo, 0, 0, 0, 3, 0, 0, 1, 0); /* add with force - should allow */ cl_git_pass(git_index_add_all( index, &paths, GIT_INDEX_ADD_FORCE, NULL, NULL)); check_stat_data(index, TEST_DIR "/file.foo", true); - check_status(g_repo, 1, 0, 0, 3, 0, 0, 0); + check_status(g_repo, 1, 0, 0, 3, 0, 0, 0, 0); /* now it's in the index, so regular add should work */ cl_git_rewritefile(TEST_DIR "/file.foo", "new content for file"); check_stat_data(index, TEST_DIR "/file.foo", false); - check_status(g_repo, 1, 0, 0, 3, 0, 1, 0); + check_status(g_repo, 1, 0, 0, 3, 0, 1, 0, 0); cl_git_pass(git_index_add_all(index, &paths, 0, NULL, NULL)); check_stat_data(index, TEST_DIR "/file.foo", true); - check_status(g_repo, 1, 0, 0, 3, 0, 0, 0); + check_status(g_repo, 1, 0, 0, 3, 0, 0, 0, 0); cl_git_pass(git_index_add_bypath(index, "more.zzz")); check_stat_data(index, TEST_DIR "/more.zzz", true); - check_status(g_repo, 2, 0, 0, 2, 0, 0, 0); + check_status(g_repo, 2, 0, 0, 2, 0, 0, 0, 0); cl_git_rewritefile(TEST_DIR "/file.zzz", "new content for file"); - check_status(g_repo, 2, 0, 0, 2, 0, 1, 0); + check_status(g_repo, 2, 0, 0, 2, 0, 1, 0, 0); cl_git_pass(git_index_add_bypath(index, "file.zzz")); check_stat_data(index, TEST_DIR "/file.zzz", true); - check_status(g_repo, 2, 0, 1, 2, 0, 0, 0); + check_status(g_repo, 2, 0, 1, 2, 0, 0, 0, 0); strs[0] = "*.zzz"; cl_git_pass(git_index_remove_all(index, &paths, NULL, NULL)); - check_status(g_repo, 1, 1, 0, 4, 0, 0, 0); + check_status(g_repo, 1, 1, 0, 4, 0, 0, 0, 0); cl_git_pass(git_index_add_bypath(index, "file.zzz")); - check_status(g_repo, 1, 0, 1, 3, 0, 0, 0); + check_status(g_repo, 1, 0, 1, 3, 0, 0, 0, 0); cl_repo_commit_from_index(NULL, g_repo, NULL, 0, "second commit"); - check_status(g_repo, 0, 0, 0, 3, 0, 0, 0); + check_status(g_repo, 0, 0, 0, 3, 0, 0, 0, 0); cl_must_pass(p_unlink(TEST_DIR "/file.zzz")); - check_status(g_repo, 0, 0, 0, 3, 1, 0, 0); + check_status(g_repo, 0, 0, 0, 3, 1, 0, 0, 0); /* update_all should be able to remove entries */ cl_git_pass(git_index_update_all(index, NULL, NULL, NULL)); - check_status(g_repo, 0, 1, 0, 3, 0, 0, 0); + check_status(g_repo, 0, 1, 0, 3, 0, 0, 0, 0); strs[0] = "*"; cl_git_pass(git_index_add_all(index, &paths, 0, NULL, NULL)); - check_status(g_repo, 3, 1, 0, 0, 0, 0, 0); + check_status(g_repo, 3, 1, 0, 0, 0, 0, 0, 0); /* must be able to remove at any position while still updating other files */ cl_must_pass(p_unlink(TEST_DIR "/.gitignore")); cl_git_rewritefile(TEST_DIR "/file.zzz", "reconstructed file"); cl_git_rewritefile(TEST_DIR "/more.zzz", "altered file reality"); - check_status(g_repo, 3, 1, 0, 1, 1, 1, 0); + check_status(g_repo, 3, 1, 0, 1, 1, 1, 0, 0); cl_git_pass(git_index_update_all(index, NULL, NULL, NULL)); - check_status(g_repo, 2, 1, 0, 1, 0, 0, 0); + check_status(g_repo, 2, 1, 0, 1, 0, 0, 0, 0); /* this behavior actually matches 'git add -u' where "file.zzz" has * been removed from the index, so when you go to update, even though * it exists in the HEAD, it is not re-added to the index, leaving it @@ -293,14 +296,14 @@ void test_index_addall__files_in_folders(void) cl_git_pass(git_index_add_all(index, NULL, 0, NULL, NULL)); check_stat_data(index, TEST_DIR "/file.bar", true); - check_status(g_repo, 2, 0, 0, 0, 0, 0, 1); + check_status(g_repo, 2, 0, 0, 0, 0, 0, 1, 0); cl_must_pass(p_mkdir(TEST_DIR "/subdir", 0777)); cl_git_mkfile(TEST_DIR "/subdir/file", "hello!\n"); - check_status(g_repo, 2, 0, 0, 1, 0, 0, 1); + check_status(g_repo, 2, 0, 0, 1, 0, 0, 1, 0); cl_git_pass(git_index_add_all(index, NULL, 0, NULL, NULL)); - check_status(g_repo, 3, 0, 0, 0, 0, 0, 1); + check_status(g_repo, 3, 0, 0, 0, 0, 0, 1, 0); git_index_free(index); } @@ -337,7 +340,7 @@ void test_index_addall__callback_filtering(void) cl_git_pass( git_index_add_all(index, NULL, 0, addall_match_prefix, "file.")); check_stat_data(index, TEST_DIR "/file.bar", true); - check_status(g_repo, 1, 0, 0, 1, 0, 0, 1); + check_status(g_repo, 1, 0, 0, 1, 0, 0, 1, 0); cl_git_mkfile(TEST_DIR "/file.zzz", "yet another one"); cl_git_mkfile(TEST_DIR "/more.zzz", "yet another one"); @@ -345,32 +348,32 @@ void test_index_addall__callback_filtering(void) cl_git_pass(git_index_update_all(index, NULL, NULL, NULL)); check_stat_data(index, TEST_DIR "/file.bar", true); - check_status(g_repo, 1, 0, 0, 4, 0, 0, 1); + check_status(g_repo, 1, 0, 0, 4, 0, 0, 1, 0); cl_git_pass( git_index_add_all(index, NULL, 0, addall_match_prefix, "other")); check_stat_data(index, TEST_DIR "/other.zzz", true); - check_status(g_repo, 2, 0, 0, 3, 0, 0, 1); + check_status(g_repo, 2, 0, 0, 3, 0, 0, 1, 0); cl_git_pass( git_index_add_all(index, NULL, 0, addall_match_suffix, ".zzz")); - check_status(g_repo, 4, 0, 0, 1, 0, 0, 1); + check_status(g_repo, 4, 0, 0, 1, 0, 0, 1, 0); cl_git_pass( git_index_remove_all(index, NULL, addall_match_suffix, ".zzz")); - check_status(g_repo, 1, 0, 0, 4, 0, 0, 1); + check_status(g_repo, 1, 0, 0, 4, 0, 0, 1, 0); cl_git_fail_with( git_index_add_all(index, NULL, 0, addall_cancel_at, "more.zzz"), -123); - check_status(g_repo, 3, 0, 0, 2, 0, 0, 1); + check_status(g_repo, 3, 0, 0, 2, 0, 0, 1, 0); cl_git_fail_with( git_index_add_all(index, NULL, 0, addall_cancel_at, "other.zzz"), -123); - check_status(g_repo, 4, 0, 0, 1, 0, 0, 1); + check_status(g_repo, 4, 0, 0, 1, 0, 0, 1, 0); cl_git_pass( git_index_add_all(index, NULL, 0, addall_match_suffix, ".zzz")); - check_status(g_repo, 5, 0, 0, 0, 0, 0, 1); + check_status(g_repo, 5, 0, 0, 0, 0, 0, 1, 0); cl_must_pass(p_unlink(TEST_DIR "/file.zzz")); cl_must_pass(p_unlink(TEST_DIR "/more.zzz")); @@ -380,13 +383,65 @@ void test_index_addall__callback_filtering(void) git_index_update_all(index, NULL, addall_cancel_at, "more.zzz"), -123); /* file.zzz removed from index (so Index Adds 5 -> 4) and * more.zzz + other.zzz removed (so Worktree Dels 0 -> 2) */ - check_status(g_repo, 4, 0, 0, 0, 2, 0, 1); + check_status(g_repo, 4, 0, 0, 0, 2, 0, 1, 0); cl_git_fail_with( git_index_update_all(index, NULL, addall_cancel_at, "other.zzz"), -123); /* more.zzz removed from index (so Index Adds 4 -> 3) and * Just other.zzz removed (so Worktree Dels 2 -> 1) */ - check_status(g_repo, 3, 0, 0, 0, 1, 0, 1); + check_status(g_repo, 3, 0, 0, 0, 1, 0, 1, 0); git_index_free(index); } + +void test_index_addall__adds_conflicts(void) +{ + git_index *index; + git_reference *ref; + git_annotated_commit *annotated; + + g_repo = cl_git_sandbox_init("merge-resolve"); + cl_git_pass(git_repository_index(&index, g_repo)); + + check_status(g_repo, 0, 0, 0, 0, 0, 0, 0, 0); + + cl_git_pass(git_reference_lookup(&ref, g_repo, "refs/heads/branch")); + cl_git_pass(git_annotated_commit_from_ref(&annotated, g_repo, ref)); + + cl_git_pass(git_merge(g_repo, &annotated, 1, NULL, NULL)); + check_status(g_repo, 0, 1, 2, 0, 0, 0, 0, 1); + + cl_git_pass(git_index_add_all(index, NULL, 0, NULL, NULL)); + check_status(g_repo, 0, 1, 3, 0, 0, 0, 0, 0); + + git_annotated_commit_free(annotated); + git_reference_free(ref); + git_index_free(index); +} + +void test_index_addall__removes_deleted_conflicted_files(void) +{ + git_index *index; + git_reference *ref; + git_annotated_commit *annotated; + + g_repo = cl_git_sandbox_init("merge-resolve"); + cl_git_pass(git_repository_index(&index, g_repo)); + + check_status(g_repo, 0, 0, 0, 0, 0, 0, 0, 0); + + cl_git_pass(git_reference_lookup(&ref, g_repo, "refs/heads/branch")); + cl_git_pass(git_annotated_commit_from_ref(&annotated, g_repo, ref)); + + cl_git_pass(git_merge(g_repo, &annotated, 1, NULL, NULL)); + check_status(g_repo, 0, 1, 2, 0, 0, 0, 0, 1); + + cl_git_rmfile("merge-resolve/conflicting.txt"); + + cl_git_pass(git_index_add_all(index, NULL, 0, NULL, NULL)); + check_status(g_repo, 0, 2, 2, 0, 0, 0, 0, 0); + + git_annotated_commit_free(annotated); + git_reference_free(ref); + git_index_free(index); +} \ No newline at end of file From 10549a2df166cd3b951c65ab89c33f7ef21c6b7a Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Tue, 19 May 2015 18:26:04 -0400 Subject: [PATCH 16/17] Introduce `GIT_DIFF_FLAG_EXISTS` Mark the `old_file` and `new_file` sides of a delta with a new bit, `GIT_DIFF_FLAG_EXISTS`, that introduces that a particular side of the delta exists in the diff. This is useful for indicating whether a working directory item exists or not, in the presence of a conflict. Diff users may have previously used DELETED to determine this information. --- CHANGELOG.md | 5 +++++ include/git2/diff.h | 1 + src/diff.c | 7 ++++++- 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cdd1a5d27..cf65fcfbd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,6 +52,11 @@ support for HTTPS connections insead of OpenSSL. * `git_index_conflict_add()` will remove staged entries that exist for conflicted paths. +* The flags for a `git_diff_file` will now have the `GIT_DIFF_FLAG_EXISTS` + bit set when a file exists on that side of the diff. This is useful + for understanding whether a side of the diff exists in the presence of + a conflict. + ### API additions * The `git_merge_options` gained a `file_flags` member. diff --git a/include/git2/diff.h b/include/git2/diff.h index 7505c08c2..c0d42e30e 100644 --- a/include/git2/diff.h +++ b/include/git2/diff.h @@ -226,6 +226,7 @@ typedef enum { GIT_DIFF_FLAG_BINARY = (1u << 0), /**< file(s) treated as binary data */ GIT_DIFF_FLAG_NOT_BINARY = (1u << 1), /**< file(s) treated as text data */ GIT_DIFF_FLAG_VALID_ID = (1u << 2), /**< `id` value is known correct */ + GIT_DIFF_FLAG_EXISTS = (1u << 3), /**< file exists at this side of the delta */ } git_diff_flag_t; /** diff --git a/src/diff.c b/src/diff.c index d4f7d260c..c9d1d8730 100644 --- a/src/diff.c +++ b/src/diff.c @@ -127,10 +127,12 @@ static int diff_delta__from_one( if (has_old) { delta->old_file.mode = entry->mode; delta->old_file.size = entry->file_size; + delta->old_file.flags |= GIT_DIFF_FLAG_EXISTS; git_oid_cpy(&delta->old_file.id, &entry->id); } else /* ADDED, IGNORED, UNTRACKED */ { delta->new_file.mode = entry->mode; delta->new_file.size = entry->file_size; + delta->new_file.flags |= GIT_DIFF_FLAG_EXISTS; git_oid_cpy(&delta->new_file.id, &entry->id); } @@ -184,13 +186,16 @@ static int diff_delta__from_two( delta->old_file.size = old_entry->file_size; delta->old_file.mode = old_mode; git_oid_cpy(&delta->old_file.id, old_id); - delta->old_file.flags |= GIT_DIFF_FLAG_VALID_ID; + delta->old_file.flags |= GIT_DIFF_FLAG_VALID_ID | + GIT_DIFF_FLAG_EXISTS; } if (!git_index_entry_is_conflict(new_entry)) { git_oid_cpy(&delta->new_file.id, new_id); delta->new_file.size = new_entry->file_size; delta->new_file.mode = new_mode; + delta->old_file.flags |= GIT_DIFF_FLAG_EXISTS; + delta->new_file.flags |= GIT_DIFF_FLAG_EXISTS; if (!git_oid_iszero(&new_entry->id)) delta->new_file.flags |= GIT_DIFF_FLAG_VALID_ID; From 9b3e41f72bd9ab62e8d129713755c3a3a71359bc Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Tue, 19 May 2015 18:29:15 -0400 Subject: [PATCH 17/17] index_add_all: remove conflicts when no wd file If there exists a conflict in the index, but no file in the working directory, this implies that the user wants to accept the resolution by removing the file. Thus, remove the conflict entry from the index, instead of trying to add a (nonexistent) file. --- src/index.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/index.c b/src/index.c index bd65d924b..0422ff1d4 100644 --- a/src/index.c +++ b/src/index.c @@ -2670,7 +2670,8 @@ static int apply_each_file(const git_diff_delta *delta, float progress, void *pa if (error < 0) /* actual error */ return error; - if (delta->status == GIT_DELTA_DELETED) + /* If the workdir item does not exist, remove it from the index. */ + if ((delta->new_file.flags & GIT_DIFF_FLAG_EXISTS) == 0) error = git_index_remove_bypath(data->index, path); else error = git_index_add_bypath(data->index, delta->new_file.path);