checkout: add notification callback for skipped files
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
diff --git a/include/git2/checkout.h b/include/git2/checkout.h
index 42d4700..ef3badb 100644
--- a/include/git2/checkout.h
+++ b/include/git2/checkout.h
@@ -36,6 +36,21 @@ typedef struct git_checkout_opts {
int file_mode; /* default is 0644 */
int file_open_flags; /* default is O_CREAT | O_TRUNC | O_WRONLY */
+ /* Optional callback to notify the consumer of files that
+ * haven't be checked out because a modified version of them
+ * exist in the working directory.
+ *
+ * When provided, this callback will be invoked when the flag
+ * GIT_CHECKOUT_OVERWRITE_MODIFIED isn't part of the checkout strategy.
+ */
+ int (* skipped_notify_cb)(
+ const char *skipped_file,
+ const git_oid *blob_oid,
+ int file_mode,
+ void *payload);
+
+ void *notify_payload;
+
/* when not NULL, arrays of fnmatch pattern specifying
* which paths should be taken into account
*/
diff --git a/src/checkout.c b/src/checkout.c
index 89f7354..ea5e79a 100644
--- a/src/checkout.c
+++ b/src/checkout.c
@@ -155,6 +155,7 @@ static int checkout_diff_fn(
{
struct checkout_diff_data *data;
int error = -1;
+ git_checkout_opts *opts;
data = (struct checkout_diff_data *)cb_data;
@@ -164,9 +165,11 @@ static int checkout_diff_fn(
if (git_buf_joinpath(data->path, git_buf_cstr(data->path), delta->new_file.path) < 0)
return -1;
+ opts = data->checkout_opts;
+
switch (delta->status) {
case GIT_DELTA_UNTRACKED:
- if (!(data->checkout_opts->checkout_strategy & GIT_CHECKOUT_REMOVE_UNTRACKED))
+ if (!(opts->checkout_strategy & GIT_CHECKOUT_REMOVE_UNTRACKED))
return 0;
if (!git__suffixcmp(delta->new_file.path, "/"))
@@ -176,8 +179,20 @@ static int checkout_diff_fn(
break;
case GIT_DELTA_MODIFIED:
- if (!(data->checkout_opts->checkout_strategy & GIT_CHECKOUT_OVERWRITE_MODIFIED))
+ if (!(opts->checkout_strategy & GIT_CHECKOUT_OVERWRITE_MODIFIED)) {
+
+ if ((opts->skipped_notify_cb != NULL)
+ && (opts->skipped_notify_cb(
+ delta->new_file.path,
+ &delta->old_file.oid,
+ delta->old_file.mode,
+ opts->notify_payload))) {
+ giterr_clear();
+ return GIT_EUSER;
+ }
+
return 0;
+ }
if (checkout_blob(
data->owner,
@@ -185,13 +200,13 @@ static int checkout_diff_fn(
git_buf_cstr(data->path),
delta->old_file.mode,
data->can_symlink,
- data->checkout_opts) < 0)
+ opts) < 0)
goto cleanup;
break;
case GIT_DELTA_DELETED:
- if (!(data->checkout_opts->checkout_strategy & GIT_CHECKOUT_CREATE_MISSING))
+ if (!(opts->checkout_strategy & GIT_CHECKOUT_CREATE_MISSING))
return 0;
if (checkout_blob(
@@ -200,7 +215,7 @@ static int checkout_diff_fn(
git_buf_cstr(data->path),
delta->old_file.mode,
data->can_symlink,
- data->checkout_opts) < 0)
+ opts) < 0)
goto cleanup;
break;
@@ -378,4 +393,3 @@ int git_checkout_head(
return error;
}
-
diff --git a/tests-clar/checkout/index.c b/tests-clar/checkout/index.c
index d1c59e3..f017a0f 100644
--- a/tests-clar/checkout/index.c
+++ b/tests-clar/checkout/index.c
@@ -287,3 +287,76 @@ void test_checkout_index__options_open_flags(void)
test_file_contents("./testrepo/new.txt", "hi\nmy new file\n");
}
+
+struct notify_data {
+ const char *file;
+ const char *sha;
+};
+
+static int notify_cb(
+ const char *skipped_file,
+ const git_oid *blob_oid,
+ int file_mode,
+ void *payload)
+{
+ struct notify_data *expectations = (struct notify_data *)payload;
+
+ GIT_UNUSED(file_mode);
+
+ cl_assert_equal_s(expectations->file, skipped_file);
+ cl_assert_equal_i(0, git_oid_streq(blob_oid, expectations->sha));
+
+ return 0;
+}
+
+void test_checkout_index__can_notify_of_skipped_files(void)
+{
+ struct notify_data data;
+
+ cl_git_mkfile("./testrepo/new.txt", "This isn't what's stored!");
+
+ /*
+ * $ git ls-tree HEAD
+ * 100644 blob a8233120f6ad708f843d861ce2b7228ec4e3dec6 README
+ * 100644 blob 3697d64be941a53d4ae8f6a271e4e3fa56b022cc branch_file.txt
+ * 100644 blob a71586c1dfe8a71c6cbf6c129f404c5642ff31bd new.txt
+ */
+ data.file = "new.txt";
+ data.sha = "a71586c1dfe8a71c6cbf6c129f404c5642ff31bd";
+
+ g_opts.checkout_strategy = GIT_CHECKOUT_CREATE_MISSING;
+ g_opts.skipped_notify_cb = notify_cb;
+ g_opts.notify_payload = &data;
+
+ cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL));
+}
+
+static int dont_notify_cb(
+ const char *skipped_file,
+ const git_oid *blob_oid,
+ int file_mode,
+ void *payload)
+{
+ GIT_UNUSED(skipped_file);
+ GIT_UNUSED(blob_oid);
+ GIT_UNUSED(file_mode);
+ GIT_UNUSED(payload);
+
+ cl_assert(false);
+
+ return 0;
+}
+
+void test_checkout_index__wont_notify_of_expected_line_ending_changes(void)
+{
+ cl_git_pass(p_unlink("./testrepo/.gitattributes"));
+ set_core_autocrlf_to(true);
+
+ cl_git_mkfile("./testrepo/new.txt", "my new file\r\n");
+
+ g_opts.checkout_strategy = GIT_CHECKOUT_CREATE_MISSING;
+ g_opts.skipped_notify_cb = dont_notify_cb;
+ g_opts.notify_payload = NULL;
+
+ cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL));
+}