From 1f5ec810adbdb5d94da7adcc24490106d686bfa2 Mon Sep 17 00:00:00 2001 From: Victor Garcia Date: Wed, 4 Dec 2013 18:17:10 +0100 Subject: [PATCH] implementing merge with default options --- src/mergeresult.c | 161 ++++++++++++++++++++++++++++++++++++++++ src/mergeresult.h | 37 +++++++++ src/pygit2.c | 5 ++ src/repository.c | 56 +++++++++++++- src/repository.h | 4 +- src/types.h | 9 +++ test/test_repository.py | 60 ++++++++++++++- test/utils.py | 5 ++ 8 files changed, 333 insertions(+), 4 deletions(-) create mode 100644 src/mergeresult.c create mode 100644 src/mergeresult.h diff --git a/src/mergeresult.c b/src/mergeresult.c new file mode 100644 index 0000000..c0ffddd --- /dev/null +++ b/src/mergeresult.c @@ -0,0 +1,161 @@ +/* + * Copyright 2010-2013 The pygit2 contributors + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2, + * as published by the Free Software Foundation. + * + * In addition to the permissions in the GNU General Public License, + * the authors give you unlimited permission to link the compiled + * version of this file into combinations with other programs, + * and to distribute those combinations without any restriction + * coming from the use of this file. (The General Public License + * restrictions do apply in other respects; for example, they cover + * modification of the file, and distribution when not linked into + * a combined executable.) + * + * This file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#define PY_SSIZE_T_CLEAN +#include +#include "utils.h" +#include "types.h" +#include "oid.h" +#include "repository.h" +#include "mergeresult.h" + +extern PyTypeObject MergeResultType; +extern PyTypeObject IndexType; + +PyObject * +git_merge_result_to_python(git_merge_result *merge_result, Repository *repo) +{ + git_oid fastforward_oid; + MergeResult *py_merge_result; + + py_merge_result = PyObject_New(MergeResult, &MergeResultType); + + py_merge_result->is_uptodate = git_merge_result_is_uptodate(merge_result) == GIT_CVAR_TRUE; + + if (git_merge_result_is_fastforward(merge_result) == GIT_CVAR_TRUE) + { + py_merge_result->is_fastforward = GIT_CVAR_TRUE; + git_merge_result_fastforward_oid(&fastforward_oid, merge_result); + py_merge_result->fastforward_oid = git_oid_to_python((const git_oid *)&fastforward_oid); + } + else + { + py_merge_result->is_fastforward = GIT_CVAR_FALSE; + py_merge_result->fastforward_oid = NULL; + } + + py_merge_result->status = Repository_status(repo); + + return (PyObject*) py_merge_result; +} + +PyDoc_STRVAR(MergeResult_is_uptodate__doc__, "Is up to date"); + +PyObject * +MergeResult_is_uptodate__get__(MergeResult *self) +{ + if (self->is_uptodate) + Py_RETURN_TRUE; + else + Py_RETURN_FALSE; +} + +PyDoc_STRVAR(MergeResult_is_fastforward__doc__, "Is fastforward"); + +PyObject * +MergeResult_is_fastforward__get__(MergeResult *self) +{ + if (self->is_fastforward) + Py_RETURN_TRUE; + else + Py_RETURN_FALSE; +} + +PyDoc_STRVAR(MergeResult_fastforward_oid__doc__, "Fastforward Oid"); + +PyObject * +MergeResult_fastforward_oid__get__(MergeResult *self) +{ + if (self->is_fastforward == 1) + { + Py_INCREF(self->fastforward_oid); + return self->fastforward_oid; + } + else + Py_RETURN_NONE; +} + +PyDoc_STRVAR(MergeResult_status__doc__, "Merge repository status"); + +PyObject * +MergeResult_status__get__(MergeResult *self) +{ + Py_INCREF(self->status); + return self->status; +} + +PyGetSetDef MergeResult_getseters[] = { + GETTER(MergeResult, is_uptodate), + GETTER(MergeResult, is_fastforward), + GETTER(MergeResult, fastforward_oid), + GETTER(MergeResult, status), + {NULL}, +}; + +PyDoc_STRVAR(MergeResult__doc__, "MergeResult object."); + +PyTypeObject MergeResultType = { + PyVarObject_HEAD_INIT(NULL, 0) + "_pygit2.MergeResult", /* tp_name */ + sizeof(MergeResult), /* tp_basicsize */ + 0, /* tp_itemsize */ + 0, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + 0, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT, /* tp_flags */ + MergeResult__doc__, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + 0, /* tp_methods */ + 0, /* tp_members */ + MergeResult_getseters, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + 0, /* tp_new */ +}; + diff --git a/src/mergeresult.h b/src/mergeresult.h new file mode 100644 index 0000000..7aadac4 --- /dev/null +++ b/src/mergeresult.h @@ -0,0 +1,37 @@ +/* + * Copyright 2010-2013 The pygit2 contributors + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2, + * as published by the Free Software Foundation. + * + * In addition to the permissions in the GNU General Public License, + * the authors give you unlimited permission to link the compiled + * version of this file into combinations with other programs, + * and to distribute those combinations without any restriction + * coming from the use of this file. (The General Public License + * restrictions do apply in other respects; for example, they cover + * modification of the file, and distribution when not linked into + * a combined executable.) + * + * This file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef INCLUDE_pygit2_merge_result_h +#define INCLUDE_pygit2_merge_result_h + +#define PY_SSIZE_T_CLEAN +#include +#include + +PyObject* git_merge_result_to_python(git_merge_result *merge_result, Repository *repo); + +#endif diff --git a/src/pygit2.c b/src/pygit2.c index 4bd37c1..e1766d2 100644 --- a/src/pygit2.c +++ b/src/pygit2.c @@ -67,6 +67,7 @@ extern PyTypeObject NoteIterType; extern PyTypeObject BlameType; extern PyTypeObject BlameIterType; extern PyTypeObject BlameHunkType; +extern PyTypeObject MergeResultType; @@ -428,6 +429,10 @@ moduleinit(PyObject* m) ADD_CONSTANT_INT(m, GIT_BLAME_TRACK_COPIES_SAME_COMMIT_COPIES) ADD_CONSTANT_INT(m, GIT_BLAME_TRACK_COPIES_ANY_COMMIT_COPIES) + /* Merge */ + INIT_TYPE(MergeResultType, NULL, NULL) + ADD_TYPE(m, MergeResult) + /* Global initialization of libgit2 */ git_threads_init(); diff --git a/src/repository.c b/src/repository.c index 20e2a7c..3d4889c 100644 --- a/src/repository.c +++ b/src/repository.c @@ -38,6 +38,7 @@ #include "remote.h" #include "branch.h" #include "blame.h" +#include "mergeresult.h" #include extern PyObject *GitError; @@ -578,6 +579,58 @@ Repository_merge_base(Repository *self, PyObject *args) return git_oid_to_python(&oid); } +PyDoc_STRVAR(Repository_merge__doc__, + "merge(oid) -> MergeResult\n" + "\n" + "Merges the given oid and returns the MergeResult.\n" + "\n" + "If the merge is fastforward the MergeResult will contain the new\n" + "fastforward oid.\n" + "If the branch is uptodate, nothing to merge, the MergeResult will\n" + "have the fastforward oid as None.\n" + "If the merge is not fastforward the MergeResult will have the status\n" + "produced by the merge, even if there are conflicts."); + +PyObject * +Repository_merge(Repository *self, PyObject *py_oid) +{ + git_merge_result *merge_result; + git_merge_head *oid_merge_head; + git_oid oid; + const git_merge_opts default_opts = GIT_MERGE_OPTS_INIT; + int err; + size_t len; + PyObject *py_merge_result; + + len = py_oid_to_git_oid(py_oid, &oid); + if (len == 0) + return NULL; + + err = git_merge_head_from_oid(&oid_merge_head, self->repo, &oid); + if (err < 0) + goto error; + + err = git_merge(&merge_result, self->repo, + (const git_merge_head **)&oid_merge_head, 1, + &default_opts); + if (err < 0) + { + git_merge_result_free(merge_result); + goto error; + } + + py_merge_result = git_merge_result_to_python(merge_result, self); + + git_merge_head_free(oid_merge_head); + git_merge_result_free(merge_result); + + return py_merge_result; + +error: + git_merge_head_free(oid_merge_head); + return Error_set(err); +} + PyDoc_STRVAR(Repository_walk__doc__, "walk(oid, sort_mode) -> iterator\n" "\n" @@ -1093,7 +1146,7 @@ PyDoc_STRVAR(Repository_status__doc__, "paths as keys and status flags as values. See pygit2.GIT_STATUS_*."); PyObject * -Repository_status(Repository *self, PyObject *args) +Repository_status(Repository *self) { PyObject *dict; int err; @@ -1551,6 +1604,7 @@ PyMethodDef Repository_methods[] = { METHOD(Repository, TreeBuilder, METH_VARARGS), METHOD(Repository, walk, METH_VARARGS), METHOD(Repository, merge_base, METH_VARARGS), + METHOD(Repository, merge, METH_O), METHOD(Repository, read, METH_O), METHOD(Repository, write, METH_VARARGS), METHOD(Repository, create_reference_direct, METH_VARARGS), diff --git a/src/repository.h b/src/repository.h index 3c60948..735f774 100644 --- a/src/repository.h +++ b/src/repository.h @@ -63,10 +63,12 @@ PyObject* Repository_create_reference(Repository *self, PyObject *args, PyObject* kw); PyObject* Repository_packall_references(Repository *self, PyObject *args); -PyObject* Repository_status(Repository *self, PyObject *args); +PyObject* Repository_status(Repository *self); PyObject* Repository_status_file(Repository *self, PyObject *value); PyObject* Repository_TreeBuilder(Repository *self, PyObject *args); PyObject* Repository_blame(Repository *self, PyObject *args, PyObject *kwds); +PyObject* Repository_merge(Repository *self, PyObject *py_oid); + #endif diff --git a/src/types.h b/src/types.h index 46062a4..06504fd 100644 --- a/src/types.h +++ b/src/types.h @@ -217,5 +217,14 @@ typedef struct { char boundary; } BlameHunk; +/* git_merge */ +typedef struct { + PyObject_HEAD + int is_uptodate; + int is_fastforward; + PyObject* fastforward_oid; + PyObject* status; + +} MergeResult; #endif diff --git a/test/test_repository.py b/test/test_repository.py index 43e196b..bcbb48f 100644 --- a/test/test_repository.py +++ b/test/test_repository.py @@ -302,6 +302,63 @@ class RepositoryTest_II(utils.RepoTestCase): self.assertTrue("bonjour le monde\n" in diff.patch) +class RepositoryTest_III(utils.RepoTestCaseForMerging): + + def test_merge_uptodate(self): + branch_head_hex = '5ebeeebb320790caf276b9fc8b24546d63316533' + branch_oid = self.repo.get(branch_head_hex).oid + merge_result = self.repo.merge(branch_oid) + self.assertTrue(merge_result.is_uptodate) + self.assertFalse(merge_result.is_fastforward) + self.assertEquals(None, merge_result.fastforward_oid) + self.assertEquals({}, merge_result.status) + + def test_merge_fastforward(self): + branch_head_hex = 'e97b4cfd5db0fb4ebabf4f203979ca4e5d1c7c87' + branch_oid = self.repo.get(branch_head_hex).oid + merge_result = self.repo.merge(branch_oid) + self.assertFalse(merge_result.is_uptodate) + self.assertTrue(merge_result.is_fastforward) + # Asking twice to assure the reference counting is correct + self.assertEquals(branch_head_hex, merge_result.fastforward_oid.hex) + self.assertEquals(branch_head_hex, merge_result.fastforward_oid.hex) + self.assertEquals({}, merge_result.status) + + def test_merge_no_fastforward_no_conflicts(self): + branch_head_hex = '03490f16b15a09913edb3a067a3dc67fbb8d41f1' + branch_oid = self.repo.get(branch_head_hex).oid + merge_result = self.repo.merge(branch_oid) + self.assertFalse(merge_result.is_uptodate) + self.assertFalse(merge_result.is_fastforward) + self.assertEquals(None, merge_result.fastforward_oid) + # Asking twice to assure the reference counting is correct + self.assertEquals({'bye.txt': 1}, merge_result.status) + self.assertEquals({'bye.txt': 1}, merge_result.status) + + def test_merge_no_fastforward_conflicts(self): + branch_head_hex = '1b2bae55ac95a4be3f8983b86cd579226d0eb247' + branch_oid = self.repo.get(branch_head_hex).oid + merge_result = self.repo.merge(branch_oid) + self.assertFalse(merge_result.is_uptodate) + self.assertFalse(merge_result.is_fastforward) + self.assertEquals(None, merge_result.fastforward_oid) + # Asking twice to assure the reference counting is correct + self.assertEquals({'.gitignore': 132}, merge_result.status) + self.assertEquals({'.gitignore': 132}, merge_result.status) + + def test_merge_invalid_hex(self): + branch_head_hex = '12345678' + self.assertRaises(KeyError, self.repo.merge, branch_head_hex) + + def test_merge_already_something_in_index(self): + branch_head_hex = '03490f16b15a09913edb3a067a3dc67fbb8d41f1' + branch_oid = self.repo.get(branch_head_hex).oid + with open(os.path.join(self.repo.workdir, 'inindex.txt'), 'w') as f: + f.write('new content') + self.repo.index.add('inindex.txt') + self.assertRaises(pygit2.GitError, self.repo.merge, branch_oid) + + class NewRepositoryTest(utils.NoRepoTestCase): def test_new_repo(self): @@ -376,8 +433,7 @@ class CloneRepositoryTest(utils.NoRepoTestCase): def test_clone_remote_name(self): repo_path = "./test/data/testrepo.git/" repo = clone_repository( - repo_path, self._temp_dir, remote_name="custom_remote" - ) + repo_path, self._temp_dir, remote_name="custom_remote") self.assertFalse(repo.is_empty) self.assertEqual(repo.remotes[0].name, "custom_remote") diff --git a/test/utils.py b/test/utils.py index fb7ca58..4d941e5 100644 --- a/test/utils.py +++ b/test/utils.py @@ -146,6 +146,11 @@ class RepoTestCase(AutoRepoTestCase): repo_spec = 'tar', 'testrepo' +class RepoTestCaseForMerging(AutoRepoTestCase): + + repo_spec = 'tar', 'testrepoformerging' + + class DirtyRepoTestCase(AutoRepoTestCase): repo_spec = 'tar', 'dirtyrepo'