Merge pull request #232 from schu/ref-available-cb reference_rename: respect all references v2
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
diff --git a/src/refs.c b/src/refs.c
index 7aaa1e7..1c2d1a8 100644
--- a/src/refs.c
+++ b/src/refs.c
@@ -83,6 +83,7 @@ static int packed_write(git_repository *repo);
static int reference_create_symbolic(git_reference **ref_out, git_repository *repo, const char *name, const char *target, int force);
static int reference_create_oid(git_reference **ref_out, git_repository *repo, const char *name, const git_oid *id, int force);
static int reference_rename(git_reference *ref, const char *new_name, int force);
+static int reference_available(git_repository *repo, const char *ref, const char *old_ref);
/* name normalization */
static int check_valid_ref_char(char ch);
@@ -1006,6 +1007,9 @@ static int reference_create_oid(git_reference **ref_out, git_repository *repo, c
if(git_reference_lookup(&ref, repo, name) == GIT_SUCCESS && !force)
return git__throw(GIT_EEXISTS, "Failed to create reference OID. Reference already exists");
+ if ((error = reference_available(repo, name, NULL)) < GIT_SUCCESS)
+ return git__rethrow(error, "Failed to create reference");
+
/*
* If they old ref was of the same type, then we can just update
* it (once we've checked that the target is valid). Otherwise we
@@ -1045,6 +1049,47 @@ cleanup:
return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to create reference OID");
}
+static int _reference_available_cb(const char *ref, void *data)
+{
+ assert(ref && data);
+
+ git_vector *refs = (git_vector *)data;
+
+ const char *new = (const char *)git_vector_get(refs, 0);
+ const char *old = (const char *)git_vector_get(refs, 1);
+
+ if (!old || strcmp(old, ref)) {
+ int reflen = strlen(ref);
+ int newlen = strlen(new);
+ int cmplen = reflen < newlen ? reflen : newlen;
+ const char *lead = reflen < newlen ? new : ref;
+
+ if (!strncmp(new, ref, cmplen) &&
+ lead[cmplen] == '/')
+ return GIT_EEXISTS;
+ }
+
+ return GIT_SUCCESS;
+}
+
+static int reference_available(git_repository *repo, const char *ref, const char* old_ref)
+{
+ int error;
+ git_vector refs;
+
+ if (git_vector_init(&refs, 2, NULL) < GIT_SUCCESS)
+ return GIT_ENOMEM;
+
+ git_vector_insert(&refs, (void *)ref);
+ git_vector_insert(&refs, (void *)old_ref);
+
+ error = git_reference_listcb(repo, GIT_REF_LISTALL, _reference_available_cb, (void *)&refs);
+
+ git_vector_free(&refs);
+
+ return error == GIT_SUCCESS ? GIT_SUCCESS : git__throw(GIT_EEXISTS, "Reference name `%s` conflicts with existing reference", ref);
+}
+
/*
* Rename a reference
*
@@ -1082,6 +1127,8 @@ static int reference_rename(git_reference *ref, const char *new_name, int force)
error != GIT_ENOTFOUND)
return git__rethrow(error, "Failed to rename reference");
+ if ((error = reference_available(ref->owner, new_name, ref->name)) < GIT_SUCCESS)
+ return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to rename reference. Reference already exists");
old_name = ref->name;
ref->name = git__strdup(new_name);
diff --git a/tests/t10-refs.c b/tests/t10-refs.c
index 3e7b079..33d2840 100644
--- a/tests/t10-refs.c
+++ b/tests/t10-refs.c
@@ -654,6 +654,38 @@ BEGIN_TEST(rename5, "can force-rename a reference with the name of an existing r
close_temp_repo(repo);
END_TEST
+static const char *ref_one_name = "refs/heads/one/branch";
+static const char *ref_one_name_new = "refs/heads/two/branch";
+static const char *ref_two_name = "refs/heads/two";
+
+BEGIN_TEST(rename6, "can not overwrite name of existing reference")
+ git_reference *ref, *ref_one, *ref_one_new, *ref_two;
+ git_repository *repo;
+ git_oid id;
+
+ must_pass(open_temp_repo(&repo, REPOSITORY_FOLDER));
+
+ must_pass(git_reference_lookup(&ref, repo, ref_master_name));
+ must_be_true(ref->type & GIT_REF_OID);
+
+ git_oid_cpy(&id, git_reference_oid(ref));
+
+ /* Create loose references */
+ must_pass(git_reference_create_oid(&ref_one, repo, ref_one_name, &id));
+ must_pass(git_reference_create_oid(&ref_two, repo, ref_two_name, &id));
+
+ /* Pack everything */
+ must_pass(git_reference_packall(repo));
+
+ /* Attempt to create illegal reference */
+ must_fail(git_reference_create_oid(&ref_one_new, repo, ref_one_name_new, &id));
+
+ /* Illegal reference couldn't be created so this is supposed to fail */
+ must_fail(git_reference_lookup(&ref_one_new, repo, ref_one_name_new));
+
+ close_temp_repo(repo);
+END_TEST
+
BEGIN_TEST(delete0, "deleting a ref which is both packed and loose should remove both tracks in the filesystem")
git_reference *looked_up_ref, *another_looked_up_ref;
git_repository *repo;
@@ -950,6 +982,7 @@ BEGIN_SUITE(refs)
ADD_TEST(rename3);
ADD_TEST(rename4);
ADD_TEST(rename5);
+ ADD_TEST(rename6);
ADD_TEST(delete0);
ADD_TEST(list0);