diff --git a/src/pygit2.c b/src/pygit2.c index 3ee2ff5..3bfbb32 100644 --- a/src/pygit2.c +++ b/src/pygit2.c @@ -407,6 +407,27 @@ moduleinit(PyObject* m) PyModule_AddIntConstant(m, "LIBGIT2_VER_REVISION", LIBGIT2_VER_REVISION); PyModule_AddStringConstant(m, "LIBGIT2_VERSION", LIBGIT2_VERSION); + /* Different checkout strategies */ + PyModule_AddIntConstant(m, "GIT_CHECKOUT_NONE", GIT_CHECKOUT_NONE); + PyModule_AddIntConstant(m, "GIT_CHECKOUT_SAFE", GIT_CHECKOUT_SAFE); + PyModule_AddIntConstant(m, "GIT_CHECKOUT_SAFE_CREATE", + GIT_CHECKOUT_SAFE_CREATE); + PyModule_AddIntConstant(m, "GIT_CHECKOUT_FORCE", GIT_CHECKOUT_FORCE); + PyModule_AddIntConstant(m, "GIT_CHECKOUT_ALLOW_CONFLICTS", + GIT_CHECKOUT_ALLOW_CONFLICTS); + PyModule_AddIntConstant(m, "GIT_CHECKOUT_REMOVE_UNTRACKED", + GIT_CHECKOUT_REMOVE_UNTRACKED); + PyModule_AddIntConstant(m, "GIT_CHECKOUT_REMOVE_IGNORED", + GIT_CHECKOUT_REMOVE_IGNORED); + PyModule_AddIntConstant(m, "GIT_CHECKOUT_UPDATE_ONLY", + GIT_CHECKOUT_UPDATE_ONLY); + PyModule_AddIntConstant(m, "GIT_CHECKOUT_DONT_UPDATE_INDEX", + GIT_CHECKOUT_DONT_UPDATE_INDEX); + PyModule_AddIntConstant(m, "GIT_CHECKOUT_NO_REFRESH", + GIT_CHECKOUT_NO_REFRESH); + PyModule_AddIntConstant(m, "GIT_CHECKOUT_DISABLE_PATHSPEC_MATC", + GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH); + return m; } diff --git a/src/remote.c b/src/remote.c index 175786f..b7a6f08 100644 --- a/src/remote.c +++ b/src/remote.c @@ -184,7 +184,7 @@ PyDoc_STRVAR(Remote_fetch__doc__, PyObject * Remote_fetch(Remote *self, PyObject *args) { - PyObject* py_stats; + PyObject* py_stats = NULL; const git_transfer_progress *stats; int err; diff --git a/src/repository.c b/src/repository.c index 4bfb71e..b187ab6 100644 --- a/src/repository.c +++ b/src/repository.c @@ -45,6 +45,7 @@ extern PyTypeObject TreeBuilderType; extern PyTypeObject ConfigType; extern PyTypeObject DiffType; extern PyTypeObject RemoteType; +extern PyTypeObject ReferenceType; git_otype int_to_loose_object_type(int type_id) @@ -1066,6 +1067,53 @@ Repository_remotes__get__(Repository *self) } +PyDoc_STRVAR(Repository_checkout__doc__, + "checkout([strategy:int, reference:Reference])\n" + "\n" + "Checks out a tree by a given reference and modifies the HEAD pointer\n" + "Standard checkout strategy is pygit2.GIT_CHECKOUT_SAFE_CREATE\n" + "If no reference is given, checkout will use HEAD instead."); + +PyObject * +Repository_checkout(Repository *self, PyObject *args, PyObject *kw) +{ + git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; + unsigned int strategy = GIT_CHECKOUT_SAFE_CREATE; + Reference* ref = NULL; + git_object* object; + const git_oid* id; + int err, head = 0; + + static char *kwlist[] = {"strategy", "reference", "head", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, kw, "|IO!i", kwlist, + &strategy, &ReferenceType, &ref, &head)) + return NULL; + + if (ref != NULL) { // checkout from treeish + id = git_reference_target(ref->reference); + err = git_object_lookup(&object, self->repo, id, GIT_OBJ_COMMIT); + if(err == GIT_OK) { + opts.checkout_strategy = strategy; + err = git_checkout_tree(self->repo, object, &opts); + if (err == GIT_OK) { + err = git_repository_set_head(self->repo, + git_reference_name(ref->reference)); + } + } + } else { // checkout from head / index + opts.checkout_strategy = strategy; + err = (!head) ? git_checkout_index(self->repo, NULL, &opts) : + git_checkout_head(self->repo, &opts); + } + + if(err < 0) + return Error_set(err); + + Py_RETURN_NONE; +} + + PyMethodDef Repository_methods[] = { METHOD(Repository, create_blob, METH_VARARGS), METHOD(Repository, create_blob_fromfile, METH_VARARGS), @@ -1083,6 +1131,7 @@ PyMethodDef Repository_methods[] = { METHOD(Repository, status, METH_NOARGS), METHOD(Repository, status_file, METH_O), METHOD(Repository, create_remote, METH_VARARGS), + METHOD(Repository, checkout, METH_VARARGS|METH_KEYWORDS), {NULL} }; diff --git a/test/data/testrepo.tar b/test/data/testrepo.tar index 383d733..9c451b2 100644 Binary files a/test/data/testrepo.tar and b/test/data/testrepo.tar differ diff --git a/test/test_repository.py b/test/test_repository.py index e16c842..10c9885 100644 --- a/test/test_repository.py +++ b/test/test_repository.py @@ -184,6 +184,45 @@ class RepositoryTest_II(utils.RepoTestCase): expected = realpath(join(self._temp_dir, 'testrepo')) self.assertEqual(directory, expected) + def test_checkout_ref(self): + ref_i18n = self.repo.lookup_reference('refs/heads/i18n') + + # checkout i18n with conflicts and default strategy should + # not be possible + self.assertRaises(pygit2.GitError, + lambda: self.repo.checkout(reference=ref_i18n)) + + # checkout i18n with GIT_CHECKOUT_FORCE + self.assertTrue('new' not in self.repo.head.tree) + self.repo.checkout(pygit2.GIT_CHECKOUT_FORCE, ref_i18n) + self.assertEqual(self.repo.head.hex, self.repo[ref_i18n.target].hex) + self.assertTrue('new' in self.repo.head.tree) + self.assertTrue('bye.txt' not in self.repo.status()) + + def test_checkout_index(self): + # some changes to working dir + with open(os.path.join(self.repo.workdir, 'hello.txt'), 'w') as f: + f.write('new content') + + # checkout index + self.assertTrue('hello.txt' in self.repo.status()) + self.repo.checkout(pygit2.GIT_CHECKOUT_FORCE) + self.assertTrue('hello.txt' not in self.repo.status()) + + def test_checkout_head(self): + # some changes to the index + with open(os.path.join(self.repo.workdir, 'bye.txt'), 'w') as f: + f.write('new content') + self.repo.index.add('bye.txt') + + # checkout from index should not change anything + self.assertTrue('bye.txt' in self.repo.status()) + self.repo.checkout(pygit2.GIT_CHECKOUT_FORCE) + self.assertTrue('bye.txt' in self.repo.status()) + + # checkout from head will reset index as well + self.repo.checkout(pygit2.GIT_CHECKOUT_FORCE, head=True) + self.assertTrue('bye.txt' not in self.repo.status()) class NewRepositoryTest(utils.NoRepoTestCase): def test_new_repo(self): diff --git a/test/test_revwalk.py b/test/test_revwalk.py index 4fb1fd9..1b6a083 100644 --- a/test/test_revwalk.py +++ b/test/test_revwalk.py @@ -44,6 +44,9 @@ log = [ 'acecd5ea2924a4b900e7e149496e1f4b57976e51'] REVLOGS = [ + ('Nico von Geyso','checkout: moving from i18n to master'), + ('Nico von Geyso','commit: added bye.txt and new'), + ('Nico von Geyso','checkout: moving from master to i18n'), ('J. David Ibañez', 'merge i18n: Merge made by recursive.'), ('J. David Ibañez', 'commit: Add .gitignore file'), ('J. David Ibañez', 'checkout: moving from i18n to master'),