diff options
author | Edward Thomson <ethomson@edwardthomson.com> | 2013-03-01 15:37:33 -0600 |
---|---|---|
committer | Edward Thomson <ethomson@edwardthomson.com> | 2013-03-07 11:01:52 -0600 |
commit | d00d54645d931c77a9b401518c0d73e3f640454b (patch) | |
tree | e1932dcc97172a53524e9db1ba4923cf137a4f9c /src | |
parent | 6a9ef012376e8a21dcfd0499ab16048eb6e954c3 (diff) | |
download | libgit2-d00d54645d931c77a9b401518c0d73e3f640454b.tar.gz |
immutable references and a pluggable ref database
Diffstat (limited to 'src')
-rw-r--r-- | src/branch.c | 64 | ||||
-rw-r--r-- | src/commit.c | 2 | ||||
-rw-r--r-- | src/fetchhead.c | 1 | ||||
-rw-r--r-- | src/refdb.c | 177 | ||||
-rw-r--r-- | src/refdb.h | 46 | ||||
-rw-r--r-- | src/refdb_fs.c | 1023 | ||||
-rw-r--r-- | src/refdb_fs.h | 15 | ||||
-rw-r--r-- | src/refs.c | 1538 | ||||
-rw-r--r-- | src/refs.h | 19 | ||||
-rw-r--r-- | src/remote.c | 6 | ||||
-rw-r--r-- | src/repository.c | 51 | ||||
-rw-r--r-- | src/repository.h | 4 | ||||
-rw-r--r-- | src/reset.c | 43 | ||||
-rw-r--r-- | src/revparse.c | 3 | ||||
-rw-r--r-- | src/stash.c | 6 | ||||
-rw-r--r-- | src/tag.c | 10 |
16 files changed, 1606 insertions, 1402 deletions
diff --git a/src/branch.c b/src/branch.c index a50387541..6b289b12e 100644 --- a/src/branch.c +++ b/src/branch.c @@ -54,11 +54,11 @@ static int not_a_local_branch(const char *reference_name) } int git_branch_create( - git_reference **ref_out, - git_repository *repository, - const char *branch_name, - const git_commit *commit, - int force) + git_reference **ref_out, + git_repository *repository, + const char *branch_name, + const git_commit *commit, + int force) { git_reference *branch = NULL; git_buf canonical_branch_name = GIT_BUF_INIT; @@ -124,10 +124,7 @@ on_error: } typedef struct { - int (*branch_cb)( - const char *branch_name, - git_branch_t branch_type, - void *payload); + git_branch_foreach_cb branch_cb; void *callback_payload; unsigned int branch_type; } branch_foreach_filter; @@ -148,14 +145,10 @@ static int branch_foreach_cb(const char *branch_name, void *payload) } int git_branch_foreach( - git_repository *repo, - unsigned int list_flags, - int (*branch_cb)( - const char *branch_name, - git_branch_t branch_type, - void *payload), - void *payload -) + git_repository *repo, + unsigned int list_flags, + git_branch_foreach_cb branch_cb, + void *payload) { branch_foreach_filter filter; @@ -167,6 +160,7 @@ int git_branch_foreach( } int git_branch_move( + git_reference **out, git_reference *branch, const char *new_branch_name, int force) @@ -181,28 +175,20 @@ int git_branch_move( if (!git_reference_is_branch(branch)) return not_a_local_branch(git_reference_name(branch)); - if ((error = git_buf_joinpath(&new_reference_name, GIT_REFS_HEADS_DIR, new_branch_name)) < 0) - goto cleanup; - - if (git_buf_printf( - &old_config_section, - "branch.%s", - git_reference_name(branch) + strlen(GIT_REFS_HEADS_DIR)) < 0) - goto cleanup; - - if ((error = git_reference_rename(branch, git_buf_cstr(&new_reference_name), force)) < 0) - goto cleanup; + if ((error = git_buf_joinpath(&new_reference_name, GIT_REFS_HEADS_DIR, new_branch_name)) < 0 || + (error = git_buf_printf(&old_config_section, "branch.%s", git_reference_name(branch) + strlen(GIT_REFS_HEADS_DIR)) < 0) || + (error = git_buf_printf(&new_config_section, "branch.%s", new_branch_name)) < 0) + goto done; - if (git_buf_printf(&new_config_section, "branch.%s", new_branch_name) < 0) - goto cleanup; - - if ((error = git_config_rename_section( - git_reference_owner(branch), + if ((error = git_config_rename_section(git_reference_owner(branch), git_buf_cstr(&old_config_section), git_buf_cstr(&new_config_section))) < 0) - goto cleanup; + goto done; + + if ((error = git_reference_rename(out, branch, git_buf_cstr(&new_reference_name), force)) < 0) + goto done; -cleanup: +done: git_buf_free(&new_reference_name); git_buf_free(&old_config_section); git_buf_free(&new_config_section); @@ -211,10 +197,10 @@ cleanup: } int git_branch_lookup( - git_reference **ref_out, - git_repository *repo, - const char *branch_name, - git_branch_t branch_type) + git_reference **ref_out, + git_repository *repo, + const char *branch_name, + git_branch_t branch_type) { assert(ref_out && repo && branch_name); diff --git a/src/commit.c b/src/commit.c index 29ce39107..7a356c5f9 100644 --- a/src/commit.c +++ b/src/commit.c @@ -121,7 +121,7 @@ int git_commit_create( git_buf_free(&commit); if (update_ref != NULL) - return git_reference__update(repo, oid, update_ref); + return git_reference__update_terminal(repo, update_ref, oid); return 0; diff --git a/src/fetchhead.c b/src/fetchhead.c index 6e8fb9fac..4dcebb857 100644 --- a/src/fetchhead.c +++ b/src/fetchhead.c @@ -16,7 +16,6 @@ #include "refs.h" #include "repository.h" - int git_fetchhead_ref_cmp(const void *a, const void *b) { const git_fetchhead_ref *one = (const git_fetchhead_ref *)a; diff --git a/src/refdb.c b/src/refdb.c new file mode 100644 index 000000000..0d2064343 --- /dev/null +++ b/src/refdb.c @@ -0,0 +1,177 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "common.h" +#include "posix.h" +#include "git2/object.h" +#include "git2/refs.h" +#include "git2/refdb.h" +#include "hash.h" +#include "refdb.h" +#include "refs.h" + +#include "git2/refdb_backend.h" + +int git_refdb_new(git_refdb **out, git_repository *repo) +{ + git_refdb *db; + + assert(out && repo); + + db = git__calloc(1, sizeof(*db)); + GITERR_CHECK_ALLOC(db); + + db->repo = repo; + + *out = db; + GIT_REFCOUNT_INC(db); + return 0; +} + +int git_refdb_open(git_refdb **out, git_repository *repo) +{ + git_refdb *db; + git_refdb_backend *dir; + + assert(out && repo); + + *out = NULL; + + if (git_refdb_new(&db, repo) < 0) + return -1; + + /* Add the default (filesystem) backend */ + if (git_refdb_backend_fs(&dir, repo, db) < 0) { + git_refdb_free(db); + return -1; + } + + db->repo = repo; + db->backend = dir; + + *out = db; + return 0; +} + +int git_refdb_set_backend(git_refdb *db, git_refdb_backend *backend) +{ + if (db->backend) { + if(db->backend->free) + db->backend->free(db->backend); + else + git__free(db->backend); + } + + db->backend = backend; + + return 0; +} + +int git_refdb_compress(git_refdb *db) +{ + assert(db); + + if (db->backend->compress) { + return db->backend->compress(db->backend); + } + + return 0; +} + +void git_refdb_free(git_refdb *db) +{ + if (db->backend) { + if(db->backend->free) + db->backend->free(db->backend); + else + git__free(db->backend); + } + + git__free(db); +} + +int git_refdb_exists(int *exists, git_refdb *refdb, const char *ref_name) +{ + assert(exists && refdb && refdb->backend); + + return refdb->backend->exists(exists, refdb->backend, ref_name); +} + +int git_refdb_lookup(git_reference **out, git_refdb *db, const char *ref_name) +{ + assert(db && db->backend && ref_name); + + return db->backend->lookup(out, db->backend, ref_name); +} + +int git_refdb_foreach( + git_refdb *db, + unsigned int list_flags, + git_reference_foreach_cb callback, + void *payload) +{ + assert(db && db->backend); + + return db->backend->foreach(db->backend, list_flags, callback, payload); +} + +struct glob_cb_data { + const char *glob; + git_reference_foreach_cb callback; + void *payload; +}; + +static int fromglob_cb(const char *reference_name, void *payload) +{ + struct glob_cb_data *data = (struct glob_cb_data *)payload; + + if (!p_fnmatch(data->glob, reference_name, 0)) + return data->callback(reference_name, data->payload); + + return 0; +} + +int git_refdb_foreach_glob( + git_refdb *db, + const char *glob, + unsigned int list_flags, + git_reference_foreach_cb callback, + void *payload) +{ + int error; + struct glob_cb_data data; + + assert(db && db->backend && glob && callback); + + if(db->backend->foreach_glob != NULL) + error = db->backend->foreach_glob(db->backend, + glob, list_flags, callback, payload); + else { + data.glob = glob; + data.callback = callback; + data.payload = payload; + + error = db->backend->foreach(db->backend, + list_flags, fromglob_cb, &data); + } + + return error; +} + +int git_refdb_write(git_refdb *db, const git_reference *ref) +{ + assert(db && db->backend); + + return db->backend->write(db->backend, ref); +} + +int git_refdb_delete(struct git_refdb *db, const git_reference *ref) +{ + assert(db && db->backend); + + return db->backend->delete(db->backend, ref); +} diff --git a/src/refdb.h b/src/refdb.h new file mode 100644 index 000000000..0969711b9 --- /dev/null +++ b/src/refdb.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_refdb_h__ +#define INCLUDE_refdb_h__ + +#include "git2/refdb.h" +#include "repository.h" + +struct git_refdb { + git_refcount rc; + git_repository *repo; + git_refdb_backend *backend; +}; + +int git_refdb_exists( + int *exists, + git_refdb *refdb, + const char *ref_name); + +int git_refdb_lookup( + git_reference **out, + git_refdb *refdb, + const char *ref_name); + +int git_refdb_foreach( + git_refdb *refdb, + unsigned int list_flags, + git_reference_foreach_cb callback, + void *payload); + +int git_refdb_foreach_glob( + git_refdb *refdb, + const char *glob, + unsigned int list_flags, + git_reference_foreach_cb callback, + void *payload); + +int git_refdb_write(git_refdb *refdb, const git_reference *ref); + +int git_refdb_delete(struct git_refdb *refdb, const git_reference *ref); + +#endif diff --git a/src/refdb_fs.c b/src/refdb_fs.c new file mode 100644 index 000000000..5f5d42f66 --- /dev/null +++ b/src/refdb_fs.c @@ -0,0 +1,1023 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "refs.h" +#include "hash.h" +#include "repository.h" +#include "fileops.h" +#include "pack.h" +#include "reflog.h" +#include "config.h" +#include "refdb.h" +#include "refdb_fs.h" + +#include <git2/tag.h> +#include <git2/object.h> +#include <git2/refdb.h> +#include <git2/refdb_backend.h> + +GIT__USE_STRMAP; + +#define DEFAULT_NESTING_LEVEL 5 +#define MAX_NESTING_LEVEL 10 + +enum { + GIT_PACKREF_HAS_PEEL = 1, + GIT_PACKREF_WAS_LOOSE = 2 +}; + +struct packref { + git_oid oid; + git_oid peel; + char flags; + char name[GIT_FLEX_ARRAY]; +}; + +typedef struct refdb_fs_backend { + git_refdb_backend parent; + + git_repository *repo; + const char *path; + git_refdb *refdb; + + git_refcache refcache; +} refdb_fs_backend; + +static int reference_read( + git_buf *file_content, + time_t *mtime, + const char *repo_path, + const char *ref_name, + int *updated) +{ + git_buf path = GIT_BUF_INIT; + int result; + + assert(file_content && repo_path && ref_name); + + /* Determine the full path of the file */ + if (git_buf_joinpath(&path, repo_path, ref_name) < 0) + return -1; + + result = git_futils_readbuffer_updated(file_content, path.ptr, mtime, NULL, updated); + git_buf_free(&path); + + return result; +} + +static int packed_parse_oid( + struct packref **ref_out, + const char **buffer_out, + const char *buffer_end) +{ + struct packref *ref = NULL; + + const char *buffer = *buffer_out; + const char *refname_begin, *refname_end; + + size_t refname_len; + git_oid id; + + refname_begin = (buffer + GIT_OID_HEXSZ + 1); + if (refname_begin >= buffer_end || refname_begin[-1] != ' ') + goto corrupt; + + /* Is this a valid object id? */ + if (git_oid_fromstr(&id, buffer) < 0) + goto corrupt; + + refname_end = memchr(refname_begin, '\n', buffer_end - refname_begin); + if (refname_end == NULL) + refname_end = buffer_end; + + if (refname_end[-1] == '\r') + refname_end--; + + refname_len = refname_end - refname_begin; + + ref = git__malloc(sizeof(struct packref) + refname_len + 1); + GITERR_CHECK_ALLOC(ref); + + memcpy(ref->name, refname_begin, refname_len); + ref->name[refname_len] = 0; + + git_oid_cpy(&ref->oid, &id); + + ref->flags = 0; + + *ref_out = ref; + *buffer_out = refname_end + 1; + + return 0; + +corrupt: + git__free(ref); + giterr_set(GITERR_REFERENCE, "The packed references file is corrupted"); + return -1; +} + +static int packed_parse_peel( + struct packref *tag_ref, + const char **buffer_out, + const char *buffer_end) +{ + const char *buffer = *buffer_out + 1; + + assert(buffer[-1] == '^'); + + /* Ensure it's not the first entry of the file */ + if (tag_ref == NULL) + goto corrupt; + + /* Ensure reference is a tag */ + if (git__prefixcmp(tag_ref->name, GIT_REFS_TAGS_DIR) != 0) + goto corrupt; + + if (buffer + GIT_OID_HEXSZ > buffer_end) + goto corrupt; + + /* Is this a valid object id? */ + if (git_oid_fromstr(&tag_ref->peel, buffer) < 0) + goto corrupt; + + buffer = buffer + GIT_OID_HEXSZ; + if (*buffer == '\r') + buffer++; + + if (buffer != buffer_end) { + if (*buffer == '\n') + buffer++; + else + goto corrupt; + } + + *buffer_out = buffer; + return 0; + +corrupt: + giterr_set(GITERR_REFERENCE, "The packed references file is corrupted"); + return -1; +} + +static int packed_load(refdb_fs_backend *backend) +{ + int result, updated; + git_buf packfile = GIT_BUF_INIT; + const char *buffer_start, *buffer_end; + git_refcache *ref_cache = &backend->refcache; + + /* First we make sure we have allocated the hash table */ + if (ref_cache->packfile == NULL) { + ref_cache->packfile = git_strmap_alloc(); + GITERR_CHECK_ALLOC(ref_cache->packfile); + } + + result = reference_read(&packfile, &ref_cache->packfile_time, + backend->path, GIT_PACKEDREFS_FILE, &updated); + + /* + * If we couldn't find the file, we need to clear the table and + * return. On any other error, we return that error. If everything + * went fine and the file wasn't updated, then there's nothing new + * for us here, so just return. Anything else means we need to + * refresh the packed refs. + */ + if (result == GIT_ENOTFOUND) { + git_strmap_clear(ref_cache->packfile); + return 0; + } + + if (result < 0) + return -1; + + if (!updated) + return 0; + + /* + * At this point, we want to refresh the packed refs. We already + * have the contents in our buffer. + */ + git_strmap_clear(ref_cache->packfile); + + buffer_start = (const char *)packfile.ptr; + buffer_end = (const char *)(buffer_start) + packfile.size; + + while (buffer_start < buffer_end && buffer_start[0] == '#') { + buffer_start = strchr(buffer_start, '\n'); + if (buffer_start == NULL) + goto parse_failed; + + buffer_start++; + } + + while (buffer_start < buffer_end) { + int err; + struct packref *ref = NULL; + + if (packed_parse_oid(&ref, &buffer_start, buffer_end) < 0) + goto parse_failed; + + if (buffer_start[0] == '^') { + if (packed_parse_peel(ref, &buffer_start, buffer_end) < 0) + goto parse_failed; + } + + git_strmap_insert(ref_cache->packfile, ref->name, ref, err); + if (err < 0) + goto parse_failed; + } + + git_buf_free(&packfile); + return 0; + +parse_failed: + git_strmap_free(ref_cache->packfile); + ref_cache->packfile = NULL; + git_buf_free(&packfile); + return -1; +} + +static int loose_parse_oid(git_oid *oid, git_buf *file_content) +{ + size_t len; + const char *str; + + len = git_buf_len(file_content); + if (len < GIT_OID_HEXSZ) + goto corrupted; + + /* str is guranteed to be zero-terminated */ + str = git_buf_cstr(file_content); + + /* we need to get 40 OID characters from the file */ + if (git_oid_fromstr(oid, git_buf_cstr(file_content)) < 0) + goto corrupted; + + /* If the file is longer than 40 chars, the 41st must be a space */ + str += GIT_OID_HEXSZ; + if (*str == '\0' || git__isspace(*str)) + return 0; + +corrupted: + giterr_set(GITERR_REFERENCE, "Corrupted loose reference file"); + return -1; +} + +static int loose_lookup_to_packfile( + struct packref **ref_out, + refdb_fs_backend *backend, + const char *name) +{ + git_buf ref_file = GIT_BUF_INIT; + struct packref *ref = NULL; + size_t name_len; + + *ref_out = NULL; + + if (reference_read(&ref_file, NULL, backend->path, name, NULL) < 0) + return -1; + + git_buf_rtrim(&ref_file); + + name_len = strlen(name); + ref = git__malloc(sizeof(struct packref) + name_len + 1); + GITERR_CHECK_ALLOC(ref); + + memcpy(ref->name, name, name_len); + ref->name[name_len] = 0; + + if (loose_parse_oid(&ref->oid, &ref_file) < 0) { + git_buf_free(&ref_file); + git__free(ref); + return -1; + } + + ref->flags = GIT_PACKREF_WAS_LOOSE; + + *ref_out = ref; + git_buf_free(&ref_file); + return 0; +} + + +static int _dirent_loose_load(void *data, git_buf *full_path) +{ + refdb_fs_backend *backend = (refdb_fs_backend *)data; + void *old_ref = NULL; + struct packref *ref; + const char *file_path; + int err; + + if (git_path_isdir(full_path->ptr) == true) + return git_path_direach(full_path, _dirent_loose_load, backend); + + file_path = full_path->ptr + strlen(backend->path); + + if (loose_lookup_to_packfile(&ref, backend, file_path) < 0) + return -1; + + git_strmap_insert2( + backend->refcache.packfile, ref->name, ref, old_ref, err); + if (err < 0) { + git__free(ref); + return -1; + } + + git__free(old_ref); + return 0; +} + +/* + * 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(refdb_fs_backend *backend) +{ + git_buf refs_path = GIT_BUF_INIT; + int result; + + /* the packfile must have been previously loaded! */ + assert(backend->refcache.packfile); + + if (git_buf_joinpath(&refs_path, backend->path, GIT_REFS_DIR) < 0) + return -1; + + /* + * Load all the loose files from disk into the Packfile table. + * This will overwrite any old packed entries with their + * updated loose versions + */ + result = git_path_direach(&refs_path, _dirent_loose_load, backend); + git_buf_free(&refs_path); + + return result; +} + +static int refdb_fs_backend__exists( + int *exists, + git_refdb_backend *_backend, + const char *ref_name) +{ + refdb_fs_backend *backend; + git_buf ref_path = GIT_BUF_INIT; + + assert(_backend); + backend = (refdb_fs_backend *)_backend; + + if (packed_load(backend) < 0) + return -1; + + if (git_buf_joinpath(&ref_path, backend->path, ref_name) < 0) + return -1; + + if (git_path_isfile(ref_path.ptr) == true || + git_strmap_exists(backend->refcache.packfile, ref_path.ptr)) + *exists = 1; + else + *exists = 0; + + git_buf_free(&ref_path); + return 0; +} + +static const char *loose_parse_symbolic(git_buf *file_content) +{ + const unsigned int header_len = (unsigned int)strlen(GIT_SYMREF); + const char *refname_start; + + refname_start = (const char *)file_content->ptr; + + if (git_buf_len(file_content) < header_len + 1) { + giterr_set(GITERR_REFERENCE, "Corrupted loose reference file"); + return NULL; + } + + /* + * Assume we have already checked for the header + * before calling this function + */ + refname_start += header_len; + + return refname_start; +} + +static int loose_lookup( + git_reference **out, + refdb_fs_backend *backend, + const char *ref_name) +{ + const char *target; + git_oid oid; + git_buf ref_file = GIT_BUF_INIT; + int error = 0; + + error = reference_read(&ref_file, NULL, backend->path, ref_name, NULL); + + if (error < 0) + goto done; + + if (git__prefixcmp((const char *)(ref_file.ptr), GIT_SYMREF) == 0) { + git_buf_rtrim(&ref_file); + + if ((target = loose_parse_symbolic(&ref_file)) == NULL) { + error = -1; + goto done; + } + + *out = git_reference__alloc(backend->refdb, ref_name, NULL, target); + } else { + if ((error = loose_parse_oid(&oid, &ref_file)) < 0) + goto done; + + *out = git_reference__alloc(backend->refdb, ref_name, &oid, NULL); + } + + if (*out == NULL) + error = -1; + +done: + git_buf_free(&ref_file); + return error; +} + +static int packed_map_entry( + struct packref **entry, + khiter_t *pos, + refdb_fs_backend *backend, + const char *ref_name) +{ + git_strmap *packfile_refs; + + if (packed_load(backend) < 0) + return -1; + + /* Look up on the packfile */ + packfile_refs = backend->refcache.packfile; + + *pos = git_strmap_lookup_index(packfile_refs, ref_name); + + if (!git_strmap_valid_index(packfile_refs, *pos)) { + giterr_set(GITERR_REFERENCE, "Reference '%s' not found", ref_name); + return GIT_ENOTFOUND; + } + + *entry = git_strmap_value_at(packfile_refs, *pos); + + return 0; +} + +static int packed_lookup( + git_reference **out, + refdb_fs_backend *backend, + const char *ref_name) +{ + struct packref *entry; + khiter_t pos; + int error = 0; + + if ((error = packed_map_entry(&entry, &pos, backend, ref_name)) < 0) + return error; + + if ((*out = git_reference__alloc(backend->refdb, ref_name, &entry->oid, NULL)) == NULL) + return -1; + + return 0; +} + +static int refdb_fs_backend__lookup( + git_reference **out, + git_refdb_backend *_backend, + const char *ref_name) +{ + refdb_fs_backend *backend; + int result; + + assert(_backend); + + backend = (refdb_fs_backend *)_backend; + + if ((result = loose_lookup(out, backend, ref_name)) == 0) + return 0; + + /* only try to lookup this reference on the packfile if it + * wasn't found on the loose refs; not if there was a critical error */ + if (result == GIT_ENOTFOUND) { + giterr_clear(); + result = packed_lookup(out, backend, ref_name); + } + + return result; +} + +struct dirent_list_data { + refdb_fs_backend *backend; + size_t repo_path_len; + unsigned int list_type:2; + + git_reference_foreach_cb callback; + void *callback_payload; + int callback_error; +}; + +static git_ref_t loose_guess_rtype(const git_buf *full_path) +{ + git_buf ref_file = GIT_BUF_INIT; + git_ref_t type; + + type = GIT_REF_INVALID; + + if (git_futils_readbuffer(&ref_file, full_path->ptr) == 0) { + if (git__prefixcmp((const char *)(ref_file.ptr), GIT_SYMREF) == 0) + type = GIT_REF_SYMBOLIC; + else + type = GIT_REF_OID; + } + + git_buf_free(&ref_file); + return type; +} + +static int _dirent_loose_listall(void *_data, git_buf *full_path) +{ + struct dirent_list_data *data = (struct dirent_list_data *)_data; + const char *file_path = full_path->ptr + data->repo_path_len; + + if (git_path_isdir(full_path->ptr) == true) + return git_path_direach(full_path, _dirent_loose_listall, _data); + + /* do not add twice a reference that exists already in the packfile */ + if (git_strmap_exists(data->backend->refcache.packfile, file_path)) + return 0; + + if (data->list_type != GIT_REF_LISTALL) { + if ((data->list_type & loose_guess_rtype(full_path)) == 0) + return 0; /* we are filtering out this reference */ + } + + /* Locked references aren't returned */ + if (!git__suffixcmp(file_path, GIT_FILELOCK_EXTENSION)) + return 0; + + if (data->callback(file_path, data->callback_payload)) + data->callback_error = GIT_EUSER; + + return data->callback_error; +} + +static int refdb_fs_backend__foreach( + git_refdb_backend *_backend, + unsigned int list_type, + git_reference_foreach_cb callback, + void *payload) +{ + refdb_fs_backend *backend; + int result; + struct dirent_list_data data; + git_buf refs_path = GIT_BUF_INIT; + const char *ref_name; + void *ref = NULL; + + GIT_UNUSED(ref); + + assert(_backend); + backend = (refdb_fs_backend *)_backend; + + if (packed_load(backend) < 0) + return -1; + + /* list all the packed references first */ + if (list_type & GIT_REF_OID) { + git_strmap_foreach(backend->refcache.packfile, ref_name, ref, { + if (callback(ref_name, payload)) + return GIT_EUSER; + }); + } + + /* now list the loose references, trying not to + * duplicate the ref names already in the packed-refs file */ + + data.repo_path_len = strlen(backend->path); + data.list_type = list_type; + data.backend = backend; + data.callback = callback; + data.callback_payload = payload; + data.callback_error = 0; + + if (git_buf_joinpath(&refs_path, backend->path, GIT_REFS_DIR) < 0) + return -1; + + result = git_path_direach(&refs_path, _dirent_loose_listall, &data); + + git_buf_free(&refs_path); + + return data.callback_error ? GIT_EUSER : result; +} + +static int loose_write(refdb_fs_backend *backend, const git_reference *ref) +{ + git_filebuf file = GIT_FILEBUF_INIT; + git_buf ref_path = GIT_BUF_INIT; + + /* Remove a possibly existing empty directory hierarchy + * which name would collide with the reference name + */ + if (git_futils_rmdir_r(ref->name, backend->path, + GIT_RMDIR_SKIP_NONEMPTY) < 0) + return -1; + + if (git_buf_joinpath(&ref_path, backend->path, ref->name) < 0) + return -1; + + if (git_filebuf_open(&file, ref_path.ptr, GIT_FILEBUF_FORCE) < 0) { + git_buf_free(&ref_path); + return -1; + } + + git_buf_free(&ref_path); + + if (ref->type == GIT_REF_OID) { + char oid[GIT_OID_HEXSZ + 1]; + + git_oid_fmt(oid, &ref->target.oid); + oid[GIT_OID_HEXSZ] = '\0'; + + git_filebuf_printf(&file, "%s\n", oid); + + } else if (ref->type == GIT_REF_SYMBOLIC) { + git_filebuf_printf(&file, GIT_SYMREF "%s\n", ref->target.symbolic); + } else { + assert(0); /* don't let this happen */ + } + + return git_filebuf_commit(&file, GIT_REFS_FILE_MODE); +} + +static int packed_sort(const void *a, const void *b) +{ + const struct packref *ref_a = (const struct packref *)a; + const struct packref *ref_b = (const struct packref *)b; + + return strcmp(ref_a->name, ref_b->name); +} + +/* + * 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(refdb_fs_backend *backend, struct packref *ref) +{ + git_object *object; + + if (ref->flags & GIT_PACKREF_HAS_PEEL) + return 0; + + /* + * Only applies to tags, i.e. references + * in the /refs/tags folder + */ + if (git__prefixcmp(ref->name, GIT_REFS_TAGS_DIR) != 0) + return 0; + + /* + * Find the tagged object in the repository + */ + if (git_object_lookup(&object, backend->repo, &ref->oid, GIT_OBJ_ANY) < 0) + return -1; + + /* + * If the tagged object is a Tag object, we need to resolve it; + * if the ref is actually a 'weak' ref, we don't need to resolve + * anything. + */ + if (git_object_type(object) == GIT_OBJ_TAG) { + git_tag *tag = (git_tag *)object; + + /* + * Find the object pointed at by this tag + */ + git_oid_cpy(&ref->peel, git_tag_target_id(tag)); + ref->flags |= GIT_PACKREF_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 + */ + } + + git_object_free(object); + return 0; +} + +/* + * Write a single reference into a packfile + */ +static int packed_write_ref(struct packref *ref, git_filebuf *file) +{ + char oid[GIT_OID_HEXSZ + 1]; + + git_oid_fmt(oid, &ref->oid); + oid[GIT_OID_HEXSZ] = 0; + + /* + * 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->flags & GIT_PACKREF_HAS_PEEL) { + char peel[GIT_OID_HEXSZ + 1]; + git_oid_fmt(peel, &ref->peel); + peel[GIT_OID_HEXSZ] = 0; + + if (git_filebuf_printf(file, "%s %s\n^%s\n", oid, ref->name, peel) < 0) + return -1; + } else { + if (git_filebuf_printf(file, "%s %s\n", oid, ref->name) < 0) + return -1; + } + + return 0; +} + +/* + * 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( + refdb_fs_backend *backend, + git_vector *packing_list) +{ + unsigned int i; + git_buf full_path = GIT_BUF_INIT; + int failed = 0; + + for (i = 0; i < packing_list->length; ++i) { + struct packref *ref = git_vector_get(packing_list, i); + + if ((ref->flags & GIT_PACKREF_WAS_LOOSE) == 0) + continue; + + if (git_buf_joinpath(&full_path, backend->path, ref->name) < 0) + return -1; /* critical; do not try to recover on oom */ + + if (git_path_exists(full_path.ptr) == true && p_unlink(full_path.ptr) < 0) { + if (failed) + continue; + + giterr_set(GITERR_REFERENCE, + "Failed to remove loose reference '%s' after packing: %s", + full_path.ptr, strerror(errno)); + + failed = 1; + } + + /* + * 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. + */ + } + + git_buf_free(&full_path); + return failed ? -1 : 0; +} + +/* + * Write all the contents in the in-memory packfile to disk. + */ +static int packed_write(refdb_fs_backend *backend) +{ + git_filebuf pack_file = GIT_FILEBUF_INIT; + unsigned int i; + git_buf pack_file_path = GIT_BUF_INIT; + git_vector packing_list; + unsigned int total_refs; + + assert(backend && backend->refcache.packfile); + + total_refs = + (unsigned int)git_strmap_num_entries(backend->refcache.packfile); + + if (git_vector_init(&packing_list, total_refs, packed_sort) < 0) + return -1; + + /* Load all the packfile into a vector */ + { + struct packref *reference; + + /* cannot fail: vector already has the right size */ + git_strmap_foreach_value(backend->refcache.packfile, reference, { + git_vector_insert(&packing_list, reference); + }); + } + + /* sort the vector so the entries appear sorted on the packfile */ + git_vector_sort(&packing_list); + + /* Now we can open the file! */ + if (git_buf_joinpath(&pack_file_path, + backend->path, GIT_PACKEDREFS_FILE) < 0) + goto cleanup_memory; + + if (git_filebuf_open(&pack_file, pack_file_path.ptr, 0) < 0) + goto cleanup_packfile; + + /* Packfiles have a header... apparently + * This is in fact not required, but we might as well print it + * just for kicks */ + if (git_filebuf_printf(&pack_file, "%s\n", GIT_PACKEDREFS_HEADER) < 0) + goto cleanup_packfile; + + for (i = 0; i < packing_list.length; ++i) { + struct packref *ref = (struct packref *)git_vector_get(&packing_list, i); + + if (packed_find_peel(backend, ref) < 0) + goto cleanup_packfile; + + if (packed_write_ref(ref, &pack_file) < 0) + goto cleanup_packfile; + } + + /* if we've written all the references properly, we can commit + * the packfile to make the changes effective */ + if (git_filebuf_commit(&pack_file, GIT_PACKEDREFS_FILE_MODE) < 0) + goto cleanup_memory; + + /* when and only when the packfile has been properly written, + * we can go ahead and remove the loose refs */ + if (packed_remove_loose(backend, &packing_list) < 0) + goto cleanup_memory; + + { + struct stat st; + if (p_stat(pack_file_path.ptr, &st) == 0) + backend->refcache.packfile_time = st.st_mtime; + } + + git_vector_free(&packing_list); + git_buf_free(&pack_file_path); + + /* we're good now */ + return 0; + +cleanup_packfile: + git_filebuf_cleanup(&pack_file); + +cleanup_memory: + git_vector_free(&packing_list); + git_buf_free(&pack_file_path); + + return -1; +} + +static int refdb_fs_backend__write( + git_refdb_backend *_backend, + const git_reference *ref) +{ + refdb_fs_backend *backend; + + assert(_backend); + backend = (refdb_fs_backend *)_backend; + + return loose_write(backend, ref); +} + +static int refdb_fs_backend__delete( + git_refdb_backend *_backend, + const git_reference *ref) +{ + refdb_fs_backend *backend; + git_repository *repo; + git_buf loose_path = GIT_BUF_INIT; + struct packref *pack_ref; + khiter_t pack_ref_pos; + int error = 0, pack_error; + bool loose_deleted; + + assert(_backend); + assert(ref); + + backend = (refdb_fs_backend *)_backend; + repo = backend->repo; + + /* If a loose reference exists, remove it from the filesystem */ + + if (git_buf_joinpath(&loose_path, repo->path_repository, ref->name) < 0) + return -1; + + if (git_path_isfile(loose_path.ptr)) { + error = p_unlink(loose_path.ptr); + loose_deleted = 1; + } + + git_buf_free(&loose_path); + + if (error != 0) + return error; + + /* If a packed reference exists, remove it from the packfile and repack */ + + if ((pack_error = packed_map_entry(&pack_ref, &pack_ref_pos, backend, ref->name)) == 0) { + git_strmap_delete_at(backend->refcache.packfile, pack_ref_pos); + git__free(pack_ref); + + error = packed_write(backend); + } + + if (pack_error == GIT_ENOTFOUND) + error = loose_deleted ? 0 : GIT_ENOTFOUND; + else + error = pack_error; + + return error; +} + +static int refdb_fs_backend__compress(git_refdb_backend *_backend) +{ + refdb_fs_backend *backend; + + assert(_backend); + backend = (refdb_fs_backend *)_backend; + + if (packed_load(backend) < 0 || /* load the existing packfile */ + packed_loadloose(backend) < 0 || /* add all the loose refs */ + packed_write(backend) < 0) /* write back to disk */ + return -1; + + return 0; +} + +static void refcache_free(git_refcache *refs) +{ + assert(refs); + + if (refs->packfile) { + struct packref *reference; + + git_strmap_foreach_value(refs->packfile, reference, { + git__free(reference); + }); + + git_strmap_free(refs->packfile); + } +} + +static void refdb_fs_backend__free(git_refdb_backend *_backend) +{ + refdb_fs_backend *backend; + + assert(_backend); + backend = (refdb_fs_backend *)_backend; + + refcache_free(&backend->refcache); + git__free(backend); +} + +int git_refdb_backend_fs( + git_refdb_backend **backend_out, + git_repository *repository, + git_refdb *refdb) +{ + refdb_fs_backend *backend; + + backend = git__calloc(1, sizeof(refdb_fs_backend)); + GITERR_CHECK_ALLOC(backend); + + backend->repo = repository; + backend->path = repository->path_repository; + backend->refdb = refdb; + + backend->parent.exists = &refdb_fs_backend__exists; + backend->parent.lookup = &refdb_fs_backend__lookup; + backend->parent.foreach = &refdb_fs_backend__foreach; + backend->parent.write = &refdb_fs_backend__write; + backend->parent.delete = &refdb_fs_backend__delete; + backend->parent.compress = &refdb_fs_backend__compress; + backend->parent.free = &refdb_fs_backend__free; + + *backend_out = (git_refdb_backend *)backend; + return 0; +} diff --git a/src/refdb_fs.h b/src/refdb_fs.h new file mode 100644 index 000000000..79e296833 --- /dev/null +++ b/src/refdb_fs.h @@ -0,0 +1,15 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_refdb_fs_h__ +#define INCLUDE_refdb_fs_h__ + +typedef struct { + git_strmap *packfile; + time_t packfile_time; +} git_refcache; + +#endif diff --git a/src/refs.c b/src/refs.c index dd3dd64b1..80307c96d 100644 --- a/src/refs.c +++ b/src/refs.c @@ -11,11 +11,15 @@ #include "fileops.h" #include "pack.h" #include "reflog.h" +#include "refdb.h" #include <git2/tag.h> #include <git2/object.h> #include <git2/oid.h> #include <git2/branch.h> +#include <git2/refs.h> +#include <git2/refdb.h> +#include <git2/refdb_backend.h> GIT__USE_STRMAP; @@ -27,786 +31,50 @@ enum { GIT_PACKREF_WAS_LOOSE = 2 }; -struct packref { - git_oid oid; - git_oid peel; - char flags; - char name[GIT_FLEX_ARRAY]; -}; - -static int reference_read( - git_buf *file_content, - time_t *mtime, - const char *repo_path, - const char *ref_name, - int *updated); - -/* loose refs */ -static int loose_parse_symbolic(git_reference *ref, git_buf *file_content); -static int loose_parse_oid(git_oid *ref, git_buf *file_content); -static int loose_lookup(git_reference *ref); -static int loose_lookup_to_packfile(struct packref **ref_out, - git_repository *repo, const char *name); -static int loose_write(git_reference *ref); - -/* packed refs */ -static int packed_parse_peel(struct packref *tag_ref, - const char **buffer_out, const char *buffer_end); -static int packed_parse_oid(struct packref **ref_out, - 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(struct packref *ref, git_filebuf *file); -static int packed_find_peel(git_repository *repo, struct packref *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_lookup(git_reference *ref); -static int packed_write(git_repository *repo); - -/* internal helpers */ -static int reference_path_available(git_repository *repo, - const char *ref, const char *old_ref); -static int reference_delete(git_reference *ref); -static int reference_lookup(git_reference *ref); - -void git_reference_free(git_reference *reference) -{ - if (reference == NULL) - return; - - git__free(reference->name); - reference->name = NULL; - - if (reference->flags & GIT_REF_SYMBOLIC) { - git__free(reference->target.symbolic); - reference->target.symbolic = NULL; - } - - git__free(reference); -} - -static int reference_alloc( - git_reference **ref_out, - git_repository *repo, - const char *name) -{ - git_reference *reference = NULL; - - assert(ref_out && repo && name); - - reference = git__malloc(sizeof(git_reference)); - GITERR_CHECK_ALLOC(reference); - - memset(reference, 0x0, sizeof(git_reference)); - reference->owner = repo; - - reference->name = git__strdup(name); - GITERR_CHECK_ALLOC(reference->name); - - *ref_out = reference; - return 0; -} - -static int reference_read( - git_buf *file_content, - time_t *mtime, - const char *repo_path, - const char *ref_name, - int *updated) -{ - git_buf path = GIT_BUF_INIT; - int result; - - assert(file_content && repo_path && ref_name); - - /* Determine the full path of the file */ - if (git_buf_joinpath(&path, repo_path, ref_name) < 0) - return -1; - - result = git_futils_readbuffer_updated( - file_content, path.ptr, mtime, NULL, updated); - git_buf_free(&path); - - return result; -} - -static int loose_parse_symbolic(git_reference *ref, git_buf *file_content) -{ - const unsigned int header_len = (unsigned int)strlen(GIT_SYMREF); - const char *refname_start; - - refname_start = (const char *)file_content->ptr; - - if (git_buf_len(file_content) < header_len + 1) { - giterr_set(GITERR_REFERENCE, "Corrupted loose reference file"); - return -1; - } - - /* - * Assume we have already checked for the header - * before calling this function - */ - refname_start += header_len; - - ref->target.symbolic = git__strdup(refname_start); - GITERR_CHECK_ALLOC(ref->target.symbolic); - - return 0; -} - -static int loose_parse_oid(git_oid *oid, git_buf *file_content) -{ - size_t len; - const char *str; - - len = git_buf_len(file_content); - if (len < GIT_OID_HEXSZ) - goto corrupted; - - /* str is guranteed to be zero-terminated */ - str = git_buf_cstr(file_content); - - /* we need to get 40 OID characters from the file */ - if (git_oid_fromstr(oid, git_buf_cstr(file_content)) < 0) - goto corrupted; - - /* If the file is longer than 40 chars, the 41st must be a space */ - str += GIT_OID_HEXSZ; - if (*str == '\0' || git__isspace(*str)) - return 0; - -corrupted: - giterr_set(GITERR_REFERENCE, "Corrupted loose reference file"); - return -1; -} - -static git_ref_t loose_guess_rtype(const git_buf *full_path) -{ - git_buf ref_file = GIT_BUF_INIT; - git_ref_t type; - - type = GIT_REF_INVALID; - - if (git_futils_readbuffer(&ref_file, full_path->ptr) == 0) { - if (git__prefixcmp((const char *)(ref_file.ptr), GIT_SYMREF) == 0) - type = GIT_REF_SYMBOLIC; - else - type = GIT_REF_OID; - } - - git_buf_free(&ref_file); - return type; -} - -static int loose_lookup(git_reference *ref) -{ - int result, updated; - git_buf ref_file = GIT_BUF_INIT; - - result = reference_read(&ref_file, &ref->mtime, - ref->owner->path_repository, ref->name, &updated); - - if (result < 0) - return result; - - if (!updated) - return 0; - - if (ref->flags & GIT_REF_SYMBOLIC) { - git__free(ref->target.symbolic); - ref->target.symbolic = NULL; - } - - ref->flags = 0; - - if (git__prefixcmp((const char *)(ref_file.ptr), GIT_SYMREF) == 0) { - ref->flags |= GIT_REF_SYMBOLIC; - git_buf_rtrim(&ref_file); - result = loose_parse_symbolic(ref, &ref_file); - } else { - ref->flags |= GIT_REF_OID; - result = loose_parse_oid(&ref->target.oid, &ref_file); - } - - git_buf_free(&ref_file); - return result; -} - -static int loose_lookup_to_packfile( - struct packref **ref_out, - git_repository *repo, - const char *name) -{ - git_buf ref_file = GIT_BUF_INIT; - struct packref *ref = NULL; - size_t name_len; - - *ref_out = NULL; - - if (reference_read(&ref_file, NULL, repo->path_repository, name, NULL) < 0) - return -1; - - git_buf_rtrim(&ref_file); - - name_len = strlen(name); - ref = git__malloc(sizeof(struct packref) + name_len + 1); - GITERR_CHECK_ALLOC(ref); - - memcpy(ref->name, name, name_len); - ref->name[name_len] = 0; - - if (loose_parse_oid(&ref->oid, &ref_file) < 0) { - git_buf_free(&ref_file); - git__free(ref); - return -1; - } - - ref->flags = GIT_PACKREF_WAS_LOOSE; - - *ref_out = ref; - git_buf_free(&ref_file); - return 0; -} - -static int loose_write(git_reference *ref) -{ - git_filebuf file = GIT_FILEBUF_INIT; - git_buf ref_path = GIT_BUF_INIT; - struct stat st; - - /* Remove a possibly existing empty directory hierarchy - * which name would collide with the reference name - */ - if (git_futils_rmdir_r(ref->name, ref->owner->path_repository, - GIT_RMDIR_SKIP_NONEMPTY) < 0) - return -1; - - if (git_buf_joinpath(&ref_path, ref->owner->path_repository, ref->name) < 0) - return -1; - - if (git_filebuf_open(&file, ref_path.ptr, GIT_FILEBUF_FORCE) < 0) { - git_buf_free(&ref_path); - return -1; - } - - git_buf_free(&ref_path); - - if (ref->flags & GIT_REF_OID) { - char oid[GIT_OID_HEXSZ + 1]; - - git_oid_fmt(oid, &ref->target.oid); - oid[GIT_OID_HEXSZ] = '\0'; - - git_filebuf_printf(&file, "%s\n", oid); - - } else if (ref->flags & GIT_REF_SYMBOLIC) { - git_filebuf_printf(&file, GIT_SYMREF "%s\n", ref->target.symbolic); - } else { - assert(0); /* don't let this happen */ - } - - if (p_stat(ref_path.ptr, &st) == 0) - ref->mtime = st.st_mtime; - - return git_filebuf_commit(&file, GIT_REFS_FILE_MODE); -} - -static int packed_parse_peel( - struct packref *tag_ref, - const char **buffer_out, - const char *buffer_end) -{ - const char *buffer = *buffer_out + 1; - - assert(buffer[-1] == '^'); - - /* Ensure it's not the first entry of the file */ - if (tag_ref == NULL) - goto corrupt; - - /* Ensure reference is a tag */ - if (git__prefixcmp(tag_ref->name, GIT_REFS_TAGS_DIR) != 0) - goto corrupt; - - if (buffer + GIT_OID_HEXSZ > buffer_end) - goto corrupt; - - /* Is this a valid object id? */ - if (git_oid_fromstr(&tag_ref->peel, buffer) < 0) - goto corrupt; - - buffer = buffer + GIT_OID_HEXSZ; - if (*buffer == '\r') - buffer++; - - if (buffer != buffer_end) { - if (*buffer == '\n') - buffer++; - else - goto corrupt; - } - - *buffer_out = buffer; - return 0; - -corrupt: - giterr_set(GITERR_REFERENCE, "The packed references file is corrupted"); - return -1; -} - -static int packed_parse_oid( - struct packref **ref_out, - const char **buffer_out, - const char *buffer_end) -{ - struct packref *ref = NULL; - - const char *buffer = *buffer_out; - const char *refname_begin, *refname_end; - - size_t refname_len; - git_oid id; - - refname_begin = (buffer + GIT_OID_HEXSZ + 1); - if (refname_begin >= buffer_end || refname_begin[-1] != ' ') - goto corrupt; - - /* Is this a valid object id? */ - if (git_oid_fromstr(&id, buffer) < 0) - goto corrupt; - - refname_end = memchr(refname_begin, '\n', buffer_end - refname_begin); - if (refname_end == NULL) - refname_end = buffer_end; - - if (refname_end[-1] == '\r') - refname_end--; - refname_len = refname_end - refname_begin; - - ref = git__malloc(sizeof(struct packref) + refname_len + 1); - GITERR_CHECK_ALLOC(ref); - - memcpy(ref->name, refname_begin, refname_len); - ref->name[refname_len] = 0; - - git_oid_cpy(&ref->oid, &id); - - ref->flags = 0; - - *ref_out = ref; - *buffer_out = refname_end + 1; - - return 0; - -corrupt: - git__free(ref); - giterr_set(GITERR_REFERENCE, "The packed references file is corrupted"); - return -1; -} - -static int packed_load(git_repository *repo) -{ - int result, updated; - git_buf packfile = GIT_BUF_INIT; - const char *buffer_start, *buffer_end; - git_refcache *ref_cache = &repo->references; - - /* First we make sure we have allocated the hash table */ - if (ref_cache->packfile == NULL) { - ref_cache->packfile = git_strmap_alloc(); - GITERR_CHECK_ALLOC(ref_cache->packfile); - } - - result = reference_read(&packfile, &ref_cache->packfile_time, - repo->path_repository, GIT_PACKEDREFS_FILE, &updated); - - /* - * If we couldn't find the file, we need to clear the table and - * return. On any other error, we return that error. If everything - * went fine and the file wasn't updated, then there's nothing new - * for us here, so just return. Anything else means we need to - * refresh the packed refs. - */ - if (result == GIT_ENOTFOUND) { - git_strmap_clear(ref_cache->packfile); - return 0; - } - - if (result < 0) - return -1; - - if (!updated) - return 0; - - /* - * At this point, we want to refresh the packed refs. We already - * have the contents in our buffer. - */ - git_strmap_clear(ref_cache->packfile); - - buffer_start = (const char *)packfile.ptr; - buffer_end = (const char *)(buffer_start) + packfile.size; - - while (buffer_start < buffer_end && buffer_start[0] == '#') { - buffer_start = strchr(buffer_start, '\n'); - if (buffer_start == NULL) - goto parse_failed; - - buffer_start++; - } - - while (buffer_start < buffer_end) { - int err; - struct packref *ref = NULL; - - if (packed_parse_oid(&ref, &buffer_start, buffer_end) < 0) - goto parse_failed; - - if (buffer_start[0] == '^') { - if (packed_parse_peel(ref, &buffer_start, buffer_end) < 0) - goto parse_failed; - } - - git_strmap_insert(ref_cache->packfile, ref->name, ref, err); - if (err < 0) - goto parse_failed; - } - - git_buf_free(&packfile); - return 0; - -parse_failed: - git_strmap_free(ref_cache->packfile); - ref_cache->packfile = NULL; - git_buf_free(&packfile); - return -1; -} - - -struct dirent_list_data { - git_repository *repo; - size_t repo_path_len; - unsigned int list_flags; - - int (*callback)(const char *, void *); - void *callback_payload; - int callback_error; -}; - -static int _dirent_loose_listall(void *_data, git_buf *full_path) -{ - struct dirent_list_data *data = (struct dirent_list_data *)_data; - const char *file_path = full_path->ptr + data->repo_path_len; - - if (git_path_isdir(full_path->ptr) == true) - return git_path_direach(full_path, _dirent_loose_listall, _data); - - /* do not add twice a reference that exists already in the packfile */ - if ((data->list_flags & GIT_REF_PACKED) != 0 && - git_strmap_exists(data->repo->references.packfile, file_path)) - return 0; - - if (data->list_flags != GIT_REF_LISTALL) { - if ((data->list_flags & loose_guess_rtype(full_path)) == 0) - return 0; /* we are filtering out this reference */ - } - - /* Locked references aren't returned */ - if (!git__suffixcmp(file_path, GIT_FILELOCK_EXTENSION)) - return 0; - - if (data->callback(file_path, data->callback_payload)) - data->callback_error = GIT_EUSER; - - return data->callback_error; -} - -static int _dirent_loose_load(void *data, git_buf *full_path) -{ - git_repository *repository = (git_repository *)data; - void *old_ref = NULL; - struct packref *ref; - const char *file_path; - int err; - - if (git_path_isdir(full_path->ptr) == true) - return git_path_direach(full_path, _dirent_loose_load, repository); - - file_path = full_path->ptr + strlen(repository->path_repository); - - if (loose_lookup_to_packfile(&ref, repository, file_path) < 0) - return -1; - - git_strmap_insert2( - repository->references.packfile, ref->name, ref, old_ref, err); - if (err < 0) { - git__free(ref); - return -1; - } - - git__free(old_ref); - return 0; -} - -/* - * 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) -{ - git_buf refs_path = GIT_BUF_INIT; - int result; - - /* the packfile must have been previously loaded! */ - assert(repository->references.packfile); - - if (git_buf_joinpath(&refs_path, repository->path_repository, GIT_REFS_DIR) < 0) - return -1; - - /* - * Load all the loose files from disk into the Packfile table. - * This will overwrite any old packed entries with their - * updated loose versions - */ - result = git_path_direach(&refs_path, _dirent_loose_load, repository); - git_buf_free(&refs_path); - - return result; -} - -/* - * Write a single reference into a packfile - */ -static int packed_write_ref(struct packref *ref, git_filebuf *file) +git_reference *git_reference__alloc( + git_refdb *refdb, + const char *name, + const git_oid *oid, + const char *symbolic) { - char oid[GIT_OID_HEXSZ + 1]; - - git_oid_fmt(oid, &ref->oid); - oid[GIT_OID_HEXSZ] = 0; + git_reference *ref; - /* - * 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->flags & GIT_PACKREF_HAS_PEEL) { - char peel[GIT_OID_HEXSZ + 1]; - git_oid_fmt(peel, &ref->peel); - peel[GIT_OID_HEXSZ] = 0; + assert(refdb && name && ((oid && !symbolic) || (!oid && symbolic))); + + if ((ref = git__calloc(1, sizeof(git_reference) + strlen(name) + 1)) == NULL) + return NULL; - if (git_filebuf_printf(file, "%s %s\n^%s\n", oid, ref->name, peel) < 0) - return -1; + if (oid) { + ref->type = GIT_REF_OID; + git_oid_cpy(&ref->target.oid, oid); } else { - if (git_filebuf_printf(file, "%s %s\n", oid, ref->name) < 0) - return -1; - } - - return 0; -} - -/* - * 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(git_repository *repo, struct packref *ref) -{ - git_object *object; - - if (ref->flags & GIT_PACKREF_HAS_PEEL) - return 0; - - /* - * Only applies to tags, i.e. references - * in the /refs/tags folder - */ - if (git__prefixcmp(ref->name, GIT_REFS_TAGS_DIR) != 0) - return 0; - - /* - * Find the tagged object in the repository - */ - if (git_object_lookup(&object, repo, &ref->oid, GIT_OBJ_ANY) < 0) - return -1; - - /* - * If the tagged object is a Tag object, we need to resolve it; - * if the ref is actually a 'weak' ref, we don't need to resolve - * anything. - */ - if (git_object_type(object) == GIT_OBJ_TAG) { - git_tag *tag = (git_tag *)object; - - /* - * Find the object pointed at by this tag - */ - git_oid_cpy(&ref->peel, git_tag_target_id(tag)); - ref->flags |= GIT_PACKREF_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 - */ - } - - git_object_free(object); - return 0; -} - -/* - * 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; - git_buf full_path = GIT_BUF_INIT; - int failed = 0; + ref->type = GIT_REF_SYMBOLIC; - for (i = 0; i < packing_list->length; ++i) { - struct packref *ref = git_vector_get(packing_list, i); - - if ((ref->flags & GIT_PACKREF_WAS_LOOSE) == 0) - continue; - - if (git_buf_joinpath(&full_path, repo->path_repository, ref->name) < 0) - return -1; /* critical; do not try to recover on oom */ - - if (git_path_exists(full_path.ptr) == true && p_unlink(full_path.ptr) < 0) { - if (failed) - continue; - - giterr_set(GITERR_REFERENCE, - "Failed to remove loose reference '%s' after packing: %s", - full_path.ptr, strerror(errno)); - - failed = 1; - } - - /* - * 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. - */ + if ((ref->target.symbolic = git__strdup(symbolic)) == NULL) + return NULL; } - - git_buf_free(&full_path); - return failed ? -1 : 0; + + ref->db = refdb; + strcpy(ref->name, name); + + return ref; } -static int packed_sort(const void *a, const void *b) -{ - const struct packref *ref_a = (const struct packref *)a; - const struct packref *ref_b = (const struct packref *)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) +void git_reference_free(git_reference *reference) { - git_filebuf pack_file = GIT_FILEBUF_INIT; - unsigned int i; - git_buf pack_file_path = GIT_BUF_INIT; - git_vector packing_list; - unsigned int total_refs; - - assert(repo && repo->references.packfile); - - total_refs = - (unsigned int)git_strmap_num_entries(repo->references.packfile); - - if (git_vector_init(&packing_list, total_refs, packed_sort) < 0) - return -1; - - /* Load all the packfile into a vector */ - { - struct packref *reference; - - /* cannot fail: vector already has the right size */ - git_strmap_foreach_value(repo->references.packfile, reference, { - git_vector_insert(&packing_list, reference); - }); - } - - /* sort the vector so the entries appear sorted on the packfile */ - git_vector_sort(&packing_list); - - /* Now we can open the file! */ - if (git_buf_joinpath(&pack_file_path, repo->path_repository, GIT_PACKEDREFS_FILE) < 0) - goto cleanup_memory; - - if (git_filebuf_open(&pack_file, pack_file_path.ptr, 0) < 0) - goto cleanup_packfile; - - /* Packfiles have a header... apparently - * This is in fact not required, but we might as well print it - * just for kicks */ - if (git_filebuf_printf(&pack_file, "%s\n", GIT_PACKEDREFS_HEADER) < 0) - goto cleanup_packfile; - - for (i = 0; i < packing_list.length; ++i) { - struct packref *ref = (struct packref *)git_vector_get(&packing_list, i); - - if (packed_find_peel(repo, ref) < 0) - goto cleanup_packfile; + if (reference == NULL) + return; - if (packed_write_ref(ref, &pack_file) < 0) - goto cleanup_packfile; + if (reference->type == GIT_REF_SYMBOLIC) { + git__free(reference->target.symbolic); + reference->target.symbolic = NULL; } + + reference->db = NULL; + reference->type = GIT_REF_INVALID; - /* if we've written all the references properly, we can commit - * the packfile to make the changes effective */ - if (git_filebuf_commit(&pack_file, GIT_PACKEDREFS_FILE_MODE) < 0) - goto cleanup_memory; - - /* when and only when the packfile has been properly written, - * we can go ahead and remove the loose refs */ - if (packed_remove_loose(repo, &packing_list) < 0) - goto cleanup_memory; - - { - struct stat st; - if (p_stat(pack_file_path.ptr, &st) == 0) - repo->references.packfile_time = st.st_mtime; - } - - git_vector_free(&packing_list); - git_buf_free(&pack_file_path); - - /* we're good now */ - return 0; - -cleanup_packfile: - git_filebuf_cleanup(&pack_file); - -cleanup_memory: - git_vector_free(&packing_list); - git_buf_free(&pack_file_path); - - return -1; + git__free(reference); } struct reference_available_t { @@ -863,28 +131,6 @@ static int reference_path_available( return 0; } -static int reference_exists(int *exists, git_repository *repo, const char *ref_name) -{ - git_buf ref_path = GIT_BUF_INIT; - - if (packed_load(repo) < 0) - return -1; - - if (git_buf_joinpath(&ref_path, repo->path_repository, ref_name) < 0) - return -1; - - if (git_path_isfile(ref_path.ptr) == true || - git_strmap_exists(repo->references.packfile, ref_path.ptr)) - { - *exists = 1; - } else { - *exists = 0; - } - - git_buf_free(&ref_path); - return 0; -} - /* * Check if a reference could be written to disk, based on: * @@ -900,6 +146,11 @@ static int reference_can_write( const char *previous_name, int force) { + git_refdb *refdb; + + if (git_repository_refdb__weakptr(&refdb, repo) < 0) + return -1; + /* see if the reference shares a path with an existing reference; * if a path is shared, we cannot create the reference, even when forcing */ if (reference_path_available(repo, refname, previous_name) < 0) @@ -910,7 +161,7 @@ static int reference_can_write( if (!force) { int exists; - if (reference_exists(&exists, repo, refname) < 0) + if (git_refdb_exists(&exists, refdb, refname) < 0) return -1; /* We cannot proceed if the reference already exists and we're not forcing @@ -937,139 +188,9 @@ static int reference_can_write( return 0; } - -static int packed_lookup(git_reference *ref) -{ - struct packref *pack_ref = NULL; - git_strmap *packfile_refs; - khiter_t pos; - - if (packed_load(ref->owner) < 0) - return -1; - - /* maybe the packfile hasn't changed at all, so we don't - * have to re-lookup the reference */ - if ((ref->flags & GIT_REF_PACKED) && - ref->mtime == ref->owner->references.packfile_time) - return 0; - - if (ref->flags & GIT_REF_SYMBOLIC) { - git__free(ref->target.symbolic); - ref->target.symbolic = NULL; - } - - /* Look up on the packfile */ - packfile_refs = ref->owner->references.packfile; - pos = git_strmap_lookup_index(packfile_refs, ref->name); - if (!git_strmap_valid_index(packfile_refs, pos)) { - giterr_set(GITERR_REFERENCE, "Reference '%s' not found", ref->name); - return GIT_ENOTFOUND; - } - - pack_ref = git_strmap_value_at(packfile_refs, pos); - - ref->flags = GIT_REF_OID | GIT_REF_PACKED; - ref->mtime = ref->owner->references.packfile_time; - git_oid_cpy(&ref->target.oid, &pack_ref->oid); - - return 0; -} - -static int reference_lookup(git_reference *ref) -{ - int result; - - result = loose_lookup(ref); - if (result == 0) - return 0; - - /* only try to lookup this reference on the packfile if it - * wasn't found on the loose refs; not if there was a critical error */ - if (result == GIT_ENOTFOUND) { - giterr_clear(); - result = packed_lookup(ref); - if (result == 0) - return 0; - } - - /* unexpected error; free the reference */ - git_reference_free(ref); - return result; -} - -/* - * Delete a reference. - * This is an internal method; the reference is removed - * from disk or the packfile, but the pointer is not freed - */ -static int reference_delete(git_reference *ref) -{ - int result; - - assert(ref); - - /* If the reference is packed, this is an expensive operation. - * We need to reload the packfile, remove the reference from the - * packing list, and repack */ - if (ref->flags & GIT_REF_PACKED) { - git_strmap *packfile_refs; - struct packref *packref; - khiter_t pos; - - /* load the existing packfile */ - if (packed_load(ref->owner) < 0) - return -1; - - packfile_refs = ref->owner->references.packfile; - pos = git_strmap_lookup_index(packfile_refs, ref->name); - if (!git_strmap_valid_index(packfile_refs, pos)) { - giterr_set(GITERR_REFERENCE, - "Reference %s stopped existing in the packfile", ref->name); - return -1; - } - - packref = git_strmap_value_at(packfile_refs, pos); - git_strmap_delete_at(packfile_refs, pos); - - git__free(packref); - if (packed_write(ref->owner) < 0) - return -1; - - /* If the reference is loose, we can just remove the reference - * from the filesystem */ - } else { - git_reference *ref_in_pack; - git_buf full_path = GIT_BUF_INIT; - - if (git_buf_joinpath(&full_path, ref->owner->path_repository, ref->name) < 0) - return -1; - - result = p_unlink(full_path.ptr); - git_buf_free(&full_path); /* done with path at this point */ - - if (result < 0) { - giterr_set(GITERR_OS, "Failed to unlink '%s'", full_path.ptr); - return -1; - } - - /* When deleting a loose reference, we have to ensure that an older - * packed version of it doesn't exist */ - if (git_reference_lookup(&ref_in_pack, ref->owner, ref->name) == 0) { - assert((ref_in_pack->flags & GIT_REF_PACKED) != 0); - return git_reference_delete(ref_in_pack); - } - - giterr_clear(); - } - - return 0; -} - int git_reference_delete(git_reference *ref) { - int result = reference_delete(ref); - git_reference_free(ref); - return result; + return git_refdb_delete(ref->db, ref); } int git_reference_lookup(git_reference **ref_out, @@ -1098,8 +219,11 @@ int git_reference_lookup_resolved( const char *name, int max_nesting) { - git_reference *scan; - int result, nesting; + char scan_name[GIT_REFNAME_MAX]; + git_ref_t scan_type; + int error = 0, nesting; + git_reference *ref = NULL; + git_refdb *refdb; assert(ref_out && repo && name); @@ -1109,48 +233,39 @@ int git_reference_lookup_resolved( max_nesting = MAX_NESTING_LEVEL; else if (max_nesting < 0) max_nesting = DEFAULT_NESTING_LEVEL; + + strncpy(scan_name, name, GIT_REFNAME_MAX); + scan_type = GIT_REF_SYMBOLIC; + + if ((error = git_repository_refdb__weakptr(&refdb, repo)) < 0) + return -1; - scan = git__calloc(1, sizeof(git_reference)); - GITERR_CHECK_ALLOC(scan); - - scan->name = git__calloc(GIT_REFNAME_MAX + 1, sizeof(char)); - GITERR_CHECK_ALLOC(scan->name); - - if ((result = git_reference__normalize_name_lax( - scan->name, - GIT_REFNAME_MAX, - name)) < 0) { - git_reference_free(scan); - return result; - } - - scan->target.symbolic = git__strdup(scan->name); - GITERR_CHECK_ALLOC(scan->target.symbolic); - - scan->owner = repo; - scan->flags = GIT_REF_SYMBOLIC; + if ((error = git_reference__normalize_name_lax(scan_name, GIT_REFNAME_MAX, name)) < 0) + return error; for (nesting = max_nesting; - nesting >= 0 && (scan->flags & GIT_REF_SYMBOLIC) != 0; + nesting >= 0 && scan_type == GIT_REF_SYMBOLIC; nesting--) { - if (nesting != max_nesting) - strncpy(scan->name, scan->target.symbolic, GIT_REFNAME_MAX); - - scan->mtime = 0; + if (nesting != max_nesting) { + strncpy(scan_name, ref->target.symbolic, GIT_REFNAME_MAX); + git_reference_free(ref); + } - if ((result = reference_lookup(scan)) < 0) - return result; /* lookup git_reference_free on scan already */ + if ((error = git_refdb_lookup(&ref, refdb, scan_name)) < 0) + return error; + + scan_type = ref->type; } - if ((scan->flags & GIT_REF_OID) == 0 && max_nesting != 0) { + if (scan_type != GIT_REF_OID && max_nesting != 0) { giterr_set(GITERR_REFERENCE, "Cannot resolve reference (>%u levels deep)", max_nesting); - git_reference_free(scan); + git_reference_free(ref); return -1; } - *ref_out = scan; + *ref_out = ref; return 0; } @@ -1160,20 +275,7 @@ int git_reference_lookup_resolved( git_ref_t git_reference_type(const git_reference *ref) { assert(ref); - - if (ref->flags & GIT_REF_OID) - return GIT_REF_OID; - - if (ref->flags & GIT_REF_SYMBOLIC) - return GIT_REF_SYMBOLIC; - - return GIT_REF_INVALID; -} - -int git_reference_is_packed(git_reference *ref) -{ - assert(ref); - return !!(ref->flags & GIT_REF_PACKED); + return ref->type; } const char *git_reference_name(const git_reference *ref) @@ -1185,14 +287,14 @@ const char *git_reference_name(const git_reference *ref) git_repository *git_reference_owner(const git_reference *ref) { assert(ref); - return ref->owner; + return ref->db->repo; } const git_oid *git_reference_target(const git_reference *ref) { assert(ref); - if ((ref->flags & GIT_REF_OID) == 0) + if (ref->type != GIT_REF_OID) return NULL; return &ref->target.oid; @@ -1202,48 +304,45 @@ const char *git_reference_symbolic_target(const git_reference *ref) { assert(ref); - if ((ref->flags & GIT_REF_SYMBOLIC) == 0) + if (ref->type != GIT_REF_SYMBOLIC) return NULL; return ref->target.symbolic; } -int git_reference_symbolic_create( +static int reference__create( git_reference **ref_out, git_repository *repo, const char *name, - const char *target, + const git_oid *oid, + const char *symbolic, int force) { char normalized[GIT_REFNAME_MAX]; + git_refdb *refdb; git_reference *ref = NULL; - int error; - - if ((error = git_reference__normalize_name_lax( - normalized, - sizeof(normalized), - name)) < 0) - return error; - - if ((error = reference_can_write(repo, normalized, NULL, force)) < 0) + int error = 0; + + if (ref_out) + *ref_out = NULL; + + if ((error = git_reference__normalize_name_lax(normalized, sizeof(normalized), name)) < 0 || + (error = reference_can_write(repo, normalized, NULL, force)) < 0 || + (error = git_repository_refdb__weakptr(&refdb, repo)) < 0) return error; - - if (reference_alloc(&ref, repo, normalized) < 0) + + if ((ref = git_reference__alloc(refdb, name, oid, symbolic)) == NULL) return -1; - ref->flags |= GIT_REF_SYMBOLIC; - - /* set the target; this will normalize the name automatically - * and write the reference on disk */ - if (git_reference_symbolic_set_target(ref, target) < 0) { + if ((error = git_refdb_write(refdb, ref)) < 0) { git_reference_free(ref); - return -1; + return error; } - if (ref_out == NULL) { + + if (ref_out == NULL) git_reference_free(ref); - } else { + else *ref_out = ref; - } return 0; } @@ -1252,232 +351,157 @@ int git_reference_create( git_reference **ref_out, git_repository *repo, const char *name, - const git_oid *id, + const git_oid *oid, int force) { - int error; - git_reference *ref = NULL; - char normalized[GIT_REFNAME_MAX]; - - if ((error = git_reference__normalize_name_lax( - normalized, - sizeof(normalized), - name)) < 0) - return error; + git_odb *odb; + int error = 0; - if ((error = reference_can_write(repo, normalized, NULL, force)) < 0) + assert(repo && name && oid); + + /* Sanity check the reference being created - target must exist. */ + if ((error = git_repository_odb__weakptr(&odb, repo)) < 0) return error; - - if (reference_alloc(&ref, repo, name) < 0) - return -1; - - ref->flags |= GIT_REF_OID; - - /* set the oid; this will write the reference on disk */ - if (git_reference_set_target(ref, id) < 0) { - git_reference_free(ref); + + if (!git_odb_exists(odb, oid)) { + giterr_set(GITERR_REFERENCE, + "Target OID for the reference doesn't exist on the repository"); return -1; } - - if (ref_out == NULL) { - git_reference_free(ref); - } else { - *ref_out = ref; - } - - return 0; + + return reference__create(ref_out, repo, name, oid, NULL, force); } -/* - * Change the OID target of a reference. - * - * For both loose and packed references, just change - * the oid in memory and (over)write the file in disk. - * - * We do not repack packed references because of performance - * reasons. - */ -int git_reference_set_target(git_reference *ref, const git_oid *id) -{ - git_odb *odb = NULL; - if ((ref->flags & GIT_REF_OID) == 0) { - giterr_set(GITERR_REFERENCE, "Cannot set OID on symbolic reference"); - return -1; - } +int git_reference_symbolic_create( + git_reference **ref_out, + git_repository *repo, + const char *name, + const char *target, + int force) +{ + char normalized[GIT_REFNAME_MAX]; + int error = 0; - assert(ref->owner); + assert(repo && name && target); + + if ((error = git_reference__normalize_name_lax( + normalized, sizeof(normalized), target)) < 0) + return error; - if (git_repository_odb__weakptr(&odb, ref->owner) < 0) - return -1; + return reference__create(ref_out, repo, name, NULL, normalized, force); +} - /* Don't let the user create references to OIDs that - * don't exist in the ODB */ - if (!git_odb_exists(odb, id)) { - giterr_set(GITERR_REFERENCE, - "Target OID for the reference doesn't exist on the repository"); +int git_reference_set_target( + git_reference **out, + git_reference *ref, + const git_oid *id) +{ + assert(out && ref && id); + + if (ref->type != GIT_REF_OID) { + giterr_set(GITERR_REFERENCE, "Cannot set OID on symbolic reference"); return -1; } - /* Update the OID value on `ref` */ - git_oid_cpy(&ref->target.oid, id); - - /* Write back to disk */ - return loose_write(ref); + return git_reference_create(out, ref->db->repo, ref->name, id, 1); } -/* - * 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_symbolic_set_target(git_reference *ref, const char *target) +int git_reference_symbolic_set_target( + git_reference **out, + git_reference *ref, + const char *target) { - int error; - char normalized[GIT_REFNAME_MAX]; - - if ((ref->flags & GIT_REF_SYMBOLIC) == 0) { + assert(out && ref && target); + + if (ref->type != GIT_REF_SYMBOLIC) { giterr_set(GITERR_REFERENCE, "Cannot set symbolic target on a direct reference"); return -1; } - - if ((error = git_reference__normalize_name_lax( - normalized, - sizeof(normalized), - target)) < 0) - return error; - - git__free(ref->target.symbolic); - ref->target.symbolic = git__strdup(normalized); - GITERR_CHECK_ALLOC(ref->target.symbolic); - - return loose_write(ref); + + return git_reference_symbolic_create(out, ref->db->repo, ref->name, target, 1); } -int git_reference_rename(git_reference *ref, const char *new_name, int force) +int git_reference_rename( + git_reference **out, + git_reference *ref, + const char *new_name, + int force) { - int result; unsigned int normalization_flags; - git_buf aux_path = GIT_BUF_INIT; char normalized[GIT_REFNAME_MAX]; bool should_head_be_updated = false; - - normalization_flags = ref->flags & GIT_REF_SYMBOLIC ? - GIT_REF_FORMAT_ALLOW_ONELEVEL - : GIT_REF_FORMAT_NORMAL; - - if ((result = git_reference_normalize_name( - normalized, - sizeof(normalized), - new_name, - normalization_flags)) < 0) - return result; - - if ((result = reference_can_write(ref->owner, normalized, ref->name, force)) < 0) - return result; - - /* Initialize path now so we won't get an allocation failure once - * we actually start removing things. */ - if (git_buf_joinpath(&aux_path, ref->owner->path_repository, new_name) < 0) - return -1; - - /* - * Check if we have to update HEAD. - */ - if ((should_head_be_updated = git_branch_is_head(ref)) < 0) - goto cleanup; - - /* - * Now delete the old ref and remove an possibly existing directory - * named `new_name`. Note that using the internal `reference_delete` - * method deletes the ref from disk but doesn't free the pointer, so - * we can still access the ref's attributes for creating the new one - */ - if (reference_delete(ref) < 0) - goto cleanup; + git_reference *result = NULL; + git_oid *oid; + const char *symbolic; + int error = 0; + + *out = NULL; + + normalization_flags = ref->type == GIT_REF_SYMBOLIC ? + GIT_REF_FORMAT_ALLOW_ONELEVEL : GIT_REF_FORMAT_NORMAL; + + if ((error = git_reference_normalize_name(normalized, sizeof(normalized), new_name, normalization_flags)) < 0 || + (error = reference_can_write(ref->db->repo, normalized, ref->name, force)) < 0) + return error; /* - * Finally we can create the new reference. + * Create the new reference. */ - if (ref->flags & GIT_REF_SYMBOLIC) { - result = git_reference_symbolic_create( - NULL, ref->owner, new_name, ref->target.symbolic, force); + if (ref->type == GIT_REF_OID) { + oid = &ref->target.oid; + symbolic = NULL; } else { - result = git_reference_create( - NULL, ref->owner, new_name, &ref->target.oid, force); + oid = NULL; + symbolic = ref->target.symbolic; } + + if ((result = git_reference__alloc(ref->db, new_name, oid, symbolic)) == NULL) + return -1; - if (result < 0) + /* Check if we have to update HEAD. */ + if ((should_head_be_updated = git_branch_is_head(ref)) < 0) + goto on_error; + + /* Now delete the old ref and save the new one. */ + if (git_refdb_delete(ref->db, ref) < 0) + goto on_error; + + /* Save the new reference. */ + if ((error = git_refdb_write(ref->db, result)) < 0) goto rollback; - - /* - * Update HEAD it was poiting to the reference being renamed. - */ - if (should_head_be_updated && - git_repository_set_head(ref->owner, new_name) < 0) { - giterr_set(GITERR_REFERENCE, - "Failed to update HEAD after renaming reference"); - goto cleanup; + + /* Update HEAD it was poiting to the reference being renamed. */ + if (should_head_be_updated && git_repository_set_head(ref->db->repo, new_name) < 0) { + giterr_set(GITERR_REFERENCE, "Failed to update HEAD after renaming reference"); + goto on_error; } - /* - * Rename the reflog file, if it exists. - */ - if ((git_reference_has_log(ref)) && (git_reflog_rename(ref, new_name) < 0)) - goto cleanup; - - /* - * Change the name of the reference given by the user. - */ - git__free(ref->name); - ref->name = git__strdup(new_name); - - /* The reference is no longer packed */ - ref->flags &= ~GIT_REF_PACKED; + /* Rename the reflog file, if it exists. */ + if (git_reference_has_log(ref) && + (error = git_reflog_rename(ref, new_name)) < 0) + goto on_error; - git_buf_free(&aux_path); - return 0; + *out = result; -cleanup: - git_buf_free(&aux_path); - return -1; + return error; rollback: - /* - * Try to create the old reference again, ignore failures - */ - if (ref->flags & GIT_REF_SYMBOLIC) - git_reference_symbolic_create( - NULL, ref->owner, ref->name, ref->target.symbolic, 0); - else - git_reference_create( - NULL, ref->owner, ref->name, &ref->target.oid, 0); + git_refdb_write(ref->db, ref); - /* The reference is no longer packed */ - ref->flags &= ~GIT_REF_PACKED; +on_error: + git_reference_free(result); - git_buf_free(&aux_path); - return -1; + return error; } int git_reference_resolve(git_reference **ref_out, const git_reference *ref) { - if (ref->flags & GIT_REF_OID) - return git_reference_lookup(ref_out, ref->owner, ref->name); + if (ref->type == GIT_REF_OID) + return git_reference_lookup(ref_out, ref->db->repo, ref->name); else - return git_reference_lookup_resolved(ref_out, ref->owner, ref->target.symbolic, -1); -} - -int git_reference_packall(git_repository *repo) -{ - if (packed_load(repo) < 0 || /* load the existing packfile */ - packed_loadloose(repo) < 0 || /* add all the loose refs */ - packed_write(repo) < 0) /* write back to disk */ - return -1; - - return 0; + return git_reference_lookup_resolved(ref_out, ref->db->repo, + ref->target.symbolic, -1); } int git_reference_foreach( @@ -1486,43 +510,10 @@ int git_reference_foreach( git_reference_foreach_cb callback, void *payload) { - int result; - struct dirent_list_data data; - git_buf refs_path = GIT_BUF_INIT; - - /* list all the packed references first */ - if (list_flags & GIT_REF_PACKED) { - const char *ref_name; - void *ref = NULL; - GIT_UNUSED(ref); - - if (packed_load(repo) < 0) - return -1; - - git_strmap_foreach(repo->references.packfile, ref_name, ref, { - if (callback(ref_name, payload)) - return GIT_EUSER; - }); - } - - /* now list the loose references, trying not to - * duplicate the ref names already in the packed-refs file */ - - data.repo_path_len = strlen(repo->path_repository); - data.list_flags = list_flags; - data.repo = repo; - data.callback = callback; - data.callback_payload = payload; - data.callback_error = 0; - - if (git_buf_joinpath(&refs_path, repo->path_repository, GIT_REFS_DIR) < 0) - return -1; + git_refdb *refdb; + git_repository_refdb__weakptr(&refdb, repo); - result = git_path_direach(&refs_path, _dirent_loose_listall, &data); - - git_buf_free(&refs_path); - - return data.callback_error ? GIT_EUSER : result; + return git_refdb_foreach(refdb, list_flags, callback, payload); } static int cb__reflist_add(const char *ref, void *data) @@ -1556,26 +547,6 @@ int git_reference_list( return 0; } -int git_reference_reload(git_reference *ref) -{ - return reference_lookup(ref); -} - -void git_repository__refcache_free(git_refcache *refs) -{ - assert(refs); - - if (refs->packfile) { - struct packref *reference; - - git_strmap_foreach_value(refs->packfile, reference, { - git__free(reference); - }); - - git_strmap_free(refs->packfile); - } -} - static int is_valid_ref_char(char ch) { if ((unsigned) ch <= ' ') @@ -1798,89 +769,62 @@ int git_reference_cmp(git_reference *ref1, git_reference *ref2) assert(ref1 && ref2); /* let's put symbolic refs before OIDs */ - if ((ref1->flags & GIT_REF_TYPEMASK) != (ref2->flags & GIT_REF_TYPEMASK)) - return (ref1->flags & GIT_REF_SYMBOLIC) ? -1 : 1; + if (ref1->type != ref2->type) + return (ref1->type == GIT_REF_SYMBOLIC) ? -1 : 1; - if (ref1->flags & GIT_REF_SYMBOLIC) + if (ref1->type == GIT_REF_SYMBOLIC) return strcmp(ref1->target.symbolic, ref2->target.symbolic); return git_oid_cmp(&ref1->target.oid, &ref2->target.oid); } -/* Update the reference named `ref_name` so it points to `oid` */ -int git_reference__update(git_repository *repo, const git_oid *oid, const char *ref_name) +static int reference__update_terminal( + git_repository *repo, + const char *ref_name, + const git_oid *oid, + int nesting) { git_reference *ref; - int res; + int error = 0; - res = git_reference_lookup(&ref, repo, ref_name); + if (nesting > MAX_NESTING_LEVEL) + return GIT_ENOTFOUND; + + error = git_reference_lookup(&ref, repo, ref_name); - /* If we haven't found the reference at all, we assume we need to create - * a new reference and that's it */ - if (res == GIT_ENOTFOUND) { + /* If we haven't found the reference at all, create a new reference. */ + if (error == GIT_ENOTFOUND) { giterr_clear(); - return git_reference_create(NULL, repo, ref_name, oid, 1); + return git_reference_create(NULL, repo, ref_name, oid, 0); } - - if (res < 0) - return -1; - - /* If we have found a reference, but it's symbolic, we need to update - * the direct reference it points to */ + + if (error < 0) + return error; + + /* If the ref is a symbolic reference, follow its target. */ if (git_reference_type(ref) == GIT_REF_SYMBOLIC) { - git_reference *aux; - const char *sym_target; - - /* The target pointed at by this reference */ - sym_target = git_reference_symbolic_target(ref); - - /* resolve the reference to the target it points to */ - res = git_reference_resolve(&aux, ref); - - /* - * if the symbolic reference pointed to an inexisting ref, - * this is means we're creating a new branch, for example. - * We need to create a new direct reference with that name - */ - if (res == GIT_ENOTFOUND) { - giterr_clear(); - res = git_reference_create(NULL, repo, sym_target, oid, 1); - git_reference_free(ref); - return res; - } - - /* free the original symbolic reference now; not before because - * we're using the `sym_target` pointer */ + error = reference__update_terminal(repo, git_reference_symbolic_target(ref), oid, + nesting+1); git_reference_free(ref); - - if (res < 0) - return -1; - - /* store the newly found direct reference in its place */ - ref = aux; + } else { + git_reference_free(ref); + error = git_reference_create(NULL, repo, ref_name, oid, 1); } - - /* ref is made to point to `oid`: ref is either the original reference, - * or the target of the symbolic reference we've looked up */ - res = git_reference_set_target(ref, oid); - git_reference_free(ref); - return res; + + return error; } -struct glob_cb_data { - const char *glob; - int (*callback)(const char *, void *); - void *payload; -}; - -static int fromglob_cb(const char *reference_name, void *payload) +/* + * Starting with the reference given by `ref_name`, follows symbolic + * references until a direct reference is found and updated the OID + * on that direct reference to `oid`. + */ +int git_reference__update_terminal( + git_repository *repo, + const char *ref_name, + const git_oid *oid) { - struct glob_cb_data *data = (struct glob_cb_data *)payload; - - if (!p_fnmatch(data->glob, reference_name, 0)) - return data->callback(reference_name, data->payload); - - return 0; + return reference__update_terminal(repo, ref_name, oid, 0); } int git_reference_foreach_glob( @@ -1892,16 +836,13 @@ int git_reference_foreach_glob( void *payload), void *payload) { - struct glob_cb_data data; + git_refdb *refdb; assert(repo && glob && callback); - data.glob = glob; - data.callback = callback; - data.payload = payload; + git_repository_refdb__weakptr(&refdb, repo); - return git_reference_foreach( - repo, list_flags, fromglob_cb, &data); + return git_refdb_foreach_glob(refdb, glob, list_flags, callback, payload); } int git_reference_has_log( @@ -1912,7 +853,8 @@ int git_reference_has_log( assert(ref); - if (git_buf_join_n(&path, '/', 3, ref->owner->path_repository, GIT_REFLOG_DIR, ref->name) < 0) + if (git_buf_join_n(&path, '/', 3, ref->db->repo->path_repository, + GIT_REFLOG_DIR, ref->name) < 0) return -1; result = git_path_isfile(git_buf_cstr(&path)); diff --git a/src/refs.h b/src/refs.h index 7bd1ae68a..7d63c3fbd 100644 --- a/src/refs.h +++ b/src/refs.h @@ -10,6 +10,7 @@ #include "common.h" #include "git2/oid.h" #include "git2/refs.h" +#include "git2/refdb.h" #include "strmap.h" #include "buffer.h" @@ -47,28 +48,22 @@ #define GIT_REFNAME_MAX 1024 struct git_reference { - unsigned int flags; - git_repository *owner; - char *name; - time_t mtime; + git_refdb *db; + + git_ref_t type; union { git_oid oid; char *symbolic; } target; + + char name[0]; }; -typedef struct { - git_strmap *packfile; - time_t packfile_time; -} git_refcache; - -void git_repository__refcache_free(git_refcache *refs); - int git_reference__normalize_name_lax(char *buffer_out, size_t out_size, const char *name); int git_reference__normalize_name(git_buf *buf, const char *name, unsigned int flags); +int git_reference__update_terminal(git_repository *repo, const char *ref_name, const git_oid *oid); int git_reference__is_valid_name(const char *refname, unsigned int flags); -int git_reference__update(git_repository *repo, const git_oid *oid, const char *ref_name); int git_reference__is_branch(const char *ref_name); int git_reference__is_remote(const char *ref_name); diff --git a/src/remote.c b/src/remote.c index 0a1f2b856..21ca6ecdb 100644 --- a/src/remote.c +++ b/src/remote.c @@ -1175,6 +1175,7 @@ static int rename_one_remote_reference( int error = -1; git_buf new_name = GIT_BUF_INIT; git_reference *reference = NULL; + git_reference *newref = NULL; if (git_buf_printf( &new_name, @@ -1186,10 +1187,11 @@ static int rename_one_remote_reference( if (git_reference_lookup(&reference, repo, reference_name) < 0) goto cleanup; - error = git_reference_rename(reference, git_buf_cstr(&new_name), 0); + error = git_reference_rename(&newref, reference, git_buf_cstr(&new_name), 0); + git_reference_free(reference); cleanup: - git_reference_free(reference); + git_reference_free(newref); git_buf_free(&new_name); return error; } diff --git a/src/repository.c b/src/repository.c index 278abfaf2..0ad7449ba 100644 --- a/src/repository.c +++ b/src/repository.c @@ -8,6 +8,7 @@ #include <ctype.h> #include "git2/object.h" +#include "git2/refdb.h" #include "common.h" #include "repository.h" @@ -39,6 +40,15 @@ static void drop_odb(git_repository *repo) } } +static void drop_refdb(git_repository *repo) +{ + if (repo->_refdb != NULL) { + GIT_REFCOUNT_OWN(repo->_refdb, NULL); + git_refdb_free(repo->_refdb); + repo->_refdb = NULL; + } +} + static void drop_config(git_repository *repo) { if (repo->_config != NULL) { @@ -65,7 +75,6 @@ void git_repository_free(git_repository *repo) return; git_cache_free(&repo->objects); - git_repository__refcache_free(&repo->references); git_attr_cache_flush(repo); git_submodule_config_free(repo); @@ -75,6 +84,7 @@ void git_repository_free(git_repository *repo) drop_config(repo); drop_index(repo); drop_odb(repo); + drop_refdb(repo); git__free(repo); } @@ -600,6 +610,45 @@ void git_repository_set_odb(git_repository *repo, git_odb *odb) GIT_REFCOUNT_INC(odb); } +int git_repository_refdb__weakptr(git_refdb **out, git_repository *repo) +{ + assert(out && repo); + + if (repo->_refdb == NULL) { + int res; + + res = git_refdb_open(&repo->_refdb, repo); + + if (res < 0) + return -1; + + GIT_REFCOUNT_OWN(repo->_refdb, repo); + } + + *out = repo->_refdb; + return 0; +} + +int git_repository_refdb(git_refdb **out, git_repository *repo) +{ + if (git_repository_refdb__weakptr(out, repo) < 0) + return -1; + + GIT_REFCOUNT_INC(*out); + return 0; +} + +void git_repository_set_refdb(git_repository *repo, git_refdb *refdb) +{ + assert (repo && refdb); + + drop_refdb(repo); + + repo->_refdb = refdb; + GIT_REFCOUNT_OWN(repo->_refdb, repo); + GIT_REFCOUNT_INC(refdb); +} + int git_repository_index__weakptr(git_index **out, git_repository *repo) { assert(out && repo); diff --git a/src/repository.h b/src/repository.h index f19758fe4..ebd797cc1 100644 --- a/src/repository.h +++ b/src/repository.h @@ -21,6 +21,7 @@ #include "object.h" #include "attr.h" #include "strmap.h" +#include "refdb.h" #define DOT_GIT ".git" #define GIT_DIR DOT_GIT "/" @@ -79,11 +80,11 @@ enum { /** Internal structure for repository object */ struct git_repository { git_odb *_odb; + git_refdb *_refdb; git_config *_config; git_index *_index; git_cache objects; - git_refcache references; git_attr_cache attrcache; git_strmap *submodules; @@ -112,6 +113,7 @@ int git_repository_head_tree(git_tree **tree, git_repository *repo); */ int git_repository_config__weakptr(git_config **out, git_repository *repo); int git_repository_odb__weakptr(git_odb **out, git_repository *repo); +int git_repository_refdb__weakptr(git_refdb **out, git_repository *repo); int git_repository_index__weakptr(git_index **out, git_repository *repo); /* diff --git a/src/reset.c b/src/reset.c index 700aac808..c1e1f865e 100644 --- a/src/reset.c +++ b/src/reset.c @@ -13,48 +13,10 @@ #include "git2/reset.h" #include "git2/checkout.h" #include "git2/merge.h" +#include "git2/refs.h" #define ERROR_MSG "Cannot perform reset" -static int update_head(git_repository *repo, git_object *commit) -{ - int error; - git_reference *head = NULL, *target = NULL; - - error = git_repository_head(&head, repo); - - if (error < 0 && error != GIT_EORPHANEDHEAD) - return error; - - if (error == GIT_EORPHANEDHEAD) { - giterr_clear(); - - /* - * TODO: This is a bit weak as this doesn't support chained - * symbolic references. yet. - */ - if ((error = git_reference_lookup(&head, repo, GIT_HEAD_FILE)) < 0) - goto cleanup; - - if ((error = git_reference_create( - &target, - repo, - git_reference_symbolic_target(head), - git_object_id(commit), 0)) < 0) - goto cleanup; - } else { - if ((error = git_reference_set_target(head, git_object_id(commit))) < 0) - goto cleanup; - } - - error = 0; - -cleanup: - git_reference_free(head); - git_reference_free(target); - return error; -} - int git_reset_default( git_repository *repo, git_object *target, @@ -167,7 +129,8 @@ int git_reset( } /* move HEAD to the new target */ - if ((error = update_head(repo, commit)) < 0) + if ((error = git_reference__update_terminal(repo, GIT_HEAD_FILE, + git_object_id(commit))) < 0) goto cleanup; if (reset_type == GIT_RESET_HARD) { diff --git a/src/revparse.c b/src/revparse.c index 884879975..8a45889bb 100644 --- a/src/revparse.c +++ b/src/revparse.c @@ -10,6 +10,7 @@ #include "common.h" #include "buffer.h" #include "tree.h" +#include "refdb.h" #include "git2.h" @@ -656,7 +657,7 @@ static int object_from_reference(git_object **object, git_reference *reference) if (git_reference_resolve(&resolved, reference) < 0) return -1; - error = git_object_lookup(object, reference->owner, git_reference_target(resolved), GIT_OBJ_ANY); + error = git_object_lookup(object, reference->db->repo, git_reference_target(resolved), GIT_OBJ_ANY); git_reference_free(resolved); return error; diff --git a/src/stash.c b/src/stash.c index e78985063..355c5dc9c 100644 --- a/src/stash.c +++ b/src/stash.c @@ -645,13 +645,15 @@ int git_stash_drop( if (max == 1) { error = git_reference_delete(stash); + git_reference_free(stash); stash = NULL; } else if (index == 0) { const git_reflog_entry *entry; entry = git_reflog_entry_byindex(reflog, 0); - - error = git_reference_set_target(stash, &entry->oid_cur); + + git_reference_free(stash); + error = git_reference_create(&stash, repo, GIT_REFS_STASH_FILE, &entry->oid_cur, 1); } cleanup: @@ -376,9 +376,9 @@ on_error: int git_tag_delete(git_repository *repo, const char *tag_name) { - int error; git_reference *tag_ref; git_buf ref_name = GIT_BUF_INIT; + int error; error = retrieve_tag_reference(&tag_ref, &ref_name, repo, tag_name); @@ -387,7 +387,10 @@ int git_tag_delete(git_repository *repo, const char *tag_name) if (error < 0) return error; - return git_reference_delete(tag_ref); + if ((error = git_reference_delete(tag_ref)) == 0) + git_reference_free(tag_ref); + + return error; } int git_tag__parse(git_tag *tag, git_odb_object *obj) @@ -426,8 +429,7 @@ int git_tag_foreach(git_repository *repo, git_tag_foreach_cb cb, void *cb_data) data.cb_data = cb_data; data.repo = repo; - return git_reference_foreach( - repo, GIT_REF_OID | GIT_REF_PACKED, &tags_cb, &data); + return git_reference_foreach(repo, GIT_REF_OID, &tags_cb, &data); } typedef struct { |