Merge remote-tracking branch 'victor/master'

This commit is contained in:
J. David Ibáñez
2013-12-10 21:00:02 +01:00
10 changed files with 345 additions and 4 deletions

View File

@@ -2,4 +2,36 @@
Merge
**********************************************************************
.. contents::
.. automethod:: pygit2.Repository.merge_base
.. automethod:: pygit2.Repository.merge
The merge method
=================
The method does a merge over the current working copy.
It gets an Oid object as a parameter and returns a MergeResult object.
As its name says, it only does the merge, does not commit nor update the
branch reference in the case of a fastforward.
For the moment, the merge does not support options, it will perform the
merge with the default ones defined in GIT_MERGE_OPTS_INIT libgit2 constant.
Example::
>>> branch_head_hex = '5ebeeebb320790caf276b9fc8b24546d63316533'
>>> branch_oid = self.repo.get(branch_head_hex).oid
>>> merge_result = self.repo.merge(branch_oid)
The MergeResult object
======================
Represents the result of a merge and contains these fields:
- is_uptodate: bool, if there wasn't any merge because the repo was already
up to date
- is_fastforward: bool, whether the merge was fastforward or not
- fastforward_oid: Oid, in the case it was a fastforward, this is the
forwarded Oid.

138
src/mergeresult.c Normal file
View File

@@ -0,0 +1,138 @@
/*
* 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 <Python.h>
#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)
{
MergeResult *py_merge_result;
py_merge_result = PyObject_New(MergeResult, &MergeResultType);
if (!py_merge_result)
return NULL;
py_merge_result->result = merge_result;
py_merge_result->repo = repo;
return (PyObject*) py_merge_result;
}
PyDoc_STRVAR(MergeResult_is_uptodate__doc__, "Is up to date");
PyObject *
MergeResult_is_uptodate__get__(MergeResult *self)
{
if (git_merge_result_is_uptodate(self->result))
Py_RETURN_TRUE;
else
Py_RETURN_FALSE;
}
PyDoc_STRVAR(MergeResult_is_fastforward__doc__, "Is fastforward");
PyObject *
MergeResult_is_fastforward__get__(MergeResult *self)
{
if (git_merge_result_is_fastforward(self->result))
Py_RETURN_TRUE;
else
Py_RETURN_FALSE;
}
PyDoc_STRVAR(MergeResult_fastforward_oid__doc__, "Fastforward Oid");
PyObject *
MergeResult_fastforward_oid__get__(MergeResult *self)
{
if (git_merge_result_is_fastforward(self->result)) {
git_oid fastforward_oid;
git_merge_result_fastforward_oid(&fastforward_oid, self->result);
return git_oid_to_python((const git_oid *)&fastforward_oid);
}
else Py_RETURN_NONE;
}
PyGetSetDef MergeResult_getseters[] = {
GETTER(MergeResult, is_uptodate),
GETTER(MergeResult, is_fastforward),
GETTER(MergeResult, fastforward_oid),
{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 */
};

37
src/mergeresult.h Normal file
View File

@@ -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 <Python.h>
#include <git2.h>
PyObject* git_merge_result_to_python(git_merge_result *merge_result, Repository *repo);
#endif

View File

@@ -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();

View File

@@ -38,6 +38,7 @@
#include "remote.h"
#include "branch.h"
#include "blame.h"
#include "mergeresult.h"
#include <git2/odb_backend.h>
extern PyObject *GitError;
@@ -579,6 +580,55 @@ 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_head_free(oid_merge_head);
goto error;
}
py_merge_result = git_merge_result_to_python(merge_result, self);
git_merge_head_free(oid_merge_head);
return py_merge_result;
error:
return Error_set(err);
}
PyDoc_STRVAR(Repository_walk__doc__,
"walk(oid, sort_mode) -> iterator\n"
"\n"
@@ -1094,7 +1144,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;
@@ -1553,6 +1603,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),

View File

@@ -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

View File

@@ -217,5 +217,7 @@ typedef struct {
char boundary;
} BlameHunk;
/* git_merge */
SIMPLE_TYPE(MergeResult, git_merge_result, result)
#endif

Binary file not shown.

View File

@@ -302,6 +302,76 @@ class RepositoryTest_II(utils.RepoTestCase):
self.assertTrue("bonjour le monde\n" in diff.patch)
class RepositoryTest_III(utils.RepoTestCaseForMerging):
def test_merge_none(self):
self.assertRaises(TypeError, self.repo.merge, None)
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({}, self.repo.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({}, self.repo.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)
# Asking twice to assure the reference counting is correct
self.assertEquals(None, merge_result.fastforward_oid)
self.assertEquals(None, merge_result.fastforward_oid)
self.assertEquals({'bye.txt': 1}, self.repo.status())
self.assertEquals({'bye.txt': 1}, self.repo.status())
# Checking the index works as expected
self.repo.index.remove('bye.txt')
self.repo.index.write()
self.assertEquals({'bye.txt': 128}, self.repo.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)
# Asking twice to assure the reference counting is correct
self.assertEquals(None, merge_result.fastforward_oid)
self.assertEquals(None, merge_result.fastforward_oid)
self.assertEquals({'.gitignore': 132}, self.repo.status())
self.assertEquals({'.gitignore': 132}, self.repo.status())
# Checking the index works as expected
self.repo.index.add('.gitignore')
self.repo.index.write()
self.assertEquals({'.gitignore': 2}, self.repo.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 +446,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")

View File

@@ -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'