diff --git a/include/git2/tag.h b/include/git2/tag.h index c47e3412c..c751a13c0 100644 --- a/include/git2/tag.h +++ b/include/git2/tag.h @@ -140,7 +140,8 @@ GIT_EXTERN(const char *) git_tag_message(git_tag *t); * @param repo Repository where to store the tag * * @param tag_name Name for the tag; this name is validated - * for consistency + * for consistency. It should also not conflict with an + * already existing tag name * * @param target OID to which this tag points; note that no * validation is done on this OID. Use the _o version of this @@ -202,6 +203,78 @@ GIT_EXTERN(int) git_tag_create_frombuffer( git_repository *repo, const char *buffer); +/** + * Create a new tag in the repository from an OID + * and overwrite an already existing tag reference, if any. + * + * @param oid Pointer where to store the OID of the + * newly created tag + * + * @param repo Repository where to store the tag + * + * @param tag_name Name for the tag; this name is validated + * for consistency. + * + * @param target OID to which this tag points; note that no + * validation is done on this OID. Use the _fo version of this + * method to assure a proper object is being tagged + * + * @param target_type Type of the tagged OID; note that no + * validation is performed here either + * + * @param tagger Signature of the tagger for this tag, and + * of the tagging time + * + * @param message Full message for this tag + * + * @return 0 on success; error code otherwise. + * A tag object is written to the ODB, and a proper reference + * is written in the /refs/tags folder, pointing to it + */ +GIT_EXTERN(int) git_tag_create_f( + git_oid *oid, + git_repository *repo, + const char *tag_name, + const git_oid *target, + git_otype target_type, + const git_signature *tagger, + const char *message); + +/** + * Create a new tag in the repository from an existing + * `git_object` instance and overwrite an already existing + * tag reference, if any. + * + * This method replaces the `target` and `target_type` + * paremeters of `git_tag_create_f` by a single instance + * of a `const git_object *`, which is assured to be + * a proper object in the ODB and hence will create + * a valid tag + * + * @see git_tag_create_f + */ +GIT_EXTERN(int) git_tag_create_fo( + git_oid *oid, + git_repository *repo, + const char *tag_name, + const git_object *target, + const git_signature *tagger, + const char *message); + +/** + * Delete an existing tag reference. + * + * @param repo Repository where lives the tag + * + * @param tag_name Name of the tag to be deleted; + * this name is validated for consistency. + * + * @return 0 on success; error code otherwise. + */ +GIT_EXTERN(int) git_tag_delete( + git_repository *repo, + const char *tag_name); + /** @} */ GIT_END_DECL #endif diff --git a/src/tag.c b/src/tag.c index 5982a053b..ef58e0982 100644 --- a/src/tag.c +++ b/src/tag.c @@ -153,38 +153,61 @@ static int parse_tag_buffer(git_tag *tag, const char *buffer, const char *buffer return GIT_SUCCESS; } -int git_tag_create_o( - git_oid *oid, - git_repository *repo, - const char *tag_name, - const git_object *target, - const git_signature *tagger, - const char *message) +static int retreive_tag_reference(git_reference **tag_reference_out, char *ref_name_out, git_repository *repo, const char *tag_name) { - return git_tag_create( - oid, repo, tag_name, - git_object_id(target), - git_object_type(target), - tagger, message); + git_reference *tag_ref; + int error; + + git__joinpath(ref_name_out, GIT_REFS_TAGS_DIR, tag_name); + error = git_reference_lookup(&tag_ref, repo, ref_name_out); + if (error < GIT_SUCCESS) + return error; + + *tag_reference_out = tag_ref; + + return GIT_SUCCESS; } -int git_tag_create( +static int tag_create( git_oid *oid, git_repository *repo, const char *tag_name, const git_oid *target, git_otype target_type, const git_signature *tagger, - const char *message) + const char *message, + int allow_ref_overwrite) { size_t final_size = 0; git_odb_stream *stream; const char *type_str; char *tagger_str; + git_reference *new_ref; + + char ref_name[MAX_GITDIR_TREE_STRUCTURE_PATH_LENGTH]; int type_str_len, tag_name_len, tagger_str_len, message_len; - int error; + int error, should_update_ref = 0; + + /** Ensure the tag name doesn't conflict with an already existing + reference unless overwriting has explictly been requested **/ + error = retreive_tag_reference(&new_ref, ref_name, repo, tag_name); + + switch (error) { + case GIT_SUCCESS: + if (!allow_ref_overwrite) + return GIT_EEXISTS; + should_update_ref = 1; + + /* Fall trough */ + + case GIT_ENOTFOUND: + break; + + default: + return error; + } type_str = git_object_type2string(target_type); @@ -222,13 +245,14 @@ int git_tag_create( error = stream->finalize_write(oid, stream); stream->free(stream); - if (error == GIT_SUCCESS) { - char ref_name[512]; - git_reference *new_ref; - git__joinpath(ref_name, GIT_REFS_TAGS_DIR, tag_name); - error = git_reference_create_oid(&new_ref, repo, ref_name, oid); - } + if (error < GIT_SUCCESS) + return error; + if (!should_update_ref) + error = git_reference_create_oid(&new_ref, repo, ref_name, oid); + else + error = git_reference_set_oid(new_ref, oid); + return error; } @@ -261,6 +285,81 @@ cleanup: return error; } +int git_tag_create_o( + git_oid *oid, + git_repository *repo, + const char *tag_name, + const git_object *target, + const git_signature *tagger, + const char *message) +{ + return tag_create( + oid, repo, tag_name, + git_object_id(target), + git_object_type(target), + tagger, message, 0); +} + +int git_tag_create( + git_oid *oid, + git_repository *repo, + const char *tag_name, + const git_oid *target, + git_otype target_type, + const git_signature *tagger, + const char *message) +{ + return tag_create( + oid, repo, tag_name, + target, + target_type, + tagger, message, 0); +} + +int git_tag_create_fo( + git_oid *oid, + git_repository *repo, + const char *tag_name, + const git_object *target, + const git_signature *tagger, + const char *message) +{ + return tag_create( + oid, repo, tag_name, + git_object_id(target), + git_object_type(target), + tagger, message, 1); +} + +int git_tag_create_f( + git_oid *oid, + git_repository *repo, + const char *tag_name, + const git_oid *target, + git_otype target_type, + const git_signature *tagger, + const char *message) +{ + return tag_create( + oid, repo, tag_name, + target, + target_type, + tagger, message, 1); +} + +int git_tag_delete(git_repository *repo, const char *tag_name) +{ + int error; + git_reference *tag_ref; + char ref_name[MAX_GITDIR_TREE_STRUCTURE_PATH_LENGTH]; + + error = retreive_tag_reference(&tag_ref, ref_name, repo, tag_name); + if (error < GIT_SUCCESS) + return error; + + return git_reference_delete(tag_ref); +} + int git_tag__parse(git_tag *tag, git_odb_object *obj) { assert(tag); diff --git a/tests/t08-tag.c b/tests/t08-tag.c index 70eeb28a6..5bd3e14b0 100644 --- a/tests/t08-tag.c +++ b/tests/t08-tag.c @@ -58,6 +58,9 @@ BEGIN_TEST(read0, "read and parse a tag from the repository") must_be_true(git_oid_cmp(&id_commit, git_commit_id(commit)) == 0); + git_tag_close(tag1); + git_tag_close(tag2); + git_commit_close(commit); git_repository_free(repo); END_TEST @@ -72,20 +75,19 @@ BEGIN_TEST(write0, "write a tag to the repository and read it again") git_oid target_id, tag_id; const git_signature *tagger; git_reference *ref_tag; - /* char hex_oid[41]; */ must_pass(git_repository_open(&repo, REPOSITORY_FOLDER)); git_oid_mkstr(&target_id, tagged_commit); - /* create signatures */ + /* create signature */ tagger = git_signature_new(TAGGER_NAME, TAGGER_EMAIL, 123456789, 60); must_be_true(tagger != NULL); must_pass(git_tag_create( &tag_id, /* out id */ repo, - "the-tag", /* do not update the HEAD */ + "the-tag", &target_id, GIT_OBJ_COMMIT, tagger, @@ -94,6 +96,7 @@ BEGIN_TEST(write0, "write a tag to the repository and read it again") git_signature_free((git_signature *)tagger); must_pass(git_tag_lookup(&tag, repo, &tag_id)); + must_be_true(git_oid_cmp(git_tag_target_oid(tag), &target_id) == 0); /* Check attributes were set correctly */ tagger = git_tag_tagger(tag); @@ -111,12 +114,136 @@ BEGIN_TEST(write0, "write a tag to the repository and read it again") must_pass(remove_loose_object(REPOSITORY_FOLDER, (git_object *)tag)); + git_tag_close(tag); git_repository_free(repo); END_TEST +BEGIN_TEST(write1, "write a tag to the repository which points to an unknown oid and read it again") + git_repository *repo; + git_tag *tag; + git_oid target_id, tag_id; + const git_signature *tagger; + git_reference *ref_tag; + git_object *zombie; + + must_pass(git_repository_open(&repo, REPOSITORY_FOLDER)); + + git_oid_mkstr(&target_id, "deadbeef1b46c854b31185ea97743be6a8774479"); + + /* create signature */ + tagger = git_signature_new(TAGGER_NAME, TAGGER_EMAIL, 123456789, 60); + must_be_true(tagger != NULL); + + must_pass(git_tag_create( + &tag_id, /* out id */ + repo, + "the-zombie-tag", + &target_id, + GIT_OBJ_COMMIT, + tagger, + TAGGER_MESSAGE)); + + git_signature_free((git_signature *)tagger); + + must_pass(git_tag_lookup(&tag, repo, &tag_id)); + + /* The non existent target can not be looked up */ + must_fail(git_tag_target(&zombie, tag)); + + must_pass(git_reference_lookup(&ref_tag, repo, "refs/tags/the-zombie-tag")); + + must_pass(git_reference_delete(ref_tag)); + must_pass(remove_loose_object(REPOSITORY_FOLDER, (git_object *)tag)); + + git_tag_close(tag); + git_repository_free(repo); + +END_TEST + +BEGIN_TEST(write2, "Attempt to write a tag bearing the same name than an already existing tag") + git_repository *repo; + git_oid target_id, tag_id; + const git_signature *tagger; + + must_pass(git_repository_open(&repo, REPOSITORY_FOLDER)); + + git_oid_mkstr(&target_id, tagged_commit); + + /* create signature */ + tagger = git_signature_new(TAGGER_NAME, TAGGER_EMAIL, 123456789, 60); + must_be_true(tagger != NULL); + + must_fail(git_tag_create( + &tag_id, /* out id */ + repo, + "very-simple", + &target_id, + GIT_OBJ_COMMIT, + tagger, + TAGGER_MESSAGE)); + + git_signature_free((git_signature *)tagger); + + git_repository_free(repo); + +END_TEST + +BEGIN_TEST(write3, "Replace an already existing tag") + git_repository *repo; + git_oid target_id, tag_id, old_tag_id; + const git_signature *tagger; + git_reference *ref_tag; + + must_pass(open_temp_repo(&repo, REPOSITORY_FOLDER)); + + git_oid_mkstr(&target_id, tagged_commit); + + must_pass(git_reference_lookup(&ref_tag, repo, "refs/tags/very-simple")); + git_oid_cpy(&old_tag_id, git_reference_oid(ref_tag)); + + /* create signature */ + tagger = git_signature_new(TAGGER_NAME, TAGGER_EMAIL, 123456789, 60); + must_be_true(tagger != NULL); + + must_pass(git_tag_create_f( + &tag_id, /* out id */ + repo, + "very-simple", + &target_id, + GIT_OBJ_COMMIT, + tagger, + TAGGER_MESSAGE)); + + git_signature_free((git_signature *)tagger); + + must_pass(git_reference_lookup(&ref_tag, repo, "refs/tags/very-simple")); + must_be_true(git_oid_cmp(git_reference_oid(ref_tag), &tag_id) == 0); + must_be_true(git_oid_cmp(git_reference_oid(ref_tag), &old_tag_id) != 0); + + close_temp_repo(repo); + +END_TEST + +BEGIN_TEST(write4, "Delete an already existing tag") + git_repository *repo; + git_reference *ref_tag; + + must_pass(open_temp_repo(&repo, REPOSITORY_FOLDER)); + + must_pass(git_tag_delete(repo,"very-simple")); + + must_fail(git_reference_lookup(&ref_tag, repo, "refs/tags/very-simple")); + + close_temp_repo(repo); + +END_TEST BEGIN_SUITE(tag) ADD_TEST(read0); - ADD_TEST(write0); + ADD_TEST(write0); + ADD_TEST(write1); + ADD_TEST(write2); + ADD_TEST(write3); + ADD_TEST(write4); END_SUITE diff --git a/tests/t10-refs.c b/tests/t10-refs.c index 413811c9d..b1124b36f 100644 --- a/tests/t10-refs.c +++ b/tests/t10-refs.c @@ -227,9 +227,8 @@ BEGIN_TEST(create0, "create a new symbolic reference") must_pass(git_reference_resolve(&resolved_ref, looked_up_ref)); must_be_true(git_oid_cmp(&id, git_reference_oid(resolved_ref)) == 0); + git_reference_delete(looked_up_ref); git_repository_free(repo); - - must_pass(gitfo_unlink(ref_path)); /* TODO: replace with git_reference_delete() when available */ END_TEST BEGIN_TEST(create1, "create a deep symbolic reference") @@ -250,9 +249,8 @@ BEGIN_TEST(create1, "create a deep symbolic reference") must_pass(git_reference_resolve(&resolved_ref, looked_up_ref)); must_be_true(git_oid_cmp(&id, git_reference_oid(resolved_ref)) == 0); + git_reference_delete(looked_up_ref); git_repository_free(repo); - - must_pass(gitfo_unlink(ref_path)); /* TODO: replace with git_reference_delete() when available */ END_TEST BEGIN_TEST(create2, "create a new OID reference") @@ -290,9 +288,28 @@ BEGIN_TEST(create2, "create a new OID reference") must_pass(git_reference_lookup(&looked_up_ref, repo, new_head)); must_be_true(git_oid_cmp(&id, git_reference_oid(looked_up_ref)) == 0); + git_reference_delete(looked_up_ref); git_repository_free(repo); +END_TEST - must_pass(gitfo_unlink(ref_path)); /* TODO: replace with git_reference_delete() when available */ +BEGIN_TEST(create3, "Can not create a new OID reference which targets at an unknown id") + git_reference *new_reference, *looked_up_ref; + git_repository *repo; + git_oid id; + + const char *new_head = "refs/heads/new-head"; + + git_oid_mkstr(&id, "deadbeef3f795b2b4353bcce3a527ad0a4f7f644"); + + must_pass(git_repository_open(&repo, REPOSITORY_FOLDER)); + + /* Create and write the new object id reference */ + must_fail(git_reference_create_oid(&new_reference, repo, new_head, &id)); + + /* Ensure the reference can't be looked-up... */ + must_fail(git_reference_lookup(&looked_up_ref, repo, new_head)); + + git_repository_free(repo); END_TEST static const char *ref_name = "refs/heads/other"; @@ -892,6 +909,7 @@ BEGIN_SUITE(refs) ADD_TEST(create0); ADD_TEST(create1); ADD_TEST(create2); + ADD_TEST(create3); ADD_TEST(overwrite0); ADD_TEST(overwrite1);