Commit 0cb24616eea3b92893b2a03e851a0db2c04862ef

Russell Belfer 2012-09-21T10:51:42

Merge pull request #942 from nulltoken/topic/checkout-notify-skipped checkout: add notification callback for skipped files

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 b20bd57..ea5e79a 100644
--- a/src/checkout.c
+++ b/src/checkout.c
@@ -25,7 +25,7 @@
 struct checkout_diff_data
 {
 	git_buf *path;
-	int workdir_len;
+	size_t workdir_len;
 	git_checkout_opts *checkout_opts;
 	git_indexer_stats *stats;
 	git_repository *owner;
@@ -35,7 +35,7 @@ struct checkout_diff_data
 static int buffer_to_file(
 	git_buf *buffer,
 	const char *path,
-	int dir_mode,
+	mode_t dir_mode,
 	int file_open_flags,
 	mode_t file_mode)
 {
@@ -56,10 +56,11 @@ static int buffer_to_file(
 static int blob_content_to_file(
 	git_blob *blob,
 	const char *path,
-	unsigned int entry_filemode,
+	mode_t entry_filemode,
 	git_checkout_opts *opts)
 {
-	int error, nb_filters = 0, file_mode = opts->file_mode;
+	int error, nb_filters = 0;
+	mode_t file_mode = opts->file_mode;
 	bool dont_free_filtered = false;
 	git_buf unfiltered = GIT_BUF_INIT, filtered = GIT_BUF_INIT;
 	git_vector filters = GIT_VECTOR_INIT;
@@ -127,7 +128,7 @@ static int checkout_blob(
 	git_repository *repo,
 	git_oid *blob_oid,
 	const char *path,
-	unsigned int filemode,
+	mode_t filemode,
 	bool can_symlink,
 	git_checkout_opts *opts)
 {
@@ -154,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;
 
@@ -163,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, "/"))
@@ -175,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,
@@ -184,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(
@@ -199,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;
@@ -377,4 +393,3 @@ int git_checkout_head(
 
 	return error;
 }
-
diff --git a/src/diff_output.c b/src/diff_output.c
index 37cceff..58a1a35 100644
--- a/src/diff_output.c
+++ b/src/diff_output.c
@@ -1354,9 +1354,9 @@ int git_diff_iterator_num_lines_in_hunk(git_diff_iterator *iter)
 		return error;
 
 	if (iter->hunk_curr)
-		return iter->hunk_curr->line_count;
+		return (int)iter->hunk_curr->line_count;
 	if (iter->hunk_head)
-		return iter->hunk_head->line_count;
+		return (int)iter->hunk_head->line_count;
 	return 0;
 }
 
diff --git a/src/transports/http.c b/src/transports/http.c
index 456b85e..d5015f5 100644
--- a/src/transports/http.c
+++ b/src/transports/http.c
@@ -166,7 +166,7 @@ static int send_request(transport_http *t, const char *service, void *data, ssiz
 	}
 
 	if (WinHttpSendRequest(t->request, WINHTTP_NO_ADDITIONAL_HEADERS, 0,
-		data, content_length, content_length, 0) == FALSE) {
+		data, (DWORD)content_length, (DWORD)content_length, 0) == FALSE) {
 		giterr_set(GITERR_OS, "Failed to send request");
 		goto on_error;
 	}
diff --git a/src/win32/utf-conv.c b/src/win32/utf-conv.c
index 88a8414..396af7c 100644
--- a/src/win32/utf-conv.c
+++ b/src/win32/utf-conv.c
@@ -72,7 +72,7 @@ void git__utf8_to_16(wchar_t *dest, size_t length, const char *src)
 
 void git__utf8_to_16(wchar_t *dest, size_t length, const char *src)
 {
-	MultiByteToWideChar(CP_UTF8, 0, src, -1, dest, length);
+	MultiByteToWideChar(CP_UTF8, 0, src, -1, dest, (int)length);
 }
 
 void git__utf16_to_8(char *out, const wchar_t *input)
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));
+}