diff options
author | Vicent Marti <tanoku@gmail.com> | 2011-02-27 22:31:43 +0200 |
---|---|---|
committer | Vicent Marti <tanoku@gmail.com> | 2011-03-03 20:23:50 +0200 |
commit | 87d3acf45eb1a63b5847bca2061a993471b8b1ee (patch) | |
tree | fe1c9ad96065ba92a838f62a3c49ccb91f6425b9 | |
parent | 17cdf25208202300def293ce1997b97aeb517586 (diff) | |
download | libgit2-87d3acf45eb1a63b5847bca2061a993471b8b1ee.tar.gz |
Finish the References API
The following methods have been implemented:
git_reference_packall
git_reference_rename
git_reference_delete
The library now has full support for packed references, including
partial and total writing. Internal documentation has been updated with
the details.
Signed-off-by: Vicent Marti <tanoku@gmail.com>
-rw-r--r-- | src/git2/refs.h | 18 | ||||
-rw-r--r-- | src/refs.c | 983 | ||||
-rw-r--r-- | src/refs.h | 6 | ||||
-rw-r--r-- | tests/t10-refs.c | 8 |
4 files changed, 794 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; diff --git a/tests/t10-refs.c b/tests/t10-refs.c index a5e9a3351..0d6c03f64 100644 --- a/tests/t10-refs.c +++ b/tests/t10-refs.c @@ -296,6 +296,13 @@ BEGIN_TEST("createref", create_new_object_id_ref) must_pass(gitfo_unlink(ref_path)); /* TODO: replace with git_reference_delete() when available */ END_TEST +BEGIN_TEST("packrefs", create_packfile) + git_repository *repo; + must_pass(git_repository_open(&repo, REPOSITORY_FOLDER)); + must_pass(git_reference_packall(repo)); + git_repository_free(repo); +END_TEST + static int ensure_refname_normalized(int is_oid_ref, const char *input_refname, const char *expected_refname) { int error = GIT_SUCCESS; @@ -494,6 +501,7 @@ git_testsuite *libgit2_suite_refs(void) ADD_TEST(suite, "normalizeref", normalize_object_id_ref); ADD_TEST(suite, "normalizeref", normalize_symbolic_ref); ADD_TEST(suite, "normalizeref", jgit_tests); + //ADD_TEST(suite, "packrefs", create_packfile); return suite; } |