diff --git a/src/git2.h b/src/git2.h index 9eb9294df..e7f56e924 100644 --- a/src/git2.h +++ b/src/git2.h @@ -38,6 +38,7 @@ #include "git2/repository.h" #include "git2/revwalk.h" +#include "git2/refs.h" #include "git2/object.h" #include "git2/blob.h" diff --git a/src/git2/refs.h b/src/git2/refs.h new file mode 100644 index 000000000..c9efac34f --- /dev/null +++ b/src/git2/refs.h @@ -0,0 +1,175 @@ +/* + * 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. + */ +#ifndef INCLUDE_git_refs_h__ +#define INCLUDE_git_refs_h__ + +#include "common.h" +#include "types.h" + +/** + * @file git2/refs.h + * @brief Git reference management routines + * @defgroup git_reference Git reference management routines + * @ingroup Git + * @{ + */ +GIT_BEGIN_DECL + +/** + * Create a new reference. + * + * The reference will be empty and exclusively + * in-memory until it is filled with the setter + * methods and written back to disk using + * `git_reference_write`. + * + * @param ref_out Pointer to the newly created reference + * @param repo Repository where that reference exists + * @return 0 on success; error code otherwise + */ +GIT_EXTERN(int) git_reference_new(git_reference **ref_out, git_repository *repo); + +/** + * Get the OID pointed to by a reference. + * + * Only available if the reference is direct (i.e. not symbolic) + * + * @param ref The reference + * @return a pointer to the oid if available, NULL otherwise + */ +GIT_EXTERN(const git_oid *) git_reference_oid(git_reference *ref); + +/** + * Get full name to the reference pointed by this reference + * + * Only available if the reference is symbolic + * + * @param ref The reference + * @return a pointer to the name if available, NULL otherwise + */ +GIT_EXTERN(const char *) git_reference_target(git_reference *ref); + +/** + * Get the type of a reference + * + * Either direct (GIT_REF_OID) or symbolic (GIT_REF_SYMBOLIC) + * + * @param ref The reference + * @return the type + */ +GIT_EXTERN(git_rtype) git_reference_type(git_reference *ref); + +/** + * Get the full name of a reference + * + * @param ref The reference + * @return the full name for the ref + */ +GIT_EXTERN(const char *) git_reference_name(git_reference *ref); + +/** + * Resolve a symbolic reference + * + * Thie method iteratively peels a symbolic reference + * until it resolves to a direct reference to an OID. + * + * If a direct reference is passed as an argument, + * that reference is returned immediately + * + * @param resolved_ref Pointer to the peeled reference + * @param ref The reference + * @return 0 on success; error code otherwise + */ +GIT_EXTERN(int) git_reference_resolve(git_reference **resolved_ref, git_reference *ref); + +/** + * Write a reference back to disk. + * + * The reference must have a valid name and a valid target + * (either direct or symbolic). + * + * If the reference has been loaded from disk and no changes + * have been made, no action will take place. + * + * The writing to disk is atomic. + * + * @param ref The reference + * @return 0 on success; error code otherwise + */ +GIT_EXTERN(int) git_reference_write(git_reference *ref); + +/** + * Get the repository where a reference resides + * + * @param ref The reference + * @return a pointer to the repo + */ +GIT_EXTERN(git_repository *) git_reference_owner(git_reference *ref); + +/** + * Set the name of a reference. + * + * This marks the reference as modified; changes + * won't take effect until it is manually written back + * to disk. + * + * @param ref The reference + * @param name The new name for the reference + */ +GIT_EXTERN(void) git_reference_set_name(git_reference *ref, const char *name); + +/** + * Set the target reference of a reference. + * + * This converts the reference into a symbolic + * reference. + * + * This marks the reference as modified; changes + * won't take effect until it is manually written back + * to disk. + * + * @param ref The reference + * @param target The new target for the reference + */ +GIT_EXTERN(void) git_reference_set_target(git_reference *ref, const char *target); + +/** + * Set the OID target of a reference. + * + * This converts the reference into a direct + * reference. + * + * This marks the reference as modified; changes + * won't take effect until it is manually written back + * to disk. + * + * @param ref The reference + * @param target The new target OID for the reference + */ +GIT_EXTERN(void) git_reference_set_oid(git_reference *ref, const git_oid *id); + +/** @} */ +GIT_END_DECL +#endif diff --git a/src/git2/repository.h b/src/git2/repository.h index 129d3bb5f..ecf3db99f 100644 --- a/src/git2/repository.h +++ b/src/git2/repository.h @@ -229,7 +229,7 @@ GIT_EXTERN(int) git_repository_init(git_repository **repo_out, const char *path, * @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_EXTERN(int) git_repository_lookup_ref(git_reference **reference_out, git_repository *repo, const char *name); /** @} */ GIT_END_DECL diff --git a/src/git2/types.h b/src/git2/types.h index 389f32868..4f66742f0 100644 --- a/src/git2/types.h +++ b/src/git2/types.h @@ -138,17 +138,11 @@ typedef struct 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_REF_INVALID = -1, /** Invalid reference */ + GIT_REF_OID = 1, /** A reference which points at an object id */ + GIT_REF_SYMBOLIC = 2, /** A reference which points at another reference */ } git_rtype; /** @} */ diff --git a/src/refs.c b/src/refs.c index 91f13aed0..6bee6fa42 100644 --- a/src/refs.c +++ b/src/refs.c @@ -33,13 +33,6 @@ 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); @@ -56,177 +49,109 @@ static int reftable_haskey(void *reference, const void *key) return strcmp(name, ref->name) == 0; } -git_reference_database *git_reference_database__alloc() + +static int check_refname(const char *name) { - 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 + * Check if the given name is a valid name + * for a reference + */ - // TODO : To be implemented - - return error; + return name ? GIT_SUCCESS : GIT_ERROR; } -static int reference_newobject(git_reference **reference_out, git_rtype type, const char *name) +static void reference_free(git_reference *reference) +{ + if (reference == NULL) + return; + + if (reference->name) + free(reference->name); + + if (reference->type == GIT_REF_SYMBOLIC) + free(reference->target.ref); + + free(reference); +} + +int git_reference_new(git_reference **ref_out, git_repository *repo) { 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); + assert(ref_out && repo); + reference = git__malloc(sizeof(git_reference)); if (reference == NULL) return GIT_ENOMEM; - memset(reference, 0x0, git_references_table[type].size); + memset(reference, 0x0, sizeof(git_reference)); + reference->type = GIT_REF_INVALID; + reference->owner = repo; - reference->name = git__malloc(strlen(name) + 1); - strcpy(reference->name, name); - - reference->type = type; - - - *reference_out = reference; - + *ref_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; +static int parse_sym_ref(git_reference *ref, gitfo_buf *file_content) +{ + const int header_len = strlen(GIT_SYMREF); + const char *refname_start; + char *eol; - refname_start = (char *)buffer->data; - buffer_end = (const char *)(buffer->data) + buffer->len; + refname_start = (const char *)file_content->data; - if (git__prefixcmp(refname_start, GIT_SYMREF)) + if (file_content->len < (header_len + 1)) return GIT_EREFCORRUPTED; - refname_start += strlen(GIT_SYMREF); + /* + * Assume we have already checked for the header + * before calling this function + */ - /* Skip the potential white spaces */ - while (isspace(refname_start[0]) && refname_start < buffer_end) - refname_start++; + refname_start += header_len; - refname_end = refname_start; + ref->target.ref = git__strdup(refname_start); + if (ref->target.ref == NULL) + return GIT_ENOMEM; - /* Seek the end of the target reference name */ - while(!isspace(refname_end[0]) && refname_end < buffer_end) - refname_end++; + /* remove newline at the end of file */ + eol = strchr(ref->target.ref, '\n'); + if (eol != NULL) + *eol = '\0'; - refname_len = refname_end - refname_start; + ref->type = GIT_REF_SYMBOLIC; - memcpy(target_name_out, refname_start, refname_len); - target_name_out[refname_len] = 0; - - return error; + return GIT_SUCCESS; } -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; +static int parse_oid_ref(git_reference *ref, gitfo_buf *file_content) +{ + char *buffer; - buffer_start = (char *)buffer->data; - buffer_end = (const char *)(buffer_start) + buffer->len; + buffer = (char *)file_content->data; - /* Is this a valid object id ? */ - error = git__parse_oid(&target_oid, &buffer_start, buffer_end, ""); - if (error < GIT_SUCCESS) - return error; + /* File format: 40 chars (OID) + newline */ + if (file_content->len != GIT_OID_HEXSZ + 1) + return GIT_EREFCORRUPTED; - error = reference_newobject(&reference, GIT_REF_OBJECT_ID, name); - if (error < GIT_SUCCESS) - return error; + if (git_oid_mkstr(&ref->target.oid, buffer) < GIT_SUCCESS) + return GIT_EREFCORRUPTED; - git_oid_cpy(&((git_reference_object_id *)reference)->id, &target_oid); + if (buffer[GIT_OID_HEXSZ] != '\n') + return GIT_EREFCORRUPTED; - *reference_out = reference; - - return error; + ref->type = GIT_REF_OID; + return GIT_SUCCESS; } -static int read_loose_reference(gitfo_buf *file_content, const char *name, const char *path_repository) +static int read_loose_ref(gitfo_buf *file_content, const char *name, const char *repo_path) { int error = GIT_SUCCESS; char ref_path[GIT_PATH_MAX]; /* Determine the full path of the ref */ - strcpy(ref_path, path_repository); + strcpy(ref_path, repo_path); strcat(ref_path, name); /* Does it even exist ? */ @@ -235,155 +160,183 @@ static int read_loose_reference(gitfo_buf *file_content, const char *name, const /* A ref can not be a directory */ if (!gitfo_isdir(ref_path)) - return GIT_EINVALIDREFNAME; + return GIT_ENOTFOUND; - error = gitfo_read_file(file_content, ref_path); + if (file_content != NULL) + 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) +static int lookup_loose_ref( + git_reference **ref_out, + git_repository *repo, + const char *name) { 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]; + gitfo_buf ref_file = GITFO_BUF_INIT; + git_reference *ref = NULL; + *ref_out = NULL; - error = read_loose_reference(&file_content, name, path_repository); + error = read_loose_ref(&ref_file, name, repo->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_new(&ref, repo); + 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; + ref->name = git__strdup(name); + if (ref->name == NULL) { + error = GIT_ENOMEM; goto cleanup; } -found: - *reference_out = reference; + if (git__prefixcmp((const char *)(ref_file.data), GIT_SYMREF) == 0) + error = parse_sym_ref(ref, &ref_file); + else + error = parse_oid_ref(ref, &ref_file); + + if (error < GIT_SUCCESS) + goto cleanup; + + *ref_out = ref; + return GIT_SUCCESS; cleanup: - if (file_content.data) - gitfo_free_buf(&file_content); - + gitfo_free_buf(&ref_file); + reference_free(ref); return error; } -static int read_packed_refs_content(gitfo_buf *file_content, const char *path_repository) + +static int read_packed_refs(gitfo_buf *packfile, const char *repo_path) { - int error = GIT_SUCCESS; char ref_path[GIT_PATH_MAX]; /* Determine the full path of the file */ - strcpy(ref_path, path_repository); + strcpy(ref_path, repo_path); 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; + return gitfo_read_file(packfile, ref_path); } -static int packed_tag_peeled_reference__parse(git_oid *peeled_oid_out, git_reference *tag_reference, char** buffer_out, const char *buffer_end) +static int parse_packed_line_peel( + git_reference **ref_out, + const git_reference *tag_ref, + const char **buffer_out, + const char *buffer_end) { - int error = GIT_SUCCESS; + git_oid oid; + const char *buffer = *buffer_out + 1; + + assert(buffer[-1] == '^'); /* Ensure it's not the first entry of the file */ - if (tag_reference == NULL) + if (tag_ref == NULL) return GIT_EPACKEDREFSCORRUPTED; /* Ensure reference is a tag */ - if (git__prefixcmp(tag_reference->name, GIT_REFS_TAGS_DIR)) + if (git__prefixcmp(tag_ref->name, GIT_REFS_TAGS_DIR) != 0) 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; - } + if (buffer + GIT_OID_HEXSZ >= buffer_end) + return GIT_EPACKEDREFSCORRUPTED; - return error; + /* Is this a valid object id? */ + if (git_oid_mkstr(&oid, buffer) < GIT_SUCCESS) + return GIT_EPACKEDREFSCORRUPTED; + + *buffer_out = buffer + GIT_OID_HEXSZ + 1; + + /* + * TODO: do we need the packed line? + * Right now we don't, so we don't create a new + * reference. + */ + + *ref_out = NULL; + return GIT_SUCCESS; } -static int packed_reference__parse(git_oid *oid_out, char *reference_name_out, char** buffer_out, const char *buffer_end) +static int parse_packed_line( + git_reference **ref_out, + git_repository *repo, + const char **buffer_out, + const char *buffer_end) { + git_reference *ref; + + const char *buffer = *buffer_out; + const char *refname_begin, *refname_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); + error = git_reference_new(&ref, repo); if (error < GIT_SUCCESS) goto cleanup; - buffer_start = (char *)file_content.data; - buffer_end = (const char *)(buffer_start) + file_content.len; + refname_begin = (buffer + GIT_OID_HEXSZ + 1); + if (refname_begin >= buffer_end || + refname_begin[-1] != ' ') { + error = GIT_EPACKEDREFSCORRUPTED; + goto cleanup; + } - /* Does the header look like valid ? */ + /* Is this a valid object id? */ + if ((error = git_oid_mkstr(&ref->target.oid, buffer)) < GIT_SUCCESS) + goto cleanup; + + refname_end = memchr(refname_begin, '\n', buffer_end - refname_begin); + if (refname_end == NULL) { + error = GIT_EPACKEDREFSCORRUPTED; + goto cleanup; + } + + refname_len = refname_end - refname_begin; + + ref->name = git__malloc(refname_len + 1); + if (ref->name == NULL) { + error = GIT_ENOMEM; + goto cleanup; + } + + memcpy(ref->name, refname_begin, refname_len); + ref->name[refname_len] = 0; + + ref->type = GIT_REF_OID; + ref->packed = 1; + + *ref_out = ref; + *buffer_out = refname_end + 1; + + return GIT_SUCCESS; + +cleanup: + reference_free(ref); + return error; +} + +static int parse_packed_refs(git_refcache *ref_cache, git_repository *repo) +{ + int error = GIT_SUCCESS; + gitfo_buf packfile = GITFO_BUF_INIT; + const char *buffer_start, *buffer_end; + + error = read_packed_refs(&packfile, repo->path_repository); + if (error < GIT_SUCCESS) + goto cleanup; + + buffer_start = (const char *)packfile.data; + buffer_end = (const char *)(buffer_start) + packfile.len; + + /* Does the header look like valid? */ if (git__prefixcmp((const char *)(buffer_start), GIT_PACKEDREFS_HEADER)) { error = GIT_EPACKEDREFSCORRUPTED; goto cleanup; @@ -393,155 +346,296 @@ static int packed_reference_file__parse(git_reference **reference_out, git_refer 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 ? */ + git_reference *ref = NULL; + git_reference *ref_tag = NULL; - /* Reinit the reference to catch potential successive lines starting by '^' */ - reference = NULL; - - continue; - } - - error = packed_reference__parse(&oid, reference_name, &buffer_start, buffer_end); + error = parse_packed_line(&ref, repo, &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 (buffer_start[0] == '^') { + error = parse_packed_line_peel(&ref_tag, ref, &buffer_start, buffer_end); if (error < GIT_SUCCESS) goto cleanup; + } - reference->is_packed = 1; + /* + * If a loose reference exists with the same name, + * we assume that the loose reference is more up-to-date. + * We don't need to cache this ref from the packfile. + */ + if (read_loose_ref(NULL, ref->name, repo->path_repository) == GIT_SUCCESS) { + reference_free(ref); + reference_free(ref_tag); + continue; + } - 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 ? + error = git_hashtable_insert(ref_cache->cache, ref->name, ref); + if (error < GIT_SUCCESS) { + reference_free(ref); + reference_free(ref_tag); + goto cleanup; } } - ref_database->have_packed_refs_been_parsed = 1; - - if (found_reference == NULL) { - error = GIT_ENOTFOUND; - goto cleanup; - } - - *reference_out = found_reference; + ref_cache->pack_loaded = 1; cleanup: - if (file_content.data) - gitfo_free_buf(&file_content); - + gitfo_free_buf(&packfile); 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) +void git_reference_set_oid(git_reference *ref, const git_oid *id) { - int error = GIT_SUCCESS; - git_reference *reference, *packed_reference = NULL; + if (ref->type == GIT_REF_SYMBOLIC) + free(ref->target.ref); - if (*nesting_level == MAX_NESTING_LEVEL) { - return GIT_ETOONESTEDSYMREF; + git_oid_cpy(&ref->target.oid, id); + ref->type = GIT_REF_OID; + + ref->modified = 1; +} + +void git_reference_set_target(git_reference *ref, const char *target) +{ + if (ref->type == GIT_REF_SYMBOLIC) + free(ref->target.ref); + + ref->target.ref = git__strdup(target); + ref->type = GIT_REF_SYMBOLIC; + + ref->modified = 1; +} + +void git_reference_set_name(git_reference *ref, const char *name) +{ + if (ref->name != NULL) { + git_hashtable_remove(ref->owner->references.cache, ref->name); + free(ref->name); } - error = check_refname_validity(name); + ref->name = git__strdup(name); + git_hashtable_insert(ref->owner->references.cache, ref->name, ref); + + ref->modified = 1; +} + +const git_oid *git_reference_oid(git_reference *ref) +{ + assert(ref); + + if (ref->type != GIT_REF_OID) + return NULL; + + return &ref->target.oid; +} + +const char *git_reference_target(git_reference *ref) +{ + if (ref->type != GIT_REF_SYMBOLIC) + return NULL; + + return ref->target.ref; +} + +git_rtype git_reference_type(git_reference *ref) +{ + assert(ref); + return ref->type; +} + +const char *git_reference_name(git_reference *ref) +{ + assert(ref); + return ref->name; +} + +git_repository *git_reference_owner(git_reference *ref) +{ + assert(ref); + return ref->owner; +} + +int git_reference_resolve(git_reference **resolved_ref, git_reference *ref) +{ + git_repository *repo; + int error, i; + + assert(resolved_ref && ref); + *resolved_ref = NULL; + + repo = ref->owner; + + for (i = 0; i < MAX_NESTING_LEVEL; ++i) { + + if (ref->type == GIT_REF_OID) { + *resolved_ref = ref; + return GIT_SUCCESS; + } + + if ((error = git_repository_lookup_ref(&ref, repo, ref->target.ref)) < GIT_SUCCESS) + return error; + } + + return GIT_ETOONESTEDSYMREF; +} + +int git_reference_write(git_reference *ref) +{ + git_filelock lock; + char ref_path[GIT_PATH_MAX]; + int error, contents_size; + char *ref_contents = NULL; + + if (ref->type == GIT_REF_INVALID || + ref->name == NULL) + return GIT_EMISSINGOBJDATA; + + if (ref->modified == 0) + return GIT_SUCCESS; + + if ((error = check_refname(ref->name)) < GIT_SUCCESS) + return error; + + strcpy(ref_path, ref->owner->path_repository); + strcat(ref_path, ref->name); + + if ((error = git_filelock_init(&lock, ref_path)) < GIT_SUCCESS) + goto error_cleanup; + + if ((error = git_filelock_lock(&lock, 0)) < GIT_SUCCESS) + goto error_cleanup; + + if (ref->type == GIT_REF_OID) { + + contents_size = GIT_OID_HEXSZ + 1; + ref_contents = git__malloc(contents_size); + if (ref_contents == NULL) { + error = GIT_ENOMEM; + goto error_cleanup; + } + + git_oid_fmt(ref_contents, &ref->target.oid); + ref_contents[contents_size - 1] = '\n'; + + } else { /* GIT_REF_SYMBOLIC */ + + contents_size = strlen(GIT_SYMREF) + strlen(ref->target.ref) + 1; + ref_contents = git__malloc(contents_size); + if (ref_contents == NULL) { + error = GIT_ENOMEM; + goto error_cleanup; + } + + strcpy(ref_contents, GIT_SYMREF); + strcat(ref_contents, ref->target.ref); + ref_contents[contents_size - 1] = '\n'; + } + + if ((error = git_filelock_write(&lock, ref_contents, contents_size)) < GIT_SUCCESS) + goto error_cleanup; + + if ((error = git_filelock_commit(&lock)) < GIT_SUCCESS) + goto error_cleanup; + + ref->modified = 0; + + free(ref_contents); + return GIT_SUCCESS; + +error_cleanup: + free(ref_contents); + git_filelock_unlock(&lock); + return error; +} + +int git_repository_lookup_ref(git_reference **ref_out, git_repository *repo, const char *name) +{ + int error; + + assert(ref_out && repo && name); + + *ref_out = NULL; + + error = check_refname(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; + /* + * First, check if the reference is on the local cache; + * references on the cache are assured to be up-to-date + */ + *ref_out = git_hashtable_lookup(repo->references.cache, name); + if (*ref_out != NULL) 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; - } - } + /* + * Then check if there is a loose file for that reference. + * If the file exists, we parse it and store it on the + * cache. + */ + error = lookup_loose_ref(ref_out, repo, name); - /* 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_SUCCESS) + return GIT_SUCCESS; if (error != GIT_ENOTFOUND) - goto cleanup; + return error; - /* Nothing has been found in the loose refs */ - assert(error == GIT_ENOTFOUND); + /* + * Check if we have loaded the packed references. + * If the packed references have been loaded, they would be + * stored already on the cache: that means that the ref + * we are looking for doesn't exist. + * + * If they haven't been loaded yet, we load the packfile + * and check if our reference is inside of it. + */ + if (!repo->references.pack_loaded) { - /* 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; + /* load all the packed references */ + error = parse_packed_refs(&repo->references, repo); + if (error < GIT_SUCCESS) + return error; + + /* check the cache again -- hopefully the reference will be there */ + *ref_out = git_hashtable_lookup(repo->references.cache, name); + if (*ref_out != NULL) + return GIT_SUCCESS; } - /* 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; + /* The reference doesn't exist anywhere */ + return GIT_ENOTFOUND; } + +int git_repository__refcache_init(git_refcache *refs) +{ + assert(refs); + + refs->cache = git_hashtable_alloc( + default_table_size, + reftable_hash, + reftable_haskey); + + return refs->cache ? GIT_SUCCESS : GIT_ENOMEM; +} + +void git_repository__refcache_free(git_refcache *refs) +{ + git_hashtable_iterator it; + git_reference *reference; + + assert(refs); + + git_hashtable_iterator_init(refs->cache, &it); + + while ((reference = (git_reference *)git_hashtable_iterator_next(&it)) != NULL) { + git_hashtable_remove(refs->cache, reference->name); + reference_free(reference); + } + + git_hashtable_free(refs->cache); +} + + diff --git a/src/refs.h b/src/refs.h index 3cfd46d58..f19d87894 100644 --- a/src/refs.h +++ b/src/refs.h @@ -9,40 +9,32 @@ #define GIT_REFS_HEADS_DIR GIT_REFS_DIR "heads/" #define GIT_REFS_TAGS_DIR GIT_REFS_DIR "tags/" -#define GIT_SYMREF "ref:" +#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_repository *owner; git_rtype type; char *name; - unsigned is_packed:1; -}; + unsigned packed:1, + modified:1; -struct git_reference_object_id { - git_reference base; - - git_oid id; -}; - -struct git_reference_symbolic { - git_reference base; - - git_reference *target; + union { + char *ref; + git_oid oid; + } 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_hashtable *cache; + unsigned pack_loaded:1; +} git_refcache; -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 +void git_repository__refcache_free(git_refcache *refs); +int git_repository__refcache_init(git_refcache *refs); + +#endif diff --git a/src/repository.c b/src/repository.c index 6fc67e409..d010d8c08 100644 --- a/src/repository.c +++ b/src/repository.c @@ -227,8 +227,7 @@ static git_repository *repository_alloc() return NULL; } - repo->ref_database = git_reference_database__alloc(); - if (repo->ref_database == NULL) { + if (git_repository__refcache_init(&repo->references) < GIT_SUCCESS) { git_hashtable_free(repo->objects); free(repo); return NULL; @@ -364,7 +363,7 @@ void git_repository_free(git_repository *repo) git_hashtable_free(repo->objects); - git_reference_database__free(repo->ref_database); + git_repository__refcache_free(&repo->references); if (repo->db != NULL) git_odb_close(repo->db); @@ -633,15 +632,3 @@ 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 1e9e0a5b9..5bf041140 100644 --- a/src/repository.h +++ b/src/repository.h @@ -28,7 +28,8 @@ struct git_repository { git_odb *db; git_index *index; git_hashtable *objects; - git_reference_database *ref_database; + + git_refcache references; char *path_repository; char *path_index; diff --git a/tests/t1001-readtag.c b/tests/t1001-readtag.c index 57de1aa23..feecf2057 100644 --- a/tests/t1001-readtag.c +++ b/tests/t1001-readtag.c @@ -12,18 +12,15 @@ BEGIN_TEST(loose_tag_reference_looking_up) 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_pass(git_repository_lookup_ref(&reference, repo, loose_tag_ref_name)); + must_be_true(reference->type == GIT_REF_OID); + must_be_true(reference->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_pass(git_repository_lookup(&object, repo, git_reference_oid(reference), GIT_OBJ_ANY)); must_be_true(object != NULL); must_be_true(git_object_type(object) == GIT_OBJ_TAG); - git_repository_free(repo); END_TEST @@ -32,8 +29,7 @@ BEGIN_TEST(non_existing_tag_reference_looking_up) git_reference *reference; must_pass(git_repository_open(&repo, REPOSITORY_FOLDER)); - - must_fail(git_repository_reference_lookup(&reference, repo, non_existing_tag_ref_name)); + must_fail(git_repository_lookup_ref(&reference, repo, non_existing_tag_ref_name)); git_repository_free(repo); -END_TEST \ No newline at end of file +END_TEST diff --git a/tests/t1002-readsymref.c b/tests/t1002-readsymref.c index b6184b562..6dee0b916 100644 --- a/tests/t1002-readsymref.c +++ b/tests/t1002-readsymref.c @@ -8,54 +8,37 @@ static const char *current_master_tip = "be3563ae3f795b2b4353bcce3a527ad0a4f7f64 BEGIN_TEST(symbolic_reference_looking_up) git_repository *repo; - git_reference *reference; - git_reference_symbolic *head_ref; - git_reference_object_id *target_ref; + git_reference *reference, *resolved_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_pass(git_repository_lookup_ref(&reference, repo, head_ref_name)); must_be_true(reference->type == GIT_REF_SYMBOLIC); - must_be_true(reference->is_packed == 0); + must_be_true(reference->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); + must_pass(git_reference_resolve(&resolved_ref, reference)); + must_be_true(resolved_ref->type == GIT_REF_OID); - target_ref = (git_reference_object_id *)head_ref->target; - - must_pass(git_repository_lookup(&object, repo, &target_ref->id, GIT_OBJ_ANY)); + must_pass(git_repository_lookup(&object, repo, git_reference_oid(resolved_ref), 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); - + must_pass(git_repository_lookup_ref(&reference, repo, head_ref_name)); + must_pass(git_repository_lookup_ref(&reference, repo, current_head_target)); git_repository_free(repo); END_TEST @@ -63,19 +46,10 @@ 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); - + must_pass(git_repository_lookup_ref(&master_ref, repo, current_head_target)); + must_pass(git_repository_lookup_ref(&reference, repo, head_ref_name)); git_repository_free(repo); -END_TEST \ No newline at end of file +END_TEST diff --git a/tests/t1003-readpackedref.c b/tests/t1003-readpackedref.c index 0d210fd35..d76bdcf3e 100644 --- a/tests/t1003-readpackedref.c +++ b/tests/t1003-readpackedref.c @@ -8,45 +8,32 @@ 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_pass(git_repository_lookup_ref(&reference, repo, packed_head_name)); + must_be_true(reference->type == GIT_REF_OID); + must_be_true(reference->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_pass(git_repository_lookup(&object, repo, git_reference_oid(reference), 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_pass(git_repository_lookup_ref(&reference, repo, packed_head_name)); + must_pass(git_repository_lookup_ref(&reference, repo, packed_test_head_name)); + must_be_true(reference->type == GIT_REF_OID); + must_be_true(reference->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 +END_TEST