Commit cc4f4cbea48ac00a5edec1b3570ac3d2ef10fe77

Edward Thomson 2020-01-12T10:12:57

Merge pull request #5355 from pks-t/pks/win32-relative-symlink-across-dirs win32: fix relative symlinks pointing into dirs

diff --git a/src/win32/posix_w32.c b/src/win32/posix_w32.c
index 2bc93a3..29641bd 100644
--- a/src/win32/posix_w32.c
+++ b/src/win32/posix_w32.c
@@ -439,8 +439,16 @@ int p_symlink(const char *target, const char *path)
 	git_win32_path target_w, path_w;
 	DWORD dwFlags;
 
+	/*
+	 * Convert both target and path to Windows-style paths. Note that we do
+	 * not want to use `git_win32_path_from_utf8` for converting the target,
+	 * as that function will automatically pre-pend the current working
+	 * directory in case the path is not absolute. As Git will instead use
+	 * relative symlinks, this is not someting we want.
+	 */
 	if (git_win32_path_from_utf8(path_w, path) < 0 ||
-	    git__utf8_to_16(target_w, MAX_PATH, target) < 0)
+	    git__utf8_to_16(target_w, MAX_PATH, target) < 0 ||
+	    git_win32_path_canonicalize(target_w) < 0)
 		return -1;
 
 	dwFlags = SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE;
diff --git a/tests/core/posix.c b/tests/core/posix.c
index 77ac65a..764ca19 100644
--- a/tests/core/posix.c
+++ b/tests/core/posix.c
@@ -189,3 +189,30 @@ void test_core_posix__symlink_resolves_to_correct_type(void)
 
 	git_buf_dispose(&contents);
 }
+
+void test_core_posix__symlink_to_file_across_dirs(void)
+{
+	git_buf contents = GIT_BUF_INIT;
+
+	if (!git_path_supports_symlinks(clar_sandbox_path()))
+		clar__skip();
+
+	/*
+	 * Create a relative symlink that points into another
+	 * directory. This used to not work on Win32, where we
+	 * forgot to convert directory separators to
+	 * Windows-style ones.
+	 */
+	cl_must_pass(git_futils_mkdir("dir", 0777, 0));
+	cl_git_mkfile("dir/target", "symlink target");
+	cl_git_pass(p_symlink("dir/target", "link"));
+
+	cl_git_pass(git_futils_readbuffer(&contents, "dir/target"));
+	cl_assert_equal_s(contents.ptr, "symlink target");
+
+	cl_must_pass(p_unlink("dir/target"));
+	cl_must_pass(p_unlink("link"));
+	cl_must_pass(p_rmdir("dir"));
+
+	git_buf_dispose(&contents);
+}