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 | |
parent | 6a9ef012376e8a21dcfd0499ab16048eb6e954c3 (diff) | |
download | libgit2-d00d54645d931c77a9b401518c0d73e3f640454b.tar.gz |
immutable references and a pluggable ref database
42 files changed, 2636 insertions, 1560 deletions
diff --git a/include/git2/branch.h b/include/git2/branch.h index d372c2c92..4d24e2d82 100644 --- a/include/git2/branch.h +++ b/include/git2/branch.h @@ -50,11 +50,11 @@ GIT_BEGIN_DECL * pointing to the provided target commit. */ GIT_EXTERN(int) git_branch_create( - git_reference **out, - git_repository *repo, - const char *branch_name, - const git_commit *target, - int force); + git_reference **out, + git_repository *repo, + const char *branch_name, + const git_commit *target, + int force); /** * Delete an existing branch reference. @@ -67,6 +67,11 @@ GIT_EXTERN(int) git_branch_create( */ GIT_EXTERN(int) git_branch_delete(git_reference *branch); +typedef int (*git_branch_foreach_cb)( + const char *branch_name, + git_branch_t branch_type, + void *payload); + /** * Loop over all the branches and issue a callback for each one. * @@ -85,14 +90,10 @@ GIT_EXTERN(int) git_branch_delete(git_reference *branch); * @return 0 on success, GIT_EUSER on non-zero callback, or error code */ GIT_EXTERN(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); /** * Move/rename an existing local branch reference. @@ -110,9 +111,10 @@ GIT_EXTERN(int) git_branch_foreach( * @return 0 on success, GIT_EINVALIDSPEC or an error code. */ GIT_EXTERN(int) git_branch_move( - git_reference *branch, - const char *new_branch_name, - int force); + git_reference **out, + git_reference *branch, + const char *new_branch_name, + int force); /** * Lookup a branch by its name in a repository. @@ -136,10 +138,10 @@ GIT_EXTERN(int) git_branch_move( * exists, GIT_EINVALIDSPEC, otherwise an error code. */ GIT_EXTERN(int) git_branch_lookup( - git_reference **out, - git_repository *repo, - const char *branch_name, - git_branch_t branch_type); + git_reference **out, + git_repository *repo, + const char *branch_name, + git_branch_t branch_type); /** * Return the name of the given local or remote branch. @@ -172,8 +174,8 @@ GIT_EXTERN(int) git_branch_name(const char **out, * reference exists, otherwise an error code. */ GIT_EXTERN(int) git_branch_tracking( - git_reference **out, - git_reference *branch); + git_reference **out, + git_reference *branch); /** * Return the name of the reference supporting the remote tracking branch, @@ -208,7 +210,7 @@ GIT_EXTERN(int) git_branch_tracking_name( * error code otherwise. */ GIT_EXTERN(int) git_branch_is_head( - git_reference *branch); + git_reference *branch); /** * Return the name of remote that the remote tracking branch belongs to. diff --git a/include/git2/refdb.h b/include/git2/refdb.h new file mode 100644 index 000000000..8d5be8e47 --- /dev/null +++ b/include/git2/refdb.h @@ -0,0 +1,98 @@ +/* + * 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_git_refdb_h__ +#define INCLUDE_git_refdb_h__ + +#include "common.h" +#include "types.h" +#include "oid.h" +#include "refs.h" + +/** + * @file git2/refdb.h + * @brief Git custom refs backend functions + * @defgroup git_refdb Git custom refs backend API + * @ingroup Git + * @{ + */ +GIT_BEGIN_DECL + +/** + * Create a new reference. Either an oid or a symbolic target must be + * specified. + * + * @param refdb the reference database to associate with this reference + * @param name the reference name + * @param oid the object id for a direct reference + * @param symbolic the target for a symbolic reference + * @return the created git_reference or NULL on error + */ +git_reference *git_reference__alloc( + git_refdb *refdb, + const char *name, + const git_oid *oid, + const char *symbolic); + +/** + * Create a new reference database with no backends. + * + * Before the Ref DB can be used for read/writing, a custom database + * backend must be manually set using `git_refdb_set_backend()` + * + * @param out location to store the database pointer, if opened. + * Set to NULL if the open failed. + * @param repo the repository + * @return 0 or an error code + */ +GIT_EXTERN(int) git_refdb_new(git_refdb **out, git_repository *repo); + +/** + * Create a new reference database and automatically add + * the default backends: + * + * - git_refdb_dir: read and write loose and packed refs + * from disk, assuming the repository dir as the folder + * + * @param out location to store the database pointer, if opened. + * Set to NULL if the open failed. + * @param repo the repository + * @return 0 or an error code + */ +GIT_EXTERN(int) git_refdb_open(git_refdb **out, git_repository *repo); + +/** + * Suggests that the given refdb compress or optimize its references. + * This mechanism is implementation specific. For on-disk reference + * databases, for example, this may pack all loose references. + */ +GIT_EXTERN(int) git_refdb_compress(git_refdb *refdb); + +/** + * Close an open reference database. + * + * @param refdb reference database pointer or NULL + */ +GIT_EXTERN(void) git_refdb_free(git_refdb *refdb); + +/** + * Sets the custom backend to an existing reference DB + * + * Read <refdb_backends.h> for more information. + * + * @param refdb database to add the backend to + * @param backend pointer to a git_refdb_backend instance + * @param priority Value for ordering the backends queue + * @return 0 on success; error code otherwise + */ +GIT_EXTERN(int) git_refdb_set_backend( + git_refdb *refdb, + git_refdb_backend *backend); + +/** @} */ +GIT_END_DECL + +#endif diff --git a/include/git2/refdb_backend.h b/include/git2/refdb_backend.h new file mode 100644 index 000000000..bf33817d6 --- /dev/null +++ b/include/git2/refdb_backend.h @@ -0,0 +1,109 @@ +/* + * 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_git_refdb_backend_h__ +#define INCLUDE_git_refdb_backend_h__ + +#include "common.h" +#include "types.h" +#include "oid.h" + +/** + * @file git2/refdb_backend.h + * @brief Git custom refs backend functions + * @defgroup git_refdb_backend Git custom refs backend API + * @ingroup Git + * @{ + */ +GIT_BEGIN_DECL + +/** An instance for a custom backend */ +struct git_refdb_backend { + unsigned int version; + + /** + * Queries the refdb backend to determine if the given ref_name + * exists. A refdb implementation must provide this function. + */ + int (*exists)( + int *exists, + struct git_refdb_backend *backend, + const char *ref_name); + + /** + * Queries the refdb backend for a given reference. A refdb + * implementation must provide this function. + */ + int (*lookup)( + git_reference **out, + struct git_refdb_backend *backend, + const char *ref_name); + + /** + * Enumerates each reference in the refdb. A refdb implementation must + * provide this function. + */ + int (*foreach)( + struct git_refdb_backend *backend, + unsigned int list_flags, + git_reference_foreach_cb callback, + void *payload); + + /** + * Enumerates each reference in the refdb that matches the given + * glob string. A refdb implementation may provide this function; + * if it is not provided, foreach will be used and the results filtered + * against the glob. + */ + int (*foreach_glob)( + struct git_refdb_backend *backend, + const char *glob, + unsigned int list_flags, + git_reference_foreach_cb callback, + void *payload); + + /** + * Writes the given reference to the refdb. A refdb implementation + * must provide this function. + */ + int (*write)(struct git_refdb_backend *backend, const git_reference *ref); + + /** + * Deletes the given reference from the refdb. A refdb implementation + * must provide this function. + */ + int (*delete)(struct git_refdb_backend *backend, const git_reference *ref); + + /** + * Suggests that the given refdb compress or optimize its references. + * This mechanism is implementation specific. (For on-disk reference + * databases, this may pack all loose references.) A refdb + * implementation may provide this function; if it is not provided, + * nothing will be done. + */ + int (*compress)(struct git_refdb_backend *backend); + + /** + * Frees any resources held by the refdb. A refdb implementation may + * provide this function; if it is not provided, nothing will be done. + */ + void (*free)(struct git_refdb_backend *backend); +}; + +#define GIT_ODB_BACKEND_VERSION 1 +#define GIT_ODB_BACKEND_INIT {GIT_ODB_BACKEND_VERSION} + +/** + * Constructors for default refdb backends. + */ +GIT_EXTERN(int) git_refdb_backend_fs( + struct git_refdb_backend **backend_out, + git_repository *repo, + git_refdb *refdb); + +GIT_END_DECL + +#endif diff --git a/include/git2/refs.h b/include/git2/refs.h index d586917c2..2373bee77 100644 --- a/include/git2/refs.h +++ b/include/git2/refs.h @@ -189,33 +189,41 @@ GIT_EXTERN(int) git_reference_resolve(git_reference **out, const git_reference * GIT_EXTERN(git_repository *) git_reference_owner(const git_reference *ref); /** - * Set the symbolic target of a reference. + * Create a new reference with the same name as the given reference but a + * different symbolic target. The reference must be a symbolic reference, + * otherwise this will fail. * - * The reference must be a symbolic reference, otherwise this will fail. - * - * The reference will be automatically updated in memory and on disk. + * The new reference will be written to disk, overwriting the given reference. * * The target name will be checked for validity. * See `git_reference_create_symbolic()` for rules about valid names. * + * @param out Pointer to the newly created reference * @param ref The reference * @param target The new target for the reference * @return 0 on success, EINVALIDSPEC or an error code */ -GIT_EXTERN(int) git_reference_symbolic_set_target(git_reference *ref, const char *target); +GIT_EXTERN(int) git_reference_symbolic_set_target( + git_reference **out, + git_reference *ref, + const char *target); /** - * Set the OID target of a reference. + * Create a new reference with the same name as the given reference but a + * different OID target. The reference must be a direct reference, otherwise + * this will fail. * - * The reference must be a direct reference, otherwise this will fail. - * - * The reference will be automatically updated in memory and on disk. + * The new reference will be written to disk, overwriting the given reference. * + * @param out Pointer to the newly created reference * @param ref The reference * @param id The new target OID for the reference * @return 0 or an error code */ -GIT_EXTERN(int) git_reference_set_target(git_reference *ref, const git_oid *id); +GIT_EXTERN(int) git_reference_set_target( + git_reference **out, + git_reference *ref, + const git_oid *id); /** * Rename an existing reference. @@ -225,7 +233,8 @@ GIT_EXTERN(int) git_reference_set_target(git_reference *ref, const git_oid *id); * The new name will be checked for validity. * See `git_reference_create_symbolic()` for rules about valid names. * - * The given git_reference will be updated in place. + * On success, the given git_reference will be deleted from disk and a + * new `git_reference` will be returned. * * The reference will be immediately renamed in-memory and on disk. * @@ -243,15 +252,18 @@ GIT_EXTERN(int) git_reference_set_target(git_reference *ref, const git_oid *id); * @return 0 on success, EINVALIDSPEC, EEXISTS or an error code * */ -GIT_EXTERN(int) git_reference_rename(git_reference *ref, const char *name, int force); +GIT_EXTERN(int) git_reference_rename( + git_reference **out, + git_reference *ref, + const char *new_name, + int force); /** * Delete an existing reference. * - * This method works for both direct and symbolic references. - * - * The reference will be immediately removed on disk and from memory - * (i.e. freed). The given reference pointer will no longer be valid. + * This method works for both direct and symbolic references. The reference + * will be immediately removed on disk but the memory will not be freed. + * Callers must call `git_reference_free`. * * @param ref The reference to remove * @return 0 or an error code @@ -259,21 +271,6 @@ GIT_EXTERN(int) git_reference_rename(git_reference *ref, const char *name, int f 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. - * - * @param repo Repository where the loose refs will be packed - * @return 0 or an error code - */ -GIT_EXTERN(int) git_reference_packall(git_repository *repo); - -/** * Fill a list with all the references that can be found in a repository. * * Using the `list_flags` parameter, the listed references may be filtered @@ -323,14 +320,6 @@ GIT_EXTERN(int) git_reference_foreach( void *payload); /** - * Check if a reference has been loaded from a packfile. - * - * @param ref A git reference - * @return 0 in case it's not packed; 1 otherwise - */ -GIT_EXTERN(int) git_reference_is_packed(git_reference *ref); - -/** * Reload a reference from disk. * * Reference pointers can become outdated if the Git repository is diff --git a/include/git2/repository.h b/include/git2/repository.h index e207e5bb5..e75c8b136 100644 --- a/include/git2/repository.h +++ b/include/git2/repository.h @@ -434,6 +434,39 @@ GIT_EXTERN(int) git_repository_odb(git_odb **out, git_repository *repo); GIT_EXTERN(void) git_repository_set_odb(git_repository *repo, git_odb *odb); /** + * Get the Reference Database Backend for this repository. + * + * If a custom refsdb has not been set, the default database for + * the repository will be returned (the one that manipulates loose + * and packed references in the `.git` directory). + * + * The refdb must be freed once it's no longer being used by + * the user. + * + * @param out Pointer to store the loaded refdb + * @param repo A repository object + * @return 0, or an error code + */ +GIT_EXTERN(int) git_repository_refdb(git_refdb **out, git_repository *repo); + +/** + * Set the Reference Database Backend for this repository + * + * The refdb will be used for all reference related operations + * involving this repository. + * + * The repository will keep a reference to the refdb; the user + * must still free the refdb object after setting it to the + * repository, or it will leak. + * + * @param repo A repository object + * @param refdb An refdb object + */ +GIT_EXTERN(void) git_repository_set_refdb( + git_repository *repo, + git_refdb *refdb); + +/** * Get the Index file for this repository. * * If a custom index has not been set, the default diff --git a/include/git2/types.h b/include/git2/types.h index c16bb8872..bc15050ce 100644 --- a/include/git2/types.h +++ b/include/git2/types.h @@ -92,6 +92,12 @@ typedef struct git_odb_stream git_odb_stream; /** A stream to write a packfile to the ODB */ typedef struct git_odb_writepack git_odb_writepack; +/** An open refs database handle. */ +typedef struct git_refdb git_refdb; + +/** A custom backend for refs */ +typedef struct git_refdb_backend git_refdb_backend; + /** * Representation of an existing git repository, * including all its object contents @@ -164,9 +170,7 @@ typedef enum { GIT_REF_INVALID = 0, /** Invalid reference */ GIT_REF_OID = 1, /** A reference which points at an object id */ GIT_REF_SYMBOLIC = 2, /** A reference which points at another reference */ - GIT_REF_PACKED = 4, - GIT_REF_HAS_PEEL = 8, - GIT_REF_LISTALL = GIT_REF_OID|GIT_REF_SYMBOLIC|GIT_REF_PACKED, + GIT_REF_LISTALL = GIT_REF_OID|GIT_REF_SYMBOLIC, } git_ref_t; /** Basic type of any Git branch. */ 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 { diff --git a/tests-clar/commit/write.c b/tests-clar/commit/write.c index 88e2f32fb..e9946af89 100644 --- a/tests-clar/commit/write.c +++ b/tests-clar/commit/write.c @@ -114,8 +114,9 @@ void test_commit_write__root(void) cl_assert(git_reference_type(head) == GIT_REF_SYMBOLIC); head_old = git__strdup(git_reference_symbolic_target(head)); cl_assert(head_old != NULL); - - cl_git_pass(git_reference_symbolic_set_target(head, branch_name)); + git_reference_free(head); + + cl_git_pass(git_reference_symbolic_create(&head, g_repo, "HEAD", branch_name, 1)); cl_git_pass(git_commit_create_v( &commit_id, /* out id */ diff --git a/tests-clar/object/tag/write.c b/tests-clar/object/tag/write.c index eb0ac2897..cd69bea89 100644 --- a/tests-clar/object/tag/write.c +++ b/tests-clar/object/tag/write.c @@ -60,6 +60,7 @@ void test_object_tag_write__basic(void) cl_git_pass(git_reference_lookup(&ref_tag, g_repo, "refs/tags/the-tag")); cl_assert(git_oid_cmp(git_reference_target(ref_tag), &tag_id) == 0); cl_git_pass(git_reference_delete(ref_tag)); + git_reference_free(ref_tag); git_tag_free(tag); } diff --git a/tests-clar/refdb/inmemory.c b/tests-clar/refdb/inmemory.c new file mode 100644 index 000000000..6ee09c0c7 --- /dev/null +++ b/tests-clar/refdb/inmemory.c @@ -0,0 +1,213 @@ +#include "clar_libgit2.h" +#include "refdb.h" +#include "repository.h" +#include "testdb.h" + +#define TEST_REPO_PATH "testrepo" + +static git_repository *repo; +static git_refdb *refdb; +static git_refdb_backend *refdb_backend; + +int unlink_ref(void *payload, git_buf *file) +{ + GIT_UNUSED(payload); + return p_unlink(git_buf_cstr(file)); +} + +int empty(void *payload, git_buf *file) +{ + GIT_UNUSED(payload); + GIT_UNUSED(file); + return -1; +} + +int ref_file_foreach(git_repository *repo, int (* cb)(void *payload, git_buf *filename)) +{ + const char *repo_path; + git_buf repo_refs_dir = GIT_BUF_INIT; + int error = 0; + + repo_path = git_repository_path(repo); + + git_buf_joinpath(&repo_refs_dir, repo_path, "HEAD"); + if (git_path_exists(git_buf_cstr(&repo_refs_dir)) && + cb(NULL, &repo_refs_dir) < 0) + return -1; + + git_buf_joinpath(&repo_refs_dir, repo_path, "refs"); + git_buf_joinpath(&repo_refs_dir, git_buf_cstr(&repo_refs_dir), "heads"); + if (git_path_direach(&repo_refs_dir, cb, NULL) != 0) + return -1; + + git_buf_joinpath(&repo_refs_dir, repo_path, "packed-refs"); + if (git_path_exists(git_buf_cstr(&repo_refs_dir)) && + cb(NULL, &repo_refs_dir) < 0) + return -1; + + git_buf_free(&repo_refs_dir); + + return error; +} + +void test_refdb_inmemory__initialize(void) +{ + git_buf repo_refs_dir = GIT_BUF_INIT; + + repo = cl_git_sandbox_init(TEST_REPO_PATH); + + cl_git_pass(git_repository_refdb(&refdb, repo)); + cl_git_pass(refdb_backend_test(&refdb_backend, repo)); + cl_git_pass(git_refdb_set_backend(refdb, refdb_backend)); + + + ref_file_foreach(repo, unlink_ref); + + git_buf_free(&repo_refs_dir); +} + +void test_refdb_inmemory__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_refdb_inmemory__doesnt_write_ref_file(void) +{ + git_reference *ref; + git_oid oid; + + cl_git_pass(git_oid_fromstr(&oid, "c47800c7266a2be04c571c04d5a6614691ea99bd")); + cl_git_pass(git_reference_create(&ref, repo, GIT_REFS_HEADS_DIR "test1", &oid, 0)); + + ref_file_foreach(repo, empty); + + git_reference_free(ref); +} + +void test_refdb_inmemory__read(void) +{ + git_reference *write1, *write2, *write3, *read1, *read2, *read3; + git_oid oid1, oid2, oid3; + + cl_git_pass(git_oid_fromstr(&oid1, "c47800c7266a2be04c571c04d5a6614691ea99bd")); + cl_git_pass(git_reference_create(&write1, repo, GIT_REFS_HEADS_DIR "test1", &oid1, 0)); + + cl_git_pass(git_oid_fromstr(&oid2, "e90810b8df3e80c413d903f631643c716887138d")); + cl_git_pass(git_reference_create(&write2, repo, GIT_REFS_HEADS_DIR "test2", &oid2, 0)); + + cl_git_pass(git_oid_fromstr(&oid3, "763d71aadf09a7951596c9746c024e7eece7c7af")); + cl_git_pass(git_reference_create(&write3, repo, GIT_REFS_HEADS_DIR "test3", &oid3, 0)); + + + cl_git_pass(git_reference_lookup(&read1, repo, GIT_REFS_HEADS_DIR "test1")); + cl_assert(strcmp(git_reference_name(read1), git_reference_name(write1)) == 0); + cl_assert(git_oid_cmp(git_reference_target(read1), git_reference_target(write1)) == 0); + + cl_git_pass(git_reference_lookup(&read2, repo, GIT_REFS_HEADS_DIR "test2")); + cl_assert(strcmp(git_reference_name(read2), git_reference_name(write2)) == 0); + cl_assert(git_oid_cmp(git_reference_target(read2), git_reference_target(write2)) == 0); + + cl_git_pass(git_reference_lookup(&read3, repo, GIT_REFS_HEADS_DIR "test3")); + cl_assert(strcmp(git_reference_name(read3), git_reference_name(write3)) == 0); + cl_assert(git_oid_cmp(git_reference_target(read3), git_reference_target(write3)) == 0); + + git_reference_free(write1); + git_reference_free(write2); + git_reference_free(write3); + + git_reference_free(read1); + git_reference_free(read2); + git_reference_free(read3); +} + +int foreach_test(const char *ref_name, void *payload) +{ + git_reference *ref; + git_oid expected; + int *i = payload; + + cl_git_pass(git_reference_lookup(&ref, repo, ref_name)); + + if (*i == 0) + cl_git_pass(git_oid_fromstr(&expected, "c47800c7266a2be04c571c04d5a6614691ea99bd")); + else if (*i == 1) + cl_git_pass(git_oid_fromstr(&expected, "e90810b8df3e80c413d903f631643c716887138d")); + else if (*i == 2) + cl_git_pass(git_oid_fromstr(&expected, "763d71aadf09a7951596c9746c024e7eece7c7af")); + + cl_assert(git_oid_cmp(&expected, &ref->target.oid) == 0); + + ++(*i); + + git_reference_free(ref); + + return 0; +} + +void test_refdb_inmemory__foreach(void) +{ + git_reference *write1, *write2, *write3; + git_oid oid1, oid2, oid3; + size_t i = 0; + + cl_git_pass(git_oid_fromstr(&oid1, "c47800c7266a2be04c571c04d5a6614691ea99bd")); + cl_git_pass(git_reference_create(&write1, repo, GIT_REFS_HEADS_DIR "test1", &oid1, 0)); + + cl_git_pass(git_oid_fromstr(&oid2, "e90810b8df3e80c413d903f631643c716887138d")); + cl_git_pass(git_reference_create(&write2, repo, GIT_REFS_HEADS_DIR "test2", &oid2, 0)); + + cl_git_pass(git_oid_fromstr(&oid3, "763d71aadf09a7951596c9746c024e7eece7c7af")); + cl_git_pass(git_reference_create(&write3, repo, GIT_REFS_HEADS_DIR "test3", &oid3, 0)); + + cl_git_pass(git_reference_foreach(repo, GIT_REF_LISTALL, foreach_test, &i)); + cl_assert(i == 3); + + git_reference_free(write1); + git_reference_free(write2); + git_reference_free(write3); +} + +int delete_test(const char *ref_name, void *payload) +{ + git_reference *ref; + git_oid expected; + int *i = payload; + + cl_git_pass(git_reference_lookup(&ref, repo, ref_name)); + + cl_git_pass(git_oid_fromstr(&expected, "e90810b8df3e80c413d903f631643c716887138d")); + cl_assert(git_oid_cmp(&expected, &ref->target.oid) == 0); + + ++(*i); + + git_reference_free(ref); + + return 0; +} + +void test_refdb_inmemory__delete(void) +{ + git_reference *write1, *write2, *write3; + git_oid oid1, oid2, oid3; + size_t i = 0; + + cl_git_pass(git_oid_fromstr(&oid1, "c47800c7266a2be04c571c04d5a6614691ea99bd")); + cl_git_pass(git_reference_create(&write1, repo, GIT_REFS_HEADS_DIR "test1", &oid1, 0)); + + cl_git_pass(git_oid_fromstr(&oid2, "e90810b8df3e80c413d903f631643c716887138d")); + cl_git_pass(git_reference_create(&write2, repo, GIT_REFS_HEADS_DIR "test2", &oid2, 0)); + + cl_git_pass(git_oid_fromstr(&oid3, "763d71aadf09a7951596c9746c024e7eece7c7af")); + cl_git_pass(git_reference_create(&write3, repo, GIT_REFS_HEADS_DIR "test3", &oid3, 0)); + + git_reference_delete(write1); + git_reference_free(write1); + + git_reference_delete(write3); + git_reference_free(write3); + + cl_git_pass(git_reference_foreach(repo, GIT_REF_LISTALL, delete_test, &i)); + cl_assert(i == 1); + + git_reference_free(write2); +} diff --git a/tests-clar/refdb/testdb.c b/tests-clar/refdb/testdb.c new file mode 100644 index 000000000..a8e7ba5fe --- /dev/null +++ b/tests-clar/refdb/testdb.c @@ -0,0 +1,217 @@ +#include "common.h" +#include "vector.h" +#include "util.h" +#include <git2/refdb.h> +#include <git2/refdb_backend.h> +#include <git2/errors.h> +#include <git2/repository.h> + +typedef struct refdb_test_backend { + git_refdb_backend parent; + + git_repository *repo; + git_refdb *refdb; + git_vector refs; +} refdb_test_backend; + +typedef struct refdb_test_entry { + char *name; + git_ref_t type; + + union { + git_oid oid; + char *symbolic; + } target; +} refdb_test_entry; + +static int ref_name_cmp(const void *a, const void *b) +{ + return strcmp(git_reference_name((git_reference *)a), + git_reference_name((git_reference *)b)); +} + +static int refdb_test_backend__exists( + int *exists, + git_refdb_backend *_backend, + const char *ref_name) +{ + refdb_test_backend *backend; + refdb_test_entry *entry; + size_t i; + + assert(_backend); + backend = (refdb_test_backend *)_backend; + + *exists = 0; + + git_vector_foreach(&backend->refs, i, entry) { + if (strcmp(entry->name, ref_name) == 0) { + *exists = 1; + break; + } + } + + return 0; +} + +static int refdb_test_backend__write( + git_refdb_backend *_backend, + const git_reference *ref) +{ + refdb_test_backend *backend; + refdb_test_entry *entry; + + assert(_backend); + backend = (refdb_test_backend *)_backend; + + entry = git__calloc(1, sizeof(refdb_test_entry)); + GITERR_CHECK_ALLOC(entry); + + entry->name = git__strdup(git_reference_name(ref)); + GITERR_CHECK_ALLOC(entry->name); + + entry->type = git_reference_type(ref); + + if (entry->type == GIT_REF_OID) + git_oid_cpy(&entry->target.oid, git_reference_target(ref)); + else { + entry->target.symbolic = git__strdup(git_reference_symbolic_target(ref)); + GITERR_CHECK_ALLOC(entry->target.symbolic); + } + + git_vector_insert(&backend->refs, entry); + + return 0; +} + +static int refdb_test_backend__lookup( + git_reference **out, + git_refdb_backend *_backend, + const char *ref_name) +{ + refdb_test_backend *backend; + refdb_test_entry *entry; + size_t i; + + assert(_backend); + backend = (refdb_test_backend *)_backend; + + git_vector_foreach(&backend->refs, i, entry) { + if (strcmp(entry->name, ref_name) == 0) { + const git_oid *oid = + entry->type == GIT_REF_OID ? &entry->target.oid : NULL; + const char *symbolic = + entry->type == GIT_REF_SYMBOLIC ? entry->target.symbolic : NULL; + + if ((*out = git_reference__alloc(backend->refdb, ref_name, oid, symbolic)) == NULL) + return -1; + + return 0; + } + } + + return GIT_ENOTFOUND; +} + +static int refdb_test_backend__foreach( + git_refdb_backend *_backend, + unsigned int list_flags, + git_reference_foreach_cb callback, + void *payload) +{ + refdb_test_backend *backend; + refdb_test_entry *entry; + size_t i; + + assert(_backend); + backend = (refdb_test_backend *)_backend; + + git_vector_foreach(&backend->refs, i, entry) { + if (entry->type == GIT_REF_OID && (list_flags & GIT_REF_OID) == 0) + continue; + + if (entry->type == GIT_REF_SYMBOLIC && (list_flags & GIT_REF_SYMBOLIC) == 0) + continue; + + if (callback(entry->name, payload) != 0) + return GIT_EUSER; + } + + return 0; +} + +static void refdb_test_entry_free(refdb_test_entry *entry) +{ + if (entry->type == GIT_REF_SYMBOLIC) + git__free(entry->target.symbolic); + + git__free(entry->name); + git__free(entry); +} + +static int refdb_test_backend__delete( + git_refdb_backend *_backend, + const git_reference *ref) +{ + refdb_test_backend *backend; + refdb_test_entry *entry; + size_t i; + + assert(_backend); + backend = (refdb_test_backend *)_backend; + + git_vector_foreach(&backend->refs, i, entry) { + if (strcmp(entry->name, git_reference_name(ref)) == 0) { + git_vector_remove(&backend->refs, i); + refdb_test_entry_free(entry); + } + } + + return GIT_ENOTFOUND; +} + +static void refdb_test_backend__free(git_refdb_backend *_backend) +{ + refdb_test_backend *backend; + refdb_test_entry *entry; + size_t i; + + assert(_backend); + backend = (refdb_test_backend *)_backend; + + git_vector_foreach(&backend->refs, i, entry) + refdb_test_entry_free(entry); + + git_vector_free(&backend->refs); + git__free(backend); +} + +int refdb_backend_test( + git_refdb_backend **backend_out, + git_repository *repo) +{ + refdb_test_backend *backend; + git_refdb *refdb; + int error = 0; + + if ((error = git_repository_refdb(&refdb, repo)) < 0) + return error; + + backend = git__calloc(1, sizeof(refdb_test_backend)); + GITERR_CHECK_ALLOC(backend); + + git_vector_init(&backend->refs, 0, ref_name_cmp); + + backend->repo = repo; + backend->refdb = refdb; + + backend->parent.exists = &refdb_test_backend__exists; + backend->parent.lookup = &refdb_test_backend__lookup; + backend->parent.foreach = &refdb_test_backend__foreach; + backend->parent.write = &refdb_test_backend__write; + backend->parent.delete = &refdb_test_backend__delete; + backend->parent.free = &refdb_test_backend__free; + + *backend_out = (git_refdb_backend *)backend; + return 0; +} diff --git a/tests-clar/refdb/testdb.h b/tests-clar/refdb/testdb.h new file mode 100644 index 000000000..e38abd967 --- /dev/null +++ b/tests-clar/refdb/testdb.h @@ -0,0 +1,3 @@ +int refdb_backend_test( + git_refdb_backend **backend_out, + git_repository *repo); diff --git a/tests-clar/refs/branches/delete.c b/tests-clar/refs/branches/delete.c index 21fbc09bb..7af5a3e86 100644 --- a/tests-clar/refs/branches/delete.c +++ b/tests-clar/refs/branches/delete.c @@ -50,9 +50,11 @@ void test_refs_branches_delete__can_delete_a_branch_even_if_HEAD_is_missing(void cl_git_pass(git_reference_lookup(&head, repo, GIT_HEAD_FILE)); git_reference_delete(head); + git_reference_free(head); cl_git_pass(git_branch_lookup(&branch, repo, "br2", GIT_BRANCH_LOCAL)); cl_git_pass(git_branch_delete(branch)); + git_reference_free(branch); } void test_refs_branches_delete__can_delete_a_branch_when_HEAD_is_orphaned(void) @@ -63,6 +65,7 @@ void test_refs_branches_delete__can_delete_a_branch_when_HEAD_is_orphaned(void) cl_git_pass(git_branch_lookup(&branch, repo, "br2", GIT_BRANCH_LOCAL)); cl_git_pass(git_branch_delete(branch)); + git_reference_free(branch); } void test_refs_branches_delete__can_delete_a_branch_pointed_at_by_detached_HEAD(void) @@ -79,6 +82,7 @@ void test_refs_branches_delete__can_delete_a_branch_pointed_at_by_detached_HEAD( cl_git_pass(git_branch_lookup(&branch, repo, "master", GIT_BRANCH_LOCAL)); cl_git_pass(git_branch_delete(branch)); + git_reference_free(branch); } void test_refs_branches_delete__can_delete_a_local_branch(void) @@ -86,6 +90,7 @@ void test_refs_branches_delete__can_delete_a_local_branch(void) git_reference *branch; cl_git_pass(git_branch_lookup(&branch, repo, "br2", GIT_BRANCH_LOCAL)); cl_git_pass(git_branch_delete(branch)); + git_reference_free(branch); } void test_refs_branches_delete__can_delete_a_remote_branch(void) @@ -93,6 +98,7 @@ void test_refs_branches_delete__can_delete_a_remote_branch(void) git_reference *branch; cl_git_pass(git_branch_lookup(&branch, repo, "nulltoken/master", GIT_BRANCH_REMOTE)); cl_git_pass(git_branch_delete(branch)); + git_reference_free(branch); } void test_refs_branches_delete__deleting_a_branch_removes_related_configuration_data(void) @@ -104,6 +110,7 @@ void test_refs_branches_delete__deleting_a_branch_removes_related_configuration_ cl_git_pass(git_branch_lookup(&branch, repo, "track-local", GIT_BRANCH_LOCAL)); cl_git_pass(git_branch_delete(branch)); + git_reference_free(branch); assert_config_entry_existence(repo, "branch.track-local.remote", false); assert_config_entry_existence(repo, "branch.track-local.merge", false); diff --git a/tests-clar/refs/branches/move.c b/tests-clar/refs/branches/move.c index 17fb6dfe6..7267f941d 100644 --- a/tests-clar/refs/branches/move.c +++ b/tests-clar/refs/branches/move.c @@ -3,20 +3,14 @@ #include "config/config_helpers.h" static git_repository *repo; -static git_reference *ref; void test_refs_branches_move__initialize(void) { repo = cl_git_sandbox_init("testrepo.git"); - - cl_git_pass(git_reference_lookup(&ref, repo, "refs/heads/br2")); } void test_refs_branches_move__cleanup(void) { - git_reference_free(ref); - ref = NULL; - cl_git_sandbox_cleanup(); } @@ -24,56 +18,99 @@ void test_refs_branches_move__cleanup(void) void test_refs_branches_move__can_move_a_local_branch(void) { - cl_git_pass(git_branch_move(ref, NEW_BRANCH_NAME, 0)); - cl_assert_equal_s(GIT_REFS_HEADS_DIR NEW_BRANCH_NAME, git_reference_name(ref)); + git_reference *original_ref, *new_ref; + + cl_git_pass(git_reference_lookup(&original_ref, repo, "refs/heads/br2")); + + cl_git_pass(git_branch_move(&new_ref, original_ref, NEW_BRANCH_NAME, 0)); + cl_assert_equal_s(GIT_REFS_HEADS_DIR NEW_BRANCH_NAME, git_reference_name(new_ref)); + + git_reference_free(original_ref); + git_reference_free(new_ref); } void test_refs_branches_move__can_move_a_local_branch_to_a_different_namespace(void) { + git_reference *original_ref, *new_ref, *newer_ref; + + cl_git_pass(git_reference_lookup(&original_ref, repo, "refs/heads/br2")); + /* Downward */ - cl_git_pass(git_branch_move(ref, "somewhere/" NEW_BRANCH_NAME, 0)); + cl_git_pass(git_branch_move(&new_ref, original_ref, "somewhere/" NEW_BRANCH_NAME, 0)); + git_reference_free(original_ref); /* Upward */ - cl_git_pass(git_branch_move(ref, "br2", 0)); + cl_git_pass(git_branch_move(&newer_ref, new_ref, "br2", 0)); + git_reference_free(new_ref); + + git_reference_free(newer_ref); } void test_refs_branches_move__can_move_a_local_branch_to_a_partially_colliding_namespace(void) { + git_reference *original_ref, *new_ref, *newer_ref; + + cl_git_pass(git_reference_lookup(&original_ref, repo, "refs/heads/br2")); + /* Downward */ - cl_git_pass(git_branch_move(ref, "br2/" NEW_BRANCH_NAME, 0)); + cl_git_pass(git_branch_move(&new_ref, original_ref, "br2/" NEW_BRANCH_NAME, 0)); + git_reference_free(original_ref); /* Upward */ - cl_git_pass(git_branch_move(ref, "br2", 0)); + cl_git_pass(git_branch_move(&newer_ref, new_ref, "br2", 0)); + git_reference_free(new_ref); + + git_reference_free(newer_ref); } void test_refs_branches_move__can_not_move_a_branch_if_its_destination_name_collide_with_an_existing_one(void) { - cl_assert_equal_i(GIT_EEXISTS, git_branch_move(ref, "master", 0)); + git_reference *original_ref, *new_ref; + + cl_git_pass(git_reference_lookup(&original_ref, repo, "refs/heads/br2")); + + cl_assert_equal_i(GIT_EEXISTS, git_branch_move(&new_ref, original_ref, "master", 0)); + + git_reference_free(original_ref); } void test_refs_branches_move__moving_a_branch_with_an_invalid_name_returns_EINVALIDSPEC(void) { - cl_assert_equal_i(GIT_EINVALIDSPEC, git_branch_move(ref, "Inv@{id", 0)); + git_reference *original_ref, *new_ref; + + cl_git_pass(git_reference_lookup(&original_ref, repo, "refs/heads/br2")); + + cl_assert_equal_i(GIT_EINVALIDSPEC, git_branch_move(&new_ref, original_ref, "Inv@{id", 0)); + + git_reference_free(original_ref); } void test_refs_branches_move__can_not_move_a_non_branch(void) { - git_reference *tag; + git_reference *tag, *new_ref; cl_git_pass(git_reference_lookup(&tag, repo, "refs/tags/e90810b")); - cl_git_fail(git_branch_move(tag, NEW_BRANCH_NAME, 0)); + cl_git_fail(git_branch_move(&new_ref, tag, NEW_BRANCH_NAME, 0)); git_reference_free(tag); } void test_refs_branches_move__can_force_move_over_an_existing_branch(void) { - cl_git_pass(git_branch_move(ref, "master", 1)); + git_reference *original_ref, *new_ref; + + cl_git_pass(git_reference_lookup(&original_ref, repo, "refs/heads/br2")); + + cl_git_pass(git_branch_move(&new_ref, original_ref, "master", 1)); + + git_reference_free(original_ref); + git_reference_free(new_ref); } void test_refs_branches_move__moving_a_branch_moves_related_configuration_data(void) { git_reference *branch; + git_reference *new_branch; cl_git_pass(git_branch_lookup(&branch, repo, "track-local", GIT_BRANCH_LOCAL)); @@ -82,23 +119,26 @@ void test_refs_branches_move__moving_a_branch_moves_related_configuration_data(v assert_config_entry_existence(repo, "branch.moved.remote", false); assert_config_entry_existence(repo, "branch.moved.merge", false); - cl_git_pass(git_branch_move(branch, "moved", 0)); + cl_git_pass(git_branch_move(&new_branch, branch, "moved", 0)); + git_reference_free(branch); assert_config_entry_existence(repo, "branch.track-local.remote", false); assert_config_entry_existence(repo, "branch.track-local.merge", false); assert_config_entry_existence(repo, "branch.moved.remote", true); assert_config_entry_existence(repo, "branch.moved.merge", true); - git_reference_free(branch); + git_reference_free(new_branch); } void test_refs_branches_move__moving_the_branch_pointed_at_by_HEAD_updates_HEAD(void) { git_reference *branch; + git_reference *new_branch; cl_git_pass(git_reference_lookup(&branch, repo, "refs/heads/master")); - cl_git_pass(git_branch_move(branch, "master2", 0)); + cl_git_pass(git_branch_move(&new_branch, branch, "master2", 0)); git_reference_free(branch); + git_reference_free(new_branch); cl_git_pass(git_repository_head(&branch, repo)); cl_assert_equal_s("refs/heads/master2", git_reference_name(branch)); diff --git a/tests-clar/refs/crashes.c b/tests-clar/refs/crashes.c index 9fb5ff627..5a1885a7a 100644 --- a/tests-clar/refs/crashes.c +++ b/tests-clar/refs/crashes.c @@ -10,8 +10,11 @@ void test_refs_crashes__double_free(void) cl_git_pass(git_reference_symbolic_create(&ref, repo, REFNAME, "refs/heads/master", 0)); cl_git_pass(git_reference_lookup(&ref2, repo, REFNAME)); cl_git_pass(git_reference_delete(ref)); + git_reference_free(ref); + git_reference_free(ref2); + /* reference is gone from disk, so reloading it will fail */ - cl_git_fail(git_reference_reload(ref2)); + cl_git_fail(git_reference_lookup(&ref2, repo, REFNAME)); git_repository_free(repo); } diff --git a/tests-clar/refs/create.c b/tests-clar/refs/create.c index 56c323d8a..85ff05aa9 100644 --- a/tests-clar/refs/create.c +++ b/tests-clar/refs/create.c @@ -3,6 +3,7 @@ #include "repository.h" #include "git2/reflog.h" #include "reflog.h" +#include "ref_helpers.h" static const char *current_master_tip = "099fabac3a9ea935598528c27f866e34089c2eff"; static const char *current_head_target = "refs/heads/master"; @@ -36,7 +37,7 @@ void test_refs_create__symbolic(void) /* Ensure the reference can be looked-up... */ cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, new_head_tracker)); cl_assert(git_reference_type(looked_up_ref) & GIT_REF_SYMBOLIC); - cl_assert(git_reference_is_packed(looked_up_ref) == 0); + cl_assert(reference_is_packed(looked_up_ref) == 0); cl_assert_equal_s(looked_up_ref->name, new_head_tracker); /* ...peeled.. */ @@ -99,7 +100,7 @@ void test_refs_create__oid(void) /* Ensure the reference can be looked-up... */ cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, new_head)); cl_assert(git_reference_type(looked_up_ref) & GIT_REF_OID); - cl_assert(git_reference_is_packed(looked_up_ref) == 0); + cl_assert(reference_is_packed(looked_up_ref) == 0); cl_assert_equal_s(looked_up_ref->name, new_head); /* ...and that it points to the current master tip */ diff --git a/tests-clar/refs/delete.c b/tests-clar/refs/delete.c index cc5ab3940..ac517d869 100644 --- a/tests-clar/refs/delete.c +++ b/tests-clar/refs/delete.c @@ -3,6 +3,7 @@ #include "repository.h" #include "git2/reflog.h" #include "reflog.h" +#include "ref_helpers.h" static const char *packed_test_head_name = "refs/heads/packed-test"; static const char *current_master_tip = "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"; @@ -37,10 +38,11 @@ void test_refs_delete__packed_loose(void) cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, packed_test_head_name)); /* Ensure it's the loose version that has been found */ - cl_assert(git_reference_is_packed(looked_up_ref) == 0); + cl_assert(reference_is_packed(looked_up_ref) == 0); /* Now that the reference is deleted... */ cl_git_pass(git_reference_delete(looked_up_ref)); + git_reference_free(looked_up_ref); /* Looking up the reference once again should not retrieve it */ cl_git_fail(git_reference_lookup(&another_looked_up_ref, g_repo, packed_test_head_name)); @@ -56,6 +58,7 @@ void test_refs_delete__packed_only(void) { // can delete a just packed reference git_reference *ref; + git_refdb *refdb; git_oid id; const char *new_ref = "refs/heads/new_ref"; @@ -69,17 +72,20 @@ void test_refs_delete__packed_only(void) cl_git_pass(git_reference_lookup(&ref, g_repo, new_ref)); /* Ensure it's a loose reference */ - cl_assert(git_reference_is_packed(ref) == 0); + cl_assert(reference_is_packed(ref) == 0); /* Pack all existing references */ - cl_git_pass(git_reference_packall(g_repo)); + cl_git_pass(git_repository_refdb(&refdb, g_repo)); + cl_git_pass(git_refdb_compress(refdb)); /* Reload the reference from disk */ - cl_git_pass(git_reference_reload(ref)); + git_reference_free(ref); + cl_git_pass(git_reference_lookup(&ref, g_repo, new_ref)); /* Ensure it's a packed reference */ - cl_assert(git_reference_is_packed(ref) == 1); + cl_assert(reference_is_packed(ref) == 1); /* This should pass */ cl_git_pass(git_reference_delete(ref)); + git_reference_free(ref); } diff --git a/tests-clar/refs/pack.c b/tests-clar/refs/pack.c index 305594c28..8da36ccd4 100644 --- a/tests-clar/refs/pack.c +++ b/tests-clar/refs/pack.c @@ -3,6 +3,7 @@ #include "repository.h" #include "git2/reflog.h" #include "reflog.h" +#include "ref_helpers.h" static const char *loose_tag_ref_name = "refs/tags/e90810b"; @@ -18,6 +19,14 @@ void test_refs_pack__cleanup(void) cl_git_sandbox_cleanup(); } +void packall() +{ + git_refdb *refdb; + + cl_git_pass(git_repository_refdb(&refdb, g_repo)); + cl_git_pass(git_refdb_compress(refdb)); +} + void test_refs_pack__empty(void) { // create a packfile for an empty folder @@ -27,7 +36,7 @@ void test_refs_pack__empty(void) cl_git_pass(git_futils_mkdir_r(temp_path.ptr, NULL, GIT_REFS_DIR_MODE)); git_buf_free(&temp_path); - cl_git_pass(git_reference_packall(g_repo)); + packall(); } void test_refs_pack__loose(void) @@ -38,7 +47,7 @@ void test_refs_pack__loose(void) /* Ensure a known loose ref can be looked up */ cl_git_pass(git_reference_lookup(&reference, g_repo, loose_tag_ref_name)); - cl_assert(git_reference_is_packed(reference) == 0); + cl_assert(reference_is_packed(reference) == 0); cl_assert_equal_s(reference->name, loose_tag_ref_name); git_reference_free(reference); @@ -47,7 +56,7 @@ void test_refs_pack__loose(void) * called `points_to_blob`, to make sure we can properly * pack weak tags */ - cl_git_pass(git_reference_packall(g_repo)); + packall(); /* Ensure the packed-refs file exists */ cl_git_pass(git_buf_joinpath(&temp_path, g_repo->path_repository, GIT_PACKEDREFS_FILE)); @@ -55,7 +64,7 @@ void test_refs_pack__loose(void) /* Ensure the known ref can still be looked up but is now packed */ cl_git_pass(git_reference_lookup(&reference, g_repo, loose_tag_ref_name)); - cl_assert(git_reference_is_packed(reference)); + cl_assert(reference_is_packed(reference)); cl_assert_equal_s(reference->name, loose_tag_ref_name); /* Ensure the known ref has been removed from the loose folder structure */ diff --git a/tests-clar/refs/read.c b/tests-clar/refs/read.c index 3e2a59afd..afb6be008 100644 --- a/tests-clar/refs/read.c +++ b/tests-clar/refs/read.c @@ -3,6 +3,7 @@ #include "repository.h" #include "git2/reflog.h" #include "reflog.h" +#include "ref_helpers.h" static const char *loose_tag_ref_name = "refs/tags/e90810b"; static const char *non_existing_tag_ref_name = "refs/tags/i-do-not-exist"; @@ -34,7 +35,7 @@ void test_refs_read__loose_tag(void) cl_git_pass(git_reference_lookup(&reference, g_repo, loose_tag_ref_name)); cl_assert(git_reference_type(reference) & GIT_REF_OID); - cl_assert(git_reference_is_packed(reference) == 0); + cl_assert(reference_is_packed(reference) == 0); cl_assert_equal_s(reference->name, loose_tag_ref_name); cl_git_pass(git_object_lookup(&object, g_repo, git_reference_target(reference), GIT_OBJ_ANY)); @@ -71,7 +72,7 @@ void test_refs_read__symbolic(void) cl_git_pass(git_reference_lookup(&reference, g_repo, GIT_HEAD_FILE)); cl_assert(git_reference_type(reference) & GIT_REF_SYMBOLIC); - cl_assert(git_reference_is_packed(reference) == 0); + cl_assert(reference_is_packed(reference) == 0); cl_assert_equal_s(reference->name, GIT_HEAD_FILE); cl_git_pass(git_reference_resolve(&resolved_ref, reference)); @@ -99,7 +100,7 @@ void test_refs_read__nested_symbolic(void) cl_git_pass(git_reference_lookup(&reference, g_repo, head_tracker_sym_ref_name)); cl_assert(git_reference_type(reference) & GIT_REF_SYMBOLIC); - cl_assert(git_reference_is_packed(reference) == 0); + cl_assert(reference_is_packed(reference) == 0); cl_assert_equal_s(reference->name, head_tracker_sym_ref_name); cl_git_pass(git_reference_resolve(&resolved_ref, reference)); @@ -167,7 +168,7 @@ void test_refs_read__packed(void) cl_git_pass(git_reference_lookup(&reference, g_repo, packed_head_name)); cl_assert(git_reference_type(reference) & GIT_REF_OID); - cl_assert(git_reference_is_packed(reference)); + cl_assert(reference_is_packed(reference)); cl_assert_equal_s(reference->name, packed_head_name); cl_git_pass(git_object_lookup(&object, g_repo, git_reference_target(reference), GIT_OBJ_ANY)); @@ -188,7 +189,7 @@ void test_refs_read__loose_first(void) git_reference_free(reference); cl_git_pass(git_reference_lookup(&reference, g_repo, packed_test_head_name)); cl_assert(git_reference_type(reference) & GIT_REF_OID); - cl_assert(git_reference_is_packed(reference) == 0); + cl_assert(reference_is_packed(reference) == 0); cl_assert_equal_s(reference->name, packed_test_head_name); git_reference_free(reference); diff --git a/tests-clar/refs/ref_helpers.c b/tests-clar/refs/ref_helpers.c new file mode 100644 index 000000000..16ab9e6ef --- /dev/null +++ b/tests-clar/refs/ref_helpers.c @@ -0,0 +1,25 @@ +#include "git2/repository.h" +#include "git2/refs.h" +#include "common.h" +#include "util.h" +#include "buffer.h" +#include "path.h" + +int reference_is_packed(git_reference *ref) +{ + git_buf ref_path = GIT_BUF_INIT; + int packed; + + assert(ref); + + if (git_buf_joinpath(&ref_path, + git_repository_path(git_reference_owner(ref)), + git_reference_name(ref)) < 0) + return -1; + + packed = !git_path_isfile(ref_path.ptr); + + git_buf_free(&ref_path); + + return packed; +} diff --git a/tests-clar/refs/ref_helpers.h b/tests-clar/refs/ref_helpers.h new file mode 100644 index 000000000..0ef55bfce --- /dev/null +++ b/tests-clar/refs/ref_helpers.h @@ -0,0 +1 @@ +int reference_is_packed(git_reference *ref); diff --git a/tests-clar/refs/reflog/reflog.c b/tests-clar/refs/reflog/reflog.c index 19ee53567..1cd0ddd92 100644 --- a/tests-clar/refs/reflog/reflog.c +++ b/tests-clar/refs/reflog/reflog.c @@ -90,7 +90,7 @@ void test_refs_reflog_reflog__append_then_read(void) void test_refs_reflog_reflog__renaming_the_reference_moves_the_reflog(void) { - git_reference *master; + git_reference *master, *new_master; git_buf master_log_path = GIT_BUF_INIT, moved_log_path = GIT_BUF_INIT; git_buf_joinpath(&master_log_path, git_repository_path(g_repo), GIT_REFLOG_DIR); @@ -102,12 +102,13 @@ void test_refs_reflog_reflog__renaming_the_reference_moves_the_reflog(void) cl_assert_equal_i(false, git_path_isfile(git_buf_cstr(&moved_log_path))); cl_git_pass(git_reference_lookup(&master, g_repo, "refs/heads/master")); - cl_git_pass(git_reference_rename(master, "refs/moved", 0)); + cl_git_pass(git_reference_rename(&new_master, master, "refs/moved", 0)); + git_reference_free(master); cl_assert_equal_i(false, git_path_isfile(git_buf_cstr(&master_log_path))); cl_assert_equal_i(true, git_path_isfile(git_buf_cstr(&moved_log_path))); - git_reference_free(master); + git_reference_free(new_master); git_buf_free(&moved_log_path); git_buf_free(&master_log_path); } @@ -152,7 +153,7 @@ void test_refs_reflog_reflog__reading_the_reflog_from_a_reference_with_no_log_re void test_refs_reflog_reflog__cannot_write_a_moved_reflog(void) { - git_reference *master; + git_reference *master, *new_master; git_buf master_log_path = GIT_BUF_INIT, moved_log_path = GIT_BUF_INIT; git_reflog *reflog; @@ -161,12 +162,13 @@ void test_refs_reflog_reflog__cannot_write_a_moved_reflog(void) cl_git_pass(git_reflog_write(reflog)); - cl_git_pass(git_reference_rename(master, "refs/moved", 0)); + cl_git_pass(git_reference_rename(&new_master, master, "refs/moved", 0)); + git_reference_free(master); cl_git_fail(git_reflog_write(reflog)); git_reflog_free(reflog); - git_reference_free(master); + git_reference_free(new_master); git_buf_free(&moved_log_path); git_buf_free(&master_log_path); } diff --git a/tests-clar/refs/rename.c b/tests-clar/refs/rename.c index 5c1e8a798..e39abeb05 100644 --- a/tests-clar/refs/rename.c +++ b/tests-clar/refs/rename.c @@ -3,6 +3,7 @@ #include "repository.h" #include "git2/reflog.h" #include "reflog.h" +#include "ref_helpers.h" static const char *loose_tag_ref_name = "refs/tags/e90810b"; static const char *packed_head_name = "refs/heads/packed"; @@ -32,7 +33,7 @@ void test_refs_rename__cleanup(void) void test_refs_rename__loose(void) { // rename a loose reference - git_reference *looked_up_ref, *another_looked_up_ref; + git_reference *looked_up_ref, *new_ref, *another_looked_up_ref; git_buf temp_path = GIT_BUF_INIT; const char *new_name = "refs/tags/Nemo/knows/refs.kung-fu"; @@ -44,28 +45,29 @@ void test_refs_rename__loose(void) cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, loose_tag_ref_name)); /* ... which is indeed loose */ - cl_assert(git_reference_is_packed(looked_up_ref) == 0); + cl_assert(reference_is_packed(looked_up_ref) == 0); /* Now that the reference is renamed... */ - cl_git_pass(git_reference_rename(looked_up_ref, new_name, 0)); - cl_assert_equal_s(looked_up_ref->name, new_name); + cl_git_pass(git_reference_rename(&new_ref, looked_up_ref, new_name, 0)); + cl_assert_equal_s(new_ref->name, new_name); + git_reference_free(looked_up_ref); /* ...It can't be looked-up with the old name... */ cl_git_fail(git_reference_lookup(&another_looked_up_ref, g_repo, loose_tag_ref_name)); /* ...but the new name works ok... */ cl_git_pass(git_reference_lookup(&another_looked_up_ref, g_repo, new_name)); - cl_assert_equal_s(another_looked_up_ref->name, new_name); + cl_assert_equal_s(new_ref->name, new_name); - /* .. the ref is still loose... */ - cl_assert(git_reference_is_packed(another_looked_up_ref) == 0); - cl_assert(git_reference_is_packed(looked_up_ref) == 0); + /* .. the new ref is loose... */ + cl_assert(reference_is_packed(another_looked_up_ref) == 0); + cl_assert(reference_is_packed(new_ref) == 0); /* ...and the ref can be found in the file system */ cl_git_pass(git_buf_joinpath(&temp_path, g_repo->path_repository, new_name)); cl_assert(git_path_exists(temp_path.ptr)); - git_reference_free(looked_up_ref); + git_reference_free(new_ref); git_reference_free(another_looked_up_ref); git_buf_free(&temp_path); } @@ -73,7 +75,7 @@ void test_refs_rename__loose(void) void test_refs_rename__packed(void) { // rename a packed reference (should make it loose) - git_reference *looked_up_ref, *another_looked_up_ref; + git_reference *looked_up_ref, *new_ref, *another_looked_up_ref; git_buf temp_path = GIT_BUF_INIT; const char *brand_new_name = "refs/heads/brand_new_name"; @@ -85,11 +87,12 @@ void test_refs_rename__packed(void) cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, packed_head_name)); /* .. and it's packed */ - cl_assert(git_reference_is_packed(looked_up_ref) != 0); + cl_assert(reference_is_packed(looked_up_ref) != 0); /* Now that the reference is renamed... */ - cl_git_pass(git_reference_rename(looked_up_ref, brand_new_name, 0)); - cl_assert_equal_s(looked_up_ref->name, brand_new_name); + cl_git_pass(git_reference_rename(&new_ref, looked_up_ref, brand_new_name, 0)); + cl_assert_equal_s(new_ref->name, brand_new_name); + git_reference_free(looked_up_ref); /* ...It can't be looked-up with the old name... */ cl_git_fail(git_reference_lookup(&another_looked_up_ref, g_repo, packed_head_name)); @@ -99,14 +102,14 @@ void test_refs_rename__packed(void) cl_assert_equal_s(another_looked_up_ref->name, brand_new_name); /* .. the ref is no longer packed... */ - cl_assert(git_reference_is_packed(another_looked_up_ref) == 0); - cl_assert(git_reference_is_packed(looked_up_ref) == 0); + cl_assert(reference_is_packed(another_looked_up_ref) == 0); + cl_assert(reference_is_packed(new_ref) == 0); /* ...and the ref now happily lives in the file system */ cl_git_pass(git_buf_joinpath(&temp_path, g_repo->path_repository, brand_new_name)); cl_assert(git_path_exists(temp_path.ptr)); - git_reference_free(looked_up_ref); + git_reference_free(new_ref); git_reference_free(another_looked_up_ref); git_buf_free(&temp_path); } @@ -114,7 +117,7 @@ void test_refs_rename__packed(void) void test_refs_rename__packed_doesnt_pack_others(void) { // renaming a packed reference does not pack another reference which happens to be in both loose and pack state - git_reference *looked_up_ref, *another_looked_up_ref; + git_reference *looked_up_ref, *another_looked_up_ref, *renamed_ref; git_buf temp_path = GIT_BUF_INIT; const char *brand_new_name = "refs/heads/brand_new_name"; @@ -126,28 +129,29 @@ void test_refs_rename__packed_doesnt_pack_others(void) cl_git_pass(git_reference_lookup(&another_looked_up_ref, g_repo, packed_test_head_name)); /* Ensure it's loose */ - cl_assert(git_reference_is_packed(another_looked_up_ref) == 0); + cl_assert(reference_is_packed(another_looked_up_ref) == 0); git_reference_free(another_looked_up_ref); /* Lookup the reference to rename */ cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, packed_head_name)); /* Ensure it's packed */ - cl_assert(git_reference_is_packed(looked_up_ref) != 0); + cl_assert(reference_is_packed(looked_up_ref) != 0); /* Now that the reference is renamed... */ - cl_git_pass(git_reference_rename(looked_up_ref, brand_new_name, 0)); + cl_git_pass(git_reference_rename(&renamed_ref, looked_up_ref, brand_new_name, 0)); + git_reference_free(looked_up_ref); /* Lookup the other reference */ cl_git_pass(git_reference_lookup(&another_looked_up_ref, g_repo, packed_test_head_name)); /* Ensure it's loose */ - cl_assert(git_reference_is_packed(another_looked_up_ref) == 0); + cl_assert(reference_is_packed(another_looked_up_ref) == 0); /* Ensure the other ref still exists on the file system */ cl_assert(git_path_exists(temp_path.ptr)); - git_reference_free(looked_up_ref); + git_reference_free(renamed_ref); git_reference_free(another_looked_up_ref); git_buf_free(&temp_path); } @@ -155,13 +159,13 @@ void test_refs_rename__packed_doesnt_pack_others(void) void test_refs_rename__name_collision(void) { // can not rename a reference with the name of an existing reference - git_reference *looked_up_ref; + git_reference *looked_up_ref, *renamed_ref; /* An existing reference... */ cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, packed_head_name)); /* Can not be renamed to the name of another existing reference. */ - cl_git_fail(git_reference_rename(looked_up_ref, packed_test_head_name, 0)); + cl_git_fail(git_reference_rename(&renamed_ref, looked_up_ref, packed_test_head_name, 0)); git_reference_free(looked_up_ref); /* Failure to rename it hasn't corrupted its state */ @@ -174,7 +178,7 @@ void test_refs_rename__name_collision(void) void test_refs_rename__invalid_name(void) { // can not rename a reference with an invalid name - git_reference *looked_up_ref; + git_reference *looked_up_ref, *renamed_ref; /* An existing oid reference... */ cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, packed_test_head_name)); @@ -182,12 +186,12 @@ void test_refs_rename__invalid_name(void) /* Can not be renamed with an invalid name. */ cl_assert_equal_i( GIT_EINVALIDSPEC, - git_reference_rename(looked_up_ref, "Hello! I'm a very invalid name.", 0)); + git_reference_rename(&renamed_ref, looked_up_ref, "Hello! I'm a very invalid name.", 0)); /* Can not be renamed outside of the refs hierarchy * unless it's ALL_CAPS_AND_UNDERSCORES. */ - cl_assert_equal_i(GIT_EINVALIDSPEC, git_reference_rename(looked_up_ref, "i-will-sudo-you", 0)); + cl_assert_equal_i(GIT_EINVALIDSPEC, git_reference_rename(&renamed_ref, looked_up_ref, "i-will-sudo-you", 0)); /* Failure to rename it hasn't corrupted its state */ git_reference_free(looked_up_ref); @@ -200,7 +204,7 @@ void test_refs_rename__invalid_name(void) void test_refs_rename__force_loose_packed(void) { // can force-rename a packed reference with the name of an existing loose and packed reference - git_reference *looked_up_ref; + git_reference *looked_up_ref, *renamed_ref; git_oid oid; /* An existing reference... */ @@ -208,8 +212,9 @@ void test_refs_rename__force_loose_packed(void) git_oid_cpy(&oid, git_reference_target(looked_up_ref)); /* Can be force-renamed to the name of another existing reference. */ - cl_git_pass(git_reference_rename(looked_up_ref, packed_test_head_name, 1)); + cl_git_pass(git_reference_rename(&renamed_ref, looked_up_ref, packed_test_head_name, 1)); git_reference_free(looked_up_ref); + git_reference_free(renamed_ref); /* Check we actually renamed it */ cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, packed_test_head_name)); @@ -224,7 +229,7 @@ void test_refs_rename__force_loose_packed(void) void test_refs_rename__force_loose(void) { // can force-rename a loose reference with the name of an existing loose reference - git_reference *looked_up_ref; + git_reference *looked_up_ref, *renamed_ref; git_oid oid; /* An existing reference... */ @@ -232,8 +237,9 @@ void test_refs_rename__force_loose(void) git_oid_cpy(&oid, git_reference_target(looked_up_ref)); /* Can be force-renamed to the name of another existing reference. */ - cl_git_pass(git_reference_rename(looked_up_ref, "refs/heads/test", 1)); + cl_git_pass(git_reference_rename(&renamed_ref, looked_up_ref, "refs/heads/test", 1)); git_reference_free(looked_up_ref); + git_reference_free(renamed_ref); /* Check we actually renamed it */ cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, "refs/heads/test")); @@ -252,6 +258,7 @@ void test_refs_rename__overwrite(void) { // can not overwrite name of existing reference git_reference *ref, *ref_one, *ref_one_new, *ref_two; + git_refdb *refdb; git_oid id; cl_git_pass(git_reference_lookup(&ref, g_repo, ref_master_name)); @@ -264,7 +271,8 @@ void test_refs_rename__overwrite(void) cl_git_pass(git_reference_create(&ref_two, g_repo, ref_two_name, &id, 0)); /* Pack everything */ - cl_git_pass(git_reference_packall(g_repo)); + cl_git_pass(git_repository_refdb(&refdb, g_repo)); + cl_git_pass(git_refdb_compress(refdb)); /* Attempt to create illegal reference */ cl_git_fail(git_reference_create(&ref_one_new, g_repo, ref_one_name_new, &id, 0)); @@ -282,7 +290,7 @@ void test_refs_rename__overwrite(void) void test_refs_rename__prefix(void) { // can be renamed to a new name prefixed with the old name - git_reference *ref, *ref_two, *looked_up_ref; + git_reference *ref, *ref_two, *looked_up_ref, *renamed_ref; git_oid id; cl_git_pass(git_reference_lookup(&ref, g_repo, ref_master_name)); @@ -297,8 +305,9 @@ void test_refs_rename__prefix(void) cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, ref_two_name)); /* Can be rename to a new name starting with the old name. */ - cl_git_pass(git_reference_rename(looked_up_ref, ref_two_name_new, 0)); + cl_git_pass(git_reference_rename(&renamed_ref, looked_up_ref, ref_two_name_new, 0)); git_reference_free(looked_up_ref); + git_reference_free(renamed_ref); /* Check we actually renamed it */ cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, ref_two_name_new)); @@ -314,7 +323,7 @@ void test_refs_rename__prefix(void) void test_refs_rename__move_up(void) { // can move a reference to a upper reference hierarchy - git_reference *ref, *ref_two, *looked_up_ref; + git_reference *ref, *ref_two, *looked_up_ref, *renamed_ref; git_oid id; cl_git_pass(git_reference_lookup(&ref, g_repo, ref_master_name)); @@ -330,13 +339,15 @@ void test_refs_rename__move_up(void) cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, ref_two_name_new)); /* Can be renamed upward the reference tree. */ - cl_git_pass(git_reference_rename(looked_up_ref, ref_two_name, 0)); + cl_git_pass(git_reference_rename(&renamed_ref, looked_up_ref, ref_two_name, 0)); git_reference_free(looked_up_ref); + git_reference_free(renamed_ref); /* Check we actually renamed it */ cl_git_pass(git_reference_lookup(&looked_up_ref, g_repo, ref_two_name)); cl_assert_equal_s(looked_up_ref->name, ref_two_name); git_reference_free(looked_up_ref); + cl_git_fail(git_reference_lookup(&looked_up_ref, g_repo, ref_two_name_new)); git_reference_free(ref); git_reference_free(looked_up_ref); @@ -344,11 +355,11 @@ void test_refs_rename__move_up(void) void test_refs_rename__propagate_eexists(void) { - git_reference *ref; + git_reference *ref, *new_ref; cl_git_pass(git_reference_lookup(&ref, g_repo, packed_head_name)); - cl_assert_equal_i(GIT_EEXISTS, git_reference_rename(ref, packed_test_head_name, 0)); + cl_assert_equal_i(GIT_EEXISTS, git_reference_rename(&new_ref, ref, packed_test_head_name, 0)); git_reference_free(ref); } diff --git a/tests-clar/refs/revparse.c b/tests-clar/refs/revparse.c index be92c1956..e0bccf4a9 100644 --- a/tests-clar/refs/revparse.c +++ b/tests-clar/refs/revparse.c @@ -227,7 +227,7 @@ void test_refs_revparse__previous_head(void) static void create_fake_stash_reference_and_reflog(git_repository *repo) { - git_reference *master; + git_reference *master, *new_master; git_buf log_path = GIT_BUF_INIT; git_buf_joinpath(&log_path, git_repository_path(repo), "logs/refs/fakestash"); @@ -235,12 +235,13 @@ static void create_fake_stash_reference_and_reflog(git_repository *repo) cl_assert_equal_i(false, git_path_isfile(git_buf_cstr(&log_path))); cl_git_pass(git_reference_lookup(&master, repo, "refs/heads/master")); - cl_git_pass(git_reference_rename(master, "refs/fakestash", 0)); + cl_git_pass(git_reference_rename(&new_master, master, "refs/fakestash", 0)); + git_reference_free(master); cl_assert_equal_i(true, git_path_isfile(git_buf_cstr(&log_path))); git_buf_free(&log_path); - git_reference_free(master); + git_reference_free(new_master); } void test_refs_revparse__reflog_of_a_ref_under_refs(void) diff --git a/tests-clar/refs/setter.c b/tests-clar/refs/setter.c new file mode 100644 index 000000000..713af814f --- /dev/null +++ b/tests-clar/refs/setter.c @@ -0,0 +1,99 @@ +#include "clar_libgit2.h" + +#include "repository.h" +#include "git2/reflog.h" +#include "reflog.h" +#include "git2/refs.h" + +static const char *ref_name = "refs/heads/other"; +static const char *ref_master_name = "refs/heads/master"; +static const char *ref_test_name = "refs/heads/test"; + +static git_repository *g_repo; + +void test_refs_setter__initialize(void) +{ + g_repo = cl_git_sandbox_init("testrepo"); +} + +void test_refs_setter__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_refs_setter__update_direct(void) +{ + git_reference *ref, *test_ref, *new_ref; + git_oid id; + + cl_git_pass(git_reference_lookup(&ref, g_repo, ref_master_name)); + cl_assert(git_reference_type(ref) == GIT_REF_OID); + git_oid_cpy(&id, git_reference_target(ref)); + git_reference_free(ref); + + cl_git_pass(git_reference_lookup(&test_ref, g_repo, ref_test_name)); + cl_assert(git_reference_type(test_ref) == GIT_REF_OID); + + cl_git_pass(git_reference_set_target(&new_ref, test_ref, &id)); + + git_reference_free(test_ref); + git_reference_free(new_ref); + + cl_git_pass(git_reference_lookup(&test_ref, g_repo, ref_test_name)); + cl_assert(git_reference_type(test_ref) == GIT_REF_OID); + cl_assert(git_oid_cmp(&id, git_reference_target(test_ref)) == 0); + git_reference_free(test_ref); +} + +void test_refs_setter__update_symbolic(void) +{ + git_reference *head, *new_head; + + cl_git_pass(git_reference_lookup(&head, g_repo, "HEAD")); + cl_assert(git_reference_type(head) == GIT_REF_SYMBOLIC); + cl_assert(strcmp(git_reference_symbolic_target(head), ref_master_name) == 0); + + cl_git_pass(git_reference_symbolic_set_target(&new_head, head, ref_test_name)); + git_reference_free(new_head); + git_reference_free(head); + + cl_git_pass(git_reference_lookup(&head, g_repo, "HEAD")); + cl_assert(git_reference_type(head) == GIT_REF_SYMBOLIC); + cl_assert(strcmp(git_reference_symbolic_target(head), ref_test_name) == 0); + git_reference_free(head); +} + +void test_refs_setter__cant_update_direct_with_symbolic(void) +{ + // Overwrite an existing object id reference with a symbolic one + git_reference *ref, *new; + git_oid id; + + cl_git_pass(git_reference_lookup(&ref, g_repo, ref_master_name)); + cl_assert(git_reference_type(ref) == GIT_REF_OID); + git_oid_cpy(&id, git_reference_target(ref)); + + cl_git_fail(git_reference_symbolic_set_target(&new, ref, ref_name)); + + git_reference_free(ref); +} + +void test_refs_setter__cant_update_symbolic_with_direct(void) +{ + // Overwrite an existing symbolic reference with an object id one + git_reference *ref, *new; + git_oid id; + + cl_git_pass(git_reference_lookup(&ref, g_repo, ref_master_name)); + cl_assert(git_reference_type(ref) == GIT_REF_OID); + git_oid_cpy(&id, git_reference_target(ref)); + git_reference_free(ref); + + /* Create the symbolic ref */ + cl_git_pass(git_reference_symbolic_create(&ref, g_repo, ref_name, ref_master_name, 0)); + + /* Can't set an OID on a direct ref */ + cl_git_fail(git_reference_set_target(&new, ref, &id)); + + git_reference_free(ref); +} diff --git a/tests-clar/refs/update.c b/tests-clar/refs/update.c index 6c2107ee2..205b526a2 100644 --- a/tests-clar/refs/update.c +++ b/tests-clar/refs/update.c @@ -19,11 +19,8 @@ void test_refs_update__updating_the_target_of_a_symref_with_an_invalid_name_retu git_reference *head; cl_git_pass(git_reference_lookup(&head, g_repo, GIT_HEAD_FILE)); - cl_assert_equal_i(GIT_REF_SYMBOLIC, git_reference_type(head)); - - cl_assert_equal_i(GIT_EINVALIDSPEC, git_reference_symbolic_set_target( - head, "refs/heads/inv@{id")); - git_reference_free(head); + + cl_assert_equal_i(GIT_EINVALIDSPEC, git_reference_symbolic_create(&head, g_repo, GIT_HEAD_FILE, "refs/heads/inv@{id", 1)); } diff --git a/tests-clar/stash/save.c b/tests-clar/stash/save.c index ea2eb282d..588dfc3ea 100644 --- a/tests-clar/stash/save.c +++ b/tests-clar/stash/save.c @@ -193,8 +193,7 @@ void test_stash_save__cannot_stash_against_an_unborn_branch(void) { git_reference *head; - cl_git_pass(git_reference_lookup(&head, repo, "HEAD")); - cl_git_pass(git_reference_symbolic_set_target(head, "refs/heads/unborn")); + cl_git_pass(git_reference_symbolic_create(&head, repo, "HEAD", "refs/heads/unborn", 1)); cl_assert_equal_i(GIT_EORPHANEDHEAD, git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_DEFAULT)); |