summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorVicent Marti <tanoku@gmail.com>2011-01-29 01:56:25 +0200
committerVicent Marti <tanoku@gmail.com>2011-01-30 02:35:29 +0200
commit2f8a8ab24b034cb91e6ce80a7aa8cc330b7baebf (patch)
tree4729e6dc622a3c5a5da31f126b2e03091c597e14 /src
parent9282e921a394d422188ee43e18a63d418f88ac95 (diff)
downloadlibgit2-2f8a8ab24b034cb91e6ce80a7aa8cc330b7baebf.tar.gz
Refactor reference parsing code
Several changes have been committed to allow the user to create in-memory references and write back to disk. Peeling of symbolic references has been made explicit. Added getter and setter methods for all attributes on a reference. Added corresponding documentation. Signed-off-by: Vicent Marti <tanoku@gmail.com>
Diffstat (limited to 'src')
-rw-r--r--src/git2.h1
-rw-r--r--src/git2/refs.h175
-rw-r--r--src/git2/repository.h2
-rw-r--r--src/git2/types.h12
-rw-r--r--src/refs.c728
-rw-r--r--src/refs.h40
-rw-r--r--src/repository.c17
-rw-r--r--src/repository.h3
8 files changed, 611 insertions, 367 deletions
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()
-{
- 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)
+static int check_refname(const char *name)
{
- 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;
- }
+ /*
+ * TODO: To be implemented
+ * Check if the given name is a valid name
+ * for a reference
+ */
+
+ return name ? GIT_SUCCESS : GIT_ERROR;
}
-void git_reference_database__free(git_reference_database *ref_database)
+static void reference_free(git_reference *reference)
{
- 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);
- }
+ if (reference == NULL)
+ return;
- git_hashtable_free(ref_database->references);
- free(ref_database);
-}
+ if (reference->name)
+ free(reference->name);
+ if (reference->type == GIT_REF_SYMBOLIC)
+ free(reference->target.ref);
-static int check_refname_validity(const char *name) {
- int error = GIT_SUCCESS;
-
- // TODO : To be implemented
-
- return error;
+ free(reference);
}
-static int reference_newobject(git_reference **reference_out, git_rtype type, const char *name)
+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);
-
- reference->name = git__malloc(strlen(name) + 1);
- strcpy(reference->name, name);
-
- reference->type = type;
-
+ memset(reference, 0x0, sizeof(git_reference));
+ reference->type = GIT_REF_INVALID;
+ reference->owner = repo;
- *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);
-
- /* Skip the potential white spaces */
- while (isspace(refname_start[0]) && refname_start < buffer_end)
- refname_start++;
+ /*
+ * Assume we have already checked for the header
+ * before calling this function
+ */
- refname_end = refname_start;
+ refname_start += header_len;
- /* Seek the end of the target reference name */
- while(!isspace(refname_end[0]) && refname_end < buffer_end)
- refname_end++;
+ ref->target.ref = git__strdup(refname_start);
+ if (ref->target.ref == NULL)
+ return GIT_ENOMEM;
- refname_len = refname_end - refname_start;
+ /* remove newline at the end of file */
+ eol = strchr(ref->target.ref, '\n');
+ if (eol != NULL)
+ *eol = '\0';
- memcpy(target_name_out, refname_start, refname_len);
- target_name_out[refname_len] = 0;
+ ref->type = GIT_REF_SYMBOLIC;
- 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;
-
- buffer_start = (char *)buffer->data;
- buffer_end = (const char *)(buffer_start) + buffer->len;
+static int parse_oid_ref(git_reference *ref, gitfo_buf *file_content)
+{
+ char *buffer;
- /* Is this a valid object id ? */
- error = git__parse_oid(&target_oid, &buffer_start, buffer_end, "");
- if (error < GIT_SUCCESS)
- return error;
+ buffer = (char *)file_content->data;
- error = reference_newobject(&reference, GIT_REF_OBJECT_ID, name);
- if (error < GIT_SUCCESS)
- return error;
+ /* File format: 40 chars (OID) + newline */
+ if (file_content->len != GIT_OID_HEXSZ + 1)
+ return GIT_EREFCORRUPTED;
- git_oid_cpy(&((git_reference_object_id *)reference)->id, &target_oid);
+ if (git_oid_mkstr(&ref->target.oid, buffer) < GIT_SUCCESS)
+ return GIT_EREFCORRUPTED;
- *reference_out = reference;
+ if (buffer[GIT_OID_HEXSZ] != '\n')
+ return GIT_EREFCORRUPTED;
- 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_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;
+ error = git_reference_new(&ref, repo);
+ if (error < GIT_SUCCESS)
+ goto cleanup;
- goto found;
+ ref->name = git__strdup(name);
+ if (ref->name == NULL) {
+ error = GIT_ENOMEM;
+ goto cleanup;
}
- if (object_id_reference__parse(&reference, name, &file_content) < GIT_SUCCESS) {
- error = GIT_EREFCORRUPTED;
+ 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;
- }
-found:
- *reference_out = reference;
+ *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;
+ error = git_reference_new(&ref, repo);
+ if (error < GIT_SUCCESS)
+ goto cleanup;
- /* Slight hack to reuse git__parse_oid() which assumes that the id is LF terminated */
- (*buffer_out + GIT_OID_HEXSZ)[0] = '\n';
+ refname_begin = (buffer + GIT_OID_HEXSZ + 1);
+ if (refname_begin >= buffer_end ||
+ refname_begin[-1] != ' ') {
+ error = GIT_EPACKEDREFSCORRUPTED;
+ goto cleanup;
+ }
- /* Is this a valid object id ? */
- if (git__parse_oid(oid_out, buffer_out, buffer_end, "") < GIT_SUCCESS)
- return GIT_EPACKEDREFSCORRUPTED;
+ /* Is this a valid object id? */
+ if ((error = git_oid_mkstr(&ref->target.oid, buffer)) < GIT_SUCCESS)
+ goto cleanup;
- /* We should be at the begining of the name of the reference */
- if (isspace(*buffer_out[0]))
- return GIT_EPACKEDREFSCORRUPTED;
+ refname_end = memchr(refname_begin, '\n', buffer_end - refname_begin);
+ if (refname_end == NULL) {
+ error = GIT_EPACKEDREFSCORRUPTED;
+ goto cleanup;
+ }
- refname_end = *buffer_out;
+ refname_len = refname_end - refname_begin;
- /* Seek the end of the target reference name */
- while(!isspace(refname_end[0]) && refname_end < buffer_end)
- refname_end++;
+ ref->name = git__malloc(refname_len + 1);
+ if (ref->name == NULL) {
+ error = GIT_ENOMEM;
+ goto cleanup;
+ }
- refname_len = refname_end - *buffer_out;
+ memcpy(ref->name, refname_begin, refname_len);
+ ref->name[refname_len] = 0;
- memcpy(reference_name_out, *buffer_out, refname_len);
- reference_name_out[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 packed_reference_file__parse(git_reference **reference_out, git_reference_database *ref_database, const char *name, const char *path_repository, int *nesting_level)
+static int parse_packed_refs(git_refcache *ref_cache, git_repository *repo)
{
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);
+ 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 = (char *)file_content.data;
- buffer_end = (const char *)(buffer_start) + file_content.len;
+ buffer_start = (const char *)packfile.data;
+ buffer_end = (const char *)(buffer_start) + packfile.len;
- /* Does the header look like valid ? */
+ /* 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 ? */
+
+ git_reference *ref = NULL;
+ git_reference *ref_tag = NULL;
+
+ error = parse_packed_line(&ref, repo, &buffer_start, buffer_end);
+ if (error < GIT_SUCCESS)
+ goto cleanup;
+
if (buffer_start[0] == '^') {
- error = packed_tag_peeled_reference__parse(&peeled_oid, reference, &buffer_start, buffer_end);
+ error = parse_packed_line_peel(&ref_tag, ref, &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;
-
+ /*
+ * 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;
}
- error = packed_reference__parse(&oid, reference_name, &buffer_start, buffer_end);
- if (error < GIT_SUCCESS)
+ error = git_hashtable_insert(ref_cache->cache, ref->name, ref);
+ if (error < GIT_SUCCESS) {
+ reference_free(ref);
+ reference_free(ref_tag);
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;
+ ref_cache->pack_loaded = 1;
- reference->is_packed = 1;
+cleanup:
+ gitfo_free_buf(&packfile);
+ return error;
+}
- git_oid_cpy(&((git_reference_object_id *)reference)->id, &oid);
+void git_reference_set_oid(git_reference *ref, const git_oid *id)
+{
+ if (ref->type == GIT_REF_SYMBOLIC)
+ free(ref->target.ref);
- git_hashtable_insert(ref_database->references, reference->name, reference);
+ git_oid_cpy(&ref->target.oid, id);
+ ref->type = GIT_REF_OID;
- /* 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->modified = 1;
+}
+
+void git_reference_set_target(git_reference *ref, const char *target)
+{
+ if (ref->type == GIT_REF_SYMBOLIC)
+ free(ref->target.ref);
- ref_database->have_packed_refs_been_parsed = 1;
+ ref->target.ref = git__strdup(target);
+ ref->type = GIT_REF_SYMBOLIC;
- if (found_reference == NULL) {
- error = GIT_ENOTFOUND;
- goto cleanup;
+ 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);
}
- *reference_out = found_reference;
+ ref->name = git__strdup(name);
+ git_hashtable_insert(ref->owner->references.cache, ref->name, ref);
-cleanup:
- if (file_content.data)
- gitfo_free_buf(&file_content);
+ ref->modified = 1;
+}
- return error;
+const git_oid *git_reference_oid(git_reference *ref)
+{
+ assert(ref);
+
+ if (ref->type != GIT_REF_OID)
+ return NULL;
+
+ return &ref->target.oid;
}
-int git_reference_lookup(git_reference **reference_out, git_reference_database *ref_database, const char *name, const char *path_repository, int *nesting_level)
+const char *git_reference_target(git_reference *ref)
{
- int error = GIT_SUCCESS;
- git_reference *reference, *packed_reference = NULL;
+ if (ref->type != GIT_REF_SYMBOLIC)
+ return NULL;
- if (*nesting_level == MAX_NESTING_LEVEL) {
- return GIT_ETOONESTEDSYMREF;
- }
+ return ref->target.ref;
+}
- error = check_refname_validity(name);
- if (error < GIT_SUCCESS)
- return error;
+git_rtype git_reference_type(git_reference *ref)
+{
+ assert(ref);
+ return ref->type;
+}
- /* Has the ref already been previously parsed ? */
- reference = git_hashtable_lookup(ref_database->references, name);
+const char *git_reference_name(git_reference *ref)
+{
+ assert(ref);
+ return ref->name;
+}
- /* Has a loose reference been found ? */
- if (reference != NULL && !reference->is_packed) {
- *reference_out = reference;
- return GIT_SUCCESS;
- }
+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) {
- /* 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;
+ if (ref->type == GIT_REF_OID) {
+ *resolved_ref = ref;
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 ((error = git_repository_lookup_ref(&ref, repo, ref->target.ref)) < GIT_SUCCESS)
+ return error;
}
- if (*nesting_level == 0) {
- /* Is the database being populated */
- if (ref_database->is_busy)
- return GIT_EBUSY;
+ return GIT_ETOONESTEDSYMREF;
+}
- ref_database->is_busy = 1;
- }
+int git_reference_write(git_reference *ref)
+{
+ git_filelock lock;
+ char ref_path[GIT_PATH_MAX];
+ int error, contents_size;
+ char *ref_contents = NULL;
- (*nesting_level)++;
+ if (ref->type == GIT_REF_INVALID ||
+ ref->name == NULL)
+ return GIT_EMISSINGOBJDATA;
- /* 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);
+ if (ref->modified == 0)
+ return GIT_SUCCESS;
- /* 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 = check_refname(ref->name)) < GIT_SUCCESS)
+ return error;
+
+ strcpy(ref_path, ref->owner->path_repository);
+ strcat(ref_path, ref->name);
- if (error == GIT_SUCCESS) {
- git_hashtable_insert(ref_database->references, reference->name, reference);
- goto found;
+ 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;
+
+ /*
+ * 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;
+
+ /*
+ * 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);
+
+ 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) {
+
+ /* load all the packed references */
+ error = parse_packed_refs(&repo->references, repo);
+ if (error < GIT_SUCCESS)
+ return error;
- /* 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;
+ /* 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;
+ /* The reference doesn't exist anywhere */
+ 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;
- }
+int git_repository__refcache_init(git_refcache *refs)
+{
+ assert(refs);
-found:
- *reference_out = reference;
+ refs->cache = git_hashtable_alloc(
+ default_table_size,
+ reftable_hash,
+ reftable_haskey);
-cleanup:
- (*nesting_level)--;
+ return refs->cache ? GIT_SUCCESS : GIT_ENOMEM;
+}
- if (*nesting_level == 0)
- ref_database->is_busy = 0;
+void git_repository__refcache_free(git_refcache *refs)
+{
+ git_hashtable_iterator it;
+ git_reference *reference;
- return error;
+ 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;
-};
-
-struct git_reference_object_id {
- git_reference base;
+ unsigned packed:1,
+ modified:1;
- git_oid id;
+ union {
+ char *ref;
+ git_oid oid;
+ } target;
};
-struct git_reference_symbolic {
- git_reference base;
+typedef struct {
+ git_hashtable *cache;
+ unsigned pack_loaded:1;
+} git_refcache;
+
- git_reference *target;
-};
+void git_repository__refcache_free(git_refcache *refs);
+int git_repository__refcache_init(git_refcache *refs);
-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
+#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;