Commit 94f742bac60656f4f915711b953814477633b984

Carlos Martín Nieto 2014-05-28T10:18:05

fileops: allow linking files when copying directory structures When passed the LINK_FILES flag, the recursive copy will hardlink files instead of copying them.

diff --git a/src/fileops.c b/src/fileops.c
index 13b8f6a..bebbae4 100644
--- a/src/fileops.c
+++ b/src/fileops.c
@@ -740,9 +740,11 @@ static int _cp_r_callback(void *ref, git_buf *from)
 		return error;
 
 	/* make symlink or regular file */
-	if (S_ISLNK(from_st.st_mode))
+	if (info->flags & GIT_CPDIR_LINK_FILES) {
+		error = p_link(from->ptr, info->to.ptr);
+	} else if (S_ISLNK(from_st.st_mode)) {
 		error = cp_link(from->ptr, info->to.ptr, (size_t)from_st.st_size);
-	else {
+	} else {
 		mode_t usemode = from_st.st_mode;
 
 		if ((info->flags & GIT_CPDIR_SIMPLE_TO_MODE) != 0)
diff --git a/src/fileops.h b/src/fileops.h
index 62227ab..4f5700a 100644
--- a/src/fileops.h
+++ b/src/fileops.h
@@ -173,6 +173,7 @@ extern int git_futils_cp(
  * - GIT_CPDIR_SIMPLE_TO_MODE: default tries to replicate the mode of the
  *   source file to the target; with this flag, always use 0666 (or 0777 if
  *   source has exec bits set) for target.
+ * - GIT_CPDIR_LINK_FILES will try to use hardlinks for the files
  */
 typedef enum {
 	GIT_CPDIR_CREATE_EMPTY_DIRS = (1u << 0),
@@ -181,6 +182,7 @@ typedef enum {
 	GIT_CPDIR_OVERWRITE         = (1u << 3),
 	GIT_CPDIR_CHMOD_DIRS        = (1u << 4),
 	GIT_CPDIR_SIMPLE_TO_MODE    = (1u << 5),
+	GIT_CPDIR_LINK_FILES        = (1u << 6),
 } git_futils_cpdir_flags;
 
 /**
diff --git a/tests/core/copy.c b/tests/core/copy.c
index c0c59c0..04b2dfa 100644
--- a/tests/core/copy.c
+++ b/tests/core/copy.c
@@ -45,6 +45,16 @@ void test_core_copy__file_in_dir(void)
 	cl_assert(!git_path_isdir("an_dir"));
 }
 
+void assert_hard_link(const char *path)
+{
+	/* we assert this by checking that there's more than one link to the file */
+	struct stat st;
+
+	cl_assert(git_path_isfile(path));
+	cl_git_pass(p_stat(path, &st));
+	cl_assert(st.st_nlink > 1);
+}
+
 void test_core_copy__tree(void)
 {
 	struct stat st;
@@ -122,5 +132,21 @@ void test_core_copy__tree(void)
 	cl_git_pass(git_futils_rmdir_r("t2", NULL, GIT_RMDIR_REMOVE_FILES));
 	cl_assert(!git_path_isdir("t2"));
 
+#ifndef GIT_WIN32
+	cl_git_pass(git_futils_cp_r("src", "t3", GIT_CPDIR_CREATE_EMPTY_DIRS | GIT_CPDIR_LINK_FILES, 0));
+	cl_assert(git_path_isdir("t3"));
+
+	cl_assert(git_path_isdir("t3"));
+	cl_assert(git_path_isdir("t3/b"));
+	cl_assert(git_path_isdir("t3/c"));
+	cl_assert(git_path_isdir("t3/c/d"));
+	cl_assert(git_path_isdir("t3/c/e"));
+
+	assert_hard_link("t3/f1");
+	assert_hard_link("t3/b/f2");
+	assert_hard_link("t3/c/f3");
+	assert_hard_link("t3/c/d/f4");
+#endif
+
 	cl_git_pass(git_futils_rmdir_r("src", NULL, GIT_RMDIR_REMOVE_FILES));
 }