Commit 0ddc60944ca4727246414e8bcf3170fe8286f854

Patrick Steinhardt 2018-11-30T09:46:14

Merge pull request #4770 from tiennou/feature/merge-analysis-any-branch Allow merge analysis against any reference

diff --git a/include/git2/merge.h b/include/git2/merge.h
index 47bf32d..8627213 100644
--- a/include/git2/merge.h
+++ b/include/git2/merge.h
@@ -389,6 +389,25 @@ GIT_EXTERN(int) git_merge_analysis(
 	size_t their_heads_len);
 
 /**
+ * Analyzes the given branch(es) and determines the opportunities for
+ * merging them into a reference.
+ *
+ * @param analysis_out analysis enumeration that the result is written into
+ * @param repo the repository to merge
+ * @param our_ref the reference to perform the analysis from
+ * @param their_heads the heads to merge into
+ * @param their_heads_len the number of heads to merge
+ * @return 0 on success or error code
+ */
+GIT_EXTERN(int) git_merge_analysis_for_ref(
+	git_merge_analysis_t *analysis_out,
+	git_merge_preference_t *preference_out,
+	git_repository *repo,
+	git_reference *our_ref,
+	const git_annotated_commit **their_heads,
+	size_t their_heads_len);
+
+/**
  * Find a merge base between two commits
  *
  * @param out the OID of a merge base between 'one' and 'two'
diff --git a/src/merge.c b/src/merge.c
index a369817..23a593c 100644
--- a/src/merge.c
+++ b/src/merge.c
@@ -3110,11 +3110,11 @@ static int merge_heads(
 	git_annotated_commit **ancestor_head_out,
 	git_annotated_commit **our_head_out,
 	git_repository *repo,
+	git_reference *our_ref,
 	const git_annotated_commit **their_heads,
 	size_t their_heads_len)
 {
 	git_annotated_commit *ancestor_head = NULL, *our_head = NULL;
-	git_reference *our_ref = NULL;
 	int error = 0;
 
 	*ancestor_head_out = NULL;
@@ -3123,8 +3123,7 @@ static int merge_heads(
 	if ((error = git_repository__ensure_not_bare(repo, "merge")) < 0)
 		goto done;
 
-	if ((error = git_reference_lookup(&our_ref, repo, GIT_HEAD_FILE)) < 0 ||
-		(error = git_annotated_commit_from_ref(&our_head, repo, our_ref)) < 0)
+	if ((error = git_annotated_commit_from_ref(&our_head, repo, our_ref)) < 0)
 		goto done;
 
 	if ((error = merge_ancestor_head(&ancestor_head, repo, our_head, their_heads, their_heads_len)) < 0) {
@@ -3144,8 +3143,6 @@ done:
 		git_annotated_commit_free(our_head);
 	}
 
-	git_reference_free(our_ref);
-
 	return error;
 }
 
@@ -3182,17 +3179,19 @@ done:
 	return error;
 }
 
-int git_merge_analysis(
+int git_merge_analysis_for_ref(
 	git_merge_analysis_t *analysis_out,
 	git_merge_preference_t *preference_out,
 	git_repository *repo,
+	git_reference *our_ref,
 	const git_annotated_commit **their_heads,
 	size_t their_heads_len)
 {
 	git_annotated_commit *ancestor_head = NULL, *our_head = NULL;
 	int error = 0;
+	bool unborn;
 
-	assert(analysis_out && preference_out && repo && their_heads);
+	assert(analysis_out && preference_out && repo && their_heads && their_heads_len > 0);
 
 	if (their_heads_len != 1) {
 		giterr_set(GITERR_MERGE, "can only merge a single branch");
@@ -3205,12 +3204,16 @@ int git_merge_analysis(
 	if ((error = merge_preference(preference_out, repo)) < 0)
 		goto done;
 
-	if (git_repository_head_unborn(repo)) {
+	if ((error = git_reference__is_unborn_head(&unborn, our_ref, repo)) < 0)
+		goto done;
+
+	if (unborn) {
 		*analysis_out |= GIT_MERGE_ANALYSIS_FASTFORWARD | GIT_MERGE_ANALYSIS_UNBORN;
+		error = 0;
 		goto done;
 	}
 
-	if ((error = merge_heads(&ancestor_head, &our_head, repo, their_heads, their_heads_len)) < 0)
+	if ((error = merge_heads(&ancestor_head, &our_head, repo, our_ref, their_heads, their_heads_len)) < 0)
 		goto done;
 
 	/* We're up-to-date if we're trying to merge our own common ancestor. */
@@ -3233,6 +3236,28 @@ done:
 	return error;
 }
 
+int git_merge_analysis(
+	git_merge_analysis_t *analysis_out,
+	git_merge_preference_t *preference_out,
+	git_repository *repo,
+	const git_annotated_commit **their_heads,
+	size_t their_heads_len)
+{
+	git_reference *head_ref = NULL;
+	int error = 0;
+
+	if ((error = git_reference_lookup(&head_ref, repo, GIT_HEAD_FILE)) < 0) {
+		giterr_set(GITERR_MERGE, "failed to lookup HEAD reference");
+		return error;
+	}
+
+	error = git_merge_analysis_for_ref(analysis_out, preference_out, repo, head_ref, their_heads, their_heads_len);
+
+	git_reference_free(head_ref);
+
+	return error;
+}
+
 int git_merge(
 	git_repository *repo,
 	const git_annotated_commit **their_heads,
@@ -3248,7 +3273,7 @@ int git_merge(
 	unsigned int checkout_strategy;
 	int error = 0;
 
-	assert(repo && their_heads);
+	assert(repo && their_heads && their_heads_len > 0);
 
 	if (their_heads_len != 1) {
 		giterr_set(GITERR_MERGE, "can only merge a single branch");
diff --git a/src/refs.c b/src/refs.c
index b3e94d8..9339210 100644
--- a/src/refs.c
+++ b/src/refs.c
@@ -1430,3 +1430,27 @@ const char *git_reference_shorthand(const git_reference *ref)
 {
 	return git_reference__shorthand(ref->name);
 }
+
+int git_reference__is_unborn_head(bool *unborn, const git_reference *ref, git_repository *repo)
+{
+	int error;
+	git_reference *tmp_ref;
+	assert(unborn && ref && repo);
+
+	if (ref->type == GIT_REF_OID) {
+		*unborn = 0;
+		return 0;
+	}
+
+	error = git_reference_lookup_resolved(&tmp_ref, repo, ref->name, -1);
+	git_reference_free(tmp_ref);
+
+	if (error != 0 && error != GIT_ENOTFOUND)
+		return error;
+	else if (error == GIT_ENOTFOUND && git__strcmp(ref->name, GIT_HEAD_FILE) == 0)
+		*unborn = true;
+	else
+		*unborn = false;
+
+	return 0;
+}
diff --git a/src/refs.h b/src/refs.h
index 84daf8b..f4f9342 100644
--- a/src/refs.h
+++ b/src/refs.h
@@ -136,4 +136,6 @@ int git_reference__update_for_commit(
 	const git_oid *id,
 	const char *operation);
 
+int git_reference__is_unborn_head(bool *unborn, const git_reference *ref, git_repository *repo);
+
 #endif
diff --git a/src/repository.c b/src/repository.c
index cac25ad..2305bfe 100644
--- a/src/repository.c
+++ b/src/repository.c
@@ -2157,6 +2157,8 @@ int git_repository_head(git_reference **head_out, git_repository *repo)
 	git_reference *head;
 	int error;
 
+	assert(head_out);
+
 	if ((error = git_reference_lookup(&head, repo, GIT_HEAD_FILE)) < 0)
 		return error;
 
diff --git a/tests/merge/workdir/analysis.c b/tests/merge/workdir/analysis.c
index f87fc58..27d7dba 100644
--- a/tests/merge/workdir/analysis.c
+++ b/tests/merge/workdir/analysis.c
@@ -40,21 +40,33 @@ void test_merge_workdir_analysis__cleanup(void)
 static void analysis_from_branch(
 	git_merge_analysis_t *merge_analysis,
 	git_merge_preference_t *merge_pref,
-	const char *branchname)
+	const char *our_branchname,
+	const char *their_branchname)
 {
-	git_buf refname = GIT_BUF_INIT;
+	git_buf our_refname = GIT_BUF_INIT;
+	git_buf their_refname = GIT_BUF_INIT;
+	git_reference *our_ref;
 	git_reference *their_ref;
 	git_annotated_commit *their_head;
 
-	git_buf_printf(&refname, "%s%s", GIT_REFS_HEADS_DIR, branchname);
+	if (our_branchname != NULL) {
+		cl_git_pass(git_buf_printf(&our_refname, "%s%s", GIT_REFS_HEADS_DIR, our_branchname));
+		cl_git_pass(git_reference_lookup(&our_ref, repo, git_buf_cstr(&our_refname)));
+	} else {
+		cl_git_pass(git_reference_lookup(&our_ref, repo, GIT_HEAD_FILE));
+	}
 
-	cl_git_pass(git_reference_lookup(&their_ref, repo, git_buf_cstr(&refname)));
+	cl_git_pass(git_buf_printf(&their_refname, "%s%s", GIT_REFS_HEADS_DIR, their_branchname));
+
+	cl_git_pass(git_reference_lookup(&their_ref, repo, git_buf_cstr(&their_refname)));
 	cl_git_pass(git_annotated_commit_from_ref(&their_head, repo, their_ref));
 
-	cl_git_pass(git_merge_analysis(merge_analysis, merge_pref, repo, (const git_annotated_commit **)&their_head, 1));
+	cl_git_pass(git_merge_analysis_for_ref(merge_analysis, merge_pref, repo, our_ref, (const git_annotated_commit **)&their_head, 1));
 
-	git_buf_dispose(&refname);
+	git_buf_dispose(&our_refname);
+	git_buf_dispose(&their_refname);
 	git_annotated_commit_free(their_head);
+	git_reference_free(our_ref);
 	git_reference_free(their_ref);
 }
 
@@ -63,9 +75,8 @@ void test_merge_workdir_analysis__fastforward(void)
 	git_merge_analysis_t merge_analysis;
 	git_merge_preference_t merge_pref;
 
-	analysis_from_branch(&merge_analysis, &merge_pref, FASTFORWARD_BRANCH);
-	cl_assert_equal_i(GIT_MERGE_ANALYSIS_FASTFORWARD, (merge_analysis & GIT_MERGE_ANALYSIS_FASTFORWARD));
-	cl_assert_equal_i(GIT_MERGE_ANALYSIS_NORMAL, (merge_analysis & GIT_MERGE_ANALYSIS_NORMAL));
+	analysis_from_branch(&merge_analysis, &merge_pref, NULL, FASTFORWARD_BRANCH);
+	cl_assert_equal_i(GIT_MERGE_ANALYSIS_NORMAL|GIT_MERGE_ANALYSIS_FASTFORWARD, merge_analysis);
 }
 
 void test_merge_workdir_analysis__no_fastforward(void)
@@ -73,7 +84,7 @@ void test_merge_workdir_analysis__no_fastforward(void)
 	git_merge_analysis_t merge_analysis;
 	git_merge_preference_t merge_pref;
 
-	analysis_from_branch(&merge_analysis, &merge_pref, NOFASTFORWARD_BRANCH);
+	analysis_from_branch(&merge_analysis, &merge_pref, NULL, NOFASTFORWARD_BRANCH);
 	cl_assert_equal_i(GIT_MERGE_ANALYSIS_NORMAL, merge_analysis);
 }
 
@@ -82,7 +93,7 @@ void test_merge_workdir_analysis__uptodate(void)
 	git_merge_analysis_t merge_analysis;
 	git_merge_preference_t merge_pref;
 
-	analysis_from_branch(&merge_analysis, &merge_pref, UPTODATE_BRANCH);
+	analysis_from_branch(&merge_analysis, &merge_pref, NULL, UPTODATE_BRANCH);
 	cl_assert_equal_i(GIT_MERGE_ANALYSIS_UP_TO_DATE, merge_analysis);
 }
 
@@ -91,7 +102,7 @@ void test_merge_workdir_analysis__uptodate_merging_prev_commit(void)
 	git_merge_analysis_t merge_analysis;
 	git_merge_preference_t merge_pref;
 
-	analysis_from_branch(&merge_analysis, &merge_pref, PREVIOUS_BRANCH);
+	analysis_from_branch(&merge_analysis, &merge_pref, NULL, PREVIOUS_BRANCH);
 	cl_assert_equal_i(GIT_MERGE_ANALYSIS_UP_TO_DATE, merge_analysis);
 }
 
@@ -104,9 +115,8 @@ void test_merge_workdir_analysis__unborn(void)
 	git_buf_joinpath(&master, git_repository_path(repo), "refs/heads/master");
 	p_unlink(git_buf_cstr(&master));
 
-	analysis_from_branch(&merge_analysis, &merge_pref, NOFASTFORWARD_BRANCH);
-	cl_assert_equal_i(GIT_MERGE_ANALYSIS_FASTFORWARD, (merge_analysis & GIT_MERGE_ANALYSIS_FASTFORWARD));
-	cl_assert_equal_i(GIT_MERGE_ANALYSIS_UNBORN, (merge_analysis & GIT_MERGE_ANALYSIS_UNBORN));
+	analysis_from_branch(&merge_analysis, &merge_pref, NULL, NOFASTFORWARD_BRANCH);
+	cl_assert_equal_i(GIT_MERGE_ANALYSIS_FASTFORWARD|GIT_MERGE_ANALYSIS_UNBORN, merge_analysis);
 
 	git_buf_dispose(&master);
 }
@@ -120,9 +130,9 @@ void test_merge_workdir_analysis__fastforward_with_config_noff(void)
 	git_repository_config(&config, repo);
 	git_config_set_string(config, "merge.ff", "false");
 
-	analysis_from_branch(&merge_analysis, &merge_pref, FASTFORWARD_BRANCH);
-	cl_assert_equal_i(GIT_MERGE_ANALYSIS_FASTFORWARD, (merge_analysis & GIT_MERGE_ANALYSIS_FASTFORWARD));
-	cl_assert_equal_i(GIT_MERGE_ANALYSIS_NORMAL, (merge_analysis & GIT_MERGE_ANALYSIS_NORMAL));
+	analysis_from_branch(&merge_analysis, &merge_pref, NULL, FASTFORWARD_BRANCH);
+	cl_assert_equal_i(GIT_MERGE_ANALYSIS_NORMAL|GIT_MERGE_ANALYSIS_FASTFORWARD, merge_analysis);
+
 	cl_assert_equal_i(GIT_MERGE_PREFERENCE_NO_FASTFORWARD, (merge_pref & GIT_MERGE_PREFERENCE_NO_FASTFORWARD));
 }
 
@@ -135,7 +145,26 @@ void test_merge_workdir_analysis__no_fastforward_with_config_ffonly(void)
 	git_repository_config(&config, repo);
 	git_config_set_string(config, "merge.ff", "only");
 
-	analysis_from_branch(&merge_analysis, &merge_pref, NOFASTFORWARD_BRANCH);
-	cl_assert_equal_i(GIT_MERGE_ANALYSIS_NORMAL, (merge_analysis & GIT_MERGE_ANALYSIS_NORMAL));
+	analysis_from_branch(&merge_analysis, &merge_pref, NULL, NOFASTFORWARD_BRANCH);
+	cl_assert_equal_i(GIT_MERGE_ANALYSIS_NORMAL, merge_analysis);
+
 	cl_assert_equal_i(GIT_MERGE_PREFERENCE_FASTFORWARD_ONLY, (merge_pref & GIT_MERGE_PREFERENCE_FASTFORWARD_ONLY));
 }
+
+void test_merge_workdir_analysis__between_uptodate_refs(void)
+{
+	git_merge_analysis_t merge_analysis;
+	git_merge_preference_t merge_pref;
+
+	analysis_from_branch(&merge_analysis, &merge_pref, NOFASTFORWARD_BRANCH, PREVIOUS_BRANCH);
+	cl_assert_equal_i(GIT_MERGE_ANALYSIS_UP_TO_DATE, merge_analysis);
+}
+
+void test_merge_workdir_analysis__between_noff_refs(void)
+{
+	git_merge_analysis_t merge_analysis;
+	git_merge_preference_t merge_pref;
+
+	analysis_from_branch(&merge_analysis, &merge_pref, "branch", FASTFORWARD_BRANCH);
+	cl_assert_equal_i(GIT_MERGE_ANALYSIS_NORMAL, merge_analysis);
+}