Commit 878fb66f5765115eff34213cfc8dd04b8a56b2a5

Carlos Martín Nieto 2014-02-05T10:19:17

refs: bring conditional symbolic updates to the frontend Bring the race detection goodness to symbolic references as well.

diff --git a/include/git2/refs.h b/include/git2/refs.h
index aab8715..284671d 100644
--- a/include/git2/refs.h
+++ b/include/git2/refs.h
@@ -68,6 +68,46 @@ GIT_EXTERN(int) git_reference_name_to_id(
 GIT_EXTERN(int) git_reference_dwim(git_reference **out, git_repository *repo, const char *shorthand);
 
 /**
+ * Conditionally create a new symbolic reference.
+ *
+ * A symbolic reference is a reference name that refers to another
+ * reference name.  If the other name moves, the symbolic name will move,
+ * too.  As a simple example, the "HEAD" reference might refer to
+ * "refs/heads/master" while on the "master" branch of a repository.
+ *
+ * The symbolic reference will be created in the repository and written to
+ * the disk.  The generated reference object must be freed by the user.
+ *
+ * Valid reference names must follow one of two patterns:
+ *
+ * 1. Top-level names must contain only capital letters and underscores,
+ *    and must begin and end with a letter. (e.g. "HEAD", "ORIG_HEAD").
+ * 2. Names prefixed with "refs/" can be almost anything.  You must avoid
+ *    the characters '~', '^', ':', '\\', '?', '[', and '*', and the
+ *    sequences ".." and "@{" which have special meaning to revparse.
+ *
+ * This function will return an error if a reference already exists with the
+ * given name unless `force` is true, in which case it will be overwritten.
+ *
+ * The signature and message for the reflog will be ignored if the
+ * reference does not belong in the standard set (HEAD, branches and
+ * remote-tracking branches) and it does not have a reflog.
+ *
+ * It will also return an error if the reference's value at the time
+ * of updating does not match the one passed.
+ *
+ * @param out Pointer to the newly created reference
+ * @param repo Repository where that reference will live
+ * @param name The name of the reference
+ * @param target The target of the reference
+ * @param force Overwrite existing references
+ * @param signature The identity that will used to populate the reflog entry
+ * @param log_message The one line long message to be appended to the reflog
+ * @return 0 on success, GIT_EEXISTS, GIT_EINVALIDSPEC, GIT_EMODIFIED or an error code
+ */
+GIT_EXTERN(int) git_reference_symbolic_create_matching(git_reference **out, git_repository *repo, const char *name, const char *target, int force, const git_signature *signature, const char *log_message, const char *old_value);
+
+/**
  * Create a new symbolic reference.
  *
  * A symbolic reference is a reference name that refers to another
diff --git a/src/refs.c b/src/refs.c
index 888b5cb..864bfce 100644
--- a/src/refs.c
+++ b/src/refs.c
@@ -450,14 +450,15 @@ int git_reference_create(
         return git_reference_create_matching(ref_out, repo, name, id, force, signature, log_message, NULL);
 }
 
-int git_reference_symbolic_create(
+int git_reference_symbolic_create_matching(
 	git_reference **ref_out,
 	git_repository *repo,
 	const char *name,
 	const char *target,
 	int force,
 	const git_signature *signature,
-	const char *log_message)
+	const char *log_message,
+	const char *old_target)
 {
 	int error;
 	git_signature *who = NULL;
@@ -472,12 +473,24 @@ int git_reference_symbolic_create(
 	}
 
 	error = reference__create(
-		ref_out, repo, name, NULL, target, force, signature, log_message, NULL, NULL);
+		ref_out, repo, name, NULL, target, force, signature, log_message, NULL, old_target);
 
 	git_signature_free(who);
 	return error;
 }
 
+int git_reference_symbolic_create(
+	git_reference **ref_out,
+	git_repository *repo,
+	const char *name,
+	const char *target,
+	int force,
+	const git_signature *signature,
+	const char *log_message)
+{
+	return git_reference_symbolic_create_matching(ref_out, repo, name, target, force, signature, log_message, NULL);
+}
+
 static int ensure_is_an_updatable_direct_reference(git_reference *ref)
 {
 	if (ref->type == GIT_REF_OID)
@@ -530,8 +543,8 @@ int git_reference_symbolic_set_target(
 	if ((error = ensure_is_an_updatable_symbolic_reference(ref)) < 0)
 		return error;
 
-	return git_reference_symbolic_create(
-		out, ref->db->repo, ref->name, target, 1, signature, log_message);
+	return git_reference_symbolic_create_matching(
+		out, ref->db->repo, ref->name, target, 1, signature, log_message, ref->target.symbolic);
 }
 
 static int reference__rename(git_reference **out, git_reference *ref, const char *new_name, int force,
diff --git a/tests/refs/races.c b/tests/refs/races.c
index 4d24896..6613fc2 100644
--- a/tests/refs/races.c
+++ b/tests/refs/races.c
@@ -7,6 +7,7 @@
 
 static const char *commit_id = "099fabac3a9ea935598528c27f866e34089c2eff";
 static const char *refname = "refs/heads/master";
+static const char *other_refname = "refs/heads/foo";
 static const char *other_commit_id = "a65fedf39aefe402d3bb6e24df4d4f5fe4547750";
 
 static git_repository *g_repo;
@@ -23,7 +24,6 @@ void test_refs_races__cleanup(void)
 
 void test_refs_races__create_matching(void)
 {
-	int error;
 	git_reference *ref, *ref2, *ref3;
 	git_oid id, other_id;
 
@@ -40,3 +40,22 @@ void test_refs_races__create_matching(void)
 	git_reference_free(ref2);
 	git_reference_free(ref3);
 }
+
+void test_refs_races__symbolic_create_matching(void)
+{
+	git_reference *ref, *ref2, *ref3;
+	git_oid id, other_id;
+
+	git_oid_fromstr(&id, commit_id);
+	git_oid_fromstr(&other_id, other_commit_id);
+
+	cl_git_fail_with(GIT_EMODIFIED, git_reference_symbolic_create_matching(&ref, g_repo, "HEAD", other_refname, 1, NULL, NULL, other_refname));
+
+	cl_git_pass(git_reference_lookup(&ref, g_repo, "HEAD"));
+	cl_git_pass(git_reference_symbolic_create_matching(&ref2, g_repo, "HEAD", other_refname, 1, NULL, NULL, refname));
+	cl_git_fail_with(GIT_EMODIFIED, git_reference_symbolic_set_target(&ref3, ref, other_refname, NULL, NULL));
+
+	git_reference_free(ref);
+	git_reference_free(ref2);
+	git_reference_free(ref3);
+}