Commit a1f4d65ae69684f13a006a0aae325ce1dc613546

Carlos Martín Nieto 2014-10-27T01:07:26

Merge pull request #2655 from ethomson/v0.21.2 More backports to 0.21

diff --git a/.travis.yml b/.travis.yml
index bab02bb..f93a921 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -46,10 +46,11 @@ after_success:
  - if [ "$TRAVIS_OS_NAME" = "linux" ]; then sudo apt-get -qq install valgrind; fi
  - if [ "$TRAVIS_OS_NAME" = "linux" ]; then valgrind --leak-check=full --show-reachable=yes --suppressions=./libgit2_clar.supp _build/libgit2_clar -ionline; fi
 
-# Only watch the development branch
+# Watch development and stable branches
 branches:
  only:
    - development
+   - /^maint\/.*$/
 
 # Notify development list when needed
 notifications:
diff --git a/src/checkout.c b/src/checkout.c
index 20763fd..1c5fdd3 100644
--- a/src/checkout.c
+++ b/src/checkout.c
@@ -1736,10 +1736,12 @@ static int checkout_write_merge(
 	checkout_conflictdata *conflict)
 {
 	git_buf our_label = GIT_BUF_INIT, their_label = GIT_BUF_INIT,
-		path_suffixed = GIT_BUF_INIT, path_workdir = GIT_BUF_INIT;
+		path_suffixed = GIT_BUF_INIT, path_workdir = GIT_BUF_INIT,
+		in_data = GIT_BUF_INIT, out_data = GIT_BUF_INIT;
 	git_merge_file_options opts = GIT_MERGE_FILE_OPTIONS_INIT;
 	git_merge_file_result result = {0};
 	git_filebuf output = GIT_FILEBUF_INIT;
+	git_filter_list *fl = NULL;
 	int error = 0;
 
 	if (data->opts.checkout_strategy & GIT_CHECKOUT_CONFLICT_STYLE_DIFF3)
@@ -1785,13 +1787,29 @@ static int checkout_write_merge(
 		(error = checkout_safe_for_update_only(git_buf_cstr(&path_workdir), result.mode)) <= 0)
 		goto done;
 
+	if (!data->opts.disable_filters) {
+		in_data.ptr = (char *)result.ptr;
+		in_data.size = result.len;
+
+		if ((error = git_filter_list_load(&fl, data->repo, NULL, git_buf_cstr(&path_workdir),
+				GIT_FILTER_TO_WORKTREE, GIT_FILTER_OPT_DEFAULT)) < 0 ||
+			(error = git_filter_list_apply_to_data(&out_data, fl, &in_data)) < 0)
+			goto done;
+	} else {
+		out_data.ptr = (char *)result.ptr;
+		out_data.size = result.len;
+	}
+
 	if ((error = git_futils_mkpath2file(path_workdir.ptr, 0755)) < 0 ||
-		(error = git_filebuf_open(&output, path_workdir.ptr, GIT_FILEBUF_DO_NOT_BUFFER, result.mode)) < 0 ||
-		(error = git_filebuf_write(&output, result.ptr, result.len)) < 0 ||
+		(error = git_filebuf_open(&output, git_buf_cstr(&path_workdir), GIT_FILEBUF_DO_NOT_BUFFER, result.mode)) < 0 ||
+		(error = git_filebuf_write(&output, out_data.ptr, out_data.size)) < 0 ||
 		(error = git_filebuf_commit(&output)) < 0)
 		goto done;
 
 done:
+	git_filter_list_free(fl);
+
+	git_buf_free(&out_data);
 	git_buf_free(&our_label);
 	git_buf_free(&their_label);
 
diff --git a/src/config_file.c b/src/config_file.c
index 8f55c42..1f73e7e 100644
--- a/src/config_file.c
+++ b/src/config_file.c
@@ -767,12 +767,17 @@ static int config_readonly_open(git_config_backend *cfg, git_config_level_t leve
 {
 	diskfile_readonly_backend *b = (diskfile_readonly_backend *) cfg;
 	diskfile_backend *src = b->snapshot_from;
+	diskfile_header *src_header = &src->header;
 	refcounted_strmap *src_map;
+	int error;
+
+	if (!src_header->readonly && (error = config_refresh(&src_header->parent)) < 0)
+		return error;
 
 	/* We're just copying data, don't care about the level */
 	GIT_UNUSED(level);
 
-	src_map = refcounted_strmap_take(&src->header);
+	src_map = refcounted_strmap_take(src_header);
 	b->header.values = src_map;
 
 	return 0;
diff --git a/src/global.c b/src/global.c
index f8b3876..5238af9 100644
--- a/src/global.c
+++ b/src/global.c
@@ -61,8 +61,12 @@ void openssl_locking_function(int mode, int n, const char *file, int line)
 		git_mutex_unlock(&openssl_locks[n]);
 	}
 }
-#endif
 
+static void shutdown_ssl(void)
+{
+	git__free(openssl_locks);
+}
+#endif
 
 static void init_ssl(void)
 {
@@ -110,6 +114,8 @@ static void init_ssl(void)
 
 		CRYPTO_set_locking_callback(openssl_locking_function);
 	}
+
+	git__on_shutdown(shutdown_ssl);
 # endif
 #endif
 }
diff --git a/src/path.c b/src/path.c
index 5beab97..f7d05dc 100644
--- a/src/path.c
+++ b/src/path.c
@@ -494,23 +494,33 @@ bool git_path_is_empty_dir(const char *path)
 		WIN32_FIND_DATAW findData;
 		HANDLE hFind = FindFirstFileW(filter_w, &findData);
 
+		/* FindFirstFile will fail if there are no children to the given
+		 * path, which can happen if the given path is a file (and obviously
+		 * has no children) or if the given path is an empty mount point.
+		 * (Most directories have at least directory entries '.' and '..',
+		 * but ridiculously another volume mounted in another drive letter's
+		 * path space do not, and thus have nothing to enumerate.)  If
+		 * FindFirstFile fails, check if this is a directory-like thing
+		 * (a mount point).
+		 */
+		if (hFind == INVALID_HANDLE_VALUE)
+			return git_path_isdir(path);
+
 		/* If the find handle was created successfully, then it's a directory */
-		if (hFind != INVALID_HANDLE_VALUE) {
-			empty = true;
-
-			do {
-				/* Allow the enumeration to return . and .. and still be considered
-				 * empty. In the special case of drive roots (i.e. C:\) where . and
-				 * .. do not occur, we can still consider the path to be an empty
-				 * directory if there's nothing there. */
-				if (!git_path_is_dot_or_dotdotW(findData.cFileName)) {
-					empty = false;
-					break;
-				}
-			} while (FindNextFileW(hFind, &findData));
-
-			FindClose(hFind);
-		}
+		empty = true;
+
+		do {
+			/* Allow the enumeration to return . and .. and still be considered
+			 * empty. In the special case of drive roots (i.e. C:\) where . and
+			 * .. do not occur, we can still consider the path to be an empty
+			 * directory if there's nothing there. */
+			if (!git_path_is_dot_or_dotdotW(findData.cFileName)) {
+				empty = false;
+				break;
+			}
+		} while (FindNextFileW(hFind, &findData));
+
+		FindClose(hFind);
 	}
 
 	return empty;
diff --git a/src/repository.c b/src/repository.c
index e8d50ae..d52c0c6 100644
--- a/src/repository.c
+++ b/src/repository.c
@@ -1551,8 +1551,10 @@ int git_repository_head_unborn(git_repository *repo)
 	error = git_repository_head(&ref, repo);
 	git_reference_free(ref);
 
-	if (error == GIT_EUNBORNBRANCH)
+	if (error == GIT_EUNBORNBRANCH) {
+		giterr_clear();
 		return 1;
+	}
 
 	if (error < 0)
 		return -1;
diff --git a/src/tag.c b/src/tag.c
index d7b531d..ca2ed53 100644
--- a/src/tag.c
+++ b/src/tag.c
@@ -406,8 +406,9 @@ int git_tag_delete(git_repository *repo, const char *tag_name)
 	if (error < 0)
 		return error;
 
-	if ((error = git_reference_delete(tag_ref)) == 0)
-		git_reference_free(tag_ref);
+	error = git_reference_delete(tag_ref);
+
+	git_reference_free(tag_ref);
 
 	return error;
 }
diff --git a/src/win32/posix_w32.c b/src/win32/posix_w32.c
index 3493843..c6169e8 100644
--- a/src/win32/posix_w32.c
+++ b/src/win32/posix_w32.c
@@ -110,6 +110,11 @@ GIT_INLINE(time_t) filetime_to_time_t(const FILETIME *ft)
 	return (time_t)winTime;
 }
 
+static bool path_is_volume(wchar_t *target, size_t target_len)
+{
+	return (target_len && wcsncmp(target, L"\\??\\Volume{", 11) == 0);
+}
+
 /* On success, returns the length, in characters, of the path stored in dest.
  * On failure, returns a negative value. */
 static int readlink_w(
@@ -156,7 +161,13 @@ static int readlink_w(
 		goto on_error;
 	}
 
-	if (target_len) {
+	if (path_is_volume(target, target_len)) {
+		/* This path is a reparse point that represents another volume mounted
+		 * at this location, it is not a symbolic link our input was canonical.
+		 */
+		errno = EINVAL;
+		error = -1;
+	} else if (target_len) {
 		/* The path may need to have a prefix removed. */
 		target_len = git_win32__canonicalize_path(target, target_len);
 
diff --git a/tests/checkout/index.c b/tests/checkout/index.c
index 7f641b3..3c01e24 100644
--- a/tests/checkout/index.c
+++ b/tests/checkout/index.c
@@ -618,3 +618,72 @@ void test_checkout_index__can_get_repo_from_index(void)
 
 	git_index_free(index);
 }
+
+static void add_conflict(void)
+{
+	git_index *index;
+	git_index_entry entry;
+
+	memset(&entry, 0, sizeof(git_index_entry));
+
+	cl_git_pass(git_repository_index(&index, g_repo));
+
+	entry.mode = 0100644;
+	entry.path = "conflicting.txt";
+
+	git_oid_fromstr(&entry.id, "d427e0b2e138501a3d15cc376077a3631e15bd46");
+	entry.flags = (1 << GIT_IDXENTRY_STAGESHIFT);
+	cl_git_pass(git_index_add(index, &entry));
+
+	git_oid_fromstr(&entry.id, "4e886e602529caa9ab11d71f86634bd1b6e0de10");
+	entry.flags = (2 << GIT_IDXENTRY_STAGESHIFT);
+	cl_git_pass(git_index_add(index, &entry));
+
+	git_oid_fromstr(&entry.id, "2bd0a343aeef7a2cf0d158478966a6e587ff3863");
+	entry.flags = (3 << GIT_IDXENTRY_STAGESHIFT);
+	cl_git_pass(git_index_add(index, &entry));
+
+	cl_git_pass(git_index_write(index));
+	git_index_free(index);
+}
+
+void test_checkout_index__writes_conflict_file(void)
+{
+	git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+	git_buf conflicting_buf = GIT_BUF_INIT;
+
+	add_conflict();
+	cl_git_pass(git_checkout_index(g_repo, NULL, &opts));
+
+	cl_git_pass(git_futils_readbuffer(&conflicting_buf, "testrepo/conflicting.txt"));
+	cl_assert(strcmp(conflicting_buf.ptr,
+		"<<<<<<< ours\n"
+		"this file is changed in master and branch\n"
+		"=======\n"
+		"this file is changed in branch and master\n"
+		">>>>>>> theirs\n") == 0);
+	git_buf_free(&conflicting_buf);
+}
+
+void test_checkout_index__conflicts_honor_coreautocrlf(void)
+{
+#ifdef GIT_WIN32
+	git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
+	git_buf conflicting_buf = GIT_BUF_INIT;
+
+	cl_git_pass(p_unlink("./testrepo/.gitattributes"));
+	cl_repo_set_bool(g_repo, "core.autocrlf", true);
+
+	add_conflict();
+	cl_git_pass(git_checkout_index(g_repo, NULL, &opts));
+
+	cl_git_pass(git_futils_readbuffer(&conflicting_buf, "testrepo/conflicting.txt"));
+	cl_assert(strcmp(conflicting_buf.ptr,
+		"<<<<<<< ours\r\n"
+		"this file is changed in master and branch\r\n"
+		"=======\r\n"
+		"this file is changed in branch and master\r\n"
+		">>>>>>> theirs\r\n") == 0);
+	git_buf_free(&conflicting_buf);
+#endif
+}
diff --git a/tests/config/snapshot.c b/tests/config/snapshot.c
index c9f1592..3ea07c1 100644
--- a/tests/config/snapshot.c
+++ b/tests/config/snapshot.c
@@ -3,7 +3,7 @@
 void test_config_snapshot__create_snapshot(void)
 {
 	int32_t tmp;
-	git_config *cfg, *snapshot;
+	git_config *cfg, *snapshot, *new_snapshot;
 	const char *filename = "config-ext-change";
 
 	cl_git_mkfile(filename, "[old]\nvalue = 5\n");
@@ -23,7 +23,21 @@ void test_config_snapshot__create_snapshot(void)
 
 	cl_git_pass(git_config_get_int32(&tmp, snapshot, "old.value"));
 	cl_assert_equal_i(5, tmp);
+
+	/* Change the value on the file itself (simulate external process) */
+	cl_git_mkfile(filename, "[old]\nvalue = 999\n");
+
+	cl_git_pass(git_config_snapshot(&new_snapshot, cfg));
+
+	/* New snapshot should see new value */
+	cl_git_pass(git_config_get_int32(&tmp, new_snapshot, "old.value"));
+	cl_assert_equal_i(999, tmp);
+
+	/* Old snapshot should still have the old value */
+	cl_git_pass(git_config_get_int32(&tmp, snapshot, "old.value"));
+	cl_assert_equal_i(5, tmp);
 	
+	git_config_free(new_snapshot);
 	git_config_free(snapshot);
 	git_config_free(cfg);
 }
diff --git a/tests/resources/testrepo/.gitted/objects/2b/d0a343aeef7a2cf0d158478966a6e587ff3863 b/tests/resources/testrepo/.gitted/objects/2b/d0a343aeef7a2cf0d158478966a6e587ff3863
new file mode 100644
index 0000000..d10ca63
Binary files /dev/null and b/tests/resources/testrepo/.gitted/objects/2b/d0a343aeef7a2cf0d158478966a6e587ff3863 differ
diff --git a/tests/resources/testrepo/.gitted/objects/4e/886e602529caa9ab11d71f86634bd1b6e0de10 b/tests/resources/testrepo/.gitted/objects/4e/886e602529caa9ab11d71f86634bd1b6e0de10
new file mode 100644
index 0000000..53168a0
Binary files /dev/null and b/tests/resources/testrepo/.gitted/objects/4e/886e602529caa9ab11d71f86634bd1b6e0de10 differ
diff --git a/tests/resources/testrepo/.gitted/objects/d4/27e0b2e138501a3d15cc376077a3631e15bd46 b/tests/resources/testrepo/.gitted/objects/d4/27e0b2e138501a3d15cc376077a3631e15bd46
new file mode 100644
index 0000000..0b3611a
Binary files /dev/null and b/tests/resources/testrepo/.gitted/objects/d4/27e0b2e138501a3d15cc376077a3631e15bd46 differ