Fix checkout of modified file when missing from wd This fixes the checkout case when a file is modified between the baseline and the target and yet missing in the working directory. The logic for that case appears to have been wrong. This also adds a useful checkout notify callback to the checkout test helpers that will count notifications and also has a debug mode to visualize what checkout thinks that it's doing.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192
diff --git a/include/git2/checkout.h b/include/git2/checkout.h
index 6798bf3..f49e875 100644
--- a/include/git2/checkout.h
+++ b/include/git2/checkout.h
@@ -183,6 +183,8 @@ typedef enum {
GIT_CHECKOUT_NOTIFY_UPDATED = (1u << 2),
GIT_CHECKOUT_NOTIFY_UNTRACKED = (1u << 3),
GIT_CHECKOUT_NOTIFY_IGNORED = (1u << 4),
+
+ GIT_CHECKOUT_NOTIFY_ALL = 0x0FFFFu
} git_checkout_notify_t;
/** Checkout notification callback function */
diff --git a/src/checkout.c b/src/checkout.c
index ede0be8..065bb50 100644
--- a/src/checkout.c
+++ b/src/checkout.c
@@ -220,9 +220,11 @@ static int checkout_action_no_wd(
action = CHECKOUT_ACTION_IF(SAFE_CREATE, UPDATE_BLOB, NONE);
break;
case GIT_DELTA_ADDED: /* case 2 or 28 (and 5 but not really) */
- case GIT_DELTA_MODIFIED: /* case 13 (and 35 but not really) */
action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE);
break;
+ case GIT_DELTA_MODIFIED: /* case 13 (and 35 but not really) */
+ action = CHECKOUT_ACTION_IF(SAFE_CREATE, UPDATE_BLOB, CONFLICT);
+ break;
case GIT_DELTA_TYPECHANGE: /* case 21 (B->T) and 28 (T->B)*/
if (delta->new_file.mode == GIT_FILEMODE_TREE)
action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE);
diff --git a/tests-clar/checkout/checkout_helpers.c b/tests-clar/checkout/checkout_helpers.c
index ab93a89..8da024d 100644
--- a/tests-clar/checkout/checkout_helpers.c
+++ b/tests-clar/checkout/checkout_helpers.c
@@ -91,3 +91,98 @@ void check_file_contents_nocr_at_line(
{
check_file_contents_internal(path, expected, true, file, line, msg);
}
+
+int checkout_count_callback(
+ git_checkout_notify_t why,
+ const char *path,
+ const git_diff_file *baseline,
+ const git_diff_file *target,
+ const git_diff_file *workdir,
+ void *payload)
+{
+ checkout_counts *ct = payload;
+
+ GIT_UNUSED(baseline); GIT_UNUSED(target); GIT_UNUSED(workdir);
+
+ if (why & GIT_CHECKOUT_NOTIFY_CONFLICT) {
+ ct->n_conflicts++;
+
+ if (ct->debug) {
+ if (workdir) {
+ if (baseline) {
+ if (target)
+ fprintf(stderr, "M %s (conflicts with M %s)\n",
+ workdir->path, target->path);
+ else
+ fprintf(stderr, "M %s (conflicts with D %s)\n",
+ workdir->path, baseline->path);
+ } else {
+ if (target)
+ fprintf(stderr, "Existing %s (conflicts with A %s)\n",
+ workdir->path, target->path);
+ else
+ fprintf(stderr, "How can an untracked file be a conflict (%s)\n", workdir->path);
+ }
+ } else {
+ if (baseline) {
+ if (target)
+ fprintf(stderr, "D %s (conflicts with M %s)\n",
+ target->path, baseline->path);
+ else
+ fprintf(stderr, "D %s (conflicts with D %s)\n",
+ baseline->path, baseline->path);
+ } else {
+ if (target)
+ fprintf(stderr, "How can an added file with no workdir be a conflict (%s)\n", target->path);
+ else
+ fprintf(stderr, "How can a nonexistent file be a conflict (%s)\n", path);
+ }
+ }
+ }
+ }
+
+ if (why & GIT_CHECKOUT_NOTIFY_DIRTY) {
+ ct->n_dirty++;
+
+ if (ct->debug) {
+ if (workdir)
+ fprintf(stderr, "M %s\n", workdir->path);
+ else
+ fprintf(stderr, "D %s\n", baseline->path);
+ }
+ }
+
+ if (why & GIT_CHECKOUT_NOTIFY_UPDATED) {
+ ct->n_updates++;
+
+ if (ct->debug) {
+ if (baseline) {
+ if (target)
+ fprintf(stderr, "update: M %s\n", path);
+ else
+ fprintf(stderr, "update: D %s\n", path);
+ } else {
+ if (target)
+ fprintf(stderr, "update: A %s\n", path);
+ else
+ fprintf(stderr, "update: this makes no sense %s\n", path);
+ }
+ }
+ }
+
+ if (why & GIT_CHECKOUT_NOTIFY_UNTRACKED) {
+ ct->n_untracked++;
+
+ if (ct->debug)
+ fprintf(stderr, "? %s\n", path);
+ }
+
+ if (why & GIT_CHECKOUT_NOTIFY_IGNORED) {
+ ct->n_ignored++;
+
+ if (ct->debug)
+ fprintf(stderr, "I %s\n", path);
+ }
+
+ return 0;
+}
diff --git a/tests-clar/checkout/checkout_helpers.h b/tests-clar/checkout/checkout_helpers.h
index 3405380..0e8da31 100644
--- a/tests-clar/checkout/checkout_helpers.h
+++ b/tests-clar/checkout/checkout_helpers.h
@@ -19,3 +19,20 @@ extern void check_file_contents_nocr_at_line(
#define check_file_contents_nocr(PATH,EXP) \
check_file_contents_nocr_at_line(PATH,EXP,__FILE__,__LINE__,"String mismatch: " #EXP " != " #PATH)
+
+typedef struct {
+ int n_conflicts;
+ int n_dirty;
+ int n_updates;
+ int n_untracked;
+ int n_ignored;
+ int debug;
+} checkout_counts;
+
+extern int checkout_count_callback(
+ git_checkout_notify_t why,
+ const char *path,
+ const git_diff_file *baseline,
+ const git_diff_file *target,
+ const git_diff_file *workdir,
+ void *payload);
diff --git a/tests-clar/checkout/tree.c b/tests-clar/checkout/tree.c
index 7a1c797..5835ab0 100644
--- a/tests-clar/checkout/tree.c
+++ b/tests-clar/checkout/tree.c
@@ -448,9 +448,15 @@ void test_checkout_tree__donot_update_deleted_file_by_default(void)
git_oid old_id, new_id;
git_commit *old_commit = NULL, *new_commit = NULL;
git_index *index = NULL;
+ checkout_counts ct;
opts.checkout_strategy = GIT_CHECKOUT_SAFE;
+ memset(&ct, 0, sizeof(ct));
+ opts.notify_flags = GIT_CHECKOUT_NOTIFY_ALL;
+ opts.notify_cb = checkout_count_callback;
+ opts.notify_payload = &ct;
+
cl_git_pass(git_repository_index(&index, g_repo));
cl_git_pass(git_oid_fromstr(&old_id, "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"));
@@ -465,8 +471,13 @@ void test_checkout_tree__donot_update_deleted_file_by_default(void)
cl_git_pass(git_oid_fromstr(&new_id, "099fabac3a9ea935598528c27f866e34089c2eff"));
cl_git_pass(git_commit_lookup(&new_commit, g_repo, &new_id));
+
+
cl_git_fail(git_checkout_tree(g_repo, (git_object *)new_commit, &opts));
+ cl_assert_equal_i(1, ct.n_conflicts);
+ cl_assert_equal_i(1, ct.n_updates);
+
git_commit_free(old_commit);
git_commit_free(new_commit);
git_index_free(index);