]> git.proxmox.com Git - libgit2.git/commitdiff
Add git_reset()
authornulltoken <emeric.fermas@gmail.com>
Tue, 1 May 2012 11:57:45 +0000 (13:57 +0200)
committernulltoken <emeric.fermas@gmail.com>
Thu, 7 Jun 2012 19:27:30 +0000 (21:27 +0200)
Currently supports Soft and Mixed modes.

include/git2.h
include/git2/reset.h [new file with mode: 0644]
include/git2/types.h
src/commit.c
src/refs.c
src/refs.h
src/reset.c [new file with mode: 0644]
tests-clar/reset/mixed.c [new file with mode: 0644]
tests-clar/reset/reset_helpers.c [new file with mode: 0644]
tests-clar/reset/reset_helpers.h [new file with mode: 0644]
tests-clar/reset/soft.c [new file with mode: 0644]

index d75387318af262fb43e6d2e8b5d2b97ab6042ae2..eefd9d04839e40fd971a7ad239552a78a6936f3f 100644 (file)
@@ -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 (file)
index 0000000..1251787
--- /dev/null
@@ -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
index cfb0acf3302d3840d94f9a5c9104db92af77f956..b4b48afa3526613b4b9e672745b6dead942ab02f 100644 (file)
@@ -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;
 
index 2f40dc67dde3f9dffccb3cae616f1e840201bd4e..57eafaa2ebe2877a12c65bf8c07085a26bdc1c1e 100644 (file)
@@ -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;
 
index 1ef3e13a441b5e43014ff9cd464ae67142e933af..104685793c8fb6408479af2cdde7c5279d46a48e 100644 (file)
@@ -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;
+}
index 369e91e1c37ea46ff4c8e94f6b14f34d81829e89..08235027823166247fb69ca93f7c15f74a2fd50a 100644 (file)
@@ -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 (file)
index 0000000..14f7a23
--- /dev/null
@@ -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 (file)
index 0000000..7cfff65
--- /dev/null
@@ -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 (file)
index 0000000..17edca4
--- /dev/null
@@ -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 (file)
index 0000000..5dbe9d2
--- /dev/null
@@ -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 (file)
index 0000000..3200c15
--- /dev/null
@@ -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));
+}