diff --git a/include/git2/refs.h b/include/git2/refs.h index da55eaa3b..9ecf77295 100644 --- a/include/git2/refs.h +++ b/include/git2/refs.h @@ -68,6 +68,27 @@ GIT_EXTERN(int) git_reference_lookup(git_reference **reference_out, git_reposito */ GIT_EXTERN(int) git_reference_create_symbolic(git_reference **ref_out, git_repository *repo, const char *name, const char *target); +/** + * Create a new symbolic reference, overwriting an existing one with + * the same name, if it exists. + * + * If the new reference isn't a symbolic one, any pointers to the old + * reference become invalid. + * + * The reference will be created in the repository and written + * to the disk. + * + * This reference is owned by the repository and shall not + * be free'd by the user. + * + * @param ref_out Pointer to the newly created reference + * @param repo Repository where that reference will live + * @param name The name of the reference + * @param target The target of the reference + * @return 0 on success; error code otherwise + */ +GIT_EXTERN(int) git_reference_create_symbolic_force(git_reference **ref_out, git_repository *repo, const char *name, const char *target); + /** * Create a new object id reference. * @@ -85,6 +106,27 @@ GIT_EXTERN(int) git_reference_create_symbolic(git_reference **ref_out, git_repos */ GIT_EXTERN(int) git_reference_create_oid(git_reference **ref_out, git_repository *repo, const char *name, const git_oid *id); +/** + * Create a new object id reference, overwriting an existing one with + * the same name, if it exists. + * + * If the new reference isn't a symbolic one, any pointers to the old + * reference become invalid. + * + * The reference will be created in the repository and written + * to the disk. + * + * This reference is owned by the repository and shall not + * be free'd by the user. + * + * @param ref_out Pointer to the newly created reference + * @param repo Repository where that reference will live + * @param name The name of the reference + * @param id The object id pointed to by the reference. + * @return 0 on success; error code otherwise + */ +GIT_EXTERN(int) git_reference_create_oid_force(git_reference **ref_out, git_repository *repo, const char *name, const git_oid *id); + /** * Get the OID pointed to by a reference. * @@ -189,6 +231,20 @@ GIT_EXTERN(int) git_reference_set_oid(git_reference *ref, const git_oid *id); */ GIT_EXTERN(int) git_reference_rename(git_reference *ref, const char *new_name); +/** + * Rename an existing reference, overwriting an existing one with the + * same name, if it exists. + * + * This method works for both direct and symbolic references. + * The new name will be checked for validity and may be + * modified into a normalized form. + * + * The refernece will be immediately renamed in-memory + * and on disk. + * + */ +GIT_EXTERN(int) git_reference_rename_force(git_reference *ref, const char *new_name); + /** * Delete an existing reference * diff --git a/src/refs.c b/src/refs.c index 16bd74149..20c1d001f 100644 --- a/src/refs.c +++ b/src/refs.c @@ -915,8 +915,238 @@ cleanup: return error; } +/***************************************** + * Internal methods - reference creation + *****************************************/ + +int git_reference_create_symbolic_internal(git_reference **ref_out, git_repository *repo, const char *name, const char *target, int force) +{ + char normalized[MAX_GITDIR_TREE_STRUCTURE_PATH_LENGTH]; + int error = GIT_SUCCESS, updated = 0; + git_reference *ref = NULL, *old_ref = NULL; + + if (git_reference_lookup(&ref, repo, name) == GIT_SUCCESS && !force) + return GIT_EEXISTS; + + /* + * If they old ref was of the same type, then we can just update + * it (once we've checked that the target is valid). Otherwise we + * need a new reference because we can't make a symbolic ref out + * of an oid one. + * If if didn't exist, then we need to create a new one anyway. + */ + if (ref && ref->type & GIT_REF_SYMBOLIC){ + updated = 1; + } else { + ref = NULL; + error = reference_create(&ref, repo, name, GIT_REF_SYMBOLIC); + if (error < GIT_SUCCESS) + goto cleanup; + } + + /* The target can aither be the name of an object id reference or the name of another symbolic reference */ + error = normalize_name(normalized, target, 0); + if (error < GIT_SUCCESS) + goto cleanup; + + /* set the target; this will write the reference on disk */ + error = git_reference_set_target(ref, normalized); + if (error < GIT_SUCCESS) + goto cleanup; + + /* + * If we didn't update the ref, then we need to insert or replace + * it in the loose cache. If we replaced a ref, free it. + */ + if (!updated){ + error = git_hashtable_insert2(repo->references.loose_cache, ref->name, ref, (void **) &old_ref); + if (error < GIT_SUCCESS) + goto cleanup; + + if(old_ref) + reference_free(old_ref); + } + + *ref_out = ref; + + return error; + +cleanup: + reference_free(ref); + return error; +} + +int git_reference_create_oid_internal(git_reference **ref_out, git_repository *repo, const char *name, const git_oid *id, int force) +{ + int error = GIT_SUCCESS, updated = 0; + git_reference *ref = NULL, *old_ref = NULL; + + if(git_reference_lookup(&ref, repo, name) == GIT_SUCCESS && !force) + return GIT_EEXISTS; + + /* + * If they old ref was of the same type, then we can just update + * it (once we've checked that the target is valid). Otherwise we + * need a new reference because we can't make a symbolic ref out + * of an oid one. + * If if didn't exist, then we need to create a new one anyway. + */ + if (ref && ref-> type & GIT_REF_OID){ + updated = 1; + } else { + ref = NULL; + error = reference_create(&ref, repo, name, GIT_REF_OID); + if (error < GIT_SUCCESS) + goto cleanup; + } + + /* set the oid; this will write the reference on disk */ + error = git_reference_set_oid(ref, id); + if (error < GIT_SUCCESS) + goto cleanup; + + if(!updated){ + error = git_hashtable_insert2(repo->references.loose_cache, ref->name, ref, (void **) &old_ref); + if (error < GIT_SUCCESS) + goto cleanup; + + if(old_ref) + reference_free(old_ref); + } + + *ref_out = ref; + + return error; + +cleanup: + reference_free(ref); + return error; +} + +/* + * Rename a reference + * + * If the reference is packed, we need to rewrite the + * packfile to remove the reference from it and create + * the reference back as a loose one. + * + * If the reference is loose, we just rename it on + * the filesystem. + * + * We also need to re-insert the reference on its corresponding + * in-memory cache, since the caches are indexed by refname. + */ +int git_reference_rename_internal(git_reference *ref, const char *new_name, int force) +{ + int error; + char *old_name; + char old_path[GIT_PATH_MAX], new_path[GIT_PATH_MAX], normalized_name[MAX_GITDIR_TREE_STRUCTURE_PATH_LENGTH]; + git_reference *looked_up_ref, *old_ref = NULL; + + assert(ref); + + /* Ensure the name is valid */ + error = normalize_name(normalized_name, new_name, ref->type & GIT_REF_OID); + if (error < GIT_SUCCESS) + return error; + + /* Ensure we're not going to overwrite an existing reference */ + error = git_reference_lookup(&looked_up_ref, ref->owner, new_name); + if (error == GIT_SUCCESS && !force) + return GIT_EEXISTS; + + if (error != GIT_ENOTFOUND) + return error; + old_name = ref->name; + ref->name = git__strdup(new_name); + + if (ref->name == NULL) { + ref->name = old_name; + return GIT_ENOMEM; + } + + if (ref->type & GIT_REF_PACKED) { + /* write the packfile to disk; note + * that the state of the in-memory cache is not + * consistent, because the reference is indexed + * by its old name but it already has the new one. + * This doesn't affect writing, though, and allows + * us to rollback if writing fails + */ + + ref->type &= ~GIT_REF_PACKED; + + /* Create the loose ref under its new name */ + error = loose_write(ref); + if (error < GIT_SUCCESS) { + ref->type |= GIT_REF_PACKED; + goto cleanup; + } + + /* Remove from the packfile cache in order to avoid packing it back + * Note : we do not rely on git_reference_delete() because this would + * invalidate the reference. + */ + git_hashtable_remove(ref->owner->references.packfile, old_name); + + /* Recreate the packed-refs file without the reference */ + error = packed_write(ref->owner); + if (error < GIT_SUCCESS) + goto rename_loose_to_old_name; + + } else { + git__joinpath(old_path, ref->owner->path_repository, old_name); + git__joinpath(new_path, ref->owner->path_repository, ref->name); + + error = gitfo_mv_force(old_path, new_path); + if (error < GIT_SUCCESS) + goto cleanup; + + /* Once succesfully renamed, remove from the cache the reference known by its old name*/ + git_hashtable_remove(ref->owner->references.loose_cache, old_name); + } + + /* Store the renamed reference into the loose ref cache */ + error = git_hashtable_insert2(ref->owner->references.loose_cache, ref->name, ref, (void **) &old_ref); + + /* If we force-replaced, we need to free the old reference */ + if(old_ref) + reference_free(old_ref); + + free(old_name); + return error; + +cleanup: + /* restore the old name if this failed */ + free(ref->name); + ref->name = old_name; + return error; + +rename_loose_to_old_name: + /* If we hit this point. Something *bad* happened! Think "Ghostbusters + * crossing the streams" definition of bad. + * Either the packed-refs has been correctly generated and something else + * has gone wrong, or the writing of the new packed-refs has failed, and + * we're stuck with the old one. As a loose ref always takes priority over + * a packed ref, we'll eventually try and rename the generated loose ref to + * its former name. It even that fails, well... we might have lost the reference + * for good. :-/ + */ + + git__joinpath(old_path, ref->owner->path_repository, ref->name); + git__joinpath(new_path, ref->owner->path_repository, old_name); + + /* No error checking. We'll return the initial error */ + gitfo_mv_force(old_path, new_path); + + /* restore the old name */ + free(ref->name); + ref->name = old_name; + + return error; +} /***************************************** * External Library API @@ -975,64 +1205,23 @@ int git_reference_lookup(git_reference **ref_out, git_repository *repo, const ch int git_reference_create_symbolic(git_reference **ref_out, git_repository *repo, const char *name, const char *target) { - char normalized[MAX_GITDIR_TREE_STRUCTURE_PATH_LENGTH]; - int error = GIT_SUCCESS; - git_reference *ref = NULL; + return git_reference_create_symbolic_internal(ref_out, repo, name, target, 0); +} - error = reference_create(&ref, repo, name, GIT_REF_SYMBOLIC); - if (error < GIT_SUCCESS) - goto cleanup; - - /* The target can aither be the name of an object id reference or the name of another symbolic reference */ - error = normalize_name(normalized, target, 0); - if (error < GIT_SUCCESS) - goto cleanup; - - /* set the target; this will write the reference on disk */ - error = git_reference_set_target(ref, normalized); - if (error < GIT_SUCCESS) - goto cleanup; - - error = git_hashtable_insert(repo->references.loose_cache, ref->name, ref); - if (error < GIT_SUCCESS) - goto cleanup; - - *ref_out = ref; - - return error; - -cleanup: - reference_free(ref); - return error; +int git_reference_create_symbolic_force(git_reference **ref_out, git_repository *repo, const char *name, const char *target) +{ + return git_reference_create_symbolic_internal(ref_out, repo, name, target, 1); } int git_reference_create_oid(git_reference **ref_out, git_repository *repo, const char *name, const git_oid *id) { - int error = GIT_SUCCESS; - git_reference *ref = NULL; - - error = reference_create(&ref, repo, name, GIT_REF_OID); - if (error < GIT_SUCCESS) - goto cleanup; - - /* set the oid; this will write the reference on disk */ - error = git_reference_set_oid(ref, id); - if (error < GIT_SUCCESS) - goto cleanup; - - error = git_hashtable_insert(repo->references.loose_cache, ref->name, ref); - if (error < GIT_SUCCESS) - goto cleanup; - - *ref_out = ref; - - return error; - -cleanup: - reference_free(ref); - return error; + return git_reference_create_oid_internal(ref_out, repo, name, id, 0); } +int git_reference_create_oid_force(git_reference **ref_out, git_repository *repo, const char *name, const git_oid *id) +{ + return git_reference_create_oid_internal(ref_out, repo, name, id, 1); +} /** * Getters @@ -1240,125 +1429,14 @@ cleanup: return error; } -/* - * Rename a reference - * - * If the reference is packed, we need to rewrite the - * packfile to remove the reference from it and create - * the reference back as a loose one. - * - * If the reference is loose, we just rename it on - * the filesystem. - * - * We also need to re-insert the reference on its corresponding - * in-memory cache, since the caches are indexed by refname. - */ int git_reference_rename(git_reference *ref, const char *new_name) { - int error; - char *old_name; - char old_path[GIT_PATH_MAX], new_path[GIT_PATH_MAX], normalized_name[MAX_GITDIR_TREE_STRUCTURE_PATH_LENGTH]; - git_reference *looked_up_ref; + return git_reference_rename_internal(ref, new_name, 0); +} - assert(ref); - - /* Ensure the name is valid */ - error = normalize_name(normalized_name, new_name, ref->type & GIT_REF_OID); - if (error < GIT_SUCCESS) - return error; - - /* Ensure we're not going to overwrite an existing reference */ - error = git_reference_lookup(&looked_up_ref, ref->owner, new_name); - if (error == GIT_SUCCESS) - return GIT_EINVALIDREFNAME; - - if (error != GIT_ENOTFOUND) - return error; - - - old_name = ref->name; - ref->name = git__strdup(new_name); - - if (ref->name == NULL) { - ref->name = old_name; - return GIT_ENOMEM; - } - - if (ref->type & GIT_REF_PACKED) { - /* write the packfile to disk; note - * that the state of the in-memory cache is not - * consistent, because the reference is indexed - * by its old name but it already has the new one. - * This doesn't affect writing, though, and allows - * us to rollback if writing fails - */ - - ref->type &= ~GIT_REF_PACKED; - - /* Create the loose ref under its new name */ - error = loose_write(ref); - if (error < GIT_SUCCESS) { - ref->type |= GIT_REF_PACKED; - goto cleanup; - } - - /* Remove from the packfile cache in order to avoid packing it back - * Note : we do not rely on git_reference_delete() because this would - * invalidate the reference. - */ - git_hashtable_remove(ref->owner->references.packfile, old_name); - - /* Recreate the packed-refs file without the reference */ - error = packed_write(ref->owner); - if (error < GIT_SUCCESS) - goto rename_loose_to_old_name; - - } else { - git__joinpath(old_path, ref->owner->path_repository, old_name); - git__joinpath(new_path, ref->owner->path_repository, ref->name); - - error = gitfo_mv_force(old_path, new_path); - if (error < GIT_SUCCESS) - goto cleanup; - - /* Once succesfully renamed, remove from the cache the reference known by its old name*/ - git_hashtable_remove(ref->owner->references.loose_cache, old_name); - } - - /* Store the renamed reference into the loose ref cache */ - error = git_hashtable_insert(ref->owner->references.loose_cache, ref->name, ref); - - free(old_name); - return error; - -cleanup: - /* restore the old name if this failed */ - free(ref->name); - ref->name = old_name; - return error; - -rename_loose_to_old_name: - /* If we hit this point. Something *bad* happened! Think "Ghostbusters - * crossing the streams" definition of bad. - * Either the packed-refs has been correctly generated and something else - * has gone wrong, or the writing of the new packed-refs has failed, and - * we're stuck with the old one. As a loose ref always takes priority over - * a packed ref, we'll eventually try and rename the generated loose ref to - * its former name. It even that fails, well... we might have lost the reference - * for good. :-/ - */ - - git__joinpath(old_path, ref->owner->path_repository, ref->name); - git__joinpath(new_path, ref->owner->path_repository, old_name); - - /* No error checking. We'll return the initial error */ - gitfo_mv_force(old_path, new_path); - - /* restore the old name */ - free(ref->name); - ref->name = old_name; - - return error; +int git_reference_rename_force(git_reference *ref, const char *new_name) +{ + return git_reference_rename_internal(ref, new_name, 1); } int git_reference_resolve(git_reference **resolved_ref, git_reference *ref)