refs: bring conditional symbolic updates to the frontend Bring the race detection goodness to symbolic references as well.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153
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);
+}