diff options
author | nulltoken <emeric.fermas@gmail.com> | 2012-05-01 13:57:45 +0200 |
---|---|---|
committer | nulltoken <emeric.fermas@gmail.com> | 2012-06-07 21:27:30 +0200 |
commit | edebceffef1d661d073b9961d13042007325832d (patch) | |
tree | 9884986782471e303f5b1f5386192b751cf57feb | |
parent | 4c977a61e598f2230e9902aa80cfea8e89d94f88 (diff) | |
download | libgit2-edebceffef1d661d073b9961d13042007325832d.tar.gz |
Add git_reset()
Currently supports Soft and Mixed modes.
-rw-r--r-- | include/git2.h | 1 | ||||
-rw-r--r-- | include/git2/reset.h | 44 | ||||
-rw-r--r-- | include/git2/types.h | 6 | ||||
-rw-r--r-- | src/commit.c | 62 | ||||
-rw-r--r-- | src/refs.c | 59 | ||||
-rw-r--r-- | src/refs.h | 1 | ||||
-rw-r--r-- | src/reset.c | 103 | ||||
-rw-r--r-- | tests-clar/reset/mixed.c | 47 | ||||
-rw-r--r-- | tests-clar/reset/reset_helpers.c | 10 | ||||
-rw-r--r-- | tests-clar/reset/reset_helpers.h | 6 | ||||
-rw-r--r-- | tests-clar/reset/soft.c | 102 |
11 files changed, 380 insertions, 61 deletions
diff --git a/include/git2.h b/include/git2.h index d75387318..eefd9d048 100644 --- a/include/git2.h +++ b/include/git2.h @@ -43,5 +43,6 @@ #include "git2/indexer.h" #include "git2/submodule.h" #include "git2/notes.h" +#include "git2/reset.h" #endif diff --git a/include/git2/reset.h b/include/git2/reset.h new file mode 100644 index 000000000..125178748 --- /dev/null +++ b/include/git2/reset.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * 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_reset_h__ +#define INCLUDE_git_reset_h__ + +/** + * @file git2/reset.h + * @brief Git reset management routines + * @ingroup Git + * @{ + */ +GIT_BEGIN_DECL + +/** + * Sets the current head to the specified commit oid and optionally + * resets the index and working tree to match. + * + * When specifying a Soft kind of reset, the head will be moved to the commit. + * + * Specifying a Mixed kind of reset will trigger a Soft reset and the index will + * be replaced with the content of the commit tree. + * + * TODO: Implement remaining kinds of resets. + * + * @param repo Repository where to perform the reset operation. + * + * @param target Object to which the Head should be moved to. This object + * must belong to the given `repo` and can either be a git_commit or a + * git_tag. When a git_tag is being passed, it should be dereferencable + * to a git_commit which oid will be used as the target of the branch. + * + * @param reset_type Kind of reset operation to perform. + * + * @return GIT_SUCCESS or an error code + */ +GIT_EXTERN(int) git_reset(git_repository *repo, const git_object *target, git_reset_type reset_type); + +/** @} */ +GIT_END_DECL +#endif diff --git a/include/git2/types.h b/include/git2/types.h index cfb0acf33..b4b48afa3 100644 --- a/include/git2/types.h +++ b/include/git2/types.h @@ -166,6 +166,12 @@ typedef enum { GIT_BRANCH_REMOTE = 2, } git_branch_t; +/** Kinds of reset operation. */ +typedef enum { + GIT_RESET_SOFT = 1, + GIT_RESET_MIXED = 2, +} git_reset_type; + typedef struct git_refspec git_refspec; typedef struct git_remote git_remote; diff --git a/src/commit.c b/src/commit.c index 2f40dc67d..57eafaa2e 100644 --- a/src/commit.c +++ b/src/commit.c @@ -81,66 +81,6 @@ int git_commit_create_v( return res; } -/* Update the reference named `ref_name` so it points to `oid` */ -static int update_reference(git_repository *repo, git_oid *oid, const char *ref_name) -{ - git_reference *ref; - int res; - - res = 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) { - giterr_clear(); - return git_reference_create_oid(NULL, repo, ref_name, oid, 1); - } - - 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 (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_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_oid(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 */ - git_reference_free(ref); - - if (res < 0) - return -1; - - /* store the newly found direct reference in its place */ - ref = aux; - } - - /* 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_oid(ref, oid); - git_reference_free(ref); - return res; -} - int git_commit_create( git_oid *oid, git_repository *repo, @@ -192,7 +132,7 @@ int git_commit_create( git_buf_free(&commit); if (update_ref != NULL) - return update_reference(repo, oid, update_ref); + return git_reference__update(repo, oid, update_ref); return 0; diff --git a/src/refs.c b/src/refs.c index 1ef3e13a4..104685793 100644 --- a/src/refs.c +++ b/src/refs.c @@ -1705,3 +1705,62 @@ int git_reference_cmp(git_reference *ref1, git_reference *ref2) 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) +{ + git_reference *ref; + int res; + + res = 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) { + giterr_clear(); + return git_reference_create_oid(NULL, repo, ref_name, oid, 1); + } + + 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 (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_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_oid(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 */ + git_reference_free(ref); + + if (res < 0) + return -1; + + /* store the newly found direct reference in its place */ + ref = aux; + } + + /* 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_oid(ref, oid); + git_reference_free(ref); + return res; +} diff --git a/src/refs.h b/src/refs.h index 369e91e1c..082350278 100644 --- a/src/refs.h +++ b/src/refs.h @@ -54,6 +54,7 @@ void git_repository__refcache_free(git_refcache *refs); int git_reference__normalize_name(char *buffer_out, size_t out_size, const char *name); int git_reference__normalize_name_oid(char *buffer_out, size_t out_size, const char *name); +int git_reference__update(git_repository *repo, const git_oid *oid, const char *ref_name); /** * Lookup a reference by name and try to resolve to an OID. diff --git a/src/reset.c b/src/reset.c new file mode 100644 index 000000000..14f7a236a --- /dev/null +++ b/src/reset.c @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * 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 "commit.h" +#include "tag.h" +#include "git2/reset.h" + +#define ERROR_MSG "Cannot perform reset" + +static int reset_error_invalid(const char *msg) +{ + giterr_set(GITERR_INVALID, "%s - %s", ERROR_MSG, msg); + return -1; +} + +int git_reset( + git_repository *repo, + const git_object *target, + git_reset_type reset_type) +{ + git_otype target_type = GIT_OBJ_BAD; + git_object *commit = NULL; + git_index *index = NULL; + git_tree *tree = NULL; + int error = -1; + + assert(repo && target); + assert(reset_type == GIT_RESET_SOFT || reset_type == GIT_RESET_MIXED); + + if (git_object_owner(target) != repo) + return reset_error_invalid("The given target does not belong to this repository."); + + if (reset_type == GIT_RESET_MIXED && git_repository_is_bare(repo)) + return reset_error_invalid("Mixed reset is not allowed in a bare repository."); + + target_type = git_object_type(target); + + switch (target_type) + { + case GIT_OBJ_TAG: + if (git_tag_peel(&commit, (git_tag *)target) < 0) + goto cleanup; + + if (git_object_type(commit) != GIT_OBJ_COMMIT) { + reset_error_invalid("The given target does not resolve to a commit."); + goto cleanup; + } + break; + + case GIT_OBJ_COMMIT: + commit = (git_object *)target; + break; + + default: + return reset_error_invalid("Only git_tag and git_commit objects are valid targets."); + } + + //TODO: Check for unmerged entries + + if (git_reference__update(repo, git_object_id(commit), GIT_HEAD_FILE) < 0) + goto cleanup; + + if (reset_type == GIT_RESET_SOFT) { + error = 0; + goto cleanup; + } + + if (git_commit_tree(&tree, (git_commit *)commit) < 0) { + giterr_set(GITERR_OBJECT, "%s - Failed to retrieve the commit tree.", ERROR_MSG); + goto cleanup; + } + + if (git_repository_index(&index, repo) < 0) { + giterr_set(GITERR_OBJECT, "%s - Failed to retrieve the index.", ERROR_MSG); + goto cleanup; + } + + if (git_index_read_tree(index, tree) < 0) { + giterr_set(GITERR_INDEX, "%s - Failed to update the index.", ERROR_MSG); + goto cleanup; + } + + if (git_index_write(index) < 0) { + giterr_set(GITERR_INDEX, "%s - Failed to write the index.", ERROR_MSG); + goto cleanup; + } + + error = 0; + +cleanup: + if (target_type == GIT_OBJ_TAG) + git_object_free(commit); + + git_index_free(index); + git_tree_free(tree); + + return error; +} diff --git a/tests-clar/reset/mixed.c b/tests-clar/reset/mixed.c new file mode 100644 index 000000000..7cfff65d4 --- /dev/null +++ b/tests-clar/reset/mixed.c @@ -0,0 +1,47 @@ +#include "clar_libgit2.h" +#include "posix.h" +#include "reset_helpers.h" +#include "path.h" + +static git_repository *repo; +static git_object *target; + +void test_reset_mixed__initialize(void) +{ + repo = cl_git_sandbox_init("attr"); + target = NULL; +} + +void test_reset_mixed__cleanup(void) +{ + git_object_free(target); + cl_git_sandbox_cleanup(); +} + +void test_reset_mixed__cannot_reset_in_a_bare_repository(void) +{ + git_repository *bare; + + cl_git_pass(git_repository_open(&bare, cl_fixture("testrepo.git"))); + cl_assert(git_repository_is_bare(bare) == true); + + retrieve_target_from_oid(&target, bare, KNOWN_COMMIT_IN_BARE_REPO); + + cl_git_fail(git_reset(bare, target, GIT_RESET_MIXED)); + + git_repository_free(bare); +} + +void test_reset_mixed__resetting_refreshes_the_index_to_the_commit_tree(void) +{ + unsigned int status; + + cl_git_pass(git_status_file(&status, repo, "macro_bad")); + cl_assert(status == GIT_STATUS_CURRENT); + retrieve_target_from_oid(&target, repo, "605812ab7fe421fdd325a935d35cb06a9234a7d7"); + + cl_git_pass(git_reset(repo, target, GIT_RESET_MIXED)); + + cl_git_pass(git_status_file(&status, repo, "macro_bad")); + cl_assert(status == GIT_STATUS_WT_NEW); +} diff --git a/tests-clar/reset/reset_helpers.c b/tests-clar/reset/reset_helpers.c new file mode 100644 index 000000000..17edca4e9 --- /dev/null +++ b/tests-clar/reset/reset_helpers.c @@ -0,0 +1,10 @@ +#include "clar_libgit2.h" +#include "reset_helpers.h" + +void retrieve_target_from_oid(git_object **object_out, git_repository *repo, const char *sha) +{ + git_oid oid; + + cl_git_pass(git_oid_fromstr(&oid, sha)); + cl_git_pass(git_object_lookup(object_out, repo, &oid, GIT_OBJ_ANY)); +} diff --git a/tests-clar/reset/reset_helpers.h b/tests-clar/reset/reset_helpers.h new file mode 100644 index 000000000..5dbe9d2c7 --- /dev/null +++ b/tests-clar/reset/reset_helpers.h @@ -0,0 +1,6 @@ +#include "common.h" + +#define KNOWN_COMMIT_IN_BARE_REPO "e90810b8df3e80c413d903f631643c716887138d" +#define KNOWN_COMMIT_IN_ATTR_REPO "217878ab49e1314388ea2e32dc6fdb58a1b969e0" + +extern void retrieve_target_from_oid(git_object **object_out, git_repository *repo, const char *sha); diff --git a/tests-clar/reset/soft.c b/tests-clar/reset/soft.c new file mode 100644 index 000000000..3200c1591 --- /dev/null +++ b/tests-clar/reset/soft.c @@ -0,0 +1,102 @@ +#include "clar_libgit2.h" +#include "reset_helpers.h" + +static git_repository *repo; +static git_object *target; + +void test_reset_soft__initialize(void) +{ + repo = cl_git_sandbox_init("testrepo.git"); +} + +void test_reset_soft__cleanup(void) +{ + git_object_free(target); + cl_git_sandbox_cleanup(); +} + +static void assert_reset_soft(bool should_be_detached) +{ + git_oid oid; + + cl_git_pass(git_reference_name_to_oid(&oid, repo, "HEAD")); + cl_git_fail(git_oid_streq(&oid, KNOWN_COMMIT_IN_BARE_REPO)); + + retrieve_target_from_oid(&target, repo, KNOWN_COMMIT_IN_BARE_REPO); + + cl_assert(git_repository_head_detached(repo) == should_be_detached); + + cl_git_pass(git_reset(repo, target, GIT_RESET_SOFT)); + + cl_assert(git_repository_head_detached(repo) == should_be_detached); + + cl_git_pass(git_reference_name_to_oid(&oid, repo, "HEAD")); + cl_git_pass(git_oid_streq(&oid, KNOWN_COMMIT_IN_BARE_REPO)); +} + +void test_reset_soft__can_reset_the_non_detached_Head_to_the_specified_commit(void) +{ + assert_reset_soft(false); +} + +static void detach_head(void) +{ + git_reference *head; + git_oid oid; + + cl_git_pass(git_reference_name_to_oid(&oid, repo, "HEAD")); + + cl_git_pass(git_reference_create_oid(&head, repo, "HEAD", &oid, true)); + git_reference_free(head); +} + +void test_reset_soft__can_reset_the_detached_Head_to_the_specified_commit(void) +{ + detach_head(); + + assert_reset_soft(true); +} + +void test_reset_soft__resetting_to_the_commit_pointed_at_by_the_Head_does_not_change_the_target_of_the_Head(void) +{ + git_oid oid; + char raw_head_oid[GIT_OID_HEXSZ + 1]; + + cl_git_pass(git_reference_name_to_oid(&oid, repo, "HEAD")); + git_oid_fmt(raw_head_oid, &oid); + raw_head_oid[GIT_OID_HEXSZ] = '\0'; + + retrieve_target_from_oid(&target, repo, raw_head_oid); + + cl_git_pass(git_reset(repo, target, GIT_RESET_SOFT)); + + cl_git_pass(git_reference_name_to_oid(&oid, repo, "HEAD")); + cl_git_pass(git_oid_streq(&oid, raw_head_oid)); +} + +void test_reset_soft__resetting_to_a_tag_sets_the_Head_to_the_peeled_commit(void) +{ + git_oid oid; + + /* b25fa35 is a tag, pointing to another tag which points to commit e90810b */ + retrieve_target_from_oid(&target, repo, "b25fa35b38051e4ae45d4222e795f9df2e43f1d1"); + + cl_git_pass(git_reset(repo, target, GIT_RESET_SOFT)); + + cl_assert(git_repository_head_detached(repo) == false); + cl_git_pass(git_reference_name_to_oid(&oid, repo, "HEAD")); + cl_git_pass(git_oid_streq(&oid, KNOWN_COMMIT_IN_BARE_REPO)); +} + +void test_reset_soft__cannot_reset_to_a_tag_not_pointing_at_a_commit(void) +{ + /* 53fc32d is the tree of commit e90810b */ + retrieve_target_from_oid(&target, repo, "53fc32d17276939fc79ed05badaef2db09990016"); + + cl_git_fail(git_reset(repo, target, GIT_RESET_SOFT)); + git_object_free(target); + + /* 521d87c is an annotated tag pointing to a blob */ + retrieve_target_from_oid(&target, repo, "521d87c1ec3aef9824daf6d96cc0ae3710766d91"); + cl_git_fail(git_reset(repo, target, GIT_RESET_SOFT)); +} |