summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/git2/checkout.h21
-rw-r--r--src/checkout.c4
-rw-r--r--tests/checkout/tree.c78
3 files changed, 93 insertions, 10 deletions
diff --git a/include/git2/checkout.h b/include/git2/checkout.h
index efafdc3e4..0e9d338c6 100644
--- a/include/git2/checkout.h
+++ b/include/git2/checkout.h
@@ -174,7 +174,12 @@ typedef enum {
* - GIT_CHECKOUT_NOTIFY_IGNORED notifies about ignored files.
*
* Returning a non-zero value from this callback will cancel the checkout.
- * Notification callbacks are made prior to modifying any files on disk.
+ * The non-zero return value will be propagated back and returned by the
+ * git_checkout_... call.
+ *
+ * Notification callbacks are made prior to modifying any files on disk,
+ * so canceling on any notification will still happen prior to any files
+ * being modified.
*/
typedef enum {
GIT_CHECKOUT_NOTIFY_NONE = 0,
@@ -252,9 +257,9 @@ typedef struct git_checkout_opts {
*
* @param repo repository to check out (must be non-bare)
* @param opts specifies checkout options (may be NULL)
- * @return 0 on success, GIT_EUNBORNBRANCH when HEAD points to a non existing
- * branch, GIT_ERROR otherwise (use giterr_last for information
- * about the error)
+ * @return 0 on success, GIT_EUNBORNBRANCH if HEAD points to a non
+ * existing branch, non-zero value returned by `notify_cb`, or
+ * other error code < 0 (use giterr_last for error details)
*/
GIT_EXTERN(int) git_checkout_head(
git_repository *repo,
@@ -266,8 +271,8 @@ GIT_EXTERN(int) git_checkout_head(
* @param repo repository into which to check out (must be non-bare)
* @param index index to be checked out (or NULL to use repository index)
* @param opts specifies checkout options (may be NULL)
- * @return 0 on success, GIT_ERROR otherwise (use giterr_last for information
- * about the error)
+ * @return 0 on success, non-zero return value from `notify_cb`, or error
+ * code < 0 (use giterr_last for error details)
*/
GIT_EXTERN(int) git_checkout_index(
git_repository *repo,
@@ -282,8 +287,8 @@ GIT_EXTERN(int) git_checkout_index(
* @param treeish a commit, tag or tree which content will be used to update
* the working directory (or NULL to use HEAD)
* @param opts specifies checkout options (may be NULL)
- * @return 0 on success, GIT_ERROR otherwise (use giterr_last for information
- * about the error)
+ * @return 0 on success, non-zero return value from `notify_cb`, or error
+ * code < 0 (use giterr_last for error details)
*/
GIT_EXTERN(int) git_checkout_tree(
git_repository *repo,
diff --git a/src/checkout.c b/src/checkout.c
index a292e3d4c..f7dd052c7 100644
--- a/src/checkout.c
+++ b/src/checkout.c
@@ -993,7 +993,7 @@ static int checkout_get_actions(
git_vector_foreach(deltas, i, delta) {
error = checkout_action(&act, data, delta, workdir, &wditem, &pathspec);
- if (error)
+ if (error != 0)
goto fail;
actions[i] = act;
@@ -1957,7 +1957,7 @@ int git_checkout_iterator(
* actions to be taken, plus look for conflicts and send notifications,
* then loop through conflicts.
*/
- if ((error = checkout_get_actions(&actions, &counts, &data, workdir)) < 0)
+ if ((error = checkout_get_actions(&actions, &counts, &data, workdir)) != 0)
goto cleanup;
data.total_steps = counts[CHECKOUT_ACTION__REMOVE] +
diff --git a/tests/checkout/tree.c b/tests/checkout/tree.c
index 66b01bc7f..ac48bfcdd 100644
--- a/tests/checkout/tree.c
+++ b/tests/checkout/tree.c
@@ -486,6 +486,84 @@ void test_checkout_tree__donot_update_deleted_file_by_default(void)
git_index_free(index);
}
+struct checkout_cancel_at {
+ const char *filename;
+ int error;
+ int count;
+};
+
+static int checkout_cancel_cb(
+ git_checkout_notify_t why,
+ const char *path,
+ const git_diff_file *b,
+ const git_diff_file *t,
+ const git_diff_file *w,
+ void *payload)
+{
+ struct checkout_cancel_at *ca = payload;
+
+ GIT_UNUSED(why); GIT_UNUSED(b); GIT_UNUSED(t); GIT_UNUSED(w);
+
+ ca->count++;
+
+ if (!strcmp(path, ca->filename))
+ return ca->error;
+
+ return 0;
+}
+
+void test_checkout_tree__can_cancel_checkout_from_notify(void)
+{
+ struct checkout_cancel_at ca;
+ git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT;
+ git_oid oid;
+ git_object *obj = NULL;
+
+ assert_on_branch(g_repo, "master");
+
+ cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/dir"));
+ cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJ_ANY));
+
+ ca.filename = "new.txt";
+ ca.error = -5555;
+ ca.count = 0;
+
+ opts.notify_flags = GIT_CHECKOUT_NOTIFY_UPDATED;
+ opts.notify_cb = checkout_cancel_cb;
+ opts.notify_payload = &ca;
+ opts.checkout_strategy = GIT_CHECKOUT_FORCE;
+
+ cl_assert(!git_path_exists("testrepo/new.txt"));
+
+ cl_git_fail_with(git_checkout_tree(g_repo, obj, &opts), -5555);
+
+ cl_assert(!git_path_exists("testrepo/new.txt"));
+
+ /* on case-insensitive FS = a/b.txt, branch_file.txt, new.txt */
+ /* on case-sensitive FS = README, then above */
+
+ if (cl_repo_get_bool(g_repo, "core.ignorecase"))
+ cl_assert_equal_i(3, ca.count);
+ else
+ cl_assert_equal_i(4, ca.count);
+
+ /* and again with a different stopping point and return code */
+ ca.filename = "README";
+ ca.error = 123;
+ ca.count = 0;
+
+ cl_git_fail_with(git_checkout_tree(g_repo, obj, &opts), 123);
+
+ cl_assert(!git_path_exists("testrepo/new.txt"));
+
+ if (cl_repo_get_bool(g_repo, "core.ignorecase"))
+ cl_assert_equal_i(4, ca.count);
+ else
+ cl_assert_equal_i(1, ca.count);
+
+ git_object_free(obj);
+}
+
void test_checkout_tree__can_checkout_with_last_workdir_item_missing(void)
{
git_index *index = NULL;