Commit 083b1a2e2d8d190db02db3db0dad4fa742eccb02

Edward Thomson 2017-12-28T10:38:31

Merge pull request #4021 from carlosmn/cmn/refspecs-fetchhead FETCH_HEAD and multiple refspecs

diff --git a/src/fetchhead.c b/src/fetchhead.c
index ac25723..e55e7c8 100644
--- a/src/fetchhead.c
+++ b/src/fetchhead.c
@@ -118,7 +118,7 @@ int git_fetchhead_write(git_repository *repo, git_vector *fetchhead_refs)
 	if (git_buf_joinpath(&path, repo->gitdir, GIT_FETCH_HEAD_FILE) < 0)
 		return -1;
 
-	if (git_filebuf_open(&file, path.ptr, GIT_FILEBUF_FORCE, GIT_REFS_FILE_MODE) < 0) {
+	if (git_filebuf_open(&file, path.ptr, GIT_FILEBUF_APPEND, GIT_REFS_FILE_MODE) < 0) {
 		git_buf_free(&path);
 		return -1;
 	}
diff --git a/src/fileops.c b/src/fileops.c
index ad3f67e..58988c2 100644
--- a/src/fileops.c
+++ b/src/fileops.c
@@ -102,6 +102,16 @@ int git_futils_open_ro(const char *path)
 	return fd;
 }
 
+int git_futils_truncate(const char *path, int mode)
+{
+	int fd = p_open(path, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, mode);
+	if (fd < 0)
+		return git_path_set_error(errno, path, "open");
+
+	close(fd);
+	return 0;
+}
+
 git_off_t git_futils_filesize(git_file fd)
 {
 	struct stat sb;
diff --git a/src/fileops.h b/src/fileops.h
index fd54412..57b9d17 100644
--- a/src/fileops.h
+++ b/src/fileops.h
@@ -248,6 +248,11 @@ extern int git_futils_cp_r(
 extern int git_futils_open_ro(const char *path);
 
 /**
+ * Truncate a file, creating it if it doesn't exist.
+ */
+extern int git_futils_truncate(const char *path, int mode);
+
+/**
  * Get the filesize in bytes of a file
  */
 extern git_off_t git_futils_filesize(git_file fd);
diff --git a/src/remote.c b/src/remote.c
index 3039117..4d675af 100644
--- a/src/remote.c
+++ b/src/remote.c
@@ -1541,6 +1541,20 @@ cleanup:
 	return error;
 }
 
+static int truncate_fetch_head(const char *gitdir)
+{
+	git_buf path = GIT_BUF_INIT;
+	int error;
+
+	if ((error = git_buf_joinpath(&path, gitdir, GIT_FETCH_HEAD_FILE)) < 0)
+		return error;
+
+	error = git_futils_truncate(path.ptr, GIT_REFS_FILE_MODE);
+	git_buf_free(&path);
+
+	return error;
+}
+
 int git_remote_update_tips(
 		git_remote *remote,
 		const git_remote_callbacks *callbacks,
@@ -1571,6 +1585,9 @@ int git_remote_update_tips(
 	else
 		tagopt = download_tags;
 
+	if ((error = truncate_fetch_head(git_repository_path(remote->repo))) < 0)
+		goto out;
+
 	if (tagopt == GIT_REMOTE_DOWNLOAD_TAGS_ALL) {
 		if ((error = update_tips_for_spec(remote, callbacks, update_fetchhead, tagopt, &tagspec, &refs, reflog_message)) < 0)
 			goto out;
diff --git a/tests/fetchhead/nonetwork.c b/tests/fetchhead/nonetwork.c
index ea4b70e..4dabb57 100644
--- a/tests/fetchhead/nonetwork.c
+++ b/tests/fetchhead/nonetwork.c
@@ -353,20 +353,25 @@ void test_fetchhead_nonetwork__quote_in_branch_name(void)
 }
 
 static bool found_master;
-static bool find_master_called;
+static bool found_haacked;
+static bool find_master_haacked_called;
 
-int find_master(const char *ref_name, const char *remote_url, const git_oid *oid, unsigned int is_merge, void *payload)
+int find_master_haacked(const char *ref_name, const char *remote_url, const git_oid *oid, unsigned int is_merge, void *payload)
 {
 	GIT_UNUSED(remote_url);
 	GIT_UNUSED(oid);
 	GIT_UNUSED(payload);
 
-	find_master_called = true;
+	find_master_haacked_called = true;
 
 	if (!strcmp("refs/heads/master", ref_name)) {
 		cl_assert(is_merge);
 		found_master = true;
 	}
+	if (!strcmp("refs/heads/haacked", ref_name)) {
+		cl_assert(is_merge);
+		found_haacked = true;
+	}
 
 	return 0;
 }
@@ -375,10 +380,12 @@ void test_fetchhead_nonetwork__create_when_refpecs_given(void)
 {
 	git_remote *remote;
 	git_buf path = GIT_BUF_INIT;
-	char *refspec = "refs/heads/master";
+	char *refspec1 = "refs/heads/master";
+	char *refspec2 = "refs/heads/haacked";
+	char *refspecs[] = { refspec1, refspec2 };
 	git_strarray specs = {
-		&refspec,
-		1,
+		refspecs,
+		2,
 	};
 
 	cl_set_cleanup(&cleanup_repository, "./test1");
@@ -391,9 +398,74 @@ void test_fetchhead_nonetwork__create_when_refpecs_given(void)
 	cl_git_pass(git_remote_fetch(remote, &specs, NULL, NULL));
 	cl_assert(git_path_exists(path.ptr));
 
-	cl_git_pass(git_repository_fetchhead_foreach(g_repo, find_master, NULL));
-	cl_assert(find_master_called);
+	cl_git_pass(git_repository_fetchhead_foreach(g_repo, find_master_haacked, NULL));
+	cl_assert(find_master_haacked_called);
 	cl_assert(found_master);
+	cl_assert(found_haacked);
+
+	git_remote_free(remote);
+	git_buf_free(&path);
+}
+
+static bool count_refs_called;
+struct prefix_count {
+	const char *prefix;
+	int count;
+	int expected;
+};
+
+int count_refs(const char *ref_name, const char *remote_url, const git_oid *oid, unsigned int is_merge, void *payload)
+{
+	int i;
+	struct prefix_count *prefix_counts = (struct prefix_count *) payload;
+
+	GIT_UNUSED(remote_url);
+	GIT_UNUSED(oid);
+	GIT_UNUSED(is_merge);
+
+	count_refs_called = true;
+
+	for (i = 0; prefix_counts[i].prefix; i++) {
+		if (!git__prefixcmp(ref_name, prefix_counts[i].prefix))
+			prefix_counts[i].count++;
+	}
+
+	return 0;
+}
+
+void test_fetchhead_nonetwork__create_with_multiple_refspecs(void)
+{
+	git_remote *remote;
+	git_buf path = GIT_BUF_INIT;
+
+	cl_set_cleanup(&cleanup_repository, "./test1");
+	cl_git_pass(git_repository_init(&g_repo, "./test1", 0));
+
+	cl_git_pass(git_remote_create(&remote, g_repo, "origin", cl_fixture("testrepo.git")));
+	git_remote_free(remote);
+	cl_git_pass(git_remote_add_fetch(g_repo, "origin", "+refs/notes/*:refs/origin/notes/*"));
+	/* Pick up the new refspec */
+	cl_git_pass(git_remote_lookup(&remote, g_repo, "origin"));
+
+	cl_git_pass(git_buf_joinpath(&path, git_repository_path(g_repo), "FETCH_HEAD"));
+	cl_assert(!git_path_exists(path.ptr));
+	cl_git_pass(git_remote_fetch(remote, NULL, NULL, NULL));
+	cl_assert(git_path_exists(path.ptr));
+
+	{
+		int i;
+		struct prefix_count prefix_counts[] = {
+			{"refs/notes/", 0, 1},
+			{"refs/heads/", 0, 12},
+			{"refs/tags/", 0, 7},
+			{NULL, 0, 0},
+		};
+
+		cl_git_pass(git_repository_fetchhead_foreach(g_repo, count_refs, &prefix_counts));
+		cl_assert(count_refs_called);
+		for (i = 0; prefix_counts[i].prefix; i++)
+			cl_assert_equal_i(prefix_counts[i].expected, prefix_counts[i].count);
+	}
 
 	git_remote_free(remote);
 	git_buf_free(&path);