Commit 632d8b230bf38cc61cd70b55a54ae2f52502b4af

Edward Thomson 2012-10-23T15:42:09

reset changes for merge

diff --git a/include/git2/repository.h b/include/git2/repository.h
index 193ac95..d724315 100644
--- a/include/git2/repository.h
+++ b/include/git2/repository.h
@@ -569,6 +569,22 @@ GIT_EXTERN(int) git_repository_set_head_detached(
 GIT_EXTERN(int) git_repository_detach_head(
 	git_repository* repo);
 
+typedef enum {
+	GIT_REPOSITORY_STATE_NONE,
+	GIT_REPOSITORY_STATE_MERGE,
+	GIT_REPOSITORY_STATE_REVERT,
+	GIT_REPOSITORY_STATE_CHERRY_PICK,
+} git_repository_state_t;
+
+/**
+ * Determines the status of a git repository - ie, whether an operation
+ * (merge, cherry-pick, etc) is in progress.
+ *
+ * @param repo Repository pointer
+ * @return The state of the repository
+ */
+GIT_EXTERN(int) git_repository_state(git_repository *repo);
+
 /** @} */
 GIT_END_DECL
 #endif
diff --git a/src/merge.c b/src/merge.c
new file mode 100644
index 0000000..135af6a
--- /dev/null
+++ b/src/merge.c
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2009-2012 the libgit2 contributors
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "repository.h"
+#include "buffer.h"
+#include "merge.h"
+#include "refs.h"
+#include "git2/repository.h"
+#include "git2/merge.h"
+#include "git2/reset.h"
+
+int git_merge__cleanup(git_repository *repo)
+{
+	int error = 0;
+	git_buf merge_head_path = GIT_BUF_INIT,
+		merge_mode_path = GIT_BUF_INIT,
+		merge_msg_path = GIT_BUF_INIT;
+
+	assert(repo);
+
+	if (git_buf_joinpath(&merge_head_path, repo->path_repository, GIT_MERGE_HEAD_FILE) < 0 ||
+		git_buf_joinpath(&merge_mode_path, repo->path_repository, GIT_MERGE_MODE_FILE) < 0 ||
+		git_buf_joinpath(&merge_mode_path, repo->path_repository, GIT_MERGE_MODE_FILE) < 0)
+		return -1;
+
+	if (git_path_isfile(merge_head_path.ptr)) {
+		if ((error = p_unlink(merge_head_path.ptr)) < 0)
+			goto cleanup;
+	}
+
+	if (git_path_isfile(merge_mode_path.ptr))
+		(void)p_unlink(merge_mode_path.ptr);
+
+	if (git_path_isfile(merge_msg_path.ptr))
+		(void)p_unlink(merge_msg_path.ptr);
+
+cleanup:
+	git_buf_free(&merge_msg_path);
+	git_buf_free(&merge_mode_path);
+	git_buf_free(&merge_head_path);
+
+	return error;
+}
+
diff --git a/src/merge.h b/src/merge.h
new file mode 100644
index 0000000..2117d92
--- /dev/null
+++ b/src/merge.h
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2009-2012 the libgit2 contributors
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#ifndef INCLUDE_merge_h__
+#define INCLUDE_merge_h__
+
+#include "git2/types.h"
+
+#define GIT_MERGE_MSG_FILE		"MERGE_MSG"
+#define GIT_MERGE_MODE_FILE		"MERGE_MODE"
+
+#define MERGE_CONFIG_FILE_MODE	0666
+
+int git_merge__cleanup(git_repository *repo);
+
+#endif
diff --git a/src/refs.h b/src/refs.h
index 54359f0..58e2fd5 100644
--- a/src/refs.h
+++ b/src/refs.h
@@ -28,8 +28,11 @@
 #define GIT_PACKEDREFS_FILE_MODE 0666
 
 #define GIT_HEAD_FILE "HEAD"
+#define GIT_ORIG_HEAD_FILE "ORIG_HEAD"
 #define GIT_FETCH_HEAD_FILE "FETCH_HEAD"
 #define GIT_MERGE_HEAD_FILE "MERGE_HEAD"
+#define GIT_REVERT_HEAD_FILE "REVERT_HEAD"
+#define GIT_CHERRY_PICK_HEAD_FILE "CHERRY_PICK_HEAD"
 #define GIT_REFS_HEADS_MASTER_FILE GIT_REFS_HEADS_DIR "master"
 
 #define GIT_REFNAME_MAX 1024
diff --git a/src/repository.c b/src/repository.c
index 43e0eda..fa4604b 100644
--- a/src/repository.c
+++ b/src/repository.c
@@ -20,6 +20,7 @@
 #include "filter.h"
 #include "odb.h"
 #include "remote.h"
+#include "merge.h"
 
 #define GIT_FILE_CONTENT_PREFIX "gitdir:"
 
@@ -1348,15 +1349,13 @@ int git_repository_head_tree(git_tree **tree, git_repository *repo)
 	return 0;
 }
 
-#define MERGE_MSG_FILE "MERGE_MSG"
-
 int git_repository_message(char *buffer, size_t len, git_repository *repo)
 {
 	git_buf buf = GIT_BUF_INIT, path = GIT_BUF_INIT;
 	struct stat st;
 	int error;
 
-	if (git_buf_joinpath(&path, repo->path_repository, MERGE_MSG_FILE) < 0)
+	if (git_buf_joinpath(&path, repo->path_repository, GIT_MERGE_MSG_FILE) < 0)
 		return -1;
 
 	if ((error = p_stat(git_buf_cstr(&path), &st)) < 0) {
@@ -1382,7 +1381,7 @@ int git_repository_message_remove(git_repository *repo)
 	git_buf path = GIT_BUF_INIT;
 	int error;
 
-	if (git_buf_joinpath(&path, repo->path_repository, MERGE_MSG_FILE) < 0)
+	if (git_buf_joinpath(&path, repo->path_repository, GIT_MERGE_MSG_FILE) < 0)
 		return -1;
 
 	error = p_unlink(git_buf_cstr(&path));
@@ -1541,3 +1540,24 @@ cleanup:
 	git_reference_free(new_head);
 	return error;
 }
+
+int git_repository_state(git_repository *repo)
+{
+	git_buf repo_path = GIT_BUF_INIT;
+	int state = GIT_REPOSITORY_STATE_NONE;
+
+	assert(repo);
+
+	if (git_buf_puts(&repo_path, repo->path_repository) < 0)
+		return -1;
+
+	if (git_path_contains_file(&repo_path, GIT_MERGE_HEAD_FILE))
+		state = GIT_REPOSITORY_STATE_MERGE;
+	else if(git_path_contains_file(&repo_path, GIT_REVERT_HEAD_FILE))
+		state = GIT_REPOSITORY_STATE_REVERT;
+	else if(git_path_contains_file(&repo_path, GIT_CHERRY_PICK_HEAD_FILE))
+		state = GIT_REPOSITORY_STATE_CHERRY_PICK;
+
+	git_buf_free(&repo_path);
+	return state;
+}
diff --git a/src/reset.c b/src/reset.c
index 560ae17..aff5b9f 100644
--- a/src/reset.c
+++ b/src/reset.c
@@ -8,8 +8,10 @@
 #include "common.h"
 #include "commit.h"
 #include "tag.h"
+#include "merge.h"
 #include "git2/reset.h"
 #include "git2/checkout.h"
+#include "git2/merge.h"
 
 #define ERROR_MSG "Cannot perform reset"
 
@@ -88,6 +90,11 @@ int git_reset(
 		goto cleanup;
 	}
 
+	if (reset_type == GIT_RESET_SOFT && (git_repository_state(repo) == GIT_REPOSITORY_STATE_MERGE)) {
+		giterr_set(GITERR_OBJECT, "%s (soft) while in the middle of a merge.", ERROR_MSG);
+		goto cleanup;
+	}
+
 	//TODO: Check for unmerged entries
 
 	if (update_head(repo, commit) < 0)
@@ -118,6 +125,11 @@ int git_reset(
 		goto cleanup;
 	}
 
+	if ((error = git_merge__cleanup(repo)) < 0) {
+		giterr_set(GITERR_INDEX, "%s - Failed to clean up merge data.", ERROR_MSG);
+		goto cleanup;
+	}
+
 	if (reset_type == GIT_RESET_MIXED) {
 		error = 0;
 		goto cleanup;
diff --git a/tests-clar/repo/state.c b/tests-clar/repo/state.c
new file mode 100644
index 0000000..1ee8437
--- /dev/null
+++ b/tests-clar/repo/state.c
@@ -0,0 +1,47 @@
+#include "clar_libgit2.h"
+#include "buffer.h"
+#include "refs.h"
+#include "posix.h"
+
+static git_repository *_repo;
+static git_buf _path;
+
+void test_repo_state__initialize(void)
+{
+	_repo = cl_git_sandbox_init("testrepo.git");
+}
+
+void test_repo_state__cleanup(void)
+{
+	cl_git_sandbox_cleanup();
+	git_buf_free(&_path);
+}
+
+void test_repo_state__none(void)
+{
+	/* The repo should be at its default state */
+	cl_assert_equal_i(GIT_REPOSITORY_STATE_NONE, git_repository_state(_repo));
+}
+
+void test_repo_state__merge(void)
+{
+
+	/* Then it should recognise that .git/MERGE_HEAD and friends mean their respective states */
+	cl_git_pass(git_buf_joinpath(&_path, git_repository_path(_repo), GIT_MERGE_HEAD_FILE));
+	cl_git_mkfile(git_buf_cstr(&_path), "dummy");
+	cl_assert_equal_i(GIT_REPOSITORY_STATE_MERGE, git_repository_state(_repo));
+}
+
+void test_repo_state__revert(void)
+{
+	cl_git_pass(git_buf_joinpath(&_path, git_repository_path(_repo), GIT_REVERT_HEAD_FILE));
+	cl_git_mkfile(git_buf_cstr(&_path), "dummy");
+	cl_assert_equal_i(GIT_REPOSITORY_STATE_REVERT, git_repository_state(_repo));
+}
+
+void test_repo_state__cherry_pick(void)
+{
+	cl_git_pass(git_buf_joinpath(&_path, git_repository_path(_repo), GIT_CHERRY_PICK_HEAD_FILE));
+	cl_git_mkfile(git_buf_cstr(&_path), "dummy");
+	cl_assert_equal_i(GIT_REPOSITORY_STATE_CHERRY_PICK, git_repository_state(_repo));
+}
diff --git a/tests-clar/reset/hard.c b/tests-clar/reset/hard.c
index fdab9c5..c3f0418 100644
--- a/tests-clar/reset/hard.c
+++ b/tests-clar/reset/hard.c
@@ -58,3 +58,38 @@ void test_reset_hard__cannot_reset_in_a_bare_repository(void)
 
 	git_repository_free(bare);
 }
+
+void test_reset_hard__cleans_up_merge(void)
+{
+	git_buf merge_head_path = GIT_BUF_INIT,
+		merge_msg_path = GIT_BUF_INIT,
+		merge_mode_path = GIT_BUF_INIT,
+		orig_head_path = GIT_BUF_INIT;
+
+	cl_git_pass(git_buf_joinpath(&merge_head_path, git_repository_path(repo), "MERGE_HEAD"));
+	cl_git_mkfile(git_buf_cstr(&merge_head_path), "beefbeefbeefbeefbeefbeefbeefbeefbeefbeef\n");
+
+	cl_git_pass(git_buf_joinpath(&merge_msg_path, git_repository_path(repo), "MERGE_MSG"));
+	cl_git_mkfile(git_buf_cstr(&merge_head_path), "Merge commit 0017bd4ab1ec30440b17bae1680cff124ab5f1f6\n");
+
+	cl_git_pass(git_buf_joinpath(&merge_msg_path, git_repository_path(repo), "MERGE_MODE"));
+	cl_git_mkfile(git_buf_cstr(&merge_head_path), "");
+
+	cl_git_pass(git_buf_joinpath(&orig_head_path, git_repository_path(repo), "ORIG_HEAD"));
+	cl_git_mkfile(git_buf_cstr(&orig_head_path), "0017bd4ab1ec30440b17bae1680cff124ab5f1f6");
+
+	retrieve_target_from_oid(&target, repo, "0017bd4ab1ec30440b17bae1680cff124ab5f1f6");
+	cl_git_pass(git_reset(repo, target, GIT_RESET_HARD));
+
+	cl_assert(!git_path_exists(git_buf_cstr(&merge_head_path)));
+	cl_assert(!git_path_exists(git_buf_cstr(&merge_msg_path)));
+	cl_assert(!git_path_exists(git_buf_cstr(&merge_mode_path)));
+
+	cl_assert(git_path_exists(git_buf_cstr(&orig_head_path)));
+	cl_git_pass(p_unlink(git_buf_cstr(&orig_head_path)));
+
+	git_buf_free(&merge_head_path);
+	git_buf_free(&merge_msg_path);
+	git_buf_free(&merge_mode_path);
+	git_buf_free(&orig_head_path);
+}
diff --git a/tests-clar/reset/soft.c b/tests-clar/reset/soft.c
index 1872baf..6796c80 100644
--- a/tests-clar/reset/soft.c
+++ b/tests-clar/reset/soft.c
@@ -1,5 +1,7 @@
 #include "clar_libgit2.h"
+#include "posix.h"
 #include "reset_helpers.h"
+#include "path.h"
 #include "repo/repo_helpers.h"
 
 static git_repository *repo;
@@ -110,3 +112,14 @@ void test_reset_soft__resetting_against_an_orphaned_head_repo_makes_the_head_no_
 
 	git_reference_free(head);
 }
+
+void test_reset_soft__fails_when_merging(void)
+{
+	git_buf merge_head_path = GIT_BUF_INIT;
+
+	cl_git_pass(git_buf_joinpath(&merge_head_path, git_repository_path(repo), "MERGE_HEAD"));
+	cl_git_mkfile(git_buf_cstr(&merge_head_path), "beefbeefbeefbeefbeefbeefbeefbeefbeefbeef\n");
+
+	cl_git_fail(git_reset(repo, target, GIT_RESET_SOFT));
+	cl_git_pass(p_unlink(git_buf_cstr(&merge_head_path)));
+}