summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/git2/refs.h18
-rw-r--r--src/refs.c983
-rw-r--r--src/refs.h6
3 files changed, 786 insertions, 221 deletions
diff --git a/src/git2/refs.h b/src/git2/refs.h
index ae77f56f4..752d80862 100644
--- a/src/git2/refs.h
+++ b/src/git2/refs.h
@@ -187,6 +187,24 @@ GIT_EXTERN(int) git_reference_rename(git_reference *ref, const char *new_name);
*/
GIT_EXTERN(int) git_reference_delete(git_reference *ref);
+/**
+ * Pack all the loose references in the repository
+ *
+ * This method will load into the cache all the loose
+ * references on the repository and update the
+ * `packed-refs` file with them.
+ *
+ * Once the `packed-refs` file has been written properly,
+ * the loose references will be removed from disk.
+ *
+ * WARNING: calling this method may invalidate any existing
+ * references previously loaded on the cache.
+ *
+ * @param repo Repository where the loose refs will be packed
+ * @return 0 on success; error code otherwise
+ */
+GIT_EXTERN(int) git_reference_packall(git_repository *repo);
+
/** @} */
GIT_END_DECL
#endif
diff --git a/src/refs.c b/src/refs.c
index ff4c3cba7..02d9b6b3e 100644
--- a/src/refs.c
+++ b/src/refs.c
@@ -28,6 +28,9 @@
#include "repository.h"
#include "fileops.h"
+#include <git2/tag.h>
+#include <git2/object.h>
+
#define MAX_NESTING_LEVEL 5
typedef struct {
@@ -41,10 +44,6 @@ typedef struct {
char *target;
} reference_symbolic;
-
-static int reference_write(git_reference *ref);
-static int normalize_name(char *buffer_out, const char *name, int is_oid_ref);
-
static const int default_table_size = 32;
static uint32_t reftable_hash(const void *key, int hash_id)
@@ -58,6 +57,35 @@ static uint32_t reftable_hash(const void *key, int hash_id)
return git__hash(key, strlen((const char *)key), hash_seeds[hash_id]);
}
+static void reference_free(git_reference *reference);
+static int reference_create(git_reference **ref_out, git_repository *repo, const char *name, git_rtype type);
+
+/* loose refs */
+static int loose_parse_symbolic(git_reference *ref, gitfo_buf *file_content);
+static int loose_parse_oid(git_reference *ref, gitfo_buf *file_content);
+static int loose_read(gitfo_buf *file_content, const char *name, const char *repo_path);
+static int loose_lookup( git_reference **ref_out, git_repository *repo, const char *name, int skip_symbolic);
+static int loose_write(git_reference *ref);
+
+/* packed refs */
+static int packed_readpack(gitfo_buf *packfile, const char *repo_path);
+static int packed_parse_peel(reference_oid *tag_ref, const char **buffer_out, const char *buffer_end);
+static int packed_parse_oid(reference_oid **ref_out, git_repository *repo, const char **buffer_out, const char *buffer_end);
+static int packed_load(git_repository *repo);
+static int packed_loadloose(git_repository *repository);
+static int packed_write_ref(reference_oid *ref, git_filebuf *file);
+static int packed_find_peel(reference_oid *ref);
+static int packed_remove_loose(git_repository *repo, git_vector *packing_list);
+static int packed_sort(const void *a, const void *b);
+static int packed_write(git_repository *repo);
+
+/* name normalization */
+static int check_valid_ref_char(char ch);
+static int normalize_name(char *buffer_out, const char *name, int is_oid_ref);
+
+/*****************************************
+ * Internal methods - Constructor/destructor
+ *****************************************/
static void reference_free(git_reference *reference)
{
if (reference == NULL)
@@ -72,7 +100,12 @@ static void reference_free(git_reference *reference)
free(reference);
}
-static int reference_create(git_reference **ref_out, git_repository *repo, const char *name, git_rtype type) {
+static int reference_create(
+ git_reference **ref_out,
+ git_repository *repo,
+ const char *name,
+ git_rtype type)
+{
char normalized[MAX_GITDIR_TREE_STRUCTURE_PATH_LENGTH];
int error = GIT_SUCCESS, size;
git_reference *reference = NULL;
@@ -113,67 +146,13 @@ cleanup:
return error;
}
-int git_reference_create_symbolic(git_reference **ref_out, git_repository *repo, const char *name, const char *target)
-{
- char normalized[MAX_GITDIR_TREE_STRUCTURE_PATH_LENGTH];
- int error = GIT_SUCCESS;
- git_reference *ref = NULL;
-
- error = reference_create(&ref, repo, name, GIT_REF_SYMBOLIC);
- if (error < GIT_SUCCESS)
- goto cleanup;
-
- /* The target can aither be the name of an object id reference or the name of another symbolic reference */
- error = normalize_name(normalized, target, 0);
- if (error < GIT_SUCCESS)
- goto cleanup;
-
- /* set the target; this will write the reference on disk */
- error = git_reference_set_target(ref, normalized);
- if (error < GIT_SUCCESS)
- goto cleanup;
-
- error = git_hashtable_insert(repo->references.loose_refs, ref->name, ref);
- if (error < GIT_SUCCESS)
- goto cleanup;
-
- *ref_out = ref;
-
- return error;
-
-cleanup:
- reference_free(ref);
- return error;
-}
-
-int git_reference_create_oid(git_reference **ref_out, git_repository *repo, const char *name, const git_oid *id)
-{
- int error = GIT_SUCCESS;
- git_reference *ref = NULL;
-
- error = reference_create(&ref, repo, name, GIT_REF_OID);
- if (error < GIT_SUCCESS)
- goto cleanup;
-
- /* set the oid; this will write the reference on disk */
- error = git_reference_set_oid(ref, id);
- if (error < GIT_SUCCESS)
- goto cleanup;
-
- error = git_hashtable_insert(repo->references.loose_refs, ref->name, ref);
- if (error < GIT_SUCCESS)
- goto cleanup;
-
- *ref_out = ref;
- return error;
-cleanup:
- reference_free(ref);
- return error;
-}
-static int parse_sym_ref(git_reference *ref, gitfo_buf *file_content)
+/*****************************************
+ * Internal methods - Loose references
+ *****************************************/
+static int loose_parse_symbolic(git_reference *ref, gitfo_buf *file_content)
{
const unsigned int header_len = strlen(GIT_SYMREF);
const char *refname_start;
@@ -209,7 +188,7 @@ static int parse_sym_ref(git_reference *ref, gitfo_buf *file_content)
return GIT_SUCCESS;
}
-static int parse_oid_ref(git_reference *ref, gitfo_buf *file_content)
+static int loose_parse_oid(git_reference *ref, gitfo_buf *file_content)
{
reference_oid *ref_oid;
char *buffer;
@@ -234,7 +213,7 @@ static int parse_oid_ref(git_reference *ref, gitfo_buf *file_content)
return GIT_SUCCESS;
}
-static int read_loose_ref(gitfo_buf *file_content, const char *name, const char *repo_path)
+static int loose_read(gitfo_buf *file_content, const char *name, const char *repo_path)
{
int error = GIT_SUCCESS;
char ref_path[GIT_PATH_MAX];
@@ -256,10 +235,11 @@ static int read_loose_ref(gitfo_buf *file_content, const char *name, const char
return error;
}
-static int lookup_loose_ref(
+static int loose_lookup(
git_reference **ref_out,
git_repository *repo,
- const char *name)
+ const char *name,
+ int skip_symbolic)
{
int error = GIT_SUCCESS;
gitfo_buf ref_file = GITFO_BUF_INIT;
@@ -267,31 +247,30 @@ static int lookup_loose_ref(
*ref_out = NULL;
- error = read_loose_ref(&ref_file, name, repo->path_repository);
+ error = loose_read(&ref_file, name, repo->path_repository);
if (error < GIT_SUCCESS)
goto cleanup;
if (git__prefixcmp((const char *)(ref_file.data), GIT_SYMREF) == 0) {
+ if (skip_symbolic)
+ return GIT_SUCCESS;
+
error = reference_create(&ref, repo, name, GIT_REF_SYMBOLIC);
if (error < GIT_SUCCESS)
goto cleanup;
- error = parse_sym_ref(ref, &ref_file);
+ error = loose_parse_symbolic(ref, &ref_file);
} else {
error = reference_create(&ref, repo, name, GIT_REF_OID);
if (error < GIT_SUCCESS)
goto cleanup;
- error = parse_oid_ref(ref, &ref_file);
+ error = loose_parse_oid(ref, &ref_file);
}
if (error < GIT_SUCCESS)
goto cleanup;
- error = git_hashtable_insert(repo->references.loose_refs, ref->name, ref);
- if (error < GIT_SUCCESS)
- goto cleanup;
-
*ref_out = ref;
return GIT_SUCCESS;
@@ -301,8 +280,73 @@ cleanup:
return error;
}
+static int loose_write(git_reference *ref)
+{
+ git_filebuf file;
+ char ref_path[GIT_PATH_MAX];
+ int error, contents_size;
+ char *ref_contents = NULL;
+
+ git__joinpath(ref_path, ref->owner->path_repository, ref->name);
+
+ if ((error = git_filebuf_open(&file, ref_path, 0)) < GIT_SUCCESS)
+ return error;
+
+ if (ref->type & GIT_REF_OID) {
+ reference_oid *ref_oid = (reference_oid *)ref;
+
+ contents_size = GIT_OID_HEXSZ + 1;
+ ref_contents = git__malloc(contents_size);
+ if (ref_contents == NULL) {
+ error = GIT_ENOMEM;
+ goto unlock;
+ }
+
+ git_oid_fmt(ref_contents, &ref_oid->oid);
+
+ } else if (ref->type & GIT_REF_SYMBOLIC) { /* GIT_REF_SYMBOLIC */
+ reference_symbolic *ref_sym = (reference_symbolic *)ref;
+
+ contents_size = strlen(GIT_SYMREF) + strlen(ref_sym->target) + 1;
+ ref_contents = git__malloc(contents_size);
+ if (ref_contents == NULL) {
+ error = GIT_ENOMEM;
+ goto unlock;
+ }
+
+ strcpy(ref_contents, GIT_SYMREF);
+ strcat(ref_contents, ref_sym->target);
+ } else {
+ error = GIT_EINVALIDREFSTATE;
+ goto unlock;
+ }
+
+ /* TODO: win32 carriage return when writing references in Windows? */
+ ref_contents[contents_size - 1] = '\n';
+
+ if ((error = git_filebuf_write(&file, ref_contents, contents_size)) < GIT_SUCCESS)
+ goto unlock;
+
+ error = git_filebuf_commit(&file);
+
+ free(ref_contents);
+ return error;
+
+unlock:
+ git_filebuf_cleanup(&file);
+ free(ref_contents);
+ return error;
+}
+
+
+
-static int read_packed_refs(gitfo_buf *packfile, const char *repo_path)
+
+
+/*****************************************
+ * Internal methods - Packed references
+ *****************************************/
+static int packed_readpack(gitfo_buf *packfile, const char *repo_path)
{
char ref_path[GIT_PATH_MAX];
@@ -316,7 +360,7 @@ static int read_packed_refs(gitfo_buf *packfile, const char *repo_path)
return gitfo_read_file(packfile, ref_path);
}
-static int parse_packed_line_peel(
+static int packed_parse_peel(
reference_oid *tag_ref,
const char **buffer_out,
const char *buffer_end)
@@ -353,7 +397,7 @@ static int parse_packed_line_peel(
return GIT_SUCCESS;
}
-static int parse_packed_line(
+static int packed_parse_oid(
reference_oid **ref_out,
git_repository *repo,
const char **buffer_out,
@@ -411,13 +455,32 @@ cleanup:
return error;
}
-static int load_packed_refs(git_refcache *ref_cache, git_repository *repo)
+static int packed_load(git_repository *repo)
{
int error = GIT_SUCCESS;
gitfo_buf packfile = GITFO_BUF_INIT;
const char *buffer_start, *buffer_end;
+ git_refcache *ref_cache = &repo->references;
+
+ /* already loaded */
+ if (repo->references.packfile != NULL)
+ return GIT_SUCCESS;
+
+ repo->references.packfile = git_hashtable_alloc(
+ default_table_size,
+ reftable_hash,
+ (git_hash_keyeq_ptr)strcmp);
+
+ if (repo->references.packfile == NULL)
+ return GIT_ENOMEM;
+
+ /* read the packfile from disk */
+ error = packed_readpack(&packfile, repo->path_repository);
+
+ /* there is no packfile on disk; that's ok */
+ if (error == GIT_ENOTFOUND)
+ return GIT_SUCCESS;
- error = read_packed_refs(&packfile, repo->path_repository);
if (error < GIT_SUCCESS)
goto cleanup;
@@ -442,86 +505,422 @@ static int load_packed_refs(git_refcache *ref_cache, git_repository *repo)
buffer_start++;
while (buffer_start < buffer_end) {
-
reference_oid *ref = NULL;
- error = parse_packed_line(&ref, repo, &buffer_start, buffer_end);
+ error = packed_parse_oid(&ref, repo, &buffer_start, buffer_end);
if (error < GIT_SUCCESS)
goto cleanup;
if (buffer_start[0] == '^') {
- error = parse_packed_line_peel(ref, &buffer_start, buffer_end);
+ error = packed_parse_peel(ref, &buffer_start, buffer_end);
if (error < GIT_SUCCESS)
goto cleanup;
}
- error = git_hashtable_insert(ref_cache->packed_refs, ref->ref.name, ref);
+ error = git_hashtable_insert(ref_cache->packfile, ref->ref.name, ref);
if (error < GIT_SUCCESS) {
reference_free((git_reference *)ref);
goto cleanup;
}
}
- ref_cache->pack_loaded = 1;
-
cleanup:
gitfo_free_buf(&packfile);
return error;
}
-int git_reference_set_oid(git_reference *ref, const git_oid *id)
+static int _dirent_loose_load(void *data, char *full_path)
{
- reference_oid *ref_oid;
+ git_repository *repository = (git_repository *)data;
+ git_reference *reference, *old_ref;
+ char *file_path;
+ int error;
- if ((ref->type & GIT_REF_OID) == 0)
- return GIT_EINVALIDREFSTATE;
+ if (gitfo_isdir(full_path) == GIT_SUCCESS)
+ gitfo_dirent(full_path, GIT_PATH_MAX, _dirent_loose_load, repository);
- ref_oid = (reference_oid *)ref;
- git_oid_cpy(&ref_oid->oid, id);
+ file_path = full_path + strlen(repository->path_repository);
+ error = loose_lookup(&reference, repository, file_path, 1);
+ if (error == GIT_SUCCESS && reference != NULL) {
+ reference->type |= GIT_REF_PACKED;
- ref->type &= ~GIT_REF_HAS_PEEL;
- /* TODO: set new peel target */
+ if (git_hashtable_insert2(repository->references.packfile, reference->name, reference, (void **)&old_ref) < GIT_SUCCESS) {
+ reference_free(reference);
+ return GIT_ENOMEM;
+ }
+
+ if (old_ref != NULL)
+ reference_free(old_ref);
+ }
- return reference_write(ref);
+ return error;
}
-int git_reference_set_target(git_reference *ref, const char *target)
+/*
+ * Load all the loose references from the repository
+ * into the in-memory Packfile, and build a vector with
+ * all the references so it can be written back to
+ * disk.
+ */
+static int packed_loadloose(git_repository *repository)
{
- reference_symbolic *ref_sym;
+ char refs_path[GIT_PATH_MAX];
- if ((ref->type & GIT_REF_SYMBOLIC) == 0)
- return GIT_EINVALIDREFSTATE;
+ /* the packfile must have been previously loaded! */
+ assert(repository->references.packfile);
- ref_sym = (reference_symbolic *)ref;
+ git__joinpath(refs_path, repository->path_repository, GIT_REFS_DIR);
- free(ref_sym->target);
- ref_sym->target = git__strdup(target);
- if (ref_sym->target == NULL)
- return GIT_ENOMEM;
+ /* Remove any loose references from the cache */
+ {
+ const void *_unused;
+ git_reference *reference;
+
+ GIT_HASHTABLE_FOREACH(repository->references.loose_cache, _unused, reference,
+ reference_free(reference);
+ );
+ }
+
+ git_hashtable_clear(repository->references.loose_cache);
- return reference_write(ref);
+ /*
+ * Load all the loose files from disk into the Packfile table.
+ * This will overwrite any old packed entries with their
+ * updated loose versions
+ */
+ return gitfo_dirent(refs_path, GIT_PATH_MAX, _dirent_loose_load, repository);
}
-const git_oid *git_reference_oid(git_reference *ref)
+/*
+ * Write a single reference into a packfile
+ */
+static int packed_write_ref(reference_oid *ref, git_filebuf *file)
{
- assert(ref);
+ int error;
+ char oid[GIT_OID_HEXSZ + 1];
- if ((ref->type & GIT_REF_OID) == 0)
- return NULL;
+ git_oid_fmt(oid, &ref->oid);
+ oid[GIT_OID_HEXSZ] = 0;
- return &((reference_oid *)ref)->oid;
+ /*
+ * For references that peel to an object in the repo, we must
+ * write the resulting peel on a separate line, e.g.
+ *
+ * 6fa8a902cc1d18527e1355773c86721945475d37 refs/tags/libgit2-0.4
+ * ^2ec0cb7959b0bf965d54f95453f5b4b34e8d3100
+ *
+ * This obviously only applies to tags.
+ * The required peels have already been loaded into `ref->peel_target`.
+ */
+ if (ref->ref.type & GIT_REF_HAS_PEEL) {
+ char peel[GIT_OID_HEXSZ + 1];
+ git_oid_fmt(peel, &ref->peel_target);
+ peel[GIT_OID_HEXSZ] = 0;
+
+ error = git_filebuf_printf(file, "%s %s\n^%s\n", oid, ref->ref.name, peel);
+ } else {
+ error = git_filebuf_printf(file, "%s %s\n", oid, ref->ref.name);
+ }
+
+ return error;
}
-const char *git_reference_target(git_reference *ref)
+/*
+ * Find out what object this reference resolves to.
+ *
+ * For references that point to a 'big' tag (e.g. an
+ * actual tag object on the repository), we need to
+ * cache on the packfile the OID of the object to
+ * which that 'big tag' is pointing to.
+ */
+static int packed_find_peel(reference_oid *ref)
{
- assert(ref);
+ git_tag *tag;
+ const git_object *peeled_target;
+ int error;
- if ((ref->type & GIT_REF_SYMBOLIC) == 0)
- return NULL;
+ if (ref->ref.type & GIT_REF_HAS_PEEL)
+ return GIT_SUCCESS;
- return ((reference_symbolic *)ref)->target;
+ /*
+ * Only applies to tags, i.e. references
+ * in the /refs/tags folder
+ */
+ if (git__prefixcmp(ref->ref.name, GIT_REFS_TAGS_DIR) != 0)
+ return GIT_SUCCESS;
+
+ /*
+ * Find the tag in the repository. The tag must exist,
+ * otherwise this reference is broken and we shouldn't
+ * pack it.
+ */
+ error = git_tag_lookup(&tag, ref->ref.owner, &ref->oid);
+ if (error < GIT_SUCCESS)
+ return GIT_EOBJCORRUPTED;
+
+ /*
+ * Find the object pointed at by this tag
+ */
+ peeled_target = git_tag_target(tag);
+ if (peeled_target == NULL)
+ return GIT_EOBJCORRUPTED;
+
+ git_oid_cpy(&ref->peel_target, git_object_id(peeled_target));
+ ref->ref.type |= GIT_REF_HAS_PEEL;
+
+ /*
+ * The reference has now cached the resolved OID, and is
+ * marked at such. When written to the packfile, it'll be
+ * accompanied by this resolved oid
+ */
+
+ return GIT_SUCCESS;
+}
+
+/*
+ * Remove all loose references
+ *
+ * Once we have successfully written a packfile,
+ * all the loose references that were packed must be
+ * removed from disk.
+ *
+ * This is a dangerous method; make sure the packfile
+ * is well-written, because we are destructing references
+ * here otherwise.
+ */
+static int packed_remove_loose(git_repository *repo, git_vector *packing_list)
+{
+ unsigned int i;
+ char full_path[GIT_PATH_MAX];
+ int error = GIT_SUCCESS;
+
+ for (i = 0; i < packing_list->length; ++i) {
+ git_reference *ref = git_vector_get(packing_list, i);
+ git__joinpath(full_path, repo->path_repository, ref->name);
+
+ if (gitfo_exists(full_path) == GIT_SUCCESS &&
+ gitfo_unlink(full_path) < GIT_SUCCESS)
+ error = GIT_EOSERR;
+
+ /*
+ * if we fail to remove a single file, this is *not* good,
+ * but we should keep going and remove as many as possible.
+ * After we've removed as many files as possible, we return
+ * the error code anyway.
+ *
+ * TODO: mark this with a very special error code?
+ * GIT_EFAILTORMLOOSE
+ */
+ }
+
+ return error;
+}
+
+static int packed_sort(const void *a, const void *b)
+{
+ const git_reference *ref_a = *(const git_reference **)a;
+ const git_reference *ref_b = *(const git_reference **)b;
+
+ return strcmp(ref_a->name, ref_b->name);
+}
+
+/*
+ * Write all the contents in the in-memory packfile to disk.
+ */
+static int packed_write(git_repository *repo)
+{
+ git_filebuf pack_file;
+ int error;
+ unsigned int i;
+ char pack_file_path[GIT_PATH_MAX];
+
+ git_vector packing_list;
+ size_t total_refs;
+
+ assert(repo && repo->references.packfile);
+
+ total_refs = repo->references.packfile->key_count;
+ if ((error = git_vector_init(&packing_list, total_refs, packed_sort, NULL)) < GIT_SUCCESS)
+ return error;
+
+ /* Load all the packfile into a vector */
+ {
+ git_reference *reference;
+ const void *_unused;
+
+ GIT_HASHTABLE_FOREACH(repo->references.packfile, _unused, reference,
+ git_vector_insert(&packing_list, reference); /* cannot fail: vector already has the right size */
+ );
+ }
+
+ /* sort the vector so the entries appear sorted on the packfile */
+ git_vector_sort(&packing_list);
+
+ /* Now we can open the file! */
+ git__joinpath(pack_file_path, repo->path_repository, GIT_PACKEDREFS_FILE);
+ if ((error = git_filebuf_open(&pack_file, pack_file_path, 0)) < GIT_SUCCESS)
+ return error;
+
+ /* Packfiles have a header! */
+ if ((error = git_filebuf_printf(&pack_file, "%s\n", GIT_PACKEDREFS_HEADER)) < GIT_SUCCESS)
+ return error;
+
+ for (i = 0; i < packing_list.length; ++i) {
+ reference_oid *ref = (reference_oid *)git_vector_get(&packing_list, i);
+
+ /* only direct references go to the packfile; otherwise
+ * this is a disaster */
+ assert(ref->ref.type & GIT_REF_OID);
+
+ if ((error = packed_find_peel(ref)) < GIT_SUCCESS)
+ goto cleanup;
+
+ if ((error = packed_write_ref(ref, &pack_file)) < GIT_SUCCESS)
+ goto cleanup;
+ }
+
+cleanup:
+ /* if we've written all the references properly, we can commit
+ * the packfile to make the changes effective */
+ if (error == GIT_SUCCESS) {
+ error = git_filebuf_commit(&pack_file);
+
+ /* when and only when the packfile has been properly written,
+ * we can go ahead and remove the loose refs */
+ if (error == GIT_SUCCESS)
+ error = packed_remove_loose(repo, &packing_list);
+ }
+ else git_filebuf_cleanup(&pack_file);
+
+ git_vector_free(&packing_list);
+
+ return error;
+}
+
+
+
+
+/*****************************************
+ * External Library API
+ *****************************************/
+
+/**
+ * Constructors
+ */
+int git_repository_lookup_ref(git_reference **ref_out, git_repository *repo, const char *name)
+{
+ int error;
+ char normalized_name[GIT_PATH_MAX];
+
+ assert(ref_out && repo && name);
+
+ *ref_out = NULL;
+
+ error = normalize_name(normalized_name, name, 0);
+ if (error < GIT_SUCCESS)
+ return error;
+
+ /* First, check has been previously loaded and cached */
+ *ref_out = git_hashtable_lookup(repo->references.loose_cache, normalized_name);
+ if (*ref_out != NULL)
+ return GIT_SUCCESS;
+
+ /* Then check if there is a loose file for that reference.*/
+ error = loose_lookup(ref_out, repo, normalized_name, 0);
+
+ /* If the file exists, we store it on the cache */
+ if (error == GIT_SUCCESS)
+ return git_hashtable_insert(repo->references.loose_cache, (*ref_out)->name, (*ref_out));
+
+ /* The loose lookup has failed, but not because the reference wasn't found;
+ * probably the loose reference is corrupted. this is bad. */
+ if (error != GIT_ENOTFOUND)
+ return error;
+
+ /*
+ * If we cannot find a loose reference, we look into the packfile
+ * Load the packfile first if it hasn't been loaded
+ */
+ if (!repo->references.packfile) {
+ /* load all the packed references */
+ error = packed_load(repo);
+ if (error < GIT_SUCCESS)
+ return error;
+ }
+
+ /* Look up on the packfile */
+ *ref_out = git_hashtable_lookup(repo->references.packfile, normalized_name);
+ if (*ref_out != NULL)
+ return GIT_SUCCESS;
+
+ /* The reference doesn't exist anywhere */
+ return GIT_ENOTFOUND;
+}
+
+int git_reference_create_symbolic(git_reference **ref_out, git_repository *repo, const char *name, const char *target)
+{
+ char normalized[MAX_GITDIR_TREE_STRUCTURE_PATH_LENGTH];
+ int error = GIT_SUCCESS;
+ git_reference *ref = NULL;
+
+ error = reference_create(&ref, repo, name, GIT_REF_SYMBOLIC);
+ if (error < GIT_SUCCESS)
+ goto cleanup;
+
+ /* The target can aither be the name of an object id reference or the name of another symbolic reference */
+ error = normalize_name(normalized, target, 0);
+ if (error < GIT_SUCCESS)
+ goto cleanup;
+
+ /* set the target; this will write the reference on disk */
+ error = git_reference_set_target(ref, normalized);
+ if (error < GIT_SUCCESS)
+ goto cleanup;
+
+ error = git_hashtable_insert(repo->references.loose_cache, ref->name, ref);
+ if (error < GIT_SUCCESS)
+ goto cleanup;
+
+ *ref_out = ref;
+
+ return error;
+
+cleanup:
+ reference_free(ref);
+ return error;
+}
+
+int git_reference_create_oid(git_reference **ref_out, git_repository *repo, const char *name, const git_oid *id)
+{
+ int error = GIT_SUCCESS;
+ git_reference *ref = NULL;
+
+ error = reference_create(&ref, repo, name, GIT_REF_OID);
+ if (error < GIT_SUCCESS)
+ goto cleanup;
+
+ /* set the oid; this will write the reference on disk */
+ error = git_reference_set_oid(ref, id);
+ if (error < GIT_SUCCESS)
+ goto cleanup;
+
+ error = git_hashtable_insert(repo->references.loose_cache, ref->name, ref);
+ if (error < GIT_SUCCESS)
+ goto cleanup;
+
+ *ref_out = ref;
+
+ return error;
+
+cleanup:
+ reference_free(ref);
+ return error;
}
+
+/**
+ * Getters
+ */
git_rtype git_reference_type(git_reference *ref)
{
assert(ref);
@@ -547,154 +946,297 @@ git_repository *git_reference_owner(git_reference *ref)
return ref->owner;
}
-int git_reference_resolve(git_reference **resolved_ref, git_reference *ref)
+const git_oid *git_reference_oid(git_reference *ref)
{
- git_repository *repo;
- int error, i;
+ assert(ref);
- assert(resolved_ref && ref);
- *resolved_ref = NULL;
-
- repo = ref->owner;
+ if ((ref->type & GIT_REF_OID) == 0)
+ return NULL;
- for (i = 0; i < MAX_NESTING_LEVEL; ++i) {
- reference_symbolic *ref_sym;
+ return &((reference_oid *)ref)->oid;
+}
- if (ref->type & GIT_REF_OID) {
- *resolved_ref = ref;
- return GIT_SUCCESS;
- }
+const char *git_reference_target(git_reference *ref)
+{
+ assert(ref);
- ref_sym = (reference_symbolic *)ref;
- if ((error = git_repository_lookup_ref(&ref, repo, ref_sym->target)) < GIT_SUCCESS)
- return error;
- }
+ if ((ref->type & GIT_REF_SYMBOLIC) == 0)
+ return NULL;
- return GIT_ETOONESTEDSYMREF;
+ return ((reference_symbolic *)ref)->target;
}
-static int reference_write(git_reference *ref)
-{
- git_filebuf file;
- char ref_path[GIT_PATH_MAX];
- int error, contents_size;
- char *ref_contents = NULL;
+/**
+ * Setters
+ */
- git__joinpath(ref_path, ref->owner->path_repository, ref->name);
+/*
+ * Change the OID target of a reference.
+ *
+ * For loose references, just change the oid in memory
+ * and overwrite the file in disk.
+ *
+ * For packed files, this is not pretty:
+ * For performance reasons, we write the new reference
+ * loose on disk (it replaces the old on the packfile),
+ * but we cannot invalidate the pointer to the reference,
+ * and most importantly, the `packfile` object must stay
+ * consistent with the representation of the packfile
+ * on disk. This is what we need to:
+ *
+ * 1. Copy the reference
+ * 2. Change the oid on the original
+ * 3. Write the original to disk
+ * 4. Write the original to the loose cache
+ * 5. Replace the original with the copy (old reference) in the packfile cache
+ */
+int git_reference_set_oid(git_reference *ref, const git_oid *id)
+{
+ reference_oid *ref_oid;
+ reference_oid *ref_old = NULL;
+ int error = GIT_SUCCESS;
- if ((error = git_filebuf_open(&file, ref_path, 0)) < GIT_SUCCESS)
- return error;
+ if ((ref->type & GIT_REF_OID) == 0)
+ return GIT_EINVALIDREFSTATE;
- if (ref->type & GIT_REF_OID) {
- reference_oid *ref_oid = (reference_oid *)ref;
+ ref_oid = (reference_oid *)ref;
- contents_size = GIT_OID_HEXSZ + 1;
- ref_contents = git__malloc(contents_size);
- if (ref_contents == NULL) {
- error = GIT_ENOMEM;
- goto unlock;
+ /* duplicate the reference;
+ * this copy will stay on the packfile cache */
+ if (ref->type & GIT_REF_PACKED) {
+ ref_old = git__malloc(sizeof(reference_oid));
+ if (ref_old == NULL)
+ return GIT_ENOMEM;
+
+ ref_old->ref.name = git__strdup(ref->name);
+ if (ref_old->ref.name == NULL) {
+ free(ref_old);
+ return GIT_ENOMEM;
}
+ }
- git_oid_fmt(ref_contents, &ref_oid->oid);
+ git_oid_cpy(&ref_oid->oid, id);
+ ref->type &= ~GIT_REF_HAS_PEEL;
- } else if (ref->type & GIT_REF_SYMBOLIC) { /* GIT_REF_SYMBOLIC */
- reference_symbolic *ref_sym = (reference_symbolic *)ref;
+ error = loose_write(ref);
+ if (error < GIT_SUCCESS)
+ goto cleanup;
- contents_size = strlen(GIT_SYMREF) + strlen(ref_sym->target) + 1;
- ref_contents = git__malloc(contents_size);
- if (ref_contents == NULL) {
- error = GIT_ENOMEM;
- goto unlock;
- }
+ if (ref->type & GIT_REF_PACKED) {
+ /* insert the original on the loose cache */
+ error = git_hashtable_insert(ref->owner->references.loose_cache, ref->name, ref);
+ if (error < GIT_SUCCESS)
+ goto cleanup;
- strcpy(ref_contents, GIT_SYMREF);
- strcat(ref_contents, ref_sym->target);
- } else {
- error = GIT_EINVALIDREFSTATE;
- goto unlock;
+ ref->type &= ~GIT_REF_PACKED;
+
+ /* replace the original in the packfile with the copy */
+ error = git_hashtable_insert(ref->owner->references.packfile, ref_old->ref.name, ref_old);
+ if (error < GIT_SUCCESS)
+ goto cleanup;
}
- /* TODO: win32 carriage return when writing references in Windows? */
- ref_contents[contents_size - 1] = '\n';
+ return GIT_SUCCESS;
- if ((error = git_filebuf_write(&file, ref_contents, contents_size)) < GIT_SUCCESS)
- goto unlock;
+cleanup:
+ reference_free((git_reference *)ref_old);
+ return error;
+}
- error = git_filebuf_commit(&file);
+/*
+ * Change the target of a symbolic reference.
+ *
+ * This is easy because symrefs cannot be inside
+ * a pack. We just change the target in memory
+ * and overwrite the file on disk.
+ */
+int git_reference_set_target(git_reference *ref, const char *target)
+{
+ reference_symbolic *ref_sym;
- free(ref_contents);
- return error;
+ if ((ref->type & GIT_REF_SYMBOLIC) == 0)
+ return GIT_EINVALIDREFSTATE;
-unlock:
- git_filebuf_cleanup(&file);
- free(ref_contents);
+ ref_sym = (reference_symbolic *)ref;
+
+ free(ref_sym->target);
+ ref_sym->target = git__strdup(target);
+ if (ref_sym->target == NULL)
+ return GIT_ENOMEM;
+
+ return loose_write(ref);
+}
+
+/**
+ * Other
+ */
+
+/*
+ * Delete a reference.
+ *
+ * If the reference is packed, this is an expensive
+ * operation. We need to remove the reference from
+ * the memory cache and then rewrite the whole pack
+ *
+ * If the reference is loose, we just remove it on
+ * the filesystem and update the in-memory cache
+ * accordingly.
+ *
+ * This obviously invalidates the `ref` pointer.
+ */
+int git_reference_delete(git_reference *ref)
+{
+ int error;
+
+ assert(ref);
+
+ if (ref->type & GIT_REF_PACKED) {
+ git_hashtable_remove(ref->owner->references.packfile, ref->name);
+ error = packed_write(ref->owner);
+ } else {
+ char full_path[GIT_PATH_MAX];
+ git__joinpath(full_path, ref->owner->path_repository, ref->name);
+ git_hashtable_remove(ref->owner->references.loose_cache, ref->name);
+ error = gitfo_unlink(full_path);
+ }
+
+ reference_free(ref);
return error;
}
-int git_repository_lookup_ref(git_reference **ref_out, git_repository *repo, const char *name)
+/*
+ * Rename a reference
+ *
+ * If the reference is packed, we need to rewrite the
+ * packfile. Not cool.
+ *
+ * If the reference is loose, we just rename it on
+ * the filesystem.
+ *
+ * We also need to re-insert the reference on its corresponding
+ * in-memory cache, since the caches are indexed by refname.
+ */
+int git_reference_rename(git_reference *ref, const char *new_name)
{
int error;
- char normalized_name[GIT_PATH_MAX];
+ char *old_name;
+ git_hashtable *dest_table;
- assert(ref_out && repo && name);
+ assert(ref);
- *ref_out = NULL;
+ old_name = ref->name;
+ ref->name = git__strdup(new_name);
- error = normalize_name(normalized_name, name, 0);
+ if (ref->name == NULL) {
+ ref->name = old_name;
+ return GIT_ENOMEM;
+ }
+
+ if (ref->type & GIT_REF_PACKED) {
+ /* write the packfile to disk; note
+ * that the state of the in-memory cache is not
+ * consistent, because the reference is indexed
+ * by its old name but it already has the new one.
+ * This doesn't affect writing, though, and allows
+ * us to rollback if writing fails
+ */
+ error = packed_write(ref->owner);
+ if (error < GIT_SUCCESS)
+ goto cleanup;
+
+ dest_table = ref->owner->references.packfile;
+ } else {
+ char old_path[GIT_PATH_MAX];
+ char new_path[GIT_PATH_MAX];
+
+ git__joinpath(old_path, ref->owner->path_repository, old_name);
+ git__joinpath(new_path, ref->owner->path_repository, ref->name);
+
+ error = gitfo_move_file(old_path, new_path);
+ if (error < GIT_SUCCESS)
+ goto cleanup;
+
+ dest_table = ref->owner->references.loose_cache;
+ }
+
+ error = git_hashtable_insert(dest_table, ref->name, ref);
if (error < GIT_SUCCESS)
- return error;
+ goto cleanup;
- /*
- * 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.loose_refs, normalized_name);
- if (*ref_out != NULL)
- return GIT_SUCCESS;
+ git_hashtable_remove(dest_table, old_name);
- /*
- * 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, normalized_name);
+ free(old_name);
+ return GIT_SUCCESS;
- if (error == GIT_SUCCESS)
- return GIT_SUCCESS;
+cleanup:
+ /* restore the old name if this failed */
+ free(ref->name);
+ ref->name = old_name;
+ return error;
+}
- if (error != GIT_ENOTFOUND)
- return error;
+int git_reference_resolve(git_reference **resolved_ref, git_reference *ref)
+{
+ git_repository *repo;
+ int error, i;
- if (!repo->references.pack_loaded) {
- /* load all the packed references */
- error = load_packed_refs(&repo->references, repo);
- if (error < GIT_SUCCESS)
+ assert(resolved_ref && ref);
+ *resolved_ref = NULL;
+
+ repo = ref->owner;
+
+ for (i = 0; i < MAX_NESTING_LEVEL; ++i) {
+ reference_symbolic *ref_sym;
+
+ if (ref->type & GIT_REF_OID) {
+ *resolved_ref = ref;
+ return GIT_SUCCESS;
+ }
+
+ ref_sym = (reference_symbolic *)ref;
+ if ((error = git_repository_lookup_ref(&ref, repo, ref_sym->target)) < GIT_SUCCESS)
return error;
}
- *ref_out = git_hashtable_lookup(repo->references.packed_refs, normalized_name);
- if (*ref_out != NULL)
- return GIT_SUCCESS;
+ return GIT_ETOONESTEDSYMREF;
+}
- /* The reference doesn't exist anywhere */
- return GIT_ENOTFOUND;
+int git_reference_packall(git_repository *repo)
+{
+ int error;
+
+ /* load the existing packfile */
+ if ((error = packed_load(repo)) < GIT_SUCCESS)
+ return error;
+
+ /* update it in-memory with all the loose references */
+ if ((error = packed_loadloose(repo)) < GIT_SUCCESS)
+ return error;
+
+ /* write it back to disk */
+ return packed_write(repo);
}
+
+
+
+
+/*****************************************
+ * Init/free (repository API)
+ *****************************************/
int git_repository__refcache_init(git_refcache *refs)
{
assert(refs);
- refs->loose_refs = git_hashtable_alloc(
+ refs->loose_cache = git_hashtable_alloc(
default_table_size,
reftable_hash,
(git_hash_keyeq_ptr)strcmp);
- refs->packed_refs = git_hashtable_alloc(
- default_table_size,
- reftable_hash,
- (git_hash_keyeq_ptr)strcmp);
+ /* packfile loaded lazily */
+ refs->packfile = NULL;
- return (refs->loose_refs && refs->packed_refs) ? GIT_SUCCESS : GIT_ENOMEM;
+ return (refs->loose_cache) ? GIT_SUCCESS : GIT_ENOMEM;
}
void git_repository__refcache_free(git_refcache *refs)
@@ -704,18 +1246,26 @@ void git_repository__refcache_free(git_refcache *refs)
assert(refs);
- GIT_HASHTABLE_FOREACH(refs->loose_refs, _unused, reference,
+ GIT_HASHTABLE_FOREACH(refs->loose_cache, _unused, reference,
reference_free(reference);
);
- GIT_HASHTABLE_FOREACH(refs->packed_refs, _unused, reference,
- reference_free(reference);
- );
+ git_hashtable_free(refs->loose_cache);
+
+ if (refs->packfile) {
+ GIT_HASHTABLE_FOREACH(refs->packfile, _unused, reference,
+ reference_free(reference);
+ );
- git_hashtable_free(refs->loose_refs);
- git_hashtable_free(refs->packed_refs);
+ git_hashtable_free(refs->packfile);
+ }
}
+
+
+/*****************************************
+ * Name normalization
+ *****************************************/
static int check_valid_ref_char(char ch)
{
if (ch <= ' ')
@@ -737,7 +1287,6 @@ static int check_valid_ref_char(char ch)
}
}
-
static int normalize_name(char *buffer_out, const char *name, int is_oid_ref)
{
int error = GIT_SUCCESS;
diff --git a/src/refs.h b/src/refs.h
index 5fc71fc83..6efc61b86 100644
--- a/src/refs.h
+++ b/src/refs.h
@@ -23,10 +23,8 @@ struct git_reference {
};
typedef struct {
- git_hashtable *packed_refs;
- git_hashtable *loose_refs;
-
- unsigned pack_loaded:1;
+ git_hashtable *packfile;
+ git_hashtable *loose_cache;
} git_refcache;