summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPatrick Steinhardt <ps@pks.im>2018-11-30 09:46:14 +0100
committerGitHub <noreply@github.com>2018-11-30 09:46:14 +0100
commit0ddc60944ca4727246414e8bcf3170fe8286f854 (patch)
tree54b2eac923cc1b711ac42d383777c7ca07bcc04c
parente7873eb2cc0e96c9eebe653b6a5ba41daea4a28e (diff)
parentcb71a9cec2e5b857c18aea678664c5f2e65c8308 (diff)
downloadlibgit2-0ddc60944ca4727246414e8bcf3170fe8286f854.tar.gz
Merge pull request #4770 from tiennou/feature/merge-analysis-any-branch
Allow merge analysis against any reference
-rw-r--r--include/git2/merge.h19
-rw-r--r--src/merge.c45
-rw-r--r--src/refs.c24
-rw-r--r--src/refs.h2
-rw-r--r--src/repository.c2
-rw-r--r--tests/merge/workdir/analysis.c69
6 files changed, 131 insertions, 30 deletions
diff --git a/include/git2/merge.h b/include/git2/merge.h
index 47bf32daa..862721370 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 a36981723..23a593cd8 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 b3e94d8e8..933921099 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 84daf8b53..f4f934222 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 cac25ad9b..2305bfef3 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 f87fc58b0..27d7dba84 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);
+}