diff --git a/src/errors.c b/src/errors.c index 41c4a8d3a..34a15b258 100644 --- a/src/errors.c +++ b/src/errors.c @@ -20,6 +20,11 @@ static struct { {GIT_EZLIB, "The Z library failed to inflate/deflate an object's data"}, {GIT_EBUSY, "The queried object is currently busy"}, {GIT_EINVALIDPATH, "The path is invalid"}, + {GIT_EBAREINDEX, "The index file is not backed up by an existing repository"}, + {GIT_EINVALIDREFNAME, "The name of the reference is not valid"}, + {GIT_EREFCORRUPTED, "The specified reference has its data corrupted"}, + {GIT_ETOONESTEDSYMREF, "The specified symbolic reference is too deeply nested"}, + {GIT_EPACKEDREFSCORRUPTED, "The pack-refs file is either corrupted of its format is not currently supported"} }; const char *git_strerror(int num) diff --git a/src/git2/common.h b/src/git2/common.h index 350429f5c..aae707cb6 100644 --- a/src/git2/common.h +++ b/src/git2/common.h @@ -134,7 +134,19 @@ #define GIT_EBUSY (GIT_ERROR - 13) /** The index file is not backed up by an existing repository */ -#define GIT_EBAREINDEX (GIT_ERROR -14) +#define GIT_EBAREINDEX (GIT_ERROR - 14) + +/** The name of the reference is not valid */ +#define GIT_EINVALIDREFNAME (GIT_ERROR - 15) + +/** The specified reference has its data corrupted */ +#define GIT_EREFCORRUPTED (GIT_ERROR - 16) + +/** The specified symbolic reference is too deeply nested */ +#define GIT_ETOONESTEDSYMREF (GIT_ERROR - 17) + +/** The pack-refs file is either corrupted of its format is not currently supported */ +#define GIT_EPACKEDREFSCORRUPTED (GIT_ERROR - 18) /** The path is invalid */ #define GIT_EINVALIDPATH (GIT_ERROR - 19) diff --git a/src/git2/repository.h b/src/git2/repository.h index 37a541f24..129d3bb5f 100644 --- a/src/git2/repository.h +++ b/src/git2/repository.h @@ -215,6 +215,22 @@ GIT_EXTERN(void) git_repository_free(git_repository *repo); */ GIT_EXTERN(int) git_repository_init(git_repository **repo_out, const char *path, unsigned is_bare); +/** + * Lookup a reference by its name in the repository. + * + * The generated reference is owned by the repository and + * should not be freed by the user. + * + * TODO: + * - Ensure the reference name is valid + * + * @param reference_out pointer to the looked-up reference + * @param repo the repository to look up the reference + * @param name the long name for the reference (e.g. HEAD, ref/heads/master, refs/tags/v0.1.0, ...) + * @return a reference to the reference + */ +GIT_EXTERN(int) git_repository_reference_lookup(git_reference **reference_out, git_repository *repo, const char *name); + /** @} */ GIT_END_DECL #endif diff --git a/src/git2/types.h b/src/git2/types.h index cfc0cf379..389f32868 100644 --- a/src/git2/types.h +++ b/src/git2/types.h @@ -135,6 +135,22 @@ typedef struct git_signature { git_time when; /** time when the action happened */ } git_signature; +/** In-memory representation of a reference. */ +typedef struct git_reference git_reference; + +/** In-memory representation of a reference to an object id. */ +typedef struct git_reference_object_id git_reference_object_id; + +/** In-memory representation of a reference which points at another reference. The target reference is embedded. */ +typedef struct git_reference_symbolic git_reference_symbolic; + +/** Basic type of any Git reference. */ +typedef enum { + GIT_REF_ANY = -2, /** Reference can be any of the following */ + GIT_REF_OBJECT_ID = 0, /** A reference which points at an object id */ + GIT_REF_SYMBOLIC = 1, /** A reference which points at another reference */ +} git_rtype; + /** @} */ GIT_END_DECL diff --git a/src/refs.c b/src/refs.c new file mode 100644 index 000000000..91f13aed0 --- /dev/null +++ b/src/refs.c @@ -0,0 +1,547 @@ +/* + * 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 "refs.h" +#include "hash.h" +#include "repository.h" +#include "fileops.h" + +#define HASH_SEED 2147483647 +#define MAX_NESTING_LEVEL 5 + +static const int default_table_size = 32; + +static struct { + size_t size; /* size in bytes of the object structure */ +} git_references_table[] = { + {sizeof(git_reference_object_id)}, /* 0 = GIT_REF_OBJECT_ID */ + {sizeof(git_reference_symbolic)}, /* 1 = GIT_REF_SYMBOLIC */ +}; + +static uint32_t reftable_hash(const void *key) +{ + return git__hash(key, strlen((const char *)key), HASH_SEED); +} + +static int reftable_haskey(void *reference, const void *key) +{ + git_reference *ref; + char *name; + + ref = (git_reference *)reference; + name = (char *)key; + + return strcmp(name, ref->name) == 0; +} + +git_reference_database *git_reference_database__alloc() +{ + git_reference_database *ref_database = git__malloc(sizeof(git_reference_database)); + if (!ref_database) + return NULL; + + memset(ref_database, 0x0, sizeof(git_reference_database)); + + ref_database->references = git_hashtable_alloc( + default_table_size, + reftable_hash, + reftable_haskey); + + if (ref_database->references == NULL) { + free(ref_database); + return NULL; + } + + return ref_database; +} + +static void reference__free(git_reference *reference) +{ + assert(reference); + + switch (reference->type) { + case GIT_REF_SYMBOLIC: + // The target of the symbolic ref has to be freed by itself. + + /* Fallthrough */ + + case GIT_REF_ANY: + case GIT_REF_OBJECT_ID: + if (reference->name) + free(reference->name); + + /* Fallthrough */ + + default: + free(reference); + break; + } +} + +void git_reference_database__free(git_reference_database *ref_database) +{ + git_hashtable_iterator it; + git_reference *reference; + + assert(ref_database); + + git_hashtable_iterator_init(ref_database->references, &it); + + while ((reference = (git_reference *)git_hashtable_iterator_next(&it)) != NULL) { + git_hashtable_remove(ref_database->references, reference->name); + reference__free(reference); + } + + git_hashtable_free(ref_database->references); + free(ref_database); +} + + +static int check_refname_validity(const char *name) { + int error = GIT_SUCCESS; + + // TODO : To be implemented + + return error; +} + +static int reference_newobject(git_reference **reference_out, git_rtype type, const char *name) +{ + git_reference *reference = NULL; + + assert(reference_out && name); + + *reference_out = NULL; + + switch (type) { + case GIT_REF_OBJECT_ID: + case GIT_REF_SYMBOLIC: + break; + + default: + return GIT_EINVALIDTYPE; + } + + reference = git__malloc(git_references_table[type].size); + + if (reference == NULL) + return GIT_ENOMEM; + + memset(reference, 0x0, git_references_table[type].size); + + reference->name = git__malloc(strlen(name) + 1); + strcpy(reference->name, name); + + reference->type = type; + + + *reference_out = reference; + + return GIT_SUCCESS; +} + +static int symbolic_reference_target_name__parse(char *target_name_out, const char *name, gitfo_buf *buffer) { + int error = GIT_SUCCESS; + char *refname_start, *refname_end; + const char *buffer_end; + int refname_len; + + refname_start = (char *)buffer->data; + buffer_end = (const char *)(buffer->data) + buffer->len; + + if (git__prefixcmp(refname_start, GIT_SYMREF)) + return GIT_EREFCORRUPTED; + + refname_start += strlen(GIT_SYMREF); + + /* Skip the potential white spaces */ + while (isspace(refname_start[0]) && refname_start < buffer_end) + refname_start++; + + refname_end = refname_start; + + /* Seek the end of the target reference name */ + while(!isspace(refname_end[0]) && refname_end < buffer_end) + refname_end++; + + refname_len = refname_end - refname_start; + + memcpy(target_name_out, refname_start, refname_len); + target_name_out[refname_len] = 0; + + return error; +} + +static int object_id_reference__parse(git_reference **reference_out, const char *name, gitfo_buf *buffer) { + int error = GIT_SUCCESS; + git_oid target_oid; + git_reference *reference; + char *buffer_start; + const char *buffer_end; + + buffer_start = (char *)buffer->data; + buffer_end = (const char *)(buffer_start) + buffer->len; + + /* Is this a valid object id ? */ + error = git__parse_oid(&target_oid, &buffer_start, buffer_end, ""); + if (error < GIT_SUCCESS) + return error; + + error = reference_newobject(&reference, GIT_REF_OBJECT_ID, name); + if (error < GIT_SUCCESS) + return error; + + git_oid_cpy(&((git_reference_object_id *)reference)->id, &target_oid); + + *reference_out = reference; + + return error; +} + +static int read_loose_reference(gitfo_buf *file_content, const char *name, const char *path_repository) +{ + int error = GIT_SUCCESS; + char ref_path[GIT_PATH_MAX]; + + /* Determine the full path of the ref */ + strcpy(ref_path, path_repository); + strcat(ref_path, name); + + /* Does it even exist ? */ + if (gitfo_exists(ref_path) < GIT_SUCCESS) + return GIT_ENOTFOUND; + + /* A ref can not be a directory */ + if (!gitfo_isdir(ref_path)) + return GIT_EINVALIDREFNAME; + + error = gitfo_read_file(file_content, ref_path); + + return error; +} + +static int try_to_find_an_existing_loose_reference(git_reference **reference_out, git_reference_database *ref_database, const char *name, const char *path_repository, int *nesting_level) +{ + int error = GIT_SUCCESS; + gitfo_buf file_content = GITFO_BUF_INIT; + git_reference *reference, *target_reference; + git_reference_symbolic *peeled_reference; + char target_name[MAX_GITDIR_TREE_STRUCTURE_PATH_LENGTH]; + + + error = read_loose_reference(&file_content, name, path_repository); + if (error < GIT_SUCCESS) + goto cleanup; + + /* Does this look like a symbolic ref ? */ + if (!git__prefixcmp((const char *)(file_content.data), GIT_SYMREF)) { + error = symbolic_reference_target_name__parse(target_name, name, &file_content); + if (error < GIT_SUCCESS) + goto cleanup; + + error = git_reference_lookup(&target_reference, ref_database, target_name, path_repository, nesting_level); + if (error < GIT_SUCCESS) + goto cleanup; + + error = reference_newobject((git_reference **)&peeled_reference, GIT_REF_SYMBOLIC, name); + if (error < GIT_SUCCESS) + goto cleanup; + + peeled_reference->target = target_reference; + + reference = (git_reference *)peeled_reference; + + goto found; + } + + if (object_id_reference__parse(&reference, name, &file_content) < GIT_SUCCESS) { + error = GIT_EREFCORRUPTED; + goto cleanup; + } + +found: + *reference_out = reference; + +cleanup: + if (file_content.data) + gitfo_free_buf(&file_content); + + return error; +} + +static int read_packed_refs_content(gitfo_buf *file_content, const char *path_repository) +{ + int error = GIT_SUCCESS; + char ref_path[GIT_PATH_MAX]; + + /* Determine the full path of the file */ + strcpy(ref_path, path_repository); + strcat(ref_path, GIT_PACKEDREFS_FILE); + + /* Does it even exist ? */ + if (gitfo_exists(ref_path) < GIT_SUCCESS) + return GIT_ENOTFOUND; + + error = gitfo_read_file(file_content, ref_path); + + return error; +} + +static int packed_tag_peeled_reference__parse(git_oid *peeled_oid_out, git_reference *tag_reference, char** buffer_out, const char *buffer_end) +{ + int error = GIT_SUCCESS; + + /* Ensure it's not the first entry of the file */ + if (tag_reference == NULL) + return GIT_EPACKEDREFSCORRUPTED; + + /* Ensure reference is a tag */ + if (git__prefixcmp(tag_reference->name, GIT_REFS_TAGS_DIR)) + return GIT_EPACKEDREFSCORRUPTED; + + /* Is this a valid object id ? */ + if (git__parse_oid(peeled_oid_out, buffer_out, buffer_end, "^") < GIT_SUCCESS) { + error = GIT_EPACKEDREFSCORRUPTED; + } + + return error; +} + +static int packed_reference__parse(git_oid *oid_out, char *reference_name_out, char** buffer_out, const char *buffer_end) +{ + int error = GIT_SUCCESS; + char *refname_end; + int refname_len; + + /* This should be the beginning of a line containing an object id, a space and its name */ + if ((*buffer_out + GIT_OID_HEXSZ)[0] != ' ') + return GIT_EPACKEDREFSCORRUPTED; + + /* Slight hack to reuse git__parse_oid() which assumes that the id is LF terminated */ + (*buffer_out + GIT_OID_HEXSZ)[0] = '\n'; + + /* Is this a valid object id ? */ + if (git__parse_oid(oid_out, buffer_out, buffer_end, "") < GIT_SUCCESS) + return GIT_EPACKEDREFSCORRUPTED; + + /* We should be at the begining of the name of the reference */ + if (isspace(*buffer_out[0])) + return GIT_EPACKEDREFSCORRUPTED; + + refname_end = *buffer_out; + + /* Seek the end of the target reference name */ + while(!isspace(refname_end[0]) && refname_end < buffer_end) + refname_end++; + + refname_len = refname_end - *buffer_out; + + memcpy(reference_name_out, *buffer_out, refname_len); + reference_name_out[refname_len] = 0; + + *buffer_out = refname_end + 1; + + return error; +} + +static int packed_reference_file__parse(git_reference **reference_out, git_reference_database *ref_database, const char *name, const char *path_repository, int *nesting_level) +{ + int error = GIT_SUCCESS; + gitfo_buf file_content = GITFO_BUF_INIT; + char *buffer_start; + const char *buffer_end; + char reference_name[MAX_GITDIR_TREE_STRUCTURE_PATH_LENGTH]; + git_oid oid, peeled_oid; + git_reference *reference = NULL, *found_reference = NULL; + + error = read_packed_refs_content(&file_content, path_repository); + if (error < GIT_SUCCESS) + goto cleanup; + + buffer_start = (char *)file_content.data; + buffer_end = (const char *)(buffer_start) + file_content.len; + + /* Does the header look like valid ? */ + if (git__prefixcmp((const char *)(buffer_start), GIT_PACKEDREFS_HEADER)) { + error = GIT_EPACKEDREFSCORRUPTED; + goto cleanup; + } + + /* Let's skip the header */ + buffer_start += strlen(GIT_PACKEDREFS_HEADER); + + while (buffer_start < buffer_end) { + /* Is it a peeled reference pointed at by a tag ? */ + if (buffer_start[0] == '^') { + error = packed_tag_peeled_reference__parse(&peeled_oid, reference, &buffer_start, buffer_end); + if (error < GIT_SUCCESS) + goto cleanup; + + /* As we do not _currently_ need the peeled object pointed at by the tag, we just don't use the parsed object id. Maybe later ? */ + + /* Reinit the reference to catch potential successive lines starting by '^' */ + reference = NULL; + + continue; + } + + error = packed_reference__parse(&oid, reference_name, &buffer_start, buffer_end); + if (error < GIT_SUCCESS) + goto cleanup; + + /* Does a more up-to-date loose reference exist ? */ + reference = git_hashtable_lookup(ref_database->references, reference_name); + if (reference == NULL) { + error = reference_newobject(&reference, GIT_REF_OBJECT_ID, reference_name); + if (error < GIT_SUCCESS) + goto cleanup; + + reference->is_packed = 1; + + git_oid_cpy(&((git_reference_object_id *)reference)->id, &oid); + + git_hashtable_insert(ref_database->references, reference->name, reference); + + /* Is it the reference we're looking for ? */ + if (!strcmp(reference_name, name)) + found_reference = reference; // TODO : Should we guard against two found references in the same packed-refs file ? + } + } + + ref_database->have_packed_refs_been_parsed = 1; + + if (found_reference == NULL) { + error = GIT_ENOTFOUND; + goto cleanup; + } + + *reference_out = found_reference; + +cleanup: + if (file_content.data) + gitfo_free_buf(&file_content); + + return error; +} + +int git_reference_lookup(git_reference **reference_out, git_reference_database *ref_database, const char *name, const char *path_repository, int *nesting_level) +{ + int error = GIT_SUCCESS; + git_reference *reference, *packed_reference = NULL; + + if (*nesting_level == MAX_NESTING_LEVEL) { + return GIT_ETOONESTEDSYMREF; + } + + error = check_refname_validity(name); + if (error < GIT_SUCCESS) + return error; + + /* Has the ref already been previously parsed ? */ + reference = git_hashtable_lookup(ref_database->references, name); + + /* Has a loose reference been found ? */ + if (reference != NULL && !reference->is_packed) { + *reference_out = reference; + return GIT_SUCCESS; + } + + /* Has every available ref already been parsed ? */ + if (ref_database->is_fully_loaded) { + + if (reference == NULL) { + return GIT_ENOTFOUND; + } else { + /* Is is safe to consider the packed reference as the most up-to-date reference */ + assert(reference->is_packed); + *reference_out = reference; + return GIT_SUCCESS; + } + } + + /* We temporarily store the packed reference until we're sure no more up-to-date loose reference exists. */ + if (reference != NULL) { + assert(reference->is_packed); + packed_reference = reference; + } + + if (*nesting_level == 0) { + /* Is the database being populated */ + if (ref_database->is_busy) + return GIT_EBUSY; + + ref_database->is_busy = 1; + } + + (*nesting_level)++; + + /* Does the reference exist as a loose file based reference ? */ + error = try_to_find_an_existing_loose_reference(&reference, ref_database, name, path_repository, nesting_level); + + /* Have we found a more up-to-date loose reference than the packed reference we stored ? */ + if (error == GIT_SUCCESS && packed_reference != NULL) { + git_hashtable_remove(ref_database->references, packed_reference->name); + reference__free(packed_reference); + } + + if (error == GIT_SUCCESS) { + git_hashtable_insert(ref_database->references, reference->name, reference); + goto found; + } + + if (error != GIT_ENOTFOUND) + goto cleanup; + + /* Nothing has been found in the loose refs */ + assert(error == GIT_ENOTFOUND); + + /* If we've stored a pack reference, now is the time to return it */ + if (packed_reference != NULL) { + reference = packed_reference; + error = GIT_SUCCESS; + goto found; + } + + /* Have the dormant references already been parsed ? */ + if (ref_database->have_packed_refs_been_parsed) + return GIT_ENOTFOUND; + + /* has the reference previously been packed ? */ + error = packed_reference_file__parse(&reference, ref_database, name, path_repository, nesting_level); + if (error < GIT_SUCCESS) { + goto cleanup; + } + +found: + *reference_out = reference; + +cleanup: + (*nesting_level)--; + + if (*nesting_level == 0) + ref_database->is_busy = 0; + + return error; +} diff --git a/src/refs.h b/src/refs.h new file mode 100644 index 000000000..3cfd46d58 --- /dev/null +++ b/src/refs.h @@ -0,0 +1,48 @@ +#ifndef INCLUDE_refs_h__ +#define INCLUDE_refs_h__ + +#include "common.h" +#include "git2/oid.h" +#include "hashtable.h" + +#define GIT_REFS_DIR "refs/" +#define GIT_REFS_HEADS_DIR GIT_REFS_DIR "heads/" +#define GIT_REFS_TAGS_DIR GIT_REFS_DIR "tags/" + +#define GIT_SYMREF "ref:" +#define GIT_PACKEDREFS_FILE "packed-refs" +#define GIT_PACKEDREFS_HEADER "# pack-refs with: peeled \n" +#define MAX_GITDIR_TREE_STRUCTURE_PATH_LENGTH 100 + +struct git_reference { + git_rtype type; + char *name; + + unsigned is_packed:1; +}; + +struct git_reference_object_id { + git_reference base; + + git_oid id; +}; + +struct git_reference_symbolic { + git_reference base; + + git_reference *target; +}; + +typedef struct { + git_hashtable *references; + + unsigned is_fully_loaded:1; + unsigned have_packed_refs_been_parsed:1; + unsigned is_busy:1; +} git_reference_database; + +git_reference_database *git_reference_database__alloc(); +void git_reference_database__free(git_reference_database *ref_database); +int git_reference_lookup(git_reference **reference_out, git_reference_database *ref_database, const char *name, const char *path_repository, int *nesting_level); + +#endif \ No newline at end of file diff --git a/src/repository.c b/src/repository.c index 349afba9f..6fc67e409 100644 --- a/src/repository.c +++ b/src/repository.c @@ -33,18 +33,16 @@ #include "blob.h" #include "fileops.h" +#include "refs.h" + #define GIT_DIR "/.git/" #define GIT_OBJECTS_DIR "objects/" #define GIT_OBJECTS_INFO_DIR GIT_OBJECTS_DIR "info/" #define GIT_OBJECTS_PACK_DIR GIT_OBJECTS_DIR "pack/" -#define GIT_REFS_DIR "refs/" -#define GIT_REFS_HEADS_DIR GIT_REFS_DIR "heads/" -#define GIT_REFS_TAGS_DIR GIT_REFS_DIR "tags/" #define GIT_INDEX_FILE "index" #define GIT_HEAD_FILE "HEAD" -#define GIT_SYMREF "ref: " #define GIT_BRANCH_MASTER "master" static const int OBJECT_TABLE_SIZE = 32; @@ -229,6 +227,13 @@ static git_repository *repository_alloc() return NULL; } + repo->ref_database = git_reference_database__alloc(); + if (repo->ref_database == NULL) { + git_hashtable_free(repo->objects); + free(repo); + return NULL; + } + return repo; } @@ -359,6 +364,8 @@ void git_repository_free(git_repository *repo) git_hashtable_free(repo->objects); + git_reference_database__free(repo->ref_database); + if (repo->db != NULL) git_odb_close(repo->db); @@ -579,8 +586,6 @@ static int repo_init_structure(repo_init *results) static int repo_init_find_dir(repo_init *results, const char* path) { - const int MAX_GITDIR_TREE_STRUCTURE_PATH_LENGTH = 66; - char temp_path[GIT_PATH_MAX]; int path_len; int error = GIT_SUCCESS; @@ -628,3 +633,15 @@ cleanup: free(results.path_repository); return error; } + +int git_repository_reference_lookup(git_reference **reference_out, git_repository *repo, const char *name) +{ + int error = GIT_SUCCESS; + int nesting_level = 0; + + assert(repo && reference_out && name); + + error = git_reference_lookup(reference_out, repo->ref_database, name, repo->path_repository, &nesting_level); + + return error; +} diff --git a/src/repository.h b/src/repository.h index 44b37e1d0..1e9e0a5b9 100644 --- a/src/repository.h +++ b/src/repository.h @@ -8,6 +8,7 @@ #include "hashtable.h" #include "index.h" +#include "refs.h" typedef struct { git_rawobj raw; @@ -27,6 +28,7 @@ struct git_repository { git_odb *db; git_index *index; git_hashtable *objects; + git_reference_database *ref_database; char *path_repository; char *path_index; diff --git a/tests/NAMING b/tests/NAMING index eba914722..ab425e23e 100644 --- a/tests/NAMING +++ b/tests/NAMING @@ -22,3 +22,15 @@ Categories 03__: Basic object writing. 04__: Parsing and loading commit data + +05__: To be described + +06__: To be described + +07__: To be described + +08__: To be described + +09__: To be described + +10__: Symbolic, loose and packed references reading and writing. \ No newline at end of file diff --git a/tests/resources/testrepo.git/HEAD b/tests/resources/testrepo.git/HEAD index e69de29bb..cb089cd89 100644 Binary files a/tests/resources/testrepo.git/HEAD and b/tests/resources/testrepo.git/HEAD differ diff --git a/tests/resources/testrepo.git/packed-refs b/tests/resources/testrepo.git/packed-refs new file mode 100644 index 000000000..52f5e876f Binary files /dev/null and b/tests/resources/testrepo.git/packed-refs differ diff --git a/tests/resources/testrepo.git/refs/heads/br2 b/tests/resources/testrepo.git/refs/heads/br2 new file mode 100644 index 000000000..aab87e5e7 Binary files /dev/null and b/tests/resources/testrepo.git/refs/heads/br2 differ diff --git a/tests/resources/testrepo.git/refs/heads/master b/tests/resources/testrepo.git/refs/heads/master new file mode 100644 index 000000000..9536ad89c Binary files /dev/null and b/tests/resources/testrepo.git/refs/heads/master differ diff --git a/tests/resources/testrepo.git/refs/heads/packed-test b/tests/resources/testrepo.git/refs/heads/packed-test new file mode 100644 index 000000000..f2c14ad83 Binary files /dev/null and b/tests/resources/testrepo.git/refs/heads/packed-test differ diff --git a/tests/resources/testrepo.git/refs/heads/test b/tests/resources/testrepo.git/refs/heads/test new file mode 100644 index 000000000..399c4c73e Binary files /dev/null and b/tests/resources/testrepo.git/refs/heads/test differ diff --git a/tests/resources/testrepo.git/refs/tags/test b/tests/resources/testrepo.git/refs/tags/test new file mode 100644 index 000000000..6ee952a03 Binary files /dev/null and b/tests/resources/testrepo.git/refs/tags/test differ diff --git a/tests/resources/testrepo.git/refs/tags/very-simple b/tests/resources/testrepo.git/refs/tags/very-simple new file mode 100644 index 000000000..584495d3c Binary files /dev/null and b/tests/resources/testrepo.git/refs/tags/very-simple differ diff --git a/tests/t1001-readtag.c b/tests/t1001-readtag.c new file mode 100644 index 000000000..57de1aa23 --- /dev/null +++ b/tests/t1001-readtag.c @@ -0,0 +1,39 @@ +#include "test_lib.h" +#include "test_helpers.h" +#include "refs.h" + +static const char *loose_tag_ref_name = "refs/tags/test"; +static const char *non_existing_tag_ref_name = "refs/tags/i-do-not-exist"; + +BEGIN_TEST(loose_tag_reference_looking_up) + git_repository *repo; + git_reference *reference; + git_object *object; + + must_pass(git_repository_open(&repo, REPOSITORY_FOLDER)); + + + must_pass(git_repository_reference_lookup(&reference, repo, loose_tag_ref_name)); + must_be_true(reference->type == GIT_REF_OBJECT_ID); + must_be_true(reference->is_packed == 0); + must_be_true(strcmp(reference->name, loose_tag_ref_name) == 0); + + + must_pass(git_repository_lookup(&object, repo, &((git_reference_object_id *)reference)->id, GIT_OBJ_ANY)); + must_be_true(object != NULL); + must_be_true(git_object_type(object) == GIT_OBJ_TAG); + + + git_repository_free(repo); +END_TEST + +BEGIN_TEST(non_existing_tag_reference_looking_up) + git_repository *repo; + git_reference *reference; + + must_pass(git_repository_open(&repo, REPOSITORY_FOLDER)); + + must_fail(git_repository_reference_lookup(&reference, repo, non_existing_tag_ref_name)); + + git_repository_free(repo); +END_TEST \ No newline at end of file diff --git a/tests/t1002-readsymref.c b/tests/t1002-readsymref.c new file mode 100644 index 000000000..b6184b562 --- /dev/null +++ b/tests/t1002-readsymref.c @@ -0,0 +1,81 @@ +#include "test_lib.h" +#include "test_helpers.h" +#include "refs.h" + +static const char *head_ref_name = "HEAD"; +static const char *current_head_target = "refs/heads/master"; +static const char *current_master_tip = "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"; + +BEGIN_TEST(symbolic_reference_looking_up) + git_repository *repo; + git_reference *reference; + git_reference_symbolic *head_ref; + git_reference_object_id *target_ref; + git_object *object; + git_oid id; + + must_pass(git_repository_open(&repo, REPOSITORY_FOLDER)); + + + must_pass(git_repository_reference_lookup(&reference, repo, head_ref_name)); + must_be_true(reference->type == GIT_REF_SYMBOLIC); + must_be_true(reference->is_packed == 0); + must_be_true(strcmp(reference->name, head_ref_name) == 0); + + head_ref = (git_reference_symbolic *)reference; + must_be_true(head_ref->target != NULL); + must_be_true(head_ref->target->type == GIT_REF_OBJECT_ID); /* Current HEAD directly points to the object id reference */ + must_be_true(strcmp(head_ref->target->name, current_head_target) == 0); + + target_ref = (git_reference_object_id *)head_ref->target; + + must_pass(git_repository_lookup(&object, repo, &target_ref->id, GIT_OBJ_ANY)); + must_be_true(object != NULL); + must_be_true(git_object_type(object) == GIT_OBJ_COMMIT); + + git_oid_mkstr(&id, current_master_tip); + must_be_true(git_oid_cmp(&id, git_object_id(object)) == 0); + + + git_repository_free(repo); +END_TEST + +BEGIN_TEST(looking_up_head_then_master) + git_repository *repo; + git_reference *reference; + git_reference_symbolic *head_ref; + + must_pass(git_repository_open(&repo, REPOSITORY_FOLDER)); + + + must_pass(git_repository_reference_lookup(&reference, repo, head_ref_name)); + + head_ref = (git_reference_symbolic *)reference; + must_be_true(head_ref->target != NULL); + + must_pass(git_repository_reference_lookup(&reference, repo, current_head_target)); + must_be_true(head_ref->target == reference); + + + git_repository_free(repo); +END_TEST + +BEGIN_TEST(looking_up_master_then_head) + git_repository *repo; + git_reference *reference, *master_ref; + git_reference_symbolic *head_ref; + + must_pass(git_repository_open(&repo, REPOSITORY_FOLDER)); + + + must_pass(git_repository_reference_lookup(&master_ref, repo, current_head_target)); + + must_pass(git_repository_reference_lookup(&reference, repo, head_ref_name)); + head_ref = (git_reference_symbolic *)reference; + must_be_true(head_ref->target != NULL); + + must_be_true(head_ref->target == master_ref); + + + git_repository_free(repo); +END_TEST \ No newline at end of file diff --git a/tests/t1003-readpackedref.c b/tests/t1003-readpackedref.c new file mode 100644 index 000000000..0d210fd35 --- /dev/null +++ b/tests/t1003-readpackedref.c @@ -0,0 +1,52 @@ +#include "test_lib.h" +#include "test_helpers.h" +#include "refs.h" + +static const char *packed_head_name = "refs/heads/packed"; +static const char *packed_test_head_name = "refs/heads/packed-test"; + +BEGIN_TEST(packed_reference_looking_up) + git_repository *repo; + git_reference *reference; + git_reference_object_id *packed_ref; + git_object *object; + git_oid id; + + must_pass(git_repository_open(&repo, REPOSITORY_FOLDER)); + + + must_pass(git_repository_reference_lookup(&reference, repo, packed_head_name)); + must_be_true(reference->type == GIT_REF_OBJECT_ID); + must_be_true(reference->is_packed == 1); + must_be_true(strcmp(reference->name, packed_head_name) == 0); + + packed_ref = (git_reference_object_id *)reference; + + must_pass(git_repository_lookup(&object, repo, &packed_ref->id, GIT_OBJ_ANY)); + must_be_true(object != NULL); + must_be_true(git_object_type(object) == GIT_OBJ_COMMIT); + + + git_repository_free(repo); +END_TEST + +BEGIN_TEST(packed_exists_but_more_recent_loose_reference_is_retrieved) + git_repository *repo; + git_reference *reference; + git_reference_object_id *packed_ref; + git_object *object; + git_oid id; + + must_pass(git_repository_open(&repo, REPOSITORY_FOLDER)); + + + must_pass(git_repository_reference_lookup(&reference, repo, packed_head_name)); /* Triggers load and eager parsing of all packed references */ + + must_pass(git_repository_reference_lookup(&reference, repo, packed_test_head_name)); + must_be_true(reference->type == GIT_REF_OBJECT_ID); + must_be_true(reference->is_packed == 0); + must_be_true(strcmp(reference->name, packed_test_head_name) == 0); + + + git_repository_free(repo); +END_TEST \ No newline at end of file