Commit 8651c10f1ed5d42ef0ad6e9e9f654799b4ffb39c

Ben Straub 2012-07-17T19:57:37

Checkout: obey core.symlinks.

diff --git a/src/checkout.c b/src/checkout.c
index 8ba3cf5..c4e75b6 100644
--- a/src/checkout.c
+++ b/src/checkout.c
@@ -13,6 +13,7 @@
 #include "git2/tree.h"
 #include "git2/commit.h"
 #include "git2/blob.h"
+#include "git2/config.h"
 
 #include "common.h"
 #include "refs.h"
@@ -29,22 +30,26 @@ typedef struct tree_walk_data
 	git_indexer_stats *stats;
 	git_repository *repo;
 	git_odb *odb;
+	bool do_symlinks;
 } tree_walk_data;
 
 
-static int blob_contents_to_link(git_repository *repo, git_buf *fnbuf,
+static int blob_contents_to_link(tree_walk_data *data, git_buf *fnbuf,
 											const git_oid *id)
 {
 	int retcode = GIT_ERROR;
 	git_blob *blob;
 
 	/* Get the link target */
-	if (!(retcode = git_blob_lookup(&blob, repo, id))) {
+	if (!(retcode = git_blob_lookup(&blob, data->repo, id))) {
 		git_buf linktarget = GIT_BUF_INIT;
 		if (!(retcode = git_blob__getbuf(&linktarget, blob))) {
 			/* Create the link */
-			retcode = p_symlink(git_buf_cstr(&linktarget),
-									  git_buf_cstr(fnbuf));
+			const char *new = git_buf_cstr(&linktarget),
+						  *old = git_buf_cstr(fnbuf);
+			retcode = data->do_symlinks
+				? p_symlink(new, old)
+				: git_futils_fake_symlink(new, old);
 		}
 		git_buf_free(&linktarget);
 		git_blob_free(blob);
@@ -77,7 +82,7 @@ static int blob_contents_to_file(git_repository *repo, git_buf *fnbuf,
 	return retcode;
 }
 
-static int checkout_walker(const char *path, git_tree_entry *entry, void *payload)
+static int checkout_walker(const char *path, const git_tree_entry *entry, void *payload)
 {
 	int retcode = 0;
 	tree_walk_data *data = (tree_walk_data*)payload;
@@ -99,7 +104,7 @@ static int checkout_walker(const char *path, git_tree_entry *entry, void *payloa
 								path,
 								git_tree_entry_name(entry));
 			if (S_ISLNK(attr)) {
-				retcode = blob_contents_to_link(data->repo, &fnbuf,
+				retcode = blob_contents_to_link(data, &fnbuf,
 														  git_tree_entry_id(entry));
 			} else {
 				retcode = blob_contents_to_file(data->repo, &fnbuf,
@@ -125,6 +130,7 @@ int git_checkout_force(git_repository *repo, git_indexer_stats *stats)
 	git_indexer_stats dummy_stats;
 	git_tree *tree;
 	tree_walk_data payload;
+	git_config *cfg;
 
 	assert(repo);
 	if (!stats) stats = &dummy_stats;
@@ -134,6 +140,15 @@ int git_checkout_force(git_repository *repo, git_indexer_stats *stats)
 		return GIT_ERROR;
 	}
 
+	/* Determine if symlinks should be handled */
+	if (!git_repository_config(&cfg, repo)) {
+		int temp = true;
+		if (!git_config_get_bool(&temp, cfg, "core.symlinks")) {
+			payload.do_symlinks = !!temp;
+		}
+		git_config_free(cfg);
+	}
+
 	stats->total = stats->processed = 0;
 	payload.stats = stats;
 	payload.repo = repo;
diff --git a/src/crlf.c b/src/crlf.c
index 888d86c..f68938e 100644
--- a/src/crlf.c
+++ b/src/crlf.c
@@ -230,7 +230,7 @@ static int find_and_add_filter(git_vector *filters, git_repository *repo, const 
 static int crlf_apply_to_workdir(git_filter *self, git_buf *dest, const git_buf *source)
 {
 	/* TODO */
-	return 0;
+	return -1;
 }
 
 int git_filter_add__crlf_to_odb(git_vector *filters, git_repository *repo, const char *path)
diff --git a/src/fileops.c b/src/fileops.c
index 5849b79..bc58a05 100644
--- a/src/fileops.c
+++ b/src/fileops.c
@@ -480,3 +480,14 @@ int git_futils_find_global_file(git_buf *path, const char *filename)
 	return 0;
 #endif
 }
+
+int git_futils_fake_symlink(const char *old, const char *new)
+{
+	int retcode = GIT_ERROR;
+	int fd = git_futils_creat_withpath(new, 0755, 0644);
+	if (fd >= 0) {
+		retcode = p_write(fd, old, strlen(old));
+		p_close(fd);
+	}
+	return retcode;
+}
diff --git a/src/fileops.h b/src/fileops.h
index b0c5779..594eacb 100644
--- a/src/fileops.h
+++ b/src/fileops.h
@@ -179,4 +179,14 @@ extern int git_futils_find_global_file(git_buf *path, const char *filename);
  */
 extern int git_futils_find_system_file(git_buf *path, const char *filename);
 
+
+/**
+ * Create a "fake" symlink (text file containing the target path).
+ *
+ * @param new symlink file to be created
+ * @param old original symlink target
+ * @return 0 on success, -1 on error
+ */
+extern int git_futils_fake_symlink(const char *new, const char *old);
+
 #endif /* INCLUDE_fileops_h__ */
diff --git a/src/win32/posix_w32.c b/src/win32/posix_w32.c
index c0d66c7..557760b 100644
--- a/src/win32/posix_w32.c
+++ b/src/win32/posix_w32.c
@@ -7,6 +7,7 @@
 #include "../posix.h"
 #include "path.h"
 #include "utf-conv.h"
+#include "repository.h"
 #include <errno.h>
 #include <io.h>
 #include <fcntl.h>
@@ -219,8 +220,10 @@ int p_readlink(const char *link, char *target, size_t target_len)
 
 int p_symlink(const char *old, const char *new)
 {
-	/* TODO */
-	return -1;
+	/* Real symlinks on NTFS require admin privileges. Until this changes,
+	 * libgit2 just creates a text file with the link target in the contents.
+	 */
+	return git_futils_fake_symlink(old, new);
 }
 
 int p_open(const char *path, int flags, ...)
diff --git a/tests-clar/checkout/checkout.c b/tests-clar/checkout/checkout.c
index 9ad41d0..e731ea7 100644
--- a/tests-clar/checkout/checkout.c
+++ b/tests-clar/checkout/checkout.c
@@ -12,7 +12,10 @@ static git_repository *g_repo;
 
 void test_checkout_checkout__initialize(void)
 {
+	const char *attributes = "*.txt text eol=cr\n";
+
 	g_repo = cl_git_sandbox_init("testrepo");
+	cl_git_mkfile("./testrepo/.gitattributes", attributes);
 }
 
 void test_checkout_checkout__cleanup(void)
@@ -26,7 +29,7 @@ static void test_file_contents(const char *path, const char *expectedcontents)
 	int fd;
 	char buffer[1024] = {0};
 	fd = p_open(path, O_RDONLY);
-	cl_assert(fd);
+	cl_assert(fd >= 0);
 	cl_assert_equal_i(p_read(fd, buffer, 1024), strlen(expectedcontents));
 	cl_assert_equal_s(expectedcontents, buffer);
 	cl_git_pass(p_close(fd));
@@ -67,15 +70,34 @@ void test_checkout_checkout__stats(void)
 	/* TODO */
 }
 
-void test_checkout_checkout__links(void)
+void test_checkout_checkout__symlinks(void)
 {
-	char link_data[1024];
-	size_t link_size = 1024;
+	git_config *cfg;
+
+	cl_git_pass(git_repository_config(&cfg, g_repo));
 
+	/* First try with symlinks forced on */
+	cl_git_pass(git_config_set_bool(cfg, "core.symlinks", true));
 	cl_git_pass(git_checkout_force(g_repo, NULL));
-	link_size = p_readlink("./testrepo/link_to_new.txt", link_data, link_size);
-	cl_assert_equal_i(link_size, strlen("new.txt"));
-	link_data[link_size] = '\0';
-	cl_assert_equal_s(link_data, "new.txt");
-	test_file_contents("./testrepo/link_to_new.txt", "my new file\n");
+
+#ifdef GIT_WIN32
+	test_file_contents("./testrepo/link_to_new.txt", "new.txt");
+#else
+	{
+		char link_data[1024];
+		size_t link_size = 1024;
+
+		link_size = p_readlink("./testrepo/link_to_new.txt", link_data, link_size);
+		link_data[link_size] = '\0';
+		cl_assert_equal_i(link_size, strlen("new.txt"));
+		cl_assert_equal_s(link_data, "new.txt");
+		test_file_contents("./testrepo/link_to_new.txt", "my new file\n");
+	}
+#endif
+
+	/* Now with symlinks forced off */
+	cl_git_pass(git_config_set_bool(cfg, "core.symlinks", false));
+	cl_git_pass(git_checkout_force(g_repo, NULL));
+
+	test_file_contents("./testrepo/link_to_new.txt", "new.txt");
 }