diff --git a/src/git/tag.h b/src/git/tag.h new file mode 100644 index 000000000..509d2f047 --- /dev/null +++ b/src/git/tag.h @@ -0,0 +1,89 @@ +#ifndef INCLUDE_git_tag_h__ +#define INCLUDE_git_tag_h__ + +#include "common.h" +#include "oid.h" +#include "tree.h" + +/** + * @file git/tag.h + * @brief Git tag parsing routines + * @defgroup git_tag Git tag management + * @ingroup Git + * @{ + */ +GIT_BEGIN_DECL + +/** Parsed representation of a tag object. */ +typedef struct git_tag git_tag; + +/** + * Locate a reference to a tag without loading it. + * The generated tag object is owned by the revision + * pool and shall not be freed by the user. + * + * @param pool the pool to use when locating the tag. + * @param id identity of the tag to locate. + * @return the tag; NULL if the tag could not be created + */ +GIT_EXTERN(git_tag *) git_tag_lookup(git_revpool *pool, const git_oid *id); + +/** + * Locate a reference to a tag, and try to load and parse it it from + * the object cache or the object database. + * The generated tag object is owned by the revision + * pool and shall not be freed by the user. + * + * @param pool the pool to use when parsing/caching the tag. + * @param id identity of the tag to locate. + * @return the tag; NULL if the tag does not exist in the + * pool's git_odb, or if the tag is present but is + * too malformed to be parsed successfully. + */ +GIT_EXTERN(git_tag *) git_tag_parse(git_revpool *pool, const git_oid *id); + +/** + * Get the id of a tag. + * @param tag a previously loaded tag. + * @return object identity for the tag. + */ +GIT_EXTERN(const git_oid *) git_tag_id(git_tag *tag); + +/** + * Get the tagged object of a tag + * @param tag a previously loaded tag. + * @return reference to a repository object + */ +GIT_EXTERN(const git_repository_object *) git_tag_target(git_tag *t); + +/** + * Get the type of a tag's tagged object + * @param tag a previously loaded tag. + * @return type of the tagged object + */ +GIT_EXTERN(git_otype) git_tag_type(git_tag *t); + +/** + * Get the name of a tag + * @param tag a previously loaded tag. + * @return name of the tag + */ +GIT_EXTERN(const char *) git_tag_name(git_tag *t); + +/** + * Get the tagger (author) of a tag + * @param tag a previously loaded tag. + * @return reference to the tag's author + */ +GIT_EXTERN(const git_person *) git_tag_tagger(git_tag *t); + +/** + * Get the message of a tag + * @param tag a previously loaded tag. + * @return message of the tag + */ +GIT_EXTERN(const char *) git_tag_message(git_tag *t); + +/** @} */ +GIT_END_DECL +#endif diff --git a/src/tag.c b/src/tag.c new file mode 100644 index 000000000..803cfa13c --- /dev/null +++ b/src/tag.c @@ -0,0 +1,228 @@ +/* + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2, + * as published by the Free Software Foundation. + * + * In addition to the permissions in the GNU General Public License, + * the authors give you unlimited permission to link the compiled + * version of this file into combinations with other programs, + * and to distribute those combinations without any restriction + * coming from the use of this file. (The General Public License + * restrictions do apply in other respects; for example, they cover + * modification of the file, and distribution when not linked into + * a combined executable.) + * + * This file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "common.h" +#include "commit.h" +#include "tag.h" +#include "revwalk.h" +#include "git/odb.h" + + +void git_tag__free(git_tag *tag) +{ + free(tag->message); + free(tag->tag_name); + free(tag->tagger); + free(tag); +} + +const git_oid *git_tag_id(git_tag *t) +{ + return &t->object.id; +} + +const git_revpool_object *git_tag_target(git_tag *t) +{ + if (t->target) + return t->target; + + git_tag__parse(t); + return t->target; +} + +git_otype git_tag_type(git_tag *t) +{ + if (t->type) + return t->type; + + git_tag__parse(t); + return t->type; +} + + +const char *git_tag_name(git_tag *t) +{ + if (t->tag_name) + return t->tag_name; + + git_tag__parse(t); + return t->tag_name; +} + +const git_person *git_tag_tagger(git_tag *t) +{ + if (t->tagger) + return t->tagger; + + git_tag__parse(t); + return t->tagger; +} + +const char *git_tag_message(git_tag *t) +{ + if (t->message) + return t->message; + + git_tag__parse(t); + return t->message; +} + +static int parse_tag_buffer(git_tag *tag, char *buffer, const char *buffer_end) +{ + static const char *tag_types[] = { + NULL, "commit\n", "tree\n", "blob\n", "tag\n" + }; + + git_oid target_oid; + unsigned int i, text_len; + char *search; + + if (git__parse_oid(&target_oid, &buffer, buffer_end, "object ") < 0) + return GIT_EOBJCORRUPTED; + + if (buffer + 5 >= buffer_end) + return GIT_EOBJCORRUPTED; + + if (memcmp(buffer, "type ", 5) != 0) + return GIT_EOBJCORRUPTED; + buffer += 5; + + tag->type = GIT_OBJ_BAD; + + for (i = 1; i < ARRAY_SIZE(tag_types); ++i) { + size_t type_length = strlen(tag_types[i]); + + if (buffer + type_length >= buffer_end) + return GIT_EOBJCORRUPTED; + + if (memcmp(buffer, tag_types[i], type_length) == 0) { + tag->type = i; + buffer += type_length; + break; + } + } + + if (tag->type == GIT_OBJ_BAD) + return GIT_EOBJCORRUPTED; + + /* Lookup the tagged object once we know its type */ + tag->target = + git_repository_lookup(tag->object.repo, &target_oid, tag->type); + + /* FIXME: is the tag file really corrupted if the tagged object + * cannot be found on the database? */ + /* if (tag->target == NULL) + return GIT_EOBJCORRUPTED; */ + + if (buffer + 4 >= buffer_end) + return GIT_EOBJCORRUPTED; + + if (memcmp(buffer, "tag ", 4) != 0) + return GIT_EOBJCORRUPTED; + buffer += 4; + + search = memchr(buffer, '\n', buffer_end - buffer); + if (search == NULL) + return GIT_EOBJCORRUPTED; + + text_len = search - buffer; + + if (tag->tag_name != NULL) + free(tag->tag_name); + + tag->tag_name = git__malloc(text_len + 1); + memcpy(tag->tag_name, buffer, text_len); + tag->tag_name[text_len] = '\0'; + + buffer = search + 1; + + if (tag->tagger != NULL) + free(tag->tagger); + + tag->tagger = git__malloc(sizeof(git_person)); + + if (git__parse_person(tag->tagger, &buffer, buffer_end, "tagger ") != 0) + return GIT_EOBJCORRUPTED; + + text_len = buffer_end - buffer; + + if (tag->message != NULL) + free(tag->message); + + tag->message = git__malloc(text_len + 1); + memcpy(tag->message, buffer, text_len); + tag->message[text_len] = '\0'; + + return 0; +} + +int git_tag__parse(git_tag *tag) +{ + int error = 0; + git_obj odb_object; + + error = git_odb_read(&odb_object, tag->object.pool->db, &tag->object.id); + if (error < 0) + return error; + + if (odb_object.type != GIT_OBJ_TAG) { + error = GIT_EOBJTYPE; + goto cleanup; + } + + error = parse_tag_buffer(tag, odb_object.data, odb_object.data + odb_object.len); + +cleanup: + git_obj_close(&odb_object); + return error; +} + +git_tag *git_tag_lookup(git_revpool *pool, const git_oid *id) +{ + git_tag *tag = NULL; + + if (pool == NULL) + return NULL; + + tag = (git_tag *)git_revpool_table_lookup(pool->objects, id); + if (tag != NULL) + return tag; + + tag = git__malloc(sizeof(git_tag)); + + if (tag == NULL) + return NULL; + + memset(tag, 0x0, sizeof(git_tag)); + + /* Initialize parent object */ + git_oid_cpy(&tag->object.id, id); + tag->object.pool = pool; + tag->object.type = GIT_OBJ_TAG; + + git_revpool_table_insert(pool->objects, (git_revpool_object *)tag); + + return tag; +} diff --git a/src/tag.h b/src/tag.h new file mode 100644 index 000000000..5b1f5e2b9 --- /dev/null +++ b/src/tag.h @@ -0,0 +1,20 @@ +#ifndef INCLUDE_tag_h__ +#define INCLUDE_tag_h__ + +#include "git/tag.h" +#include "revobject.h" + +struct git_tag { + git_revpool_object object; + + git_revpool_object *target; + git_otype type; + char *tag_name; + git_person *tagger; + char *message; +}; + +void git_tag__free(git_tag *tag); +int git_tag__parse(git_tag *tag); + +#endif diff --git a/tests/resources/pack-odb/7b/4384978d2493e851f9cca7858815fac9b10980 b/tests/resources/pack-odb/7b/4384978d2493e851f9cca7858815fac9b10980 new file mode 100644 index 000000000..23c462f34 Binary files /dev/null and b/tests/resources/pack-odb/7b/4384978d2493e851f9cca7858815fac9b10980 differ diff --git a/tests/resources/pack-odb/b2/5fa35b38051e4ae45d4222e795f9df2e43f1d1 b/tests/resources/pack-odb/b2/5fa35b38051e4ae45d4222e795f9df2e43f1d1 new file mode 100644 index 000000000..f460f2547 Binary files /dev/null and b/tests/resources/pack-odb/b2/5fa35b38051e4ae45d4222e795f9df2e43f1d1 differ diff --git a/tests/t0801-readtag.c b/tests/t0801-readtag.c new file mode 100644 index 000000000..9e675e8b7 --- /dev/null +++ b/tests/t0801-readtag.c @@ -0,0 +1,48 @@ +#include "test_lib.h" +#include "test_helpers.h" +#include "commit.h" + +#include +#include +#include + +static const char *odb_dir = "../resources/pack-odb"; +static const char *tag1_id = "b25fa35b38051e4ae45d4222e795f9df2e43f1d1"; +static const char *tag2_id = "7b4384978d2493e851f9cca7858815fac9b10980"; +static const char *tagged_commit = "e90810b8df3e80c413d903f631643c716887138d"; + +BEGIN_TEST(readtag) + git_odb *db; + git_repository *repo; + git_tag *tag1, *tag2; + git_commit *commit; + git_oid id1, id2, id_commit; + + must_pass(git_odb_open(&db, odb_dir)); + + repo = git_repository_alloc(db); + must_be_true(repo != NULL); + + git_oid_mkstr(&id1, tag1_id); + git_oid_mkstr(&id2, tag2_id); + git_oid_mkstr(&id_commit, tagged_commit); + + tag1 = git_tag_lookup(repo, &id1); + must_be_true(tag1 != NULL); + + must_be_true(strcmp(git_tag_name(tag1), "test") == 0); + must_be_true(git_tag_type(tag1) == GIT_OBJ_TAG); + + tag2 = (git_tag *)git_tag_target(tag1); + must_be_true(tag2 != NULL); + + must_be_true(git_oid_cmp(&id2, git_tag_id(tag2)) == 0); + + commit = (git_commit *)git_tag_target(tag2); + must_be_true(commit != NULL); + + must_be_true(git_oid_cmp(&id_commit, git_commit_id(commit)) == 0); + + git_repository_free(repo); + git_odb_close(db); +END_TEST