From 298f941036192f80aaa90ed2eaab2a24bc4d5e77 Mon Sep 17 00:00:00 2001 From: Brodie Rao Date: Wed, 14 Aug 2013 20:11:19 -0700 Subject: [PATCH 0001/1630] tag: add get_object() method This maps to git_tag_peel(). --- docs/objects.rst | 2 ++ src/tag.c | 27 ++++++++++++++++++++++++++- test/test_tag.py | 5 +++++ 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/docs/objects.rst b/docs/objects.rst index 36edf958f..6ee3e1ed0 100644 --- a/docs/objects.rst +++ b/docs/objects.rst @@ -267,6 +267,8 @@ A tag is a static label for a commit. See references for more information. .. autoattribute:: pygit2.Tag.tagger .. autoattribute:: pygit2.Tag.message +.. automethod:: pygit2.Tag.get_object + Creating tags -------------------- diff --git a/src/tag.c b/src/tag.c index bc4eaada7..35b9af578 100644 --- a/src/tag.c +++ b/src/tag.c @@ -27,6 +27,7 @@ #define PY_SSIZE_T_CLEAN #include +#include "object.h" #include "error.h" #include "types.h" #include "utils.h" @@ -47,6 +48,25 @@ Tag_target__get__(Tag *self) } +PyDoc_STRVAR(Tag_get_object__doc__, + "get_object() -> object\n" + "\n" + "Retrieves the object the current reference is pointing to."); + +PyObject * +Tag_get_object(Tag *self) +{ + int err; + git_object* obj; + + err = git_tag_peel(&obj, self->tag); + if (err < 0) + return Error_set(err); + + return wrap_object(obj, self->repo); +} + + PyDoc_STRVAR(Tag_name__doc__, "Tag name."); PyObject * @@ -94,6 +114,11 @@ Tag__message__get__(Tag *self) return PyBytes_FromString(git_tag_message(self->tag)); } +PyMethodDef Tag_methods[] = { + METHOD(Tag, get_object, METH_NOARGS), + {NULL} +}; + PyGetSetDef Tag_getseters[] = { GETTER(Tag, target), GETTER(Tag, name), @@ -134,7 +159,7 @@ PyTypeObject TagType = { 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ - 0, /* tp_methods */ + Tag_methods, /* tp_methods */ 0, /* tp_members */ Tag_getseters, /* tp_getset */ 0, /* tp_base */ diff --git a/test/test_tag.py b/test/test_tag.py index 12d89f055..9530eb5cc 100644 --- a/test/test_tag.py +++ b/test/test_tag.py @@ -89,6 +89,11 @@ def test_modify_tag(self): self.assertRaises(AttributeError, setattr, tag, 'tagger', tagger) self.assertRaises(AttributeError, setattr, tag, 'message', message) + def test_get_object(self): + repo = self.repo + tag = repo[TAG_SHA] + self.assertEqual(repo[tag.target].oid, tag.get_object().oid) + if __name__ == '__main__': unittest.main() From 6d87567e0f029528b7fc89e8d49e6898ab5b7ab5 Mon Sep 17 00:00:00 2001 From: Brodie Rao Date: Fri, 16 Aug 2013 10:56:15 -0700 Subject: [PATCH 0002/1630] tag: fix incorrect doc string wording for get_object() --- src/tag.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tag.c b/src/tag.c index 35b9af578..c5fdd0511 100644 --- a/src/tag.c +++ b/src/tag.c @@ -51,7 +51,7 @@ Tag_target__get__(Tag *self) PyDoc_STRVAR(Tag_get_object__doc__, "get_object() -> object\n" "\n" - "Retrieves the object the current reference is pointing to."); + "Retrieves the object the current tag is pointing to."); PyObject * Tag_get_object(Tag *self) From b55650e093bd7507eec25ce35debd37497368c4b Mon Sep 17 00:00:00 2001 From: Brodie Rao Date: Mon, 19 Aug 2013 11:53:48 -0700 Subject: [PATCH 0003/1630] commit: rename Commit._message to Commit.raw_message --- docs/objects.rst | 1 + src/commit.c | 6 +++--- test/test_commit.py | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/objects.rst b/docs/objects.rst index 6ee3e1ed0..6cfd80b3d 100644 --- a/docs/objects.rst +++ b/docs/objects.rst @@ -216,6 +216,7 @@ committer and others. .. autoattribute:: pygit2.Commit.committer .. autoattribute:: pygit2.Commit.message .. autoattribute:: pygit2.Commit.message_encoding +.. autoattribute:: pygit2.Commit.raw_message .. autoattribute:: pygit2.Commit.tree .. autoattribute:: pygit2.Commit.parents .. autoattribute:: pygit2.Commit.commit_time diff --git a/src/commit.c b/src/commit.c index 74b1ecc16..725a3c44e 100644 --- a/src/commit.c +++ b/src/commit.c @@ -64,10 +64,10 @@ Commit_message__get__(Commit *commit) } -PyDoc_STRVAR(Commit__message__doc__, "Message (bytes)."); +PyDoc_STRVAR(Commit_raw_message__doc__, "Message (bytes)."); PyObject * -Commit__message__get__(Commit *commit) +Commit_raw_message__get__(Commit *commit) { return PyBytes_FromString(git_commit_message(commit->commit)); } @@ -195,7 +195,7 @@ Commit_parents__get__(Commit *self) PyGetSetDef Commit_getseters[] = { GETTER(Commit, message_encoding), GETTER(Commit, message), - GETTER(Commit, _message), + GETTER(Commit, raw_message), GETTER(Commit, commit_time), GETTER(Commit, commit_time_offset), GETTER(Commit, committer), diff --git a/test/test_commit.py b/test/test_commit.py index 13acf982e..85f3d2350 100644 --- a/test/test_commit.py +++ b/test/test_commit.py @@ -113,7 +113,7 @@ def test_new_commit_encoding(self): self.assertEqual(GIT_OBJ_COMMIT, commit.type) self.assertEqual('iso-8859-1', commit.message_encoding) - self.assertEqual(message, commit.message) + self.assertEqual(message.encode(encoding), commit.raw_message) self.assertEqual(12346, commit.commit_time) self.assertEqualSignature(committer, commit.committer) self.assertEqualSignature(author, commit.author) From aec44c9ca2978d91c231491929f5f750fb3e4aa8 Mon Sep 17 00:00:00 2001 From: Brodie Rao Date: Mon, 19 Aug 2013 13:38:57 -0700 Subject: [PATCH 0004/1630] signature: rename Signature._name/_email to Signature.raw_name/raw_email --- docs/objects.rst | 2 ++ src/signature.c | 12 ++++++------ test/test_signature.py | 24 ++++++++++++++++++------ 3 files changed, 26 insertions(+), 12 deletions(-) diff --git a/docs/objects.rst b/docs/objects.rst index 6ee3e1ed0..2458511b7 100644 --- a/docs/objects.rst +++ b/docs/objects.rst @@ -232,7 +232,9 @@ objects:: .. autoattribute:: pygit2.Signature.name +.. autoattribute:: pygit2.Signature.raw_name .. autoattribute:: pygit2.Signature.email +.. autoattribute:: pygit2.Signature.raw_email .. autoattribute:: pygit2.Signature.time .. autoattribute:: pygit2.Signature.offset diff --git a/src/signature.c b/src/signature.c index 24e02a83e..61fd0746c 100644 --- a/src/signature.c +++ b/src/signature.c @@ -107,19 +107,19 @@ Signature__encoding__get__(Signature *self) } -PyDoc_STRVAR(Signature__name__doc__, "Name (bytes)."); +PyDoc_STRVAR(Signature_raw_name__doc__, "Name (bytes)."); PyObject * -Signature__name__get__(Signature *self) +Signature_raw_name__get__(Signature *self) { return to_bytes(self->signature->name); } -PyDoc_STRVAR(Signature__email__doc__, "Email (bytes)."); +PyDoc_STRVAR(Signature_raw_email__doc__, "Email (bytes)."); PyObject * -Signature__email__get__(Signature *self) +Signature_raw_email__get__(Signature *self) { return to_bytes(self->signature->email); } @@ -162,8 +162,8 @@ Signature_offset__get__(Signature *self) PyGetSetDef Signature_getseters[] = { GETTER(Signature, _encoding), - GETTER(Signature, _name), - GETTER(Signature, _email), + GETTER(Signature, raw_name), + GETTER(Signature, raw_email), GETTER(Signature, name), GETTER(Signature, email), GETTER(Signature, time), diff --git a/test/test_signature.py b/test/test_signature.py index cf21b33f1..4131c6c83 100644 --- a/test/test_signature.py +++ b/test/test_signature.py @@ -41,8 +41,12 @@ def test_default(self): 'Foo', 'foo@example.com', 1322174594, 60) encoding = signature._encoding self.assertEqual(encoding, 'ascii') - self.assertEqual(signature.name, signature._name.decode(encoding)) - self.assertEqual(signature.name.encode(encoding), signature._name) + self.assertEqual(signature.name, signature.raw_name.decode(encoding)) + self.assertEqual(signature.name.encode(encoding), signature.raw_name) + self.assertEqual(signature.email, + signature.raw_email.decode(encoding)) + self.assertEqual(signature.email.encode(encoding), + signature.raw_email) def test_ascii(self): self.assertRaises(UnicodeEncodeError, @@ -53,16 +57,24 @@ def test_latin1(self): signature = Signature( 'Foo Ibáñez', 'foo@example.com', encoding=encoding) self.assertEqual(encoding, signature._encoding) - self.assertEqual(signature.name, signature._name.decode(encoding)) - self.assertEqual(signature.name.encode(encoding), signature._name) + self.assertEqual(signature.name, signature.raw_name.decode(encoding)) + self.assertEqual(signature.name.encode(encoding), signature.raw_name) + self.assertEqual(signature.email, + signature.raw_email.decode(encoding)) + self.assertEqual(signature.email.encode(encoding), + signature.raw_email) def test_now(self): encoding = 'utf-8' signature = Signature( 'Foo Ibáñez', 'foo@example.com', encoding=encoding) self.assertEqual(encoding, signature._encoding) - self.assertEqual(signature.name, signature._name.decode(encoding)) - self.assertEqual(signature.name.encode(encoding), signature._name) + self.assertEqual(signature.name, signature.raw_name.decode(encoding)) + self.assertEqual(signature.name.encode(encoding), signature.raw_name) + self.assertEqual(signature.email, + signature.raw_email.decode(encoding)) + self.assertEqual(signature.email.encode(encoding), + signature.raw_email) self.assertTrue(abs(signature.time - time.time()) < 5) From af68816d4abed88cf72d7c977119cb9152c1d8d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Sat, 24 Aug 2013 11:59:44 +0200 Subject: [PATCH 0005/1630] Make pygit2.TreeBuilder() to fail (issue #265) --- src/pygit2.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pygit2.c b/src/pygit2.c index a4729df63..20fd27a1d 100644 --- a/src/pygit2.c +++ b/src/pygit2.c @@ -281,7 +281,7 @@ moduleinit(PyObject* m) INIT_TYPE(TreeType, &ObjectType, NULL) INIT_TYPE(TreeEntryType, NULL, NULL) INIT_TYPE(TreeIterType, NULL, NULL) - INIT_TYPE(TreeBuilderType, NULL, PyType_GenericNew) + INIT_TYPE(TreeBuilderType, NULL, NULL) INIT_TYPE(BlobType, &ObjectType, NULL) INIT_TYPE(TagType, &ObjectType, NULL) ADD_TYPE(m, Object) From 179c7db9400b18242cc10f4832b181f6bd5ec0ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Thu, 29 Aug 2013 17:18:01 +0200 Subject: [PATCH 0006/1630] Don't forget to remove the temporary directory in the config tests When overriding the tear-down function, we must remember to call the parent's function or we won't clean up the temporary directory for the test. --- test/test_config.py | 1 + 1 file changed, 1 insertion(+) diff --git a/test/test_config.py b/test/test_config.py index a8afe86cb..8740a64c9 100644 --- a/test/test_config.py +++ b/test/test_config.py @@ -46,6 +46,7 @@ def foreach_test_wrapper(key, name, lst): class ConfigTest(utils.RepoTestCase): def tearDown(self): + super(ConfigTest, self).tearDown() try: os.remove(CONFIG_FILENAME) except OSError: From 74b1628e5170a0406db62d726b61933de4825b56 Mon Sep 17 00:00:00 2001 From: Fraser Tweedale Date: Thu, 29 Aug 2013 23:22:36 +1000 Subject: [PATCH 0007/1630] test utils: implement a repo context manager The current hierarchy of test cases that create and blow away test repositories in the ``setUp`` and ``tearDown`` methods is inadequate for testing scenarios where multiple repositories must be edited, e.g. testing push. Extract the temporary repository behaviour into a context manager class ``TemporaryRepository`` which can be used via the ``with`` keyword. The context manager's ``__enter__` method returns creates the specified repository in a temporary directory and returns its path. The temporary directory is removed in ``__exit__``. Update the existing test case base classes to use the context manager and invoke ``__enter__`` and ``__exit__`` in the ``setUp`` and ``tearDown`` methods respectively. Finally, make some small tweaks to a handful of tests to get them working under this new system. --- test/test_blob.py | 5 ++-- test/test_repository.py | 6 ++-- test/utils.py | 65 +++++++++++++++++++++++------------------ 3 files changed, 42 insertions(+), 34 deletions(-) diff --git a/test/test_blob.py b/test/test_blob.py index 8105455f7..2f5259649 100644 --- a/test/test_blob.py +++ b/test/test_blob.py @@ -93,13 +93,12 @@ def test_create_blob_fromworkdir(self): def test_create_blob_outside_workdir(self): - path = join(dirname(__file__), 'data', self.repo_dir + '.tar') + path = __file__ self.assertRaises(KeyError, self.repo.create_blob_fromworkdir, path) def test_create_blob_fromdisk(self): - path = join(dirname(__file__), 'data', self.repo_dir + '.tar') - blob_oid = self.repo.create_blob_fromdisk(path) + blob_oid = self.repo.create_blob_fromdisk(__file__) blob = self.repo[blob_oid] self.assertTrue(isinstance(blob, pygit2.Blob)) diff --git a/test/test_repository.py b/test/test_repository.py index 87c9fc704..ab0b22c89 100644 --- a/test/test_repository.py +++ b/test/test_repository.py @@ -143,7 +143,7 @@ def test_lookup_commit_prefix(self): def test_get_path(self): directory = realpath(self.repo.path) - expected = realpath(join(self._temp_dir, 'testrepo.git')) + expected = realpath(self.repo_path) self.assertEqual(directory, expected) def test_get_workdir(self): @@ -179,12 +179,12 @@ def test_is_bare(self): def test_get_path(self): directory = realpath(self.repo.path) - expected = realpath(join(self._temp_dir, 'testrepo', '.git')) + expected = realpath(join(self.repo_path, '.git')) self.assertEqual(directory, expected) def test_get_workdir(self): directory = realpath(self.repo.workdir) - expected = realpath(join(self._temp_dir, 'testrepo')) + expected = realpath(self.repo_path) self.assertEqual(directory, expected) def test_checkout_ref(self): diff --git a/test/utils.py b/test/utils.py index 10a4e1719..fb7ca58f9 100644 --- a/test/utils.py +++ b/test/utils.py @@ -65,6 +65,27 @@ def rmtree(path): shutil.rmtree(path, onerror=onerror) +class TemporaryRepository(object): + def __init__(self, repo_spec): + self.repo_spec = repo_spec + + def __enter__(self): + container, name = self.repo_spec + repo_path = os.path.join(os.path.dirname(__file__), 'data', name) + self.temp_dir = tempfile.mkdtemp() + temp_repo_path = os.path.join(self.temp_dir, name) + if container == 'tar': + tar = tarfile.open('.'.join((repo_path, 'tar'))) + tar.extractall(self.temp_dir) + tar.close() + else: + shutil.copytree(repo_path, temp_repo_path) + return temp_repo_path + + def __exit__(self, exc_type, exc_value, traceback): + rmtree(self.temp_dir) + + class NoRepoTestCase(unittest.TestCase): def setUp(self): @@ -103,45 +124,33 @@ def assertEqualSignature(self, a, b): self.assertEqual(a.offset, b.offset) -class BareRepoTestCase(NoRepoTestCase): - - repo_dir = 'testrepo.git' - +class AutoRepoTestCase(NoRepoTestCase): def setUp(self): - super(BareRepoTestCase, self).setUp() - - repo_dir = self.repo_dir - repo_path = os.path.join(os.path.dirname(__file__), 'data', repo_dir) - temp_repo_path = os.path.join(self._temp_dir, repo_dir) - - shutil.copytree(repo_path, temp_repo_path) - - self.repo = pygit2.Repository(temp_repo_path) + super(AutoRepoTestCase, self).setUp() + self.repo_ctxtmgr = TemporaryRepository(self.repo_spec) + self.repo_path = self.repo_ctxtmgr.__enter__() + self.repo = pygit2.Repository(self.repo_path) + def tearDown(self): + self.repo_ctxtmgr.__exit__(None, None, None) + super(AutoRepoTestCase, self).tearDown() -class RepoTestCase(NoRepoTestCase): - repo_dir = 'testrepo' +class BareRepoTestCase(AutoRepoTestCase): - def setUp(self): - super(RepoTestCase, self).setUp() + repo_spec = 'git', 'testrepo.git' - repo_dir = self.repo_dir - repo_path = os.path.join(os.path.dirname(__file__), 'data', repo_dir) - temp_repo_path = os.path.join(self._temp_dir, repo_dir, '.git') - tar = tarfile.open(repo_path + '.tar') - tar.extractall(self._temp_dir) - tar.close() +class RepoTestCase(AutoRepoTestCase): - self.repo = pygit2.Repository(temp_repo_path) + repo_spec = 'tar', 'testrepo' -class DirtyRepoTestCase(RepoTestCase): +class DirtyRepoTestCase(AutoRepoTestCase): - repo_dir = 'dirtyrepo' + repo_spec = 'tar', 'dirtyrepo' -class EmptyRepoTestCase(RepoTestCase): +class EmptyRepoTestCase(AutoRepoTestCase): - repo_dir = 'emptyrepo' + repo_spec = 'tar', 'emptyrepo' From 134d87ab2a06459cb6c273cdbc82cc5a0aa5aa3c Mon Sep 17 00:00:00 2001 From: Fraser Tweedale Date: Sun, 25 Aug 2013 19:51:20 +1000 Subject: [PATCH 0008/1630] implement push support Implement push support via Remote.push which is called with a single refspec and raises GitError (with an appropriate message where possible) if the push fails. Note that local push to non-bare repository is currently not supported by libgit2. --- docs/remotes.rst | 1 + src/remote.c | 66 +++++++++++++++++++++++++++++++++++++++++++++ test/test_remote.py | 40 +++++++++++++++++++++++++++ 3 files changed, 107 insertions(+) diff --git a/docs/remotes.rst b/docs/remotes.rst index 91a69d055..4b9692fcd 100644 --- a/docs/remotes.rst +++ b/docs/remotes.rst @@ -15,4 +15,5 @@ The Remote type .. autoattribute:: pygit2.Remote.refspec_count .. automethod:: pygit2.Remote.get_refspec .. automethod:: pygit2.Remote.fetch +.. automethod:: pygit2.Remote.push .. automethod:: pygit2.Remote.save diff --git a/src/remote.c b/src/remote.c index 9faf11435..e482d8b1c 100644 --- a/src/remote.c +++ b/src/remote.c @@ -218,10 +218,76 @@ Remote_save(Remote *self, PyObject *args) } +int +push_status_foreach_callback(const char *ref, const char *msg, void *data) +{ + const char **msg_dst = (const char **)data; + if (msg != NULL && *msg_dst == NULL) + *msg_dst = msg; + return 0; +} + +PyDoc_STRVAR(Remote_push__doc__, + "push(refspec)\n" + "\n" + "Push the given refspec to the remote. Raises ``GitError`` on error."); + +PyObject * +Remote_push(Remote *self, PyObject *args) +{ + git_push *push = NULL; + const char *refspec = NULL; + const char *msg = NULL; + int err; + + if (!PyArg_ParseTuple(args, "s", &refspec)) + return NULL; + + err = git_push_new(&push, self->remote); + if (err < 0) + return Error_set(err); + + err = git_push_add_refspec(push, refspec); + if (err < 0) + goto error; + + err = git_push_finish(push); + if (err < 0) + goto error; + + if (!git_push_unpack_ok(push)) { + git_push_free(push); + PyErr_SetString(GitError, "Remote failed to unpack objects"); + return NULL; + } + + err = git_push_status_foreach(push, push_status_foreach_callback, &msg); + if (err < 0) + goto error; + if (msg != NULL) { + git_push_free(push); + PyErr_SetString(GitError, msg); + return NULL; + } + + err = git_push_update_tips(push); + if (err < 0) + goto error; + + git_push_free(push); + Py_RETURN_NONE; + +error: + git_push_free(push); + return Error_set(err); +} + + PyMethodDef Remote_methods[] = { METHOD(Remote, fetch, METH_NOARGS), METHOD(Remote, save, METH_NOARGS), METHOD(Remote, get_refspec, METH_O), + METHOD(Remote, push, METH_VARARGS), {NULL} }; diff --git a/test/test_remote.py b/test/test_remote.py index a4a125966..b6a9edc5d 100644 --- a/test/test_remote.py +++ b/test/test_remote.py @@ -124,5 +124,45 @@ def test_fetch(self): self.assertEqual(stats['received_objects'], REMOTE_REPO_OBJECTS) +class PushTestCase(unittest.TestCase): + def setUp(self): + self.origin_ctxtmgr = utils.TemporaryRepository(('git', 'testrepo.git')) + self.clone_ctxtmgr = utils.TemporaryRepository(('git', 'testrepo.git')) + self.origin = pygit2.Repository(self.origin_ctxtmgr.__enter__()) + self.clone = pygit2.Repository(self.clone_ctxtmgr.__enter__()) + self.remote = self.clone.create_remote('origin', self.origin.path) + + def tearDown(self): + self.origin_ctxtmgr.__exit__(None, None, None) + self.clone_ctxtmgr.__exit__(None, None, None) + + def test_push_fast_forward_commits_to_remote_succeeds(self): + tip = self.clone[self.clone.head.target] + oid = self.clone.create_commit( + 'refs/heads/master', tip.author, tip.author, 'empty commit', + tip.tree.oid, [tip.oid] + ) + self.remote.push('refs/heads/master') + self.assertEqual(self.origin[self.origin.head.target].oid, oid) + + def test_push_when_up_to_date_succeeds(self): + self.remote.push('refs/heads/master') + origin_tip = self.origin[self.origin.head.target].oid + clone_tip = self.clone[self.clone.head.target].oid + self.assertEqual(origin_tip, clone_tip) + + def test_push_non_fast_forward_commits_to_remote_fails(self): + tip = self.origin[self.origin.head.target] + oid = self.origin.create_commit( + 'refs/heads/master', tip.author, tip.author, 'some commit', + tip.tree.oid, [tip.oid] + ) + tip = self.clone[self.clone.head.target] + oid = self.clone.create_commit( + 'refs/heads/master', tip.author, tip.author, 'other commit', + tip.tree.oid, [tip.oid] + ) + self.assertRaises(pygit2.GitError, self.remote.push, 'refs/heads/master') + if __name__ == '__main__': unittest.main() From d5c0a23630d2471e4fe3772d39343201f7cd130a Mon Sep 17 00:00:00 2001 From: Andrew Chin Date: Sun, 1 Sep 2013 12:58:55 -0400 Subject: [PATCH 0009/1630] Doc fixes: change head.oid to head.target in examples --- docs/recipes/git-log.rst | 4 ++-- src/repository.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/recipes/git-log.rst b/docs/recipes/git-log.rst index e9c10b736..b234e0f61 100644 --- a/docs/recipes/git-log.rst +++ b/docs/recipes/git-log.rst @@ -16,7 +16,7 @@ Show HEAD commit .. code-block:: python - >>> commit = repo[repo.head.oid] + >>> commit = repo[repo.head.target] >>> commit.message 'commit message' @@ -30,7 +30,7 @@ Traverse commit history .. code-block:: python - >>> last = repo[repo.head.oid] + >>> last = repo[repo.head.target] >>> for commit in repo.walk(last.oid, pygit2.GIT_SORT_TIME): >>> print(commit.message) # or some other operation diff --git a/src/repository.c b/src/repository.c index 935c1665f..2ce42ab29 100644 --- a/src/repository.c +++ b/src/repository.c @@ -591,9 +591,9 @@ PyDoc_STRVAR(Repository_walk__doc__, " >>> from pygit2 import Repository\n" " >>> from pygit2 import GIT_SORT_TOPOLOGICAL, GIT_SORT_REVERSE\n" " >>> repo = Repository('.git')\n" - " >>> for commit in repo.walk(repo.head.oid, GIT_SORT_TOPOLOGICAL):\n" + " >>> for commit in repo.walk(repo.head.target, GIT_SORT_TOPOLOGICAL):\n" " ... print commit.message\n" - " >>> for commit in repo.walk(repo.head.oid, GIT_SORT_TOPOLOGICAL | GIT_SORT_REVERSE):\n" + " >>> for commit in repo.walk(repo.head.target, GIT_SORT_TOPOLOGICAL | GIT_SORT_REVERSE):\n" " ... print commit.message\n" " >>>\n"); From 9c13be8dec39c60460182d1632324999333f3769 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Thu, 3 Oct 2013 20:35:58 +0200 Subject: [PATCH 0010/1630] Release 0.19.1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit API changes: - Rename Commit._message to Commit.raw_message - Rename Signature._name to Signature.raw_name - Rename Signature._email to Signature.raw_email New features: - Remote.push(refspec) - Tag.get_object() And some bugs fixed. Thanks to Brodie Rao, Fraser Tweedale, Andrew Chin and Carlos Martín Nieto. --- .mailmap | 5 +++-- README.rst | 4 +++- docs/conf.py | 2 +- pygit2/version.py | 2 +- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/.mailmap b/.mailmap index da003e239..5c3799bdd 100644 --- a/.mailmap +++ b/.mailmap @@ -1,6 +1,7 @@ +Carlos Martín Nieto +Christian Boos J. David Ibáñez +Martin Lenders Richo Healey Xavier Delannoy -Christian Boos -Martin Lenders Xu Tao diff --git a/README.rst b/README.rst index a64219bda..3b2d6e562 100644 --- a/README.rst +++ b/README.rst @@ -63,10 +63,12 @@ This is the list of authors of pygit2, sorted by number of commits (as shown by - Valentin Haenel - Bernardo Heynemann - John Szakmeister +- Brodie Rao - David Versmisse - Petr Hosek - Rémi Duraffort - Sebastian Thiel +- Fraser Tweedale - Han-Wen Nienhuys - Petr Viktorin - Alex Chamberlain @@ -79,6 +81,7 @@ This is the list of authors of pygit2, sorted by number of commits (as shown by - Sarath Lakshman - Vicent Marti - Zoran Zaric +- Andrew Chin - András Veres-Szentkirályi - Benjamin Kircher - Benjamin Pollack @@ -89,7 +92,6 @@ This is the list of authors of pygit2, sorted by number of commits (as shown by - Eric Schrijver - Erik van Zijst - Ferengee -- Fraser Tweedale - Hugh Cole-Baker - Josh Bleecher Snyder - Jun Omae diff --git a/docs/conf.py b/docs/conf.py index bb653d7c2..4ec29bcbd 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -50,7 +50,7 @@ # The short X.Y version. version = '0.19' # The full version, including alpha/beta/rc tags. -release = '0.19.0' +release = '0.19.1' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/pygit2/version.py b/pygit2/version.py index 3ac321ca4..8fe724b7a 100644 --- a/pygit2/version.py +++ b/pygit2/version.py @@ -23,4 +23,4 @@ # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. -__version__ = '0.19.0' +__version__ = '0.19.1' From c6d2a65d9fe61a993b784de1e59aed0e37269ffc Mon Sep 17 00:00:00 2001 From: XTao Date: Wed, 30 Oct 2013 14:59:17 +0800 Subject: [PATCH 0011/1630] Add additions and deletions for patch. --- src/diff.c | 7 ++++++- src/types.h | 2 ++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/diff.c b/src/diff.c index f46d4bdb7..489e7f9eb 100644 --- a/src/diff.c +++ b/src/diff.c @@ -63,7 +63,7 @@ diff_get_patch_byindex(git_diff_list* list, size_t idx) const git_diff_delta* delta; const git_diff_range* range; git_diff_patch* patch = NULL; - size_t i, j, hunk_amounts, lines_in_hunk, line_len, header_len; + size_t i, j, hunk_amounts, lines_in_hunk, line_len, header_len, additions, deletions; const char* line, *header; char line_origin; int err; @@ -84,6 +84,9 @@ diff_get_patch_byindex(git_diff_list* list, size_t idx) py_patch->old_oid = git_oid_allocfmt(&delta->old_file.oid); py_patch->new_oid = git_oid_allocfmt(&delta->new_file.oid); + git_diff_patch_line_stats(NULL, &additions, &deletions, patch); + py_patch->additions = additions; + py_patch->deletions = deletions; hunk_amounts = git_diff_patch_num_hunks(patch); py_patch->hunks = PyList_New(hunk_amounts); @@ -152,6 +155,8 @@ PyMemberDef Patch_members[] = { MEMBER(Patch, status, T_CHAR, "status"), MEMBER(Patch, similarity, T_INT, "similarity"), MEMBER(Patch, hunks, T_OBJECT, "hunks"), + MEMBER(Patch, additions, T_INT, "additions"), + MEMBER(Patch, deletions, T_INT, "deletions"), {NULL} }; diff --git a/src/types.h b/src/types.h index 926d74ad2..757ea4eb4 100644 --- a/src/types.h +++ b/src/types.h @@ -113,6 +113,8 @@ typedef struct { char* new_oid; char status; unsigned similarity; + unsigned additions; + unsigned deletions; } Patch; typedef struct { From d0b366e86614ec4b7e29ca3e98c89632c1c5f226 Mon Sep 17 00:00:00 2001 From: XTao Date: Wed, 30 Oct 2013 15:06:40 +0800 Subject: [PATCH 0012/1630] Add binary for patch. --- src/diff.c | 19 ++++++++++++++++++- src/types.h | 1 + test/test_diff.py | 1 + 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/diff.c b/src/diff.c index f46d4bdb7..e7adb50cc 100644 --- a/src/diff.c +++ b/src/diff.c @@ -81,6 +81,7 @@ diff_get_patch_byindex(git_diff_list* list, size_t idx) py_patch->new_file_path = delta->new_file.path; py_patch->status = git_diff_status_char(delta->status); py_patch->similarity = delta->similarity; + py_patch->flags = delta->flags; py_patch->old_oid = git_oid_allocfmt(&delta->old_file.oid); py_patch->new_oid = git_oid_allocfmt(&delta->new_file.oid); @@ -155,6 +156,22 @@ PyMemberDef Patch_members[] = { {NULL} }; +PyDoc_STRVAR(Patch_binary__doc__, "Binary."); + +PyObject * +Patch_binary__get__(Patch *self) +{ + if (!(self->flags & GIT_DIFF_FLAG_NOT_BINARY) && + (self->flags & GIT_DIFF_FLAG_BINARY)) + Py_RETURN_TRUE; + Py_RETURN_FALSE; +} + +PyGetSetDef Patch_getseters[] = { + GETTER(Patch, binary), + {NULL} +}; + PyDoc_STRVAR(Patch__doc__, "Diff patch object."); PyTypeObject PatchType = { @@ -187,7 +204,7 @@ PyTypeObject PatchType = { 0, /* tp_iternext */ 0, /* tp_methods */ Patch_members, /* tp_members */ - 0, /* tp_getset */ + Patch_getseters, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ diff --git a/src/types.h b/src/types.h index 926d74ad2..2b6c2251e 100644 --- a/src/types.h +++ b/src/types.h @@ -113,6 +113,7 @@ typedef struct { char* new_oid; char status; unsigned similarity; + unsigned flags; } Patch; typedef struct { diff --git a/test/test_diff.py b/test/test_diff.py index 697b8be19..0dcd22fec 100644 --- a/test/test_diff.py +++ b/test/test_diff.py @@ -175,6 +175,7 @@ def _test(diff): self.assertEqual(patch.old_file_path, 'a') self.assertEqual(patch.new_file_path, 'a') + self.assertEqual(patch.binary, False) _test(commit_a.tree.diff_to_tree(commit_b.tree)) _test(self.repo.diff(COMMIT_SHA1_1, COMMIT_SHA1_2)) From f74110a023b98322cc271533d3aec813dbf4bb9d Mon Sep 17 00:00:00 2001 From: XTao Date: Wed, 30 Oct 2013 15:24:07 +0800 Subject: [PATCH 0013/1630] Add binary for blob. --- src/blob.c | 12 ++++++++++++ test/test_blob.py | 1 + 2 files changed, 13 insertions(+) diff --git a/src/blob.c b/src/blob.c index 465d8d651..1d41a2283 100644 --- a/src/blob.c +++ b/src/blob.c @@ -41,12 +41,24 @@ Blob_size__get__(Blob *self) } +PyDoc_STRVAR(Blob_binary__doc__, "Binary."); + +PyObject * +Blob_binary__get__(Blob *self) +{ + if (git_blob_is_binary(self->blob)) + Py_RETURN_TRUE; + Py_RETURN_FALSE; +} + + PyDoc_STRVAR(Blob_data__doc__, "The contents of the blob, a bytes string. This is the same as\n" "Blob.read_raw()"); PyGetSetDef Blob_getseters[] = { GETTER(Blob, size), + GETTER(Blob, binary), {"data", (getter)Object_read_raw, NULL, Blob_data__doc__, NULL}, {NULL} }; diff --git a/test/test_blob.py b/test/test_blob.py index 2f5259649..d1a6897e9 100644 --- a/test/test_blob.py +++ b/test/test_blob.py @@ -53,6 +53,7 @@ def test_read_blob(self): sha = blob.oid.hex self.assertEqual(sha, BLOB_SHA) self.assertTrue(isinstance(blob, pygit2.Blob)) + self.assertFalse(blob.binary) self.assertEqual(pygit2.GIT_OBJ_BLOB, blob.type) self.assertEqual(BLOB_CONTENT, blob.data) self.assertEqual(len(BLOB_CONTENT), blob.size) From 9730564c735c7746c85614c42d0f10e40321261e Mon Sep 17 00:00:00 2001 From: Huang Huang Date: Wed, 30 Oct 2013 15:48:29 +0800 Subject: [PATCH 0014/1630] the number of deltas/patches in one diff --- src/diff.c | 7 +++++++ test/test_diff.py | 1 + 2 files changed, 8 insertions(+) diff --git a/src/diff.c b/src/diff.c index f46d4bdb7..0728d7de5 100644 --- a/src/diff.c +++ b/src/diff.c @@ -249,6 +249,12 @@ PyTypeObject DiffIterType = { (iternextfunc) DiffIter_iternext, /* tp_iternext */ }; +PyDoc_STRVAR(Diff_size__doc__, "Returns the number of deltas/patches in this diff."); +PyObject * +Diff_size__get__(Diff *self) +{ + return PyLong_FromSize_t(git_diff_num_deltas(self->list)); +} PyDoc_STRVAR(Diff_patch__doc__, "Patch diff string."); @@ -440,6 +446,7 @@ Diff_dealloc(Diff *self) PyGetSetDef Diff_getseters[] = { GETTER(Diff, patch), + GETTER(Diff, size), {NULL} }; diff --git a/test/test_diff.py b/test/test_diff.py index 697b8be19..a2564c0cd 100644 --- a/test/test_diff.py +++ b/test/test_diff.py @@ -254,6 +254,7 @@ def test_diff_patch(self): diff = commit_a.tree.diff_to_tree(commit_b.tree) self.assertEqual(diff.patch, PATCH) + self.assertEqual(diff.size, len([patch for patch in diff])) def test_diff_oids(self): commit_a = self.repo[COMMIT_SHA1_1] From 50744352d72c5810e0d22dfd00ae2c85a4b79598 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Thu, 31 Oct 2013 09:24:00 +0100 Subject: [PATCH 0015/1630] Rename Patch.binary to Patch.is_binary for consistency --- src/diff.c | 6 +++--- test/test_diff.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/diff.c b/src/diff.c index 2ed1160c2..a539000ee 100644 --- a/src/diff.c +++ b/src/diff.c @@ -161,10 +161,10 @@ PyMemberDef Patch_members[] = { {NULL} }; -PyDoc_STRVAR(Patch_binary__doc__, "Binary."); +PyDoc_STRVAR(Patch_is_binary__doc__, "True if binary data, False if not."); PyObject * -Patch_binary__get__(Patch *self) +Patch_is_binary__get__(Patch *self) { if (!(self->flags & GIT_DIFF_FLAG_NOT_BINARY) && (self->flags & GIT_DIFF_FLAG_BINARY)) @@ -173,7 +173,7 @@ Patch_binary__get__(Patch *self) } PyGetSetDef Patch_getseters[] = { - GETTER(Patch, binary), + GETTER(Patch, is_binary), {NULL} }; diff --git a/test/test_diff.py b/test/test_diff.py index 0dcd22fec..3f762d8ed 100644 --- a/test/test_diff.py +++ b/test/test_diff.py @@ -175,7 +175,7 @@ def _test(diff): self.assertEqual(patch.old_file_path, 'a') self.assertEqual(patch.new_file_path, 'a') - self.assertEqual(patch.binary, False) + self.assertEqual(patch.is_binary, False) _test(commit_a.tree.diff_to_tree(commit_b.tree)) _test(self.repo.diff(COMMIT_SHA1_1, COMMIT_SHA1_2)) From c0b1ab9024ba67addfb83648ea5c14780a7330b5 Mon Sep 17 00:00:00 2001 From: Huang Huang Date: Fri, 1 Nov 2013 14:32:49 +0800 Subject: [PATCH 0016/1630] add len(diff) instead of diff.size --- src/diff.c | 11 +++++------ test/test_diff.py | 2 +- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/diff.c b/src/diff.c index 0728d7de5..2f946e7db 100644 --- a/src/diff.c +++ b/src/diff.c @@ -249,11 +249,11 @@ PyTypeObject DiffIterType = { (iternextfunc) DiffIter_iternext, /* tp_iternext */ }; -PyDoc_STRVAR(Diff_size__doc__, "Returns the number of deltas/patches in this diff."); -PyObject * -Diff_size__get__(Diff *self) +Py_ssize_t +Diff_len(Diff *self) { - return PyLong_FromSize_t(git_diff_num_deltas(self->list)); + assert(self->list); + return (Py_ssize_t)git_diff_num_deltas(self->list); } PyDoc_STRVAR(Diff_patch__doc__, "Patch diff string."); @@ -446,12 +446,11 @@ Diff_dealloc(Diff *self) PyGetSetDef Diff_getseters[] = { GETTER(Diff, patch), - GETTER(Diff, size), {NULL} }; PyMappingMethods Diff_as_mapping = { - 0, /* mp_length */ + (lenfunc)Diff_len, /* mp_length */ (binaryfunc)Diff_getitem, /* mp_subscript */ 0, /* mp_ass_subscript */ }; diff --git a/test/test_diff.py b/test/test_diff.py index a2564c0cd..a42fdf396 100644 --- a/test/test_diff.py +++ b/test/test_diff.py @@ -254,7 +254,7 @@ def test_diff_patch(self): diff = commit_a.tree.diff_to_tree(commit_b.tree) self.assertEqual(diff.patch, PATCH) - self.assertEqual(diff.size, len([patch for patch in diff])) + self.assertEqual(len(diff), len([patch for patch in diff])) def test_diff_oids(self): commit_a = self.repo[COMMIT_SHA1_1] From 3ad89b3f624f140f03b99aef6baf6ef97bde1571 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Fri, 1 Nov 2013 10:33:53 +0100 Subject: [PATCH 0017/1630] Rename Blob.binary to Blob.is_binary for consistency --- src/blob.c | 6 +++--- test/test_blob.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/blob.c b/src/blob.c index 1d41a2283..83c7ece55 100644 --- a/src/blob.c +++ b/src/blob.c @@ -41,10 +41,10 @@ Blob_size__get__(Blob *self) } -PyDoc_STRVAR(Blob_binary__doc__, "Binary."); +PyDoc_STRVAR(Blob_is_binary__doc__, "True if binary data, False if not."); PyObject * -Blob_binary__get__(Blob *self) +Blob_is_binary__get__(Blob *self) { if (git_blob_is_binary(self->blob)) Py_RETURN_TRUE; @@ -58,7 +58,7 @@ PyDoc_STRVAR(Blob_data__doc__, PyGetSetDef Blob_getseters[] = { GETTER(Blob, size), - GETTER(Blob, binary), + GETTER(Blob, is_binary), {"data", (getter)Object_read_raw, NULL, Blob_data__doc__, NULL}, {NULL} }; diff --git a/test/test_blob.py b/test/test_blob.py index d1a6897e9..4e7a6cdc9 100644 --- a/test/test_blob.py +++ b/test/test_blob.py @@ -53,7 +53,7 @@ def test_read_blob(self): sha = blob.oid.hex self.assertEqual(sha, BLOB_SHA) self.assertTrue(isinstance(blob, pygit2.Blob)) - self.assertFalse(blob.binary) + self.assertFalse(blob.is_binary) self.assertEqual(pygit2.GIT_OBJ_BLOB, blob.type) self.assertEqual(BLOB_CONTENT, blob.data) self.assertEqual(len(BLOB_CONTENT), blob.size) From ceac655eabfe556a95bb36afca103c6d8adb91a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sat, 2 Nov 2013 01:33:46 +0100 Subject: [PATCH 0018/1630] status: don't use the callback version This allows us more straightforward code as well as returning NULL on error with much less hassle than it would take with the callback version. --- src/repository.c | 56 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 38 insertions(+), 18 deletions(-) diff --git a/src/repository.c b/src/repository.c index 935c1665f..4bfa300e5 100644 --- a/src/repository.c +++ b/src/repository.c @@ -1096,33 +1096,53 @@ PyDoc_STRVAR(Repository_status__doc__, "Reads the status of the repository and returns a dictionary with file\n" "paths as keys and status flags as values. See pygit2.GIT_STATUS_*."); -int -read_status_cb(const char *path, unsigned int status_flags, void *payload) +PyObject * +Repository_status(Repository *self, PyObject *args) { - /* This is the callback that will be called in git_status_foreach. It - * will be called for every path.*/ - PyObject *flags; + PyObject *dict; int err; + size_t len, i; + git_status_list *list; - flags = PyLong_FromLong((long) status_flags); - err = PyDict_SetItemString(payload, path, flags); - Py_CLEAR(flags); + dict = PyDict_New(); + if (dict == NULL) + return NULL; + err = git_status_list_new(&list, self->repo, NULL); if (err < 0) - return GIT_ERROR; + return Error_set(err); - return GIT_OK; -} + len = git_status_list_entrycount(list); + for (i = 0; i < len; i++) { + const git_status_entry *entry; + const char *path; + PyObject *status; -PyObject * -Repository_status(Repository *self, PyObject *args) -{ - PyObject *payload_dict; + entry = git_status_byindex(list, i); + if (entry == NULL) + goto on_error; + + /* We need to choose one of the strings */ + path = entry->head_to_index ? + entry->head_to_index->old_file.path : + entry->index_to_workdir->old_file.path; + status = PyLong_FromLong((long) entry->status); + + err = PyDict_SetItemString(dict, path, status); + Py_CLEAR(status); + + if (err < 0) + goto on_error; + + } - payload_dict = PyDict_New(); - git_status_foreach(self->repo, read_status_cb, payload_dict); + git_status_list_free(list); + return dict; - return payload_dict; + on_error: + git_status_list_free(list); + Py_CLEAR(dict); + return NULL; } From 9da4d4fb9f766c110a6df6e0098053d47da8092e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sat, 2 Nov 2013 15:45:55 +0100 Subject: [PATCH 0019/1630] reference: add a shorthand property Add Reference.shorthand to make it simpler to present a human-readable name for a particular reference. --- src/reference.c | 9 +++++++++ test/test_refs.py | 5 +++++ 2 files changed, 14 insertions(+) diff --git a/src/reference.c b/src/reference.c index 4d11cb5c5..1df1fe9c3 100644 --- a/src/reference.c +++ b/src/reference.c @@ -276,6 +276,14 @@ Reference_name__get__(Reference *self) return to_path(git_reference_name(self->reference)); } +PyDoc_STRVAR(Reference_shorthand__doc__, "The shorthand \"human-readable\" name of the reference."); + +PyObject * +Reference_shorthand__get__(Reference *self) +{ + CHECK_REFERENCE(self); + return to_path(git_reference_shorthand(self->reference)); +} PyDoc_STRVAR(Reference_type__doc__, "Type, either GIT_REF_OID or GIT_REF_SYMBOLIC."); @@ -432,6 +440,7 @@ PyMethodDef Reference_methods[] = { PyGetSetDef Reference_getseters[] = { GETTER(Reference, name), + GETTER(Reference, shorthand), GETSET(Reference, target), GETTER(Reference, type), {NULL} diff --git a/test/test_refs.py b/test/test_refs.py index 461218527..347d38114 100644 --- a/test/test_refs.py +++ b/test/test_refs.py @@ -103,6 +103,11 @@ def test_set_target(self): reference.target = 'refs/heads/i18n' self.assertEqual(reference.target, 'refs/heads/i18n') + def test_get_shorthand(self): + reference = self.repo.lookup_reference('refs/heads/master') + self.assertEqual(reference.shorthand, 'master') + reference = self.repo.create_reference('refs/remotes/origin/master', LAST_COMMIT) + self.assertEqual(reference.shorthand, 'origin/master') def test_delete(self): repo = self.repo From e345d23501fda235ce94620e0b001ac97870d0fd Mon Sep 17 00:00:00 2001 From: XTao Date: Wed, 30 Oct 2013 15:14:15 +0800 Subject: [PATCH 0020/1630] Add append_log to reference. --- src/oid.c | 2 +- src/reference.c | 68 +++++++++++++++++++++++++++++++++++++++++++++ test/test_reflog.py | 44 +++++++++++++++++++++++++++++ 3 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 test/test_reflog.py diff --git a/src/oid.c b/src/oid.c index d1db630ef..40a5d79e4 100644 --- a/src/oid.c +++ b/src/oid.c @@ -46,7 +46,7 @@ git_oid_to_python(const git_oid *oid) } size_t -py_hex_to_git_oid (PyObject *py_oid, git_oid *oid) +py_hex_to_git_oid(PyObject *py_oid, git_oid *oid) { PyObject *py_hex; int err; diff --git a/src/reference.c b/src/reference.c index 4d11cb5c5..e9abc6c6c 100644 --- a/src/reference.c +++ b/src/reference.c @@ -40,6 +40,7 @@ extern PyObject *GitError; extern PyTypeObject RefLogEntryType; +extern PyTypeObject SignatureType; void RefLogIter_dealloc(RefLogIter *self) @@ -312,6 +313,72 @@ Reference_log(Reference *self) return (PyObject*)iter; } +PyDoc_STRVAR(Reference_log_append__doc__, + "log_append(committer, message, oid)\n" + "\n" + "Append reflog to the current reference."); + +PyObject * +Reference_log_append(Reference *self, PyObject *args, PyObject *kwds) +{ + git_signature *committer; + const char *message = NULL; + git_reflog *reflog; + git_oid oid; + git_oid *ref_oid; + int err; + Signature *py_committer; + PyObject *py_message = NULL; + PyObject *py_hex = NULL; + char *keywords[] = {"committer", "message", "oid", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O!|OO", keywords, + &SignatureType, &py_committer, + &py_message, + &py_hex)) + return NULL; + + /* FIXME: encoding */ + if (py_message != NULL) { + message = py_str_to_c_str(py_message, NULL); + if (message == NULL) + return NULL; + } + + if (py_hex != NULL) { + err = py_oid_to_git_oid_expand(self->repo->repo, py_hex, &oid); + if (err < 0) + return NULL; + } + + CHECK_REFERENCE(self); + + err = git_reflog_read(&reflog, self->reference); + if (err < 0) { + free((void *)message); + return NULL; + } + + if (py_hex != NULL) + ref_oid = &oid; + else + ref_oid = git_reference_target(self->reference); + + committer = (git_signature *)py_committer->signature; + if (!(err = git_reflog_append(reflog, + ref_oid, + committer, + message))) + err = git_reflog_write(reflog); + + git_reflog_free(reflog); + free((void *)message); + + if (err < 0) + return NULL; + + Py_RETURN_NONE; +} PyDoc_STRVAR(Reference_get_object__doc__, "get_object() -> object\n" @@ -426,6 +493,7 @@ PyMethodDef Reference_methods[] = { METHOD(Reference, rename, METH_O), METHOD(Reference, resolve, METH_NOARGS), METHOD(Reference, log, METH_NOARGS), + METHOD(Reference, log_append, METH_VARARGS|METH_KEYWORDS), METHOD(Reference, get_object, METH_NOARGS), {NULL} }; diff --git a/test/test_reflog.py b/test/test_reflog.py new file mode 100644 index 000000000..af68f5b89 --- /dev/null +++ b/test/test_reflog.py @@ -0,0 +1,44 @@ +# -*- coding: UTF-8 -*- +# +# 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. + +"""Tests for reference log.""" + +from __future__ import absolute_import +from __future__ import unicode_literals + +from pygit2 import Signature +from . import utils + + +class ReflogTest(utils.RepoTestCase): + + def test_log_append(self): + repo = self.repo + master = repo.lookup_reference("refs/heads/master") + signature = Signature('xtao', 'xutao@douban.com') + master.log_append(signature, 'reflog') + self.assertTrue('reflog' in [entry.message for entry in master.log()]) From 629e31827573d127d0773b04eef116c081694029 Mon Sep 17 00:00:00 2001 From: Petr Hosek Date: Mon, 4 Nov 2013 14:58:08 +0000 Subject: [PATCH 0021/1630] Add Blame support --- src/blame.c | 384 +++++++++++++++++++++++++++++++++++++++++++++ src/blame.h | 38 +++++ src/pygit2.c | 15 ++ src/repository.c | 67 ++++++++ src/repository.h | 2 + src/types.h | 24 +++ test/test_blame.py | 111 +++++++++++++ 7 files changed, 641 insertions(+) create mode 100644 src/blame.c create mode 100644 src/blame.h create mode 100644 test/test_blame.py diff --git a/src/blame.c b/src/blame.c new file mode 100644 index 000000000..d5545bf83 --- /dev/null +++ b/src/blame.c @@ -0,0 +1,384 @@ +/* + * 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 +#include "error.h" +#include "types.h" +#include "utils.h" +#include "signature.h" +#include "blame.h" + +extern PyObject *GitError; + +extern PyTypeObject BlameType; +extern PyTypeObject BlameIterType; +extern PyTypeObject BlameHunkType; + +PyObject* +wrap_blame(git_blame *blame, Repository *repo) +{ + Blame *py_blame; + + py_blame = PyObject_New(Blame, &BlameType); + if (py_blame) { + Py_INCREF(repo); + py_blame->repo = repo; + py_blame->blame = blame; + } + + return (PyObject*) py_blame; +} + +#include +PyObject* +wrap_blame_hunk(const git_blame_hunk *hunk, Blame *blame) +{ + BlameHunk *py_hunk = NULL; + + if (!hunk) + Py_RETURN_NONE; + + py_hunk = PyObject_New(BlameHunk, &BlameHunkType); + if (py_hunk != NULL) { + py_hunk->lines_in_hunk = hunk->lines_in_hunk; + py_hunk->final_commit_id = git_oid_allocfmt(&hunk->final_commit_id); + py_hunk->final_start_line_number = hunk->final_start_line_number; + py_hunk->final_signature = hunk->final_signature != NULL ? + git_signature_dup(hunk->final_signature) : NULL; + py_hunk->orig_commit_id = git_oid_allocfmt(&hunk->orig_commit_id); + py_hunk->orig_path = hunk->orig_path != NULL ? + strdup(hunk->orig_path) : NULL; + py_hunk->orig_start_line_number = hunk->orig_start_line_number; + py_hunk->orig_signature = hunk->orig_signature != NULL ? + git_signature_dup(hunk->orig_signature) : NULL; + py_hunk->boundary = hunk->boundary; + } + + return (PyObject*) py_hunk; +} + +PyDoc_STRVAR(BlameHunk_final_committer__doc__, "Final committer."); + +PyObject * +BlameHunk_final_committer__get__(BlameHunk *self) +{ + if (!self->final_signature) + Py_RETURN_NONE; + + return build_signature((Object*) self, self->final_signature, "utf-8"); +} + +PyDoc_STRVAR(BlameHunk_orig_committer__doc__, "Origin committer."); + +PyObject * +BlameHunk_orig_committer__get__(BlameHunk *self) +{ + if (!self->orig_signature) + Py_RETURN_NONE; + + return build_signature((Object*) self, self->orig_signature, "utf-8"); +} + +static int +BlameHunk_init(BlameHunk *self, PyObject *args, PyObject *kwds) +{ + self->final_commit_id = NULL; + self->final_signature = NULL; + self->orig_commit_id = NULL; + self->orig_path = NULL; + self->orig_signature = NULL; + + return 0; +} + +static void +BlameHunk_dealloc(BlameHunk *self) +{ + free(self->final_commit_id); + if (self->final_signature) + git_signature_free(self->final_signature); + free(self->orig_commit_id); + if (self->orig_path) + free(self->orig_path); + if (self->orig_signature) + git_signature_free(self->orig_signature); + PyObject_Del(self); +} + +PyMemberDef BlameHunk_members[] = { + MEMBER(BlameHunk, lines_in_hunk, T_UINT, "Number of lines."), + MEMBER(BlameHunk, final_commit_id, T_STRING, "Last changed oid."), + MEMBER(BlameHunk, final_start_line_number, T_UINT, "final start line no."), + MEMBER(BlameHunk, orig_commit_id, T_STRING, "oid where hunk was found."), + MEMBER(BlameHunk, orig_path, T_STRING, "Origin path."), + MEMBER(BlameHunk, orig_start_line_number, T_UINT, "Origin start line no."), + MEMBER(BlameHunk, boundary, T_BOOL, "Tracked to a boundary commit."), + {NULL} +}; + +PyGetSetDef BlameHunk_getseters[] = { + GETTER(BlameHunk, final_committer), + GETTER(BlameHunk, orig_committer), + {NULL} +}; + +PyDoc_STRVAR(BlameHunk__doc__, "Blame Hunk object."); + +PyTypeObject BlameHunkType = { + PyVarObject_HEAD_INIT(NULL, 0) + "_pygit2.BlameHunk", /* tp_name */ + sizeof(BlameHunk), /* tp_basicsize */ + 0, /* tp_itemsize */ + (destructor)BlameHunk_dealloc, /* 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 | Py_TPFLAGS_BASETYPE, /* tp_flags */ + BlameHunk__doc__, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + 0, /* tp_methods */ + BlameHunk_members, /* tp_members */ + BlameHunk_getseters, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + (initproc)BlameHunk_init, /* tp_init */ + 0, /* tp_alloc */ + 0, /* tp_new */ +}; + + +PyObject * +BlameIter_iternext(BlameIter *self) +{ + if (self->i < self->n) + return wrap_blame_hunk(git_blame_get_hunk_byindex( + self->blame->blame, self->i++), self->blame); + + PyErr_SetNone(PyExc_StopIteration); + return NULL; +} + +static void +BlameIter_dealloc(BlameIter *self) +{ + Py_CLEAR(self->blame); + PyObject_Del(self); +} + + +PyDoc_STRVAR(BlameIter__doc__, "Blame iterator object."); + +PyTypeObject BlameIterType = { + PyVarObject_HEAD_INIT(NULL, 0) + "_pygit2.BlameIter", /* tp_name */ + sizeof(BlameIter), /* tp_basicsize */ + 0, /* tp_itemsize */ + (destructor)BlameIter_dealloc, /* 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 | Py_TPFLAGS_BASETYPE, /* tp_flags */ + BlameIter__doc__, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + PyObject_SelfIter, /* tp_iter */ + (iternextfunc) BlameIter_iternext, /* tp_iternext */ +}; + + +PyObject * +Blame_iter(Blame *self) +{ + BlameIter *iter; + + iter = PyObject_New(BlameIter, &BlameIterType); + if (iter != NULL) { + Py_INCREF(self); + iter->blame = self; + iter->i = 0; + iter->n = git_blame_get_hunk_count(self->blame); + } + return (PyObject*)iter; +} + +Py_ssize_t +Blame_len(Blame *self) +{ + assert(self->blame); + return (Py_ssize_t)git_blame_get_hunk_count(self->blame); +} + +PyObject * +Blame_getitem(Blame *self, PyObject *value) +{ + size_t i; + const git_blame_hunk *hunk; + + if (PyLong_Check(value) < 0) { + PyErr_SetObject(PyExc_IndexError, value); + return NULL; + } + + i = PyLong_AsUnsignedLong(value); + if (PyErr_Occurred()) { + PyErr_SetObject(PyExc_IndexError, value); + return NULL; + } + + hunk = git_blame_get_hunk_byindex(self->blame, i); + if (!hunk) { + PyErr_SetObject(PyExc_IndexError, value); + return NULL; + } + + return wrap_blame_hunk(hunk, self); +} + +PyDoc_STRVAR(Blame_for_line__doc__, + "for_line(line_no) -> hunk\n" + "\n" + "Returns the blame hunk data for the given \"line_no\" in blame.\n" + "\n" + "Arguments:\n" + "\n" + "line_no\n" + " Line number, countings starts with 1."); + +PyObject * +Blame_for_line(Blame *self, PyObject *args) +{ + size_t line_no; + const git_blame_hunk *hunk; + + if (!PyArg_ParseTuple(args, "I", &line_no)) + return NULL; + + hunk = git_blame_get_hunk_byline(self->blame, line_no); + if (!hunk) { + PyErr_SetObject(PyExc_IndexError, args); + return NULL; + } + + return wrap_blame_hunk(hunk, self); +} + +static void +Blame_dealloc(Blame *self) +{ + git_blame_free(self->blame); + Py_CLEAR(self->repo); + PyObject_Del(self); +} + +PyMappingMethods Blame_as_mapping = { + (lenfunc)Blame_len, /* mp_length */ + (binaryfunc)Blame_getitem, /* mp_subscript */ + 0, /* mp_ass_subscript */ +}; + +static PyMethodDef Blame_methods[] = { + METHOD(Blame, for_line, METH_VARARGS), + {NULL} +}; + + +PyDoc_STRVAR(Blame__doc__, "Blame objects."); + +PyTypeObject BlameType = { + PyVarObject_HEAD_INIT(NULL, 0) + "_pygit2.Blame", /* tp_name */ + sizeof(Blame), /* tp_basicsize */ + 0, /* tp_itemsize */ + (destructor)Blame_dealloc, /* 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 */ + &Blame_as_mapping, /* 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 | Py_TPFLAGS_BASETYPE, /* tp_flags */ + Blame__doc__, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + (getiterfunc)Blame_iter, /* tp_iter */ + 0, /* tp_iternext */ + Blame_methods, /* tp_methods */ + 0, /* tp_members */ + 0, /* 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/blame.h b/src/blame.h new file mode 100644 index 000000000..a6f1e53e4 --- /dev/null +++ b/src/blame.h @@ -0,0 +1,38 @@ +/* + * 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_blame_h +#define INCLUDE_pygit2_blame_h + +#define PY_SSIZE_T_CLEAN +#include +#include +#include "types.h" + +PyObject* wrap_blame(git_blame *blame, Repository *repo); + +#endif diff --git a/src/pygit2.c b/src/pygit2.c index 20fd27a1d..cf24120b0 100644 --- a/src/pygit2.c +++ b/src/pygit2.c @@ -64,6 +64,9 @@ extern PyTypeObject SignatureType; extern PyTypeObject RemoteType; extern PyTypeObject NoteType; extern PyTypeObject NoteIterType; +extern PyTypeObject BlameType; +extern PyTypeObject BlameIterType; +extern PyTypeObject BlameHunkType; @@ -418,6 +421,18 @@ moduleinit(PyObject* m) INIT_TYPE(RemoteType, NULL, NULL) ADD_TYPE(m, Remote) + /* Blame */ + INIT_TYPE(BlameType, NULL, NULL) + INIT_TYPE(BlameIterType, NULL, NULL) + INIT_TYPE(BlameHunkType, NULL, NULL) + ADD_TYPE(m, Blame) + ADD_TYPE(m, BlameHunk) + ADD_CONSTANT_INT(m, GIT_BLAME_NORMAL) + ADD_CONSTANT_INT(m, GIT_BLAME_TRACK_COPIES_SAME_FILE) + ADD_CONSTANT_INT(m, GIT_BLAME_TRACK_COPIES_SAME_COMMIT_MOVES) + ADD_CONSTANT_INT(m, GIT_BLAME_TRACK_COPIES_SAME_COMMIT_COPIES) + ADD_CONSTANT_INT(m, GIT_BLAME_TRACK_COPIES_ANY_COMMIT_COPIES) + /* Global initialization of libgit2 */ git_threads_init(); diff --git a/src/repository.c b/src/repository.c index facc0ce2a..88f34a1e6 100644 --- a/src/repository.c +++ b/src/repository.c @@ -37,6 +37,7 @@ #include "repository.h" #include "remote.h" #include "branch.h" +#include "blame.h" #include extern PyObject *GitError; @@ -1443,6 +1444,71 @@ Repository_lookup_note(Repository *self, PyObject* args) return (PyObject*) wrap_note(self, &annotated_id, ref); } +PyDoc_STRVAR(Repository_blame__doc__, + "blame(path, [flags, min_match_characters, newest_commit, oldest_commit,\n" + " min_line, max_line]) -> blame\n" + "\n" + "Get the blame for a single file.\n" + "\n" + "Arguments:\n" + "\n" + "path\n" + " A path to file to consider.\n" + "flags\n" + " A GIT_BLAME_* constant.\n" + "min_match_characters\n" + " The number of alphanum chars that must be detected as moving/copying\n" + " within a file for it to associate those lines with the parent commit.\n" + "newest_commit\n" + " The id of the newest commit to consider.\n" + "oldest_commit\n" + " The id of the oldest commit to consider.\n" + "min_line\n" + " The first line in the file to blame.\n" + "max_line\n" + " The last line in the file to blame.\n" + "\n" + "Examples::\n" + "\n" + " repo.blame('foo.c', flags=GIT_BLAME_TRACK_COPIES_SAME_FILE)"); + +PyObject* Repository_blame(Repository *self, PyObject *args, PyObject *kwds) +{ + git_blame_options opts = GIT_BLAME_OPTIONS_INIT; + git_blame *blame; + char *path; + PyObject *value1 = NULL; + PyObject *value2 = NULL; + int err; + char *keywords[] = {"flags", "min_match_characters", "newest_commit", + "oldest_commit", "min_line", "max_line", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "s|IHOOII", keywords, + &path, &opts.flags, + &opts.min_match_characters, + &value1, &value2, + &opts.min_line, &opts.max_line)) + return NULL; + + if (value1) { + err = py_oid_to_git_oid_expand(self->repo, value1, &opts.newest_commit); + if (err < 0) + return NULL; + } + if (value2) { + err = py_oid_to_git_oid_expand(self->repo, value2, &opts.oldest_commit); + if (err < 0) + return NULL; + } + + err = git_blame_file(&blame, self->repo, path, NULL); + if (err < 0) + return Error_set(err); + + return wrap_blame(blame, self); +} + + PyMethodDef Repository_methods[] = { METHOD(Repository, create_blob, METH_VARARGS), METHOD(Repository, create_blob_fromworkdir, METH_VARARGS), @@ -1472,6 +1538,7 @@ PyMethodDef Repository_methods[] = { METHOD(Repository, lookup_branch, METH_VARARGS), METHOD(Repository, listall_branches, METH_VARARGS), METHOD(Repository, create_branch, METH_VARARGS), + METHOD(Repository, blame, METH_VARARGS | METH_KEYWORDS), {NULL} }; diff --git a/src/repository.h b/src/repository.h index fd9c524b7..3c6094872 100644 --- a/src/repository.h +++ b/src/repository.h @@ -67,4 +67,6 @@ PyObject* Repository_status(Repository *self, PyObject *args); PyObject* Repository_status_file(Repository *self, PyObject *value); PyObject* Repository_TreeBuilder(Repository *self, PyObject *args); +PyObject* Repository_blame(Repository *self, PyObject *args, PyObject *kwds); + #endif diff --git a/src/types.h b/src/types.h index 6c2136d25..04782ebf0 100644 --- a/src/types.h +++ b/src/types.h @@ -194,4 +194,28 @@ typedef struct { SIMPLE_TYPE(Remote, git_remote, remote) +/* git_blame */ +SIMPLE_TYPE(Blame, git_blame, blame) + +typedef struct { + PyObject_HEAD + Blame* blame; + size_t i; + size_t n; +} BlameIter; + +typedef struct { + PyObject_HEAD + unsigned lines_in_hunk; + char* final_commit_id; + unsigned final_start_line_number; + git_signature* final_signature; + char* orig_commit_id; + char* orig_path; + unsigned orig_start_line_number; + git_signature* orig_signature; + char boundary; +} BlameHunk; + + #endif diff --git a/test/test_blame.py b/test/test_blame.py new file mode 100644 index 000000000..909e7c4cf --- /dev/null +++ b/test/test_blame.py @@ -0,0 +1,111 @@ +# -*- coding: UTF-8 -*- +# +# 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. + +"""Tests for Blame objects.""" + +from __future__ import absolute_import +from __future__ import unicode_literals +import unittest +import pygit2 +from pygit2 import Signature +from pygit2 import GIT_DIFF_INCLUDE_UNMODIFIED +from pygit2 import GIT_DIFF_IGNORE_WHITESPACE, GIT_DIFF_IGNORE_WHITESPACE_EOL +from . import utils +from itertools import chain +from datetime import datetime + +PATH = 'hello.txt' + +HUNKS = [ + ('acecd5ea2924a4b900e7e149496e1f4b57976e51', 1, + Signature('J. David Ibañez', 'jdavid@itaapy.com', + 1297179898, 60, encoding='utf-8'), True), + ('6aaa262e655dd54252e5813c8e5acd7780ed097d', 2, + Signature('J. David Ibañez', 'jdavid@itaapy.com', + 1297696877, 60, encoding='utf-8'), False), + ('4ec4389a8068641da2d6578db0419484972284c8', 3, + Signature('J. David Ibañez', 'jdavid@itaapy.com', + 1297696908, 60, encoding='utf-8'), False) +] + +class BlameTest(utils.RepoTestCase): + + def test_blame_index(self): + repo = self.repo + blame = repo.blame(PATH) + + self.assertEqual(len(blame), 3) + + for i, hunk in enumerate(blame): + self.assertEqual(hunk.lines_in_hunk, 1) + self.assertEqual(HUNKS[i][0], hunk.final_commit_id) + self.assertEqual(HUNKS[i][1], hunk.final_start_line_number) + self.assertEqualSignature(HUNKS[i][2], hunk.final_committer) + self.assertEqual(hunk.orig_commit_id, + '0000000000000000000000000000000000000000') + self.assertEqual(hunk.orig_path, PATH) + self.assertEqual(HUNKS[i][1], hunk.orig_start_line_number) + self.assertIsNone(hunk.orig_committer) + self.assertEqual(HUNKS[i][3], hunk.boundary) + + def test_blame_with_invalid_index(self): + repo = self.repo + blame = repo.blame(PATH) + + with self.assertRaises(IndexError): + blame[100000] + blame[-1] + + def test_blame_for_line(self): + repo = self.repo + blame = repo.blame(PATH) + + for i, line in zip(range(0, 2), range(1, 3)): + hunk = blame.for_line(line) + + self.assertEqual(hunk.lines_in_hunk, 1) + self.assertEqual(HUNKS[i][0], hunk.final_commit_id) + self.assertEqual(HUNKS[i][1], hunk.final_start_line_number) + self.assertEqualSignature(HUNKS[i][2], hunk.final_committer) + self.assertEqual(hunk.orig_commit_id, + '0000000000000000000000000000000000000000') + self.assertEqual(hunk.orig_path, PATH) + self.assertEqual(HUNKS[i][1], hunk.orig_start_line_number) + self.assertIsNone(hunk.orig_committer) + self.assertEqual(HUNKS[i][3], hunk.boundary) + + def test_blame_with_invalid_line(self): + repo = self.repo + blame = repo.blame(PATH) + + with self.assertRaises(IndexError): + blame.for_line(0) + blame.for_line(100000) + blame.for_line(-1) + +if __name__ == '__main__': + unittest.main() From c267891d1f6a15e0b4225fb539c9d44d27900437 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Wed, 13 Nov 2013 11:18:20 +0100 Subject: [PATCH 0022/1630] Make Reference.log_append signature more consistent Consistent with the rest of the pygit2 API. --- src/reference.c | 58 ++++++++++++++++++++++----------------------- test/test_reflog.py | 2 +- 2 files changed, 29 insertions(+), 31 deletions(-) diff --git a/src/reference.c b/src/reference.c index ea35d8e27..62ebbc671 100644 --- a/src/reference.c +++ b/src/reference.c @@ -277,7 +277,8 @@ Reference_name__get__(Reference *self) return to_path(git_reference_name(self->reference)); } -PyDoc_STRVAR(Reference_shorthand__doc__, "The shorthand \"human-readable\" name of the reference."); +PyDoc_STRVAR(Reference_shorthand__doc__, + "The shorthand \"human-readable\" name of the reference."); PyObject * Reference_shorthand__get__(Reference *self) @@ -322,61 +323,58 @@ Reference_log(Reference *self) } PyDoc_STRVAR(Reference_log_append__doc__, - "log_append(committer, message, oid)\n" + "log_append(oid, committer, message[, encoding])\n" "\n" - "Append reflog to the current reference."); + "Append a reflog entry to the reference. If the oid is None then keep\n" + "the current reference's oid. The message parameter may be None."); PyObject * -Reference_log_append(Reference *self, PyObject *args, PyObject *kwds) +Reference_log_append(Reference *self, PyObject *args) { git_signature *committer; const char *message = NULL; git_reflog *reflog; git_oid oid; - git_oid *ref_oid; + const git_oid *ref_oid; int err; + PyObject *py_oid = NULL; Signature *py_committer; PyObject *py_message = NULL; - PyObject *py_hex = NULL; - char *keywords[] = {"committer", "message", "oid", NULL}; + char *encoding = NULL; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "O!|OO", keywords, - &SignatureType, &py_committer, - &py_message, - &py_hex)) + CHECK_REFERENCE(self); + + /* Input parameters */ + if (!PyArg_ParseTuple(args, "OO!O|s", &py_oid, + &SignatureType, &py_committer, + &py_message, &encoding)) return NULL; - /* FIXME: encoding */ - if (py_message != NULL) { - message = py_str_to_c_str(py_message, NULL); - if (message == NULL) + if (py_oid == Py_None) + ref_oid = git_reference_target(self->reference); + else { + err = py_oid_to_git_oid_expand(self->repo->repo, py_oid, &oid); + if (err < 0) return NULL; + ref_oid = &oid; } - if (py_hex != NULL) { - err = py_oid_to_git_oid_expand(self->repo->repo, py_hex, &oid); - if (err < 0) + if (py_message != Py_None) { + message = py_str_to_c_str(py_message, encoding); + if (message == NULL) return NULL; } - CHECK_REFERENCE(self); - + /* Go */ err = git_reflog_read(&reflog, self->reference); if (err < 0) { free((void *)message); return NULL; } - if (py_hex != NULL) - ref_oid = &oid; - else - ref_oid = git_reference_target(self->reference); - committer = (git_signature *)py_committer->signature; - if (!(err = git_reflog_append(reflog, - ref_oid, - committer, - message))) + err = git_reflog_append(reflog, ref_oid, committer, message); + if (!err) err = git_reflog_write(reflog); git_reflog_free(reflog); @@ -501,7 +499,7 @@ PyMethodDef Reference_methods[] = { METHOD(Reference, rename, METH_O), METHOD(Reference, resolve, METH_NOARGS), METHOD(Reference, log, METH_NOARGS), - METHOD(Reference, log_append, METH_VARARGS|METH_KEYWORDS), + METHOD(Reference, log_append, METH_VARARGS), METHOD(Reference, get_object, METH_NOARGS), {NULL} }; diff --git a/test/test_reflog.py b/test/test_reflog.py index af68f5b89..84473c8ee 100644 --- a/test/test_reflog.py +++ b/test/test_reflog.py @@ -40,5 +40,5 @@ def test_log_append(self): repo = self.repo master = repo.lookup_reference("refs/heads/master") signature = Signature('xtao', 'xutao@douban.com') - master.log_append(signature, 'reflog') + master.log_append(None, signature, 'reflog') self.assertTrue('reflog' in [entry.message for entry in master.log()]) From b7e906eee9d503fd6dee55fee5c7e7bc8e28978d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Wed, 30 Oct 2013 08:57:17 +0100 Subject: [PATCH 0023/1630] Adjust to libgit2 development branch This wraps the previous functionality, though there are some iterator changes we might still want to bring over. --- pygit2/__init__.py | 17 +++---------- src/config.c | 55 +++++++++++++++++------------------------ src/diff.c | 54 +++++++++++++++++++--------------------- src/diff.h | 2 +- src/index.c | 4 +-- src/pygit2.c | 29 ++++++---------------- src/reference.c | 12 +++++++-- src/remote.c | 2 +- src/repository.c | 16 ++++++------ src/tree.c | 6 ++--- src/types.h | 2 +- test/test_repository.py | 4 +-- 12 files changed, 87 insertions(+), 116 deletions(-) diff --git a/pygit2/__init__.py b/pygit2/__init__.py index e59d3c31d..e2d52467c 100644 --- a/pygit2/__init__.py +++ b/pygit2/__init__.py @@ -49,9 +49,8 @@ def init_repository(path, bare=False): def clone_repository( - url, path, bare=False, remote_name="origin", - push_url=None, fetch_spec=None, - push_spec=None, checkout_branch=None): + url, path, bare=False, ignore_cert_errors=False, + remote_name="origin", checkout_branch=None): """ Clones a new Git repository from *url* in the given *path*. @@ -60,15 +59,6 @@ def clone_repository( **remote_name** is the name given to the "origin" remote. The default is "origin". - **push_url** is a URL to be used for pushing. - None means use the *url* parameter. - - **fetch_spec** defines the the default fetch spec. - None results in the same behavior as *GIT_REMOTE_DEFAULT_FETCH*. - - **push_spec** is the fetch specification to be used for pushing. - None means use the same spec as for *fetch_spec*. - **checkout_branch** gives the name of the branch to checkout. None means use the remote's *HEAD*. @@ -83,6 +73,5 @@ def clone_repository( """ _pygit2.clone_repository( - url, path, bare, remote_name, push_url, - fetch_spec, push_spec, checkout_branch) + url, path, bare, ignore_cert_errors, remote_name, checkout_branch) return Repository(path) diff --git a/src/config.c b/src/config.c index dc9b44f53..b1e7afdc1 100644 --- a/src/config.c +++ b/src/config.c @@ -336,55 +336,44 @@ PyDoc_STRVAR(Config_get_multivar__doc__, "parameter is expected to be a regular expression to filter the variables\n" "we're interested in."); -int -Config_get_multivar_fn_wrapper(const git_config_entry *value, void *data) -{ - PyObject *item; - - item = to_unicode(value->value, NULL, NULL); - if (item == NULL) - /* FIXME Right now there is no way to forward errors through the - * libgit2 API, open an issue or pull-request to libgit2. - * - * See libgit2/src/config_file.c:443 (config_get_multivar). - * Comment says "early termination by the user is not an error". - * That's wrong. - */ - return -2; - - PyList_Append((PyObject *)data, item); - Py_CLEAR(item); - return 0; -} - PyObject * Config_get_multivar(Config *self, PyObject *args) { int err; PyObject *list; - Py_ssize_t size; const char *name = NULL; const char *regex = NULL; + git_config_iterator *iter; + git_config_entry *entry; if (!PyArg_ParseTuple(args, "s|s", &name, ®ex)) return NULL; list = PyList_New(0); - err = git_config_get_multivar(self->config, name, regex, - Config_get_multivar_fn_wrapper, - (void *)list); + err = git_config_multivar_iterator_new(&iter, self->config, name, regex); + if (err < 0) + return Error_set(err); - if (err < 0) { - /* XXX The return value of git_config_get_multivar is not reliable, - * see https://github.com/libgit2/libgit2/pull/1712 - * Once libgit2 0.20 is released, we will remove this test. */ - if (err == GIT_ENOTFOUND && PyList_Size(list) != 0) - return list; + while ((err = git_config_next(&entry, iter)) == 0) { + PyObject *item; - Py_CLEAR(list); - return Error_set(err); + item = to_unicode(entry->value, NULL, NULL); + if (item == NULL) { + git_config_iterator_free(iter); + return NULL; + } + + PyList_Append(list, item); + Py_CLEAR(item); } + git_config_iterator_free(iter); + if (err == GIT_ITEROVER) + err = 0; + + if (err < 0) + return Error_set(err); + return list; } diff --git a/src/diff.c b/src/diff.c index 3b5c4fe52..01e6e7dbe 100644 --- a/src/diff.c +++ b/src/diff.c @@ -43,7 +43,7 @@ extern PyTypeObject HunkType; PyTypeObject PatchType; PyObject* -wrap_diff(git_diff_list *diff, Repository *repo) +wrap_diff(git_diff *diff, Repository *repo) { Diff *py_diff; @@ -58,23 +58,24 @@ wrap_diff(git_diff_list *diff, Repository *repo) } PyObject* -diff_get_patch_byindex(git_diff_list* list, size_t idx) +diff_get_patch_byindex(git_diff* diff, size_t idx) { const git_diff_delta* delta; - const git_diff_range* range; - git_diff_patch* patch = NULL; - size_t i, j, hunk_amounts, lines_in_hunk, line_len, header_len, additions, deletions; - const char* line, *header; - char line_origin; + const git_diff_hunk *hunk; + const git_diff_line *line; + git_patch* patch = NULL; + size_t i, j, hunk_amounts, lines_in_hunk, additions, deletions; int err; Hunk *py_hunk = NULL; Patch *py_patch = NULL; PyObject *py_line_origin=NULL, *py_line=NULL; - err = git_diff_get_patch(&patch, &delta, list, idx); - if (err < 0) + err = git_patch_from_diff(&patch, diff, idx); + if (err < 0) return Error_set(err); + delta = git_patch_get_delta(patch); + py_patch = PyObject_New(Patch, &PatchType); if (py_patch != NULL) { py_patch->old_file_path = delta->old_file.path; @@ -85,36 +86,34 @@ diff_get_patch_byindex(git_diff_list* list, size_t idx) py_patch->old_oid = git_oid_allocfmt(&delta->old_file.oid); py_patch->new_oid = git_oid_allocfmt(&delta->new_file.oid); - git_diff_patch_line_stats(NULL, &additions, &deletions, patch); + git_patch_line_stats(NULL, &additions, &deletions, patch); py_patch->additions = additions; py_patch->deletions = deletions; - hunk_amounts = git_diff_patch_num_hunks(patch); + hunk_amounts = git_patch_num_hunks(patch); py_patch->hunks = PyList_New(hunk_amounts); for (i=0; i < hunk_amounts; ++i) { - err = git_diff_patch_get_hunk(&range, &header, &header_len, - &lines_in_hunk, patch, i); + err = git_patch_get_hunk(&hunk, &lines_in_hunk, patch, i); if (err < 0) goto cleanup; py_hunk = PyObject_New(Hunk, &HunkType); if (py_hunk != NULL) { - py_hunk->old_start = range->old_start; - py_hunk->old_lines = range->old_lines; - py_hunk->new_start = range->new_start; - py_hunk->new_lines = range->new_lines; + py_hunk->old_start = hunk->old_start; + py_hunk->old_lines = hunk->old_lines; + py_hunk->new_start = hunk->new_start; + py_hunk->new_lines = hunk->new_lines; py_hunk->lines = PyList_New(lines_in_hunk); for (j=0; j < lines_in_hunk; ++j) { - err = git_diff_patch_get_line_in_hunk(&line_origin, - &line, &line_len, NULL, NULL, patch, i, j); + err = git_patch_get_line_in_hunk(&line, patch, i, j); if (err < 0) goto cleanup; - py_line_origin = to_unicode_n(&line_origin, 1, NULL, NULL); - py_line = to_unicode_n(line, line_len, NULL, NULL); + py_line_origin = to_unicode_n(&line->origin, 1, NULL, NULL); + py_line = to_unicode_n(line->content, line->content_len, NULL, NULL); PyList_SetItem(py_hunk->lines, j, Py_BuildValue("OO", py_line_origin, @@ -132,7 +131,7 @@ diff_get_patch_byindex(git_diff_list* list, size_t idx) } cleanup: - git_diff_patch_free(patch); + git_patch_free(patch); return (err < 0) ? Error_set(err) : (PyObject*) py_patch; } @@ -283,8 +282,7 @@ PyDoc_STRVAR(Diff_patch__doc__, "Patch diff string."); PyObject * Diff_patch__get__(Diff *self) { - const git_diff_delta* delta; - git_diff_patch* patch; + git_patch* patch; char **strings = NULL; char *buffer = NULL; int err = GIT_ERROR; @@ -295,16 +293,16 @@ Diff_patch__get__(Diff *self) MALLOC(strings, num * sizeof(char*), cleanup); for (i = 0, len = 1; i < num ; ++i) { - err = git_diff_get_patch(&patch, &delta, self->list, i); + err = git_patch_from_diff(&patch, self->list, i); if (err < 0) goto cleanup; - err = git_diff_patch_to_str(&(strings[i]), patch); + err = git_patch_to_str(&(strings[i]), patch); if (err < 0) goto cleanup; len += strlen(strings[i]); - git_diff_patch_free(patch); + git_patch_free(patch); } CALLOC(buffer, (len + 1), sizeof(char), cleanup); @@ -461,7 +459,7 @@ Diff_getitem(Diff *self, PyObject *value) static void Diff_dealloc(Diff *self) { - git_diff_list_free(self->list); + git_diff_free(self->list); Py_CLEAR(self->repo); PyObject_Del(self); } diff --git a/src/diff.h b/src/diff.h index 75e633334..f32c93090 100644 --- a/src/diff.h +++ b/src/diff.h @@ -41,6 +41,6 @@ PyObject* Diff_changes(Diff *self); PyObject* Diff_patch(Diff *self); -PyObject* wrap_diff(git_diff_list *diff, Repository *repo); +PyObject* wrap_diff(git_diff *diff, Repository *repo); #endif diff --git a/src/index.c b/src/index.c index 3c73f2178..bd7a76310 100644 --- a/src/index.c +++ b/src/index.c @@ -135,7 +135,7 @@ PyObject * Index_diff_to_workdir(Index *self, PyObject *args) { git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_diff_list *diff; + git_diff *diff; int err; if (!PyArg_ParseTuple(args, "|IHH", &opts.flags, &opts.context_lines, @@ -177,7 +177,7 @@ Index_diff_to_tree(Index *self, PyObject *args) { Repository *py_repo; git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_diff_list *diff; + git_diff *diff; int err; Tree *py_tree = NULL; diff --git a/src/pygit2.c b/src/pygit2.c index 20fd27a1d..85c146f87 100644 --- a/src/pygit2.c +++ b/src/pygit2.c @@ -99,8 +99,7 @@ init_repository(PyObject *self, PyObject *args) { }; PyDoc_STRVAR(clone_repository__doc__, - "clone_repository(url, path, bare, remote_name, push_url," - "fetch_spec, push_spec, checkout_branch)\n" + "clone_repository(url, path, bare, remote_name, checkout_branch)\n" "\n" "Clones a Git repository in the given url to the given path " "with the specified options.\n" @@ -115,14 +114,6 @@ PyDoc_STRVAR(clone_repository__doc__, " If 'bare' is not 0, then a bare git repository will be created.\n" "remote_name\n" " The name given to the 'origin' remote. The default is 'origin'.\n" - "push_url\n" - " URL to be used for pushing.\n" - "fetch_spec\n" - " The fetch specification to be used for fetching. None results in " - "the same behavior as GIT_REMOTE_DEFAULT_FETCH.\n" - "push_spec\n" - " The fetch specification to be used for pushing. None means use the " - "same spec as for 'fetch_spec'\n" "checkout_branch\n" " The name of the branch to checkout. None means use the remote's " "HEAD.\n"); @@ -133,22 +124,18 @@ clone_repository(PyObject *self, PyObject *args) { git_repository *repo; const char *url; const char *path; - unsigned int bare; - const char *remote_name, *push_url, *fetch_spec; - const char *push_spec, *checkout_branch; + unsigned int bare, ignore_cert_errors; + const char *remote_name, *checkout_branch; int err; git_clone_options opts = GIT_CLONE_OPTIONS_INIT; - if (!PyArg_ParseTuple(args, "zzIzzzzz", - &url, &path, &bare, &remote_name, &push_url, - &fetch_spec, &push_spec, &checkout_branch)) + if (!PyArg_ParseTuple(args, "zzIIzz", + &url, &path, &bare, &ignore_cert_errors, &remote_name, &checkout_branch)) return NULL; opts.bare = bare; + opts.ignore_cert_errors = ignore_cert_errors; opts.remote_name = remote_name; - opts.pushurl = push_url; - opts.fetch_spec = fetch_spec; - opts.push_spec = push_spec; opts.checkout_branch = checkout_branch; err = git_clone(&repo, url, path, &opts); @@ -392,8 +379,8 @@ moduleinit(PyObject* m) ADD_CONSTANT_INT(m, GIT_DIFF_RECURSE_UNTRACKED_DIRS) ADD_CONSTANT_INT(m, GIT_DIFF_RECURSE_UNTRACKED_DIRS) ADD_CONSTANT_INT(m, GIT_DIFF_DISABLE_PATHSPEC_MATCH) - ADD_CONSTANT_INT(m, GIT_DIFF_DELTAS_ARE_ICASE) - ADD_CONSTANT_INT(m, GIT_DIFF_INCLUDE_UNTRACKED_CONTENT) + ADD_CONSTANT_INT(m, GIT_DIFF_IGNORE_CASE) + ADD_CONSTANT_INT(m, GIT_DIFF_SHOW_UNTRACKED_CONTENT) ADD_CONSTANT_INT(m, GIT_DIFF_SKIP_BINARY_CHECK) ADD_CONSTANT_INT(m, GIT_DIFF_INCLUDE_TYPECHANGE) ADD_CONSTANT_INT(m, GIT_DIFF_INCLUDE_TYPECHANGE_TREES) diff --git a/src/reference.c b/src/reference.c index 62ebbc671..70108af6b 100644 --- a/src/reference.c +++ b/src/reference.c @@ -309,13 +309,19 @@ PyDoc_STRVAR(Reference_log__doc__, PyObject * Reference_log(Reference *self) { + int err; RefLogIter *iter; + git_repository *repo; CHECK_REFERENCE(self); + repo = git_reference_owner(self->reference); iter = PyObject_New(RefLogIter, &RefLogIterType); if (iter != NULL) { - git_reflog_read(&iter->reflog, self->reference); + err = git_reflog_read(&iter->reflog, repo, git_reference_name(self->reference)); + if (err < 0) + return Error_set(err); + iter->size = git_reflog_entrycount(iter->reflog); iter->i = 0; } @@ -341,6 +347,7 @@ Reference_log_append(Reference *self, PyObject *args) Signature *py_committer; PyObject *py_message = NULL; char *encoding = NULL; + git_repository *repo; CHECK_REFERENCE(self); @@ -366,7 +373,8 @@ Reference_log_append(Reference *self, PyObject *args) } /* Go */ - err = git_reflog_read(&reflog, self->reference); + repo = git_reference_owner(self->reference); + err = git_reflog_read(&reflog, repo, git_reference_name(self->reference)); if (err < 0) { free((void *)message); return NULL; diff --git a/src/remote.c b/src/remote.c index e482d8b1c..6b684ecfc 100644 --- a/src/remote.c +++ b/src/remote.c @@ -179,7 +179,7 @@ Remote_fetch(Remote *self, PyObject *args) err = git_remote_connect(self->remote, GIT_DIRECTION_FETCH); if (err == GIT_OK) { - err = git_remote_download(self->remote, NULL, NULL); + err = git_remote_download(self->remote); if (err == GIT_OK) { stats = git_remote_stats(self->remote); py_stats = Py_BuildValue("{s:I,s:I,s:n}", diff --git a/src/repository.c b/src/repository.c index facc0ce2a..222fd2346 100644 --- a/src/repository.c +++ b/src/repository.c @@ -208,14 +208,14 @@ Repository_head_is_detached__get__(Repository *self) } -PyDoc_STRVAR(Repository_head_is_orphaned__doc__, - "An orphan branch is one named from HEAD but which doesn't exist in the\n" +PyDoc_STRVAR(Repository_head_is_unborn__doc__, + "An unborn branch is one named from HEAD but which doesn't exist in the\n" "refs namespace, because it doesn't have any commit to point to."); PyObject * -Repository_head_is_orphaned__get__(Repository *self) +Repository_head_is_unborn__get__(Repository *self) { - if (git_repository_head_orphan(self->repo) > 0) + if (git_repository_head_unborn(self->repo) > 0) Py_RETURN_TRUE; Py_RETURN_FALSE; @@ -430,9 +430,9 @@ Repository_write(Repository *self, PyObject *args) if (err < 0) return Error_set(err); - stream->write(stream, buffer, buflen); - err = stream->finalize_write(&oid, stream); - stream->free(stream); + git_odb_stream_write(stream, buffer, buflen); + err = git_odb_stream_finalize_write(&oid, stream); + git_odb_stream_free(stream); return git_oid_to_python(&oid); } @@ -1480,7 +1480,7 @@ PyGetSetDef Repository_getseters[] = { GETTER(Repository, path), GETSET(Repository, head), GETTER(Repository, head_is_detached), - GETTER(Repository, head_is_orphaned), + GETTER(Repository, head_is_unborn), GETTER(Repository, is_empty), GETTER(Repository, is_bare), GETTER(Repository, config), diff --git a/src/tree.c b/src/tree.c index e0abbbd57..7599d659b 100644 --- a/src/tree.c +++ b/src/tree.c @@ -290,7 +290,7 @@ PyObject * Tree_diff_to_workdir(Tree *self, PyObject *args) { git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_diff_list *diff; + git_diff *diff; Repository *py_repo; int err; @@ -328,7 +328,7 @@ PyObject * Tree_diff_to_index(Tree *self, PyObject *args, PyObject *kwds) { git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_diff_list *diff; + git_diff *diff; Repository *py_repo; int err; @@ -373,7 +373,7 @@ PyObject * Tree_diff_to_tree(Tree *self, PyObject *args, PyObject *kwds) { git_diff_options opts = GIT_DIFF_OPTIONS_INIT; - git_diff_list *diff; + git_diff *diff; git_tree *from, *to, *tmp; Repository *py_repo; int err, swap = 0; diff --git a/src/types.h b/src/types.h index 6c2136d25..cbfa9aa62 100644 --- a/src/types.h +++ b/src/types.h @@ -95,7 +95,7 @@ typedef struct { /* git _diff */ -SIMPLE_TYPE(Diff, git_diff_list, list) +SIMPLE_TYPE(Diff, git_diff, list) typedef struct { PyObject_HEAD diff --git a/test/test_repository.py b/test/test_repository.py index ab0b22c89..e783f80c6 100644 --- a/test/test_repository.py +++ b/test/test_repository.py @@ -65,7 +65,7 @@ def test_head(self): head = self.repo.head self.assertEqual(HEAD_SHA, head.target.hex) self.assertEqual(type(head), Reference) - self.assertFalse(self.repo.head_is_orphaned) + self.assertFalse(self.repo.head_is_unborn) self.assertFalse(self.repo.head_is_detached) def test_read(self): @@ -294,7 +294,7 @@ def test_is_base(self): self.assertFalse(self.repo.is_bare) def test_head(self): - self.assertTrue(self.repo.head_is_orphaned) + self.assertTrue(self.repo.head_is_unborn) self.assertFalse(self.repo.head_is_detached) From 12ea3d2ddaf8b504d368eb71239b4db9b8eba866 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Tue, 5 Nov 2013 19:20:58 +0100 Subject: [PATCH 0024/1630] Branch: move from foreach to the iterator --- src/repository.c | 95 +++++++++++++++++++++++------------------------- 1 file changed, 45 insertions(+), 50 deletions(-) diff --git a/src/repository.c b/src/repository.c index 222fd2346..1ac660831 100644 --- a/src/repository.c +++ b/src/repository.c @@ -920,69 +920,64 @@ PyDoc_STRVAR(Repository_listall_branches__doc__, "\n" "Return a tuple with all the branches in the repository."); -struct branch_foreach_s { - PyObject *tuple; - Py_ssize_t pos; -}; - -int -branch_foreach_cb(const char *branch_name, git_branch_t branch_type, void *payload) -{ - /* This is the callback that will be called in git_branch_foreach. It - * will be called for every branch. - * payload is a struct branch_foreach_s. - */ - int err; - struct branch_foreach_s *payload_s = (struct branch_foreach_s *)payload; - - if (PyTuple_Size(payload_s->tuple) <= payload_s->pos) - { - err = _PyTuple_Resize(&(payload_s->tuple), payload_s->pos * 2); - if (err) { - Py_CLEAR(payload_s->tuple); - return GIT_ERROR; - } - } - - PyObject *py_branch_name = to_path(branch_name); - if (py_branch_name == NULL) { - Py_CLEAR(payload_s->tuple); - return GIT_ERROR; - } - - PyTuple_SET_ITEM(payload_s->tuple, payload_s->pos++, py_branch_name); - - return GIT_OK; -} - - PyObject * Repository_listall_branches(Repository *self, PyObject *args) { - unsigned int list_flags = GIT_BRANCH_LOCAL; + git_branch_t list_flags = GIT_BRANCH_LOCAL; + git_branch_iterator *iter; + git_reference *ref = NULL; + Py_ssize_t pos = 0; int err; + git_branch_t type; + PyObject *tuple; /* 1- Get list_flags */ if (!PyArg_ParseTuple(args, "|I", &list_flags)) return NULL; - /* 2- Get the C result */ - struct branch_foreach_s payload; - payload.tuple = PyTuple_New(4); - if (payload.tuple == NULL) - return NULL; + tuple = PyTuple_New(4); + if (tuple == NULL) + return NULL; - payload.pos = 0; - err = git_branch_foreach(self->repo, list_flags, branch_foreach_cb, &payload); - if (err != GIT_OK) - return Error_set(err); + if ((err = git_branch_iterator_new(&iter, self->repo, list_flags)) < 0) + return Error_set(err); + + while ((err = git_branch_next(&ref, &type, iter)) == 0) { + if (PyTuple_Size(tuple) <= pos) { + if (_PyTuple_Resize(&tuple, pos * 2) < 0) + goto on_error; + } + + PyObject *py_branch_name = to_path(git_reference_shorthand(ref)); + git_reference_free(ref); + ref = NULL; + + if (py_branch_name == NULL) + goto on_error; + + PyTuple_SET_ITEM(tuple, pos++, py_branch_name); + } - /* 3- Trim the tuple */ - err = _PyTuple_Resize(&payload.tuple, payload.pos); - if (err) + git_branch_iterator_free(iter); + if (err == GIT_ITEROVER) + err = 0; + + if (err < 0) { + Py_CLEAR(tuple); + return Error_set(err); + } + + /* Remove the elements we might have overallocated in the loop */ + if (_PyTuple_Resize(&tuple, pos) < 0) return Error_set(err); - return payload.tuple; + return tuple; + + on_error: + git_reference_free(ref); + git_branch_iterator_free(iter); + Py_CLEAR(tuple); + return NULL; } From 4c47eba8c6ddc19da69983d0ed89c8c864c850d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Tue, 5 Nov 2013 19:38:01 +0100 Subject: [PATCH 0025/1630] Index: adjust to index_read() force flag --- src/index.c | 19 +++++++++++++------ src/index.h | 2 +- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/index.c b/src/index.c index bd7a76310..ce47f13a0 100644 --- a/src/index.c +++ b/src/index.c @@ -222,17 +222,24 @@ Index__find(Index *self, PyObject *py_path) PyDoc_STRVAR(Index_read__doc__, - "read()\n" + "read(force=True)\n" "\n" "Update the contents of an existing index object in memory by reading from\n" - "the hard disk."); + "the hard disk." + "Arguments:\n" + "\n" + "force: if True (the default) allways reload. If False, only if the file has changed" +); PyObject * -Index_read(Index *self) +Index_read(Index *self, PyObject *args) { - int err; + int err, force = 1; + + if (!PyArg_ParseTuple(args, "|i", &force)) + return NULL; - err = git_index_read(self->index); + err = git_index_read(self->index, force); if (err < GIT_OK) return Error_set(err); @@ -427,7 +434,7 @@ PyMethodDef Index_methods[] = { METHOD(Index, diff_to_workdir, METH_VARARGS), METHOD(Index, diff_to_tree, METH_VARARGS), METHOD(Index, _find, METH_O), - METHOD(Index, read, METH_NOARGS), + METHOD(Index, read, METH_VARARGS), METHOD(Index, write, METH_NOARGS), METHOD(Index, read_tree, METH_O), METHOD(Index, write_tree, METH_NOARGS), diff --git a/src/index.h b/src/index.h index 7c63c1c0e..f38cb4a04 100644 --- a/src/index.h +++ b/src/index.h @@ -35,7 +35,7 @@ PyObject* Index_add(Index *self, PyObject *args); PyObject* Index_clear(Index *self); PyObject* Index_find(Index *self, PyObject *py_path); -PyObject* Index_read(Index *self); +PyObject* Index_read(Index *self, PyObject *args); PyObject* Index_write(Index *self); PyObject* Index_iter(Index *self); PyObject* Index_getitem(Index *self, PyObject *value); From aa5877e011ecd9e6c764a643ebefba819ac835f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Wed, 20 Nov 2013 16:43:22 +0100 Subject: [PATCH 0026/1630] repository: return a list from listall Make listall_references() and listall_branches() return a list instead of a tuple, as they're simply sequences of objects. --- src/repository.c | 42 +++++++++++++++++------------------------- 1 file changed, 17 insertions(+), 25 deletions(-) diff --git a/src/repository.c b/src/repository.c index 1ac660831..ab5ebb430 100644 --- a/src/repository.c +++ b/src/repository.c @@ -877,9 +877,9 @@ PyObject* Repository_create_branch(Repository *self, PyObject *args) PyDoc_STRVAR(Repository_listall_references__doc__, - "listall_references() -> (str, ...)\n" + "listall_references() -> [str, ...]\n" "\n" - "Return a tuple with all the references in the repository."); + "Return a list with all the references in the repository."); PyObject * Repository_listall_references(Repository *self, PyObject *args) @@ -895,18 +895,18 @@ Repository_listall_references(Repository *self, PyObject *args) return Error_set(err); /* Create a new PyTuple */ - py_result = PyTuple_New(c_result.count); + py_result = PyList_New(c_result.count); if (py_result == NULL) goto out; /* Fill it */ for (index=0; index < c_result.count; index++) { - py_string = to_path((c_result.strings)[index]); + py_string = to_path(c_result.strings[index]); if (py_string == NULL) { Py_CLEAR(py_result); goto out; } - PyTuple_SET_ITEM(py_result, index, py_string); + PyList_SET_ITEM(py_result, index, py_string); } out: @@ -916,7 +916,7 @@ Repository_listall_references(Repository *self, PyObject *args) PyDoc_STRVAR(Repository_listall_branches__doc__, - "listall_branches([flags]) -> (str, ...)\n" + "listall_branches([flags]) -> [str, ...]\n" "\n" "Return a tuple with all the branches in the repository."); @@ -926,36 +926,33 @@ Repository_listall_branches(Repository *self, PyObject *args) git_branch_t list_flags = GIT_BRANCH_LOCAL; git_branch_iterator *iter; git_reference *ref = NULL; - Py_ssize_t pos = 0; int err; git_branch_t type; - PyObject *tuple; + PyObject *list; /* 1- Get list_flags */ if (!PyArg_ParseTuple(args, "|I", &list_flags)) return NULL; - tuple = PyTuple_New(4); - if (tuple == NULL) + list = PyList_New(0); + if (list == NULL) return NULL; if ((err = git_branch_iterator_new(&iter, self->repo, list_flags)) < 0) return Error_set(err); while ((err = git_branch_next(&ref, &type, iter)) == 0) { - if (PyTuple_Size(tuple) <= pos) { - if (_PyTuple_Resize(&tuple, pos * 2) < 0) - goto on_error; - } - PyObject *py_branch_name = to_path(git_reference_shorthand(ref)); git_reference_free(ref); - ref = NULL; if (py_branch_name == NULL) goto on_error; - PyTuple_SET_ITEM(tuple, pos++, py_branch_name); + err = PyList_Append(list, py_branch_name); + Py_DECREF(py_branch_name); + + if (err < 0) + goto on_error; } git_branch_iterator_free(iter); @@ -963,20 +960,15 @@ Repository_listall_branches(Repository *self, PyObject *args) err = 0; if (err < 0) { - Py_CLEAR(tuple); + Py_CLEAR(list); return Error_set(err); } - /* Remove the elements we might have overallocated in the loop */ - if (_PyTuple_Resize(&tuple, pos) < 0) - return Error_set(err); - - return tuple; + return list; on_error: - git_reference_free(ref); git_branch_iterator_free(iter); - Py_CLEAR(tuple); + Py_CLEAR(list); return NULL; } From 6fd851a97e5316b3d0b2cdaac5e63a0bd662dfae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Sat, 23 Nov 2013 13:07:13 +0100 Subject: [PATCH 0027/1630] Fix tests with Python 2.6 --- test/test_blame.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/test/test_blame.py b/test/test_blame.py index 909e7c4cf..9c04e799b 100644 --- a/test/test_blame.py +++ b/test/test_blame.py @@ -69,17 +69,19 @@ def test_blame_index(self): '0000000000000000000000000000000000000000') self.assertEqual(hunk.orig_path, PATH) self.assertEqual(HUNKS[i][1], hunk.orig_start_line_number) - self.assertIsNone(hunk.orig_committer) + self.assertTrue(hunk.orig_committer is None) self.assertEqual(HUNKS[i][3], hunk.boundary) def test_blame_with_invalid_index(self): repo = self.repo blame = repo.blame(PATH) - with self.assertRaises(IndexError): + def test(): blame[100000] blame[-1] + self.assertRaises(IndexError, test) + def test_blame_for_line(self): repo = self.repo blame = repo.blame(PATH) @@ -95,17 +97,19 @@ def test_blame_for_line(self): '0000000000000000000000000000000000000000') self.assertEqual(hunk.orig_path, PATH) self.assertEqual(HUNKS[i][1], hunk.orig_start_line_number) - self.assertIsNone(hunk.orig_committer) + self.assertTrue(hunk.orig_committer is None) self.assertEqual(HUNKS[i][3], hunk.boundary) def test_blame_with_invalid_line(self): repo = self.repo blame = repo.blame(PATH) - with self.assertRaises(IndexError): + def test(): blame.for_line(0) blame.for_line(100000) blame.for_line(-1) + self.assertRaises(IndexError, test) + if __name__ == '__main__': unittest.main() From 5652ed7e37325af0a0c6bdc33ac37fe57034ccdc Mon Sep 17 00:00:00 2001 From: XTao Date: Wed, 30 Oct 2013 15:33:47 +0800 Subject: [PATCH 0028/1630] Fix repository.write --- src/repository.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/repository.c b/src/repository.c index 5f1b4dda5..49e717923 100644 --- a/src/repository.c +++ b/src/repository.c @@ -431,8 +431,9 @@ Repository_write(Repository *self, PyObject *args) if (err < 0) return Error_set(err); - git_odb_stream_write(stream, buffer, buflen); - err = git_odb_stream_finalize_write(&oid, stream); + err = git_odb_stream_write(stream, buffer, buflen); + if (!err) + err = git_odb_stream_finalize_write(&oid, stream); git_odb_stream_free(stream); return git_oid_to_python(&oid); } From c4be96fb7ce4d1f14f954be2a5cc44fb9ec3d7ea Mon Sep 17 00:00:00 2001 From: XTao Date: Wed, 30 Oct 2013 15:42:30 +0800 Subject: [PATCH 0029/1630] Fix multivar interface. --- src/config.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/config.c b/src/config.c index b1e7afdc1..873ba7363 100644 --- a/src/config.c +++ b/src/config.c @@ -352,27 +352,27 @@ Config_get_multivar(Config *self, PyObject *args) list = PyList_New(0); err = git_config_multivar_iterator_new(&iter, self->config, name, regex); if (err < 0) - return Error_set(err); + return Error_set(err); while ((err = git_config_next(&entry, iter)) == 0) { - PyObject *item; + PyObject *item; - item = to_unicode(entry->value, NULL, NULL); - if (item == NULL) { - git_config_iterator_free(iter); - return NULL; - } + item = to_unicode(entry->value, NULL, NULL); + if (item == NULL) { + git_config_iterator_free(iter); + return NULL; + } - PyList_Append(list, item); - Py_CLEAR(item); + PyList_Append(list, item); + Py_CLEAR(item); } git_config_iterator_free(iter); if (err == GIT_ITEROVER) - err = 0; + err = 0; if (err < 0) - return Error_set(err); + return Error_set(err); return list; } From e560a89f903aaeb53330605adfb3170745487332 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Sun, 24 Nov 2013 10:46:49 +0100 Subject: [PATCH 0030/1630] Fix error handling in Repository.write --- src/repository.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/repository.c b/src/repository.c index 49e717923..f682bcb32 100644 --- a/src/repository.c +++ b/src/repository.c @@ -432,9 +432,16 @@ Repository_write(Repository *self, PyObject *args) return Error_set(err); err = git_odb_stream_write(stream, buffer, buflen); - if (!err) - err = git_odb_stream_finalize_write(&oid, stream); + if (err) { + git_odb_stream_free(stream); + return Error_set(err); + } + + err = git_odb_stream_finalize_write(&oid, stream); git_odb_stream_free(stream); + if (err) + return Error_set(err); + return git_oid_to_python(&oid); } From f26d6bedfec7bf0eca746c3a4381b66c85fe2322 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Sun, 24 Nov 2013 11:49:17 +0100 Subject: [PATCH 0031/1630] C coding style: indentation fixes --- src/diff.c | 4 ++-- src/note.c | 6 +++--- src/remote.c | 4 ++-- src/repository.c | 39 ++++++++++++++++++++------------------- 4 files changed, 27 insertions(+), 26 deletions(-) diff --git a/src/diff.c b/src/diff.c index 01e6e7dbe..f81f63574 100644 --- a/src/diff.c +++ b/src/diff.c @@ -71,7 +71,7 @@ diff_get_patch_byindex(git_diff* diff, size_t idx) PyObject *py_line_origin=NULL, *py_line=NULL; err = git_patch_from_diff(&patch, diff, idx); - if (err < 0) + if (err < 0) return Error_set(err); delta = git_patch_get_delta(patch); @@ -110,7 +110,7 @@ diff_get_patch_byindex(git_diff* diff, size_t idx) err = git_patch_get_line_in_hunk(&line, patch, i, j); if (err < 0) - goto cleanup; + goto cleanup; py_line_origin = to_unicode_n(&line->origin, 1, NULL, NULL); py_line = to_unicode_n(line->content, line->content_len, NULL, NULL); diff --git a/src/note.c b/src/note.c index c7ef0f2ca..c515d8bd4 100644 --- a/src/note.c +++ b/src/note.c @@ -72,7 +72,7 @@ PyDoc_STRVAR(Note_oid__doc__, PyObject * Note_oid__get__(Note *self) { - return git_oid_to_python(git_note_oid(self->note)); + return git_oid_to_python(git_note_oid(self->note)); } @@ -82,7 +82,7 @@ PyDoc_STRVAR(Note_message__doc__, PyObject * Note_message__get__(Note *self) { - return to_unicode(git_note_message(self->note), NULL, NULL); + return to_unicode(git_note_message(self->note), NULL, NULL); } @@ -207,7 +207,7 @@ PyTypeObject NoteIterType = { 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ PyObject_SelfIter, /* tp_iter */ - (iternextfunc) NoteIter_iternext, /* tp_iternext */ + (iternextfunc) NoteIter_iternext, /* tp_iternext */ }; diff --git a/src/remote.c b/src/remote.c index 6b684ecfc..66530ce06 100644 --- a/src/remote.c +++ b/src/remote.c @@ -86,7 +86,7 @@ Remote_name__set__(Remote *self, PyObject* py_name) free(name); if (err == GIT_OK) - return 0; + return 0; Error_set(err); } @@ -116,7 +116,7 @@ Remote_url__set__(Remote *self, PyObject* py_url) free(url); if (err == GIT_OK) - return 0; + return 0; Error_set(err); } diff --git a/src/repository.c b/src/repository.c index f682bcb32..9663999f2 100644 --- a/src/repository.c +++ b/src/repository.c @@ -945,37 +945,37 @@ Repository_listall_branches(Repository *self, PyObject *args) list = PyList_New(0); if (list == NULL) - return NULL; + return NULL; if ((err = git_branch_iterator_new(&iter, self->repo, list_flags)) < 0) - return Error_set(err); + return Error_set(err); while ((err = git_branch_next(&ref, &type, iter)) == 0) { PyObject *py_branch_name = to_path(git_reference_shorthand(ref)); git_reference_free(ref); if (py_branch_name == NULL) - goto on_error; + goto error; err = PyList_Append(list, py_branch_name); Py_DECREF(py_branch_name); if (err < 0) - goto on_error; + goto error; } git_branch_iterator_free(iter); if (err == GIT_ITEROVER) - err = 0; + err = 0; if (err < 0) { Py_CLEAR(list); - return Error_set(err); + return Error_set(err); } return list; - on_error: +error: git_branch_iterator_free(iter); Py_CLEAR(list); return NULL; @@ -1116,26 +1116,27 @@ Repository_status(Repository *self, PyObject *args) entry = git_status_byindex(list, i); if (entry == NULL) - goto on_error; + goto error; /* We need to choose one of the strings */ - path = entry->head_to_index ? - entry->head_to_index->old_file.path : - entry->index_to_workdir->old_file.path; + if (entry->head_to_index) + path = entry->head_to_index->old_file.path; + else + path = entry->index_to_workdir->old_file.path; status = PyLong_FromLong((long) entry->status); err = PyDict_SetItemString(dict, path, status); Py_CLEAR(status); if (err < 0) - goto on_error; + goto error; } git_status_list_free(list); return dict; - on_error: +error: git_status_list_free(list); Py_CLEAR(dict); return NULL; @@ -1486,14 +1487,14 @@ PyObject* Repository_blame(Repository *self, PyObject *args, PyObject *kwds) return NULL; if (value1) { - err = py_oid_to_git_oid_expand(self->repo, value1, &opts.newest_commit); - if (err < 0) - return NULL; + err = py_oid_to_git_oid_expand(self->repo, value1, &opts.newest_commit); + if (err < 0) + return NULL; } if (value2) { - err = py_oid_to_git_oid_expand(self->repo, value2, &opts.oldest_commit); - if (err < 0) - return NULL; + err = py_oid_to_git_oid_expand(self->repo, value2, &opts.oldest_commit); + if (err < 0) + return NULL; } err = git_blame_file(&blame, self->repo, path, NULL); From c80fb4814feda4846a0a1e7f96caf333040e2082 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Sun, 24 Nov 2013 13:34:27 +0100 Subject: [PATCH 0032/1630] Update documentation --- .mailmap | 4 +++- docs/blame.rst | 46 +++++++++++++++++++++++++++++++++++++++++++++ docs/diff.rst | 12 ++++++++++++ docs/index.rst | 1 + docs/objects.rst | 3 +++ docs/references.rst | 4 +++- 6 files changed, 68 insertions(+), 2 deletions(-) create mode 100644 docs/blame.rst diff --git a/.mailmap b/.mailmap index 5c3799bdd..453de866a 100644 --- a/.mailmap +++ b/.mailmap @@ -4,4 +4,6 @@ J. David Ibáñez Martin Lenders Richo Healey Xavier Delannoy -Xu Tao +Xu Tao +Xu Tao + diff --git a/docs/blame.rst b/docs/blame.rst new file mode 100644 index 000000000..b0c7381bb --- /dev/null +++ b/docs/blame.rst @@ -0,0 +1,46 @@ +********************************************************************** +Blame +********************************************************************** + +.. contents:: + + +.. automethod:: pygit2.Repository.blame + + +The Blame type +============== + +.. automethod:: pygit2.Blame.for_line +.. method:: iter(Blame) +.. method:: len(Blame) +.. method:: Blame[n] + + +The BlameHunk type +================== + +Attributes: + +.. autoattribute:: pygit2.BlameHunk.lines_in_hunk +.. autoattribute:: pygit2.BlameHunk.final_commit_id +.. autoattribute:: pygit2.BlameHunk.final_start_line_number +.. autoattribute:: pygit2.BlameHunk.orig_commit_id +.. autoattribute:: pygit2.BlameHunk.orig_path +.. autoattribute:: pygit2.BlameHunk.orig_start_line_number +.. autoattribute:: pygit2.BlameHunk.boundary + +Getters: + +.. autoattribute:: pygit2.BlameHunk.final_committer +.. autoattribute:: pygit2.BlameHunk.orig_committer + + +Constants +========= + +.. py:data:: GIT_BLAME_NORMAL +.. py:data:: GIT_BLAME_TRACK_COPIES_SAME_FILE +.. py:data:: GIT_BLAME_TRACK_COPIES_SAME_COMMIT_MOVES +.. py:data:: GIT_BLAME_TRACK_COPIES_SAME_COMMIT_COPIES +.. py:data:: GIT_BLAME_TRACK_COPIES_ANY_COMMIT_COPIES diff --git a/docs/diff.rst b/docs/diff.rst index d81fa9c9c..0e5fbaffa 100644 --- a/docs/diff.rst +++ b/docs/diff.rst @@ -35,6 +35,10 @@ The Diff type ==================== .. autoattribute:: pygit2.Diff.patch +.. method:: len(Diff) + + Returns the number of deltas/patches in this diff. + .. automethod:: pygit2.Diff.merge .. automethod:: pygit2.Diff.find_similar @@ -42,6 +46,8 @@ The Diff type The Patch type ==================== +Attributes: + .. autoattribute:: pygit2.Patch.old_file_path .. autoattribute:: pygit2.Patch.new_file_path .. autoattribute:: pygit2.Patch.old_oid @@ -49,6 +55,12 @@ The Patch type .. autoattribute:: pygit2.Patch.status .. autoattribute:: pygit2.Patch.similarity .. autoattribute:: pygit2.Patch.hunks +.. autoattribute:: pygit2.Patch.additions +.. autoattribute:: pygit2.Patch.deletions + +Getters: + +.. autoattribute:: pygit2.Patch.is_binary The Hunk type diff --git a/docs/index.rst b/docs/index.rst index 5d25e6862..f838b3518 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -44,6 +44,7 @@ Usage guide: merge config remotes + blame Indices and tables diff --git a/docs/objects.rst b/docs/objects.rst index 73db010a9..377829e1d 100644 --- a/docs/objects.rst +++ b/docs/objects.rst @@ -109,6 +109,9 @@ This is their API: >>> print blob.size 130 +.. autoattribute:: pygit2.Blob.is_binary + + Creating blobs -------------- diff --git a/docs/references.rst b/docs/references.rst index 4db5eae7a..363e3eebe 100644 --- a/docs/references.rst +++ b/docs/references.rst @@ -18,6 +18,7 @@ The Reference type ==================== .. autoattribute:: pygit2.Reference.name +.. autoattribute:: pygit2.Reference.shorthand .. autoattribute:: pygit2.Reference.target .. autoattribute:: pygit2.Reference.type @@ -25,6 +26,7 @@ The Reference type .. automethod:: pygit2.Reference.rename .. automethod:: pygit2.Reference.resolve .. automethod:: pygit2.Reference.log +.. automethod:: pygit2.Reference.log_append .. automethod:: pygit2.Reference.get_object @@ -38,7 +40,7 @@ Example. These two lines are equivalent:: .. autoattribute:: pygit2.Repository.head .. autoattribute:: pygit2.Repository.head_is_detached -.. autoattribute:: pygit2.Repository.head_is_orphaned +.. autoattribute:: pygit2.Repository.head_is_unborn Branches ==================== From e6c270fe3591c42dc6a89c0f49e6a5547baf5acd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Sun, 24 Nov 2013 15:22:10 +0100 Subject: [PATCH 0033/1630] Get ready to release v0.20.0 --- README.rst | 121 +++++++++++++++++++++++++--------------------- docs/conf.py | 4 +- docs/install.rst | 4 +- pygit2/version.py | 2 +- 4 files changed, 72 insertions(+), 59 deletions(-) diff --git a/README.rst b/README.rst index 3b2d6e562..4345acb22 100644 --- a/README.rst +++ b/README.rst @@ -45,60 +45,73 @@ for the topic), send a pull request. Authors ============== -This is the list of authors of pygit2, sorted by number of commits (as shown by -``git shortlog -sn``): - -- J David Ibáñez -- Nico von Geyso -- W Trevor King -- Dave Borowitz -- Carlos Martín Nieto -- Daniel Rodríguez Troitiño -- Richo Healey -- Christian Boos -- Julien Miotte -- Martin Lenders -- Xavier Delannoy -- Yonggang Luo -- Valentin Haenel -- Bernardo Heynemann -- John Szakmeister -- Brodie Rao -- David Versmisse -- Petr Hosek -- Rémi Duraffort -- Sebastian Thiel -- Fraser Tweedale -- Han-Wen Nienhuys -- Petr Viktorin -- Alex Chamberlain -- Amit Bakshi -- Andrey Devyatkin -- Ben Davis -- Hervé Cauwelier -- Jared Flatow -- Jiunn Haur Lim -- Sarath Lakshman -- Vicent Marti -- Zoran Zaric -- Andrew Chin -- András Veres-Szentkirályi -- Benjamin Kircher -- Benjamin Pollack -- Bryan O'Sullivan -- David Fischer -- David Sanders -- Eric Davis -- Eric Schrijver -- Erik van Zijst -- Ferengee -- Hugh Cole-Baker -- Josh Bleecher Snyder -- Jun Omae -- Ridge Kennedy -- Rui Abreu Ferreira -- Xu Tao -- pistacchio +52 developers have contributed at least 1 commit to pygit2:: + + J. David Ibáñez Andrey Devyatkin + Nico von Geyso Ben Davis + Carlos Martín Nieto Hervé Cauwelier + W. Trevor King Huang Huang + Dave Borowitz Jared Flatow + Daniel Rodríguez Troitiño Jiunn Haur Lim + Richo Healey Sarath Lakshman + Christian Boos Vicent Marti + Julien Miotte Zoran Zaric + Martin Lenders Andrew Chin + Xavier Delannoy András Veres-Szentkirályi + Yonggang Luo Benjamin Kircher + Valentin Haenel Benjamin Pollack + Xu Tao Bryan O'Sullivan + Bernardo Heynemann David Fischer + John Szakmeister David Sanders + Brodie Rao Eric Davis + Petr Hosek Eric Schrijver + David Versmisse Erik van Zijst + Rémi Duraffort Ferengee + Sebastian Thiel Hugh Cole-Baker + Fraser Tweedale Josh Bleecher Snyder + Han-Wen Nienhuys Jun Omae + Petr Viktorin Ridge Kennedy + Alex Chamberlain Rui Abreu Ferreira + Amit Bakshi pistacchio + + +Changelog +============== + +0.20.0 (2013-11-24) +------------------- + +API changes: + +- Renamed ``Repository.head_is_orphaned`` to ``Repository.head_is_unborn`` + +- ``Repository.listall_references`` and ``Repository.listall_branches`` now + return a list, instead of a tuple + +- The prototype of ``clone_repository`` changed from:: + + # Before + pygit2.clone_repository(url, path, bare=False, remote_name='origin', + push_url=None, fetch_spec=None, push_spec=None, + checkout_branch=None) + + # Now + pygit2.clone_repository(url, path, bare=False, ignore_cert_errors=False, + remote_name='origin', checkout_branch=None) + +New API: + +- Added support for blame + +- New: + + - ``Reference.log_append(...)`` + - ``Reference.shorthand`` + - ``Blog.is_binary`` + - ``len(Diff)`` + - ``Patch.additions`` + - ``Patch.deletions`` + - ``Patch.is_binary`` License diff --git a/docs/conf.py b/docs/conf.py index 4ec29bcbd..92253aaf7 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -48,9 +48,9 @@ # built documents. # # The short X.Y version. -version = '0.19' +version = '0.20' # The full version, including alpha/beta/rc tags. -release = '0.19.1' +release = '0.20.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/docs/install.rst b/docs/install.rst index 93a5563d1..95cba2b9a 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -26,8 +26,8 @@ When those are installed, you can install pygit2: $ python setup.py test .. note:: A minor version of pygit2 must be used with the corresponding minor - version of libgit2. For example, pygit2 v0.19.x must be used with libgit2 - v0.19.0. + version of libgit2. For example, pygit2 v0.20.x must be used with libgit2 + v0.20.0. Building on \*nix (including OS X) =================================== diff --git a/pygit2/version.py b/pygit2/version.py index 8fe724b7a..428045a6d 100644 --- a/pygit2/version.py +++ b/pygit2/version.py @@ -23,4 +23,4 @@ # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. -__version__ = '0.19.1' +__version__ = '0.20.0' From 336f042e223fbbf853e7c5f6ffff5b05fcef9271 Mon Sep 17 00:00:00 2001 From: Jose Plana Date: Thu, 21 Nov 2013 01:05:36 +0100 Subject: [PATCH 0034/1630] Added methods to interact with the remote refspecs --- src/remote.c | 161 ++++++++++++++++++++++++++++++++++++++++++++ test/test_remote.py | 35 ++++++++-- 2 files changed, 191 insertions(+), 5 deletions(-) diff --git a/src/remote.c b/src/remote.c index 6b684ecfc..12c391349 100644 --- a/src/remote.c +++ b/src/remote.c @@ -33,6 +33,7 @@ #include "types.h" #include "remote.h" + extern PyObject *GitError; extern PyTypeObject RepositoryType; @@ -95,8 +96,164 @@ Remote_name__set__(Remote *self, PyObject* py_name) } +PyObject * get_pylist_from_git_strarray(git_strarray *strarray) +{ + int index; + PyObject *new_list; + + new_list = PyList_New(strarray->count); + for (index = 0; index < strarray->count; (index)++ ) { + PyList_SET_ITEM( + new_list, + index, + PyString_FromString(strarray->strings[index])); + } + return new_list; +} + + +PyDoc_STRVAR(Remote_fetch_refspecs__doc__, "Fetch refspecs"); + + +PyObject * +Remote_fetch_refspecs__get__(Remote *self) +{ + int err; + git_strarray refspecs; + PyObject *new_list; + + err = git_remote_get_fetch_refspecs(&refspecs, self->remote); + + if (err != 0) { + Error_set(err); + return NULL; + } + + new_list = get_pylist_from_git_strarray(&refspecs); + + git_strarray_free(&refspecs); + return new_list; +} + + +PyDoc_STRVAR(Remote_push_refspecs__doc__, "Push refspecs"); + + +PyObject * +Remote_push_refspecs__get__(Remote *self) +{ + int err; + git_strarray refspecs; + PyObject *new_list; + + err = git_remote_get_push_refspecs(&refspecs, self->remote); + + if (err != 0) { + Error_set(err); + return NULL; + } + + new_list = get_pylist_from_git_strarray(&refspecs); + git_strarray_free(&refspecs); + return new_list; +} + + +int +get_strarraygit_from_pylist(git_strarray *array, PyObject *pylist) +{ + long index, n; + PyObject *item; + + n = PyObject_Length(pylist); + if (n < 0) + return -1; + + // allocate new git_strarray + void *ptr = calloc(n, sizeof(char *)); + + if (!ptr) + return -1; + + array->strings = ptr; + array->count = n; + + for (index = 0; index < n; index++) { + item = PyList_GetItem(pylist, index); + array->strings[index] = py_str_to_c_str(item, NULL); + } + return 0; +} + + +PyDoc_STRVAR(Remote_set_fetch_refspecs__doc__, + "set_fetch_refspecs([str])\n" + "\n"); + + +PyObject * +Remote_set_fetch_refspecs(Remote *self, PyObject *args) +{ + int err; + PyObject *pyrefspecs; + git_strarray fetch_refspecs; + + if (! PyArg_Parse(args, "O", &pyrefspecs)) + return NULL; + + if (get_strarraygit_from_pylist(&fetch_refspecs , pyrefspecs) != 0) { + return NULL; + } + + err = git_remote_set_fetch_refspecs(self->remote, &fetch_refspecs); + + if (err != 0) { + Error_set(err); + return NULL; + } + + git_strarray_free(&fetch_refspecs); + + Py_RETURN_NONE; +} + + +PyDoc_STRVAR(Remote_set_push_refspecs__doc__, + "set_push_refspecs([str])\n" + "\n"); + + +PyObject * +Remote_set_push_refspecs(Remote *self, PyObject *args) +{ + + int err; + PyObject *pyrefspecs; + git_strarray push_refspecs; + + if (! PyArg_Parse(args, "O", &pyrefspecs)) + return NULL; + + if (get_strarraygit_from_pylist(&push_refspecs, pyrefspecs) != 0) { + return NULL; + } + + err = git_remote_set_push_refspecs(self->remote, &push_refspecs); + + if (err != 0) { + Error_set(err); + return NULL; + } + + git_strarray_free(&push_refspecs); + + Py_RETURN_NONE; +} + + PyDoc_STRVAR(Remote_url__doc__, "Url of the remote"); + PyObject * Remote_url__get__(Remote *self) { @@ -288,6 +445,8 @@ PyMethodDef Remote_methods[] = { METHOD(Remote, save, METH_NOARGS), METHOD(Remote, get_refspec, METH_O), METHOD(Remote, push, METH_VARARGS), + METHOD(Remote, set_fetch_refspecs, METH_O), + METHOD(Remote, set_push_refspecs, METH_O), {NULL} }; @@ -295,6 +454,8 @@ PyGetSetDef Remote_getseters[] = { GETSET(Remote, name), GETSET(Remote, url), GETTER(Remote, refspec_count), + GETTER(Remote, fetch_refspecs), + GETTER(Remote, push_refspecs), {NULL} }; diff --git a/test/test_remote.py b/test/test_remote.py index b6a9edc5d..85a95e1ee 100644 --- a/test/test_remote.py +++ b/test/test_remote.py @@ -83,11 +83,36 @@ def test_refspec(self): self.assertEqual(refspec[0], REMOTE_FETCHSPEC_SRC) self.assertEqual(refspec[1], REMOTE_FETCHSPEC_DST) -# new_fetchspec = ('refs/foo/*', 'refs/remotes/foo/*') -# remote.fetchspec = new_fetchspec -# refspec = remote.get_refspec(0) -# self.assertEqual(new_fetchspec[0], refspec[0]) -# self.assertEqual(new_fetchspec[1], refspec[1]) + self.assertEqual(list, type(remote.fetch_refspecs)) + self.assertEqual(1, len(remote.fetch_refspecs)) + self.assertEqual('+refs/heads/*:refs/remotes/origin/*', + remote.fetch_refspecs[0]) + + self.assertEqual(list, type(remote.fetch_refspecs)) + self.assertEqual(0, len(remote.push_refspecs)) + + remote.set_fetch_refspecs(['+refs/*:refs/remotes/*']) + self.assertEqual('+refs/*:refs/remotes/*', + remote.fetch_refspecs[0]) + + remote.set_fetch_refspecs([ + '+refs/*:refs/remotes/*', + '+refs/test/*:refs/test/remotes/*' + ]) + self.assertEqual('+refs/*:refs/remotes/*', + remote.fetch_refspecs[0]) + self.assertEqual('+refs/test/*:refs/test/remotes/*', + remote.fetch_refspecs[1]) + + remote.set_push_refspecs([ + '+refs/*:refs/remotes/*', + '+refs/test/*:refs/test/remotes/*' + ]) + + self.assertEqual('+refs/*:refs/remotes/*', + remote.push_refspecs[0]) + self.assertEqual('+refs/test/*:refs/test/remotes/*', + remote.push_refspecs[1]) def test_remote_list(self): From ea8901f417ec29eb39524fecf23dda9760fd2148 Mon Sep 17 00:00:00 2001 From: Jose Plana Date: Mon, 25 Nov 2013 14:03:37 +0100 Subject: [PATCH 0035/1630] Fix PEP-7 --- src/remote.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/remote.c b/src/remote.c index 12c391349..c5cfb0408 100644 --- a/src/remote.c +++ b/src/remote.c @@ -87,7 +87,7 @@ Remote_name__set__(Remote *self, PyObject* py_name) free(name); if (err == GIT_OK) - return 0; + return 0; Error_set(err); } @@ -167,13 +167,13 @@ get_strarraygit_from_pylist(git_strarray *array, PyObject *pylist) n = PyObject_Length(pylist); if (n < 0) - return -1; + return -1; // allocate new git_strarray void *ptr = calloc(n, sizeof(char *)); if (!ptr) - return -1; + return -1; array->strings = ptr; array->count = n; From 41bedc05f09e36eed11d9fe858b8db9fe5f7b129 Mon Sep 17 00:00:00 2001 From: Jose Plana Date: Mon, 25 Nov 2013 17:36:35 +0100 Subject: [PATCH 0036/1630] Use unicode for python v3 support --- src/remote.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/remote.c b/src/remote.c index c5cfb0408..bf8a7a843 100644 --- a/src/remote.c +++ b/src/remote.c @@ -106,7 +106,7 @@ PyObject * get_pylist_from_git_strarray(git_strarray *strarray) PyList_SET_ITEM( new_list, index, - PyString_FromString(strarray->strings[index])); + to_unicode(strarray->strings[index], NULL, NULL)); } return new_list; } From cb9ffa8b11fd80767da0045fd7d0ccc7a62a60b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Mon, 25 Nov 2013 23:01:13 +0100 Subject: [PATCH 0037/1630] PEP-7: Do not use C++ style // one-line comments --- src/branch.c | 8 ++++---- src/diff.c | 4 ++-- src/repository.c | 3 +-- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/branch.c b/src/branch.c index 48f5bbaab..57c51a3ae 100644 --- a/src/branch.c +++ b/src/branch.c @@ -138,12 +138,12 @@ PyObject* Branch_remote_name__get__(Branch *self) CHECK_REFERENCE(self); branch_name = git_reference_name(self->reference); - // get the length of the remote name + /* Get the length of the remote name */ err = git_branch_remote_name(NULL, 0, self->repo->repo, branch_name); if (err < GIT_OK) return Error_set(err); - // get the actual remote name + /* Get the actual remote name */ c_name = calloc(err, sizeof(char)); if (c_name == NULL) return PyErr_NoMemory(); @@ -227,12 +227,12 @@ PyObject* Branch_upstream_name__get__(Branch *self) CHECK_REFERENCE(self); branch_name = git_reference_name(self->reference); - // get the length of the upstream name + /* Get the length of the upstream name */ err = git_branch_upstream_name(NULL, 0, self->repo->repo, branch_name); if (err < GIT_OK) return Error_set(err); - // get the actual upstream name + /* Get the actual upstream name */ c_name = calloc(err, sizeof(char)); if (c_name == NULL) return PyErr_NoMemory(); diff --git a/src/diff.c b/src/diff.c index f81f63574..b7bacd58d 100644 --- a/src/diff.c +++ b/src/diff.c @@ -142,8 +142,8 @@ Patch_dealloc(Patch *self) Py_CLEAR(self->hunks); free(self->old_oid); free(self->new_oid); - // we do not have to free old_file_path and new_file_path, they will - // be freed by git_diff_list_free in Diff_dealloc + /* We do not have to free old_file_path and new_file_path, they will + * be freed by git_diff_list_free in Diff_dealloc */ PyObject_Del(self); } diff --git a/src/repository.c b/src/repository.c index 9663999f2..7b6595aa4 100644 --- a/src/repository.c +++ b/src/repository.c @@ -536,8 +536,7 @@ Repository_config__get__(Repository *self) py_config->config = config; self->config = (PyObject*)py_config; - // We need 2 refs here. - // One is returned, one is kept internally. + /* We need 2 refs here. One is returned, one is kept internally. */ Py_INCREF(self->config); } else { Py_INCREF(self->config); From 37ed244a40b284f15792bd33f33341cf8f5f8118 Mon Sep 17 00:00:00 2001 From: Jose Plana Date: Tue, 26 Nov 2013 00:12:33 +0100 Subject: [PATCH 0038/1630] Removed reduntant returns, fixs PEP7 --- src/remote.c | 33 ++++++++++++--------------------- 1 file changed, 12 insertions(+), 21 deletions(-) diff --git a/src/remote.c b/src/remote.c index bf8a7a843..19274b1c2 100644 --- a/src/remote.c +++ b/src/remote.c @@ -124,10 +124,8 @@ Remote_fetch_refspecs__get__(Remote *self) err = git_remote_get_fetch_refspecs(&refspecs, self->remote); - if (err != 0) { - Error_set(err); - return NULL; - } + if (err != 0) + return Error_set(err); new_list = get_pylist_from_git_strarray(&refspecs); @@ -148,10 +146,8 @@ Remote_push_refspecs__get__(Remote *self) err = git_remote_get_push_refspecs(&refspecs, self->remote); - if (err != 0) { - Error_set(err); - return NULL; - } + if (err != 0) + return Error_set(err); new_list = get_pylist_from_git_strarray(&refspecs); git_strarray_free(&refspecs); @@ -169,7 +165,7 @@ get_strarraygit_from_pylist(git_strarray *array, PyObject *pylist) if (n < 0) return -1; - // allocate new git_strarray + /* allocate new git_strarray */ void *ptr = calloc(n, sizeof(char *)); if (!ptr) @@ -201,16 +197,13 @@ Remote_set_fetch_refspecs(Remote *self, PyObject *args) if (! PyArg_Parse(args, "O", &pyrefspecs)) return NULL; - if (get_strarraygit_from_pylist(&fetch_refspecs , pyrefspecs) != 0) { + if (get_strarraygit_from_pylist(&fetch_refspecs , pyrefspecs) != 0) return NULL; - } err = git_remote_set_fetch_refspecs(self->remote, &fetch_refspecs); - if (err != 0) { - Error_set(err); - return NULL; - } + if (err != 0) + return Error_set(err); git_strarray_free(&fetch_refspecs); @@ -234,16 +227,14 @@ Remote_set_push_refspecs(Remote *self, PyObject *args) if (! PyArg_Parse(args, "O", &pyrefspecs)) return NULL; - if (get_strarraygit_from_pylist(&push_refspecs, pyrefspecs) != 0) { + if (get_strarraygit_from_pylist(&push_refspecs, pyrefspecs) != 0) return NULL; - } err = git_remote_set_push_refspecs(self->remote, &push_refspecs); - if (err != 0) { - Error_set(err); - return NULL; - } + if (err != 0) + return Error_set(err); + git_strarray_free(&push_refspecs); From 03a6465927fea29e5911268bb6d736afc9e2ae41 Mon Sep 17 00:00:00 2001 From: Jose Plana Date: Tue, 26 Nov 2013 01:05:09 +0100 Subject: [PATCH 0039/1630] Fix documentation --- docs/remotes.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/remotes.rst b/docs/remotes.rst index 4b9692fcd..b8f9957bc 100644 --- a/docs/remotes.rst +++ b/docs/remotes.rst @@ -13,7 +13,11 @@ The Remote type .. autoattribute:: pygit2.Remote.name .. autoattribute:: pygit2.Remote.url .. autoattribute:: pygit2.Remote.refspec_count +.. autoattribute:: pygit2.Remote.fetch_refspec +.. autoattribute:: pygit2.Remote.push_refspec .. automethod:: pygit2.Remote.get_refspec +.. automethod:: pygit2.Remote.set_fetch_refspecs +.. automethod:: pygit2.Remote.set_push_refspecs .. automethod:: pygit2.Remote.fetch .. automethod:: pygit2.Remote.push .. automethod:: pygit2.Remote.save From f69eeae108322a4e46b88ad5913dbef0a89321f7 Mon Sep 17 00:00:00 2001 From: Jose Plana Date: Wed, 27 Nov 2013 19:26:24 +0100 Subject: [PATCH 0040/1630] Refactor getters to become methods --- src/remote.c | 12 ++++++------ test/test_remote.py | 20 ++++++++++---------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/remote.c b/src/remote.c index bf8a7a843..b61eb2fea 100644 --- a/src/remote.c +++ b/src/remote.c @@ -112,11 +112,11 @@ PyObject * get_pylist_from_git_strarray(git_strarray *strarray) } -PyDoc_STRVAR(Remote_fetch_refspecs__doc__, "Fetch refspecs"); +PyDoc_STRVAR(Remote_get_fetch_refspecs__doc__, "Fetch refspecs"); PyObject * -Remote_fetch_refspecs__get__(Remote *self) +Remote_get_fetch_refspecs(Remote *self) { int err; git_strarray refspecs; @@ -136,11 +136,11 @@ Remote_fetch_refspecs__get__(Remote *self) } -PyDoc_STRVAR(Remote_push_refspecs__doc__, "Push refspecs"); +PyDoc_STRVAR(Remote_get_push_refspecs__doc__, "Push refspecs"); PyObject * -Remote_push_refspecs__get__(Remote *self) +Remote_get_push_refspecs(Remote *self) { int err; git_strarray refspecs; @@ -445,7 +445,9 @@ PyMethodDef Remote_methods[] = { METHOD(Remote, save, METH_NOARGS), METHOD(Remote, get_refspec, METH_O), METHOD(Remote, push, METH_VARARGS), + METHOD(Remote, get_fetch_refspecs, METH_O), METHOD(Remote, set_fetch_refspecs, METH_O), + METHOD(Remote, get_push_refspecs, METH_O), METHOD(Remote, set_push_refspecs, METH_O), {NULL} }; @@ -454,8 +456,6 @@ PyGetSetDef Remote_getseters[] = { GETSET(Remote, name), GETSET(Remote, url), GETTER(Remote, refspec_count), - GETTER(Remote, fetch_refspecs), - GETTER(Remote, push_refspecs), {NULL} }; diff --git a/test/test_remote.py b/test/test_remote.py index 85a95e1ee..741cd504d 100644 --- a/test/test_remote.py +++ b/test/test_remote.py @@ -83,26 +83,26 @@ def test_refspec(self): self.assertEqual(refspec[0], REMOTE_FETCHSPEC_SRC) self.assertEqual(refspec[1], REMOTE_FETCHSPEC_DST) - self.assertEqual(list, type(remote.fetch_refspecs)) - self.assertEqual(1, len(remote.fetch_refspecs)) + self.assertEqual(list, type(remote.get_fetch_refspecs())) + self.assertEqual(1, len(remote.get_fetch_refspecs())) self.assertEqual('+refs/heads/*:refs/remotes/origin/*', - remote.fetch_refspecs[0]) + remote.get_fetch_refspecs()[0]) - self.assertEqual(list, type(remote.fetch_refspecs)) - self.assertEqual(0, len(remote.push_refspecs)) + self.assertEqual(list, type(remote.get_push_refspecs())) + self.assertEqual(0, len(remote.get_push_refspecs())) remote.set_fetch_refspecs(['+refs/*:refs/remotes/*']) self.assertEqual('+refs/*:refs/remotes/*', - remote.fetch_refspecs[0]) + remote.get_fetch_refspecs()[0]) remote.set_fetch_refspecs([ '+refs/*:refs/remotes/*', '+refs/test/*:refs/test/remotes/*' ]) self.assertEqual('+refs/*:refs/remotes/*', - remote.fetch_refspecs[0]) + remote.get_fetch_refspecs()[0]) self.assertEqual('+refs/test/*:refs/test/remotes/*', - remote.fetch_refspecs[1]) + remote.get_fetch_refspecs()[1]) remote.set_push_refspecs([ '+refs/*:refs/remotes/*', @@ -110,9 +110,9 @@ def test_refspec(self): ]) self.assertEqual('+refs/*:refs/remotes/*', - remote.push_refspecs[0]) + remote.get_push_refspecs()[0]) self.assertEqual('+refs/test/*:refs/test/remotes/*', - remote.push_refspecs[1]) + remote.get_push_refspecs()[1]) def test_remote_list(self): From 369fa3087d32ba18e18021110cce77ee4f58a425 Mon Sep 17 00:00:00 2001 From: Jose Plana Date: Fri, 29 Nov 2013 19:19:54 +0100 Subject: [PATCH 0041/1630] Fix methods signature --- src/remote.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/remote.c b/src/remote.c index b61eb2fea..fbfd1fc26 100644 --- a/src/remote.c +++ b/src/remote.c @@ -445,9 +445,9 @@ PyMethodDef Remote_methods[] = { METHOD(Remote, save, METH_NOARGS), METHOD(Remote, get_refspec, METH_O), METHOD(Remote, push, METH_VARARGS), - METHOD(Remote, get_fetch_refspecs, METH_O), + METHOD(Remote, get_fetch_refspecs, METH_NOARGS), METHOD(Remote, set_fetch_refspecs, METH_O), - METHOD(Remote, get_push_refspecs, METH_O), + METHOD(Remote, get_push_refspecs, METH_NOARGS), METHOD(Remote, set_push_refspecs, METH_O), {NULL} }; From e65ab19625d4f8d79377776cff15e17d3611689d Mon Sep 17 00:00:00 2001 From: Petr Hosek Date: Sat, 30 Nov 2013 23:18:19 +0000 Subject: [PATCH 0042/1630] Provide minimal diff option --- src/pygit2.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pygit2.c b/src/pygit2.c index 59ae098b4..64ff4b6db 100644 --- a/src/pygit2.c +++ b/src/pygit2.c @@ -376,6 +376,7 @@ moduleinit(PyObject* m) ADD_CONSTANT_INT(m, GIT_DIFF_IGNORE_WHITESPACE_EOL) ADD_CONSTANT_INT(m, GIT_DIFF_IGNORE_SUBMODULES) ADD_CONSTANT_INT(m, GIT_DIFF_PATIENCE) + ADD_CONSTANT_INT(m, GIT_DIFF_MINIMAL) ADD_CONSTANT_INT(m, GIT_DIFF_INCLUDE_IGNORED) ADD_CONSTANT_INT(m, GIT_DIFF_INCLUDE_UNTRACKED) ADD_CONSTANT_INT(m, GIT_DIFF_INCLUDE_UNMODIFIED) From 9f4f978845908fb44466a3f1151b7ca829203c6c Mon Sep 17 00:00:00 2001 From: osanjose Date: Mon, 2 Dec 2013 10:56:39 +0100 Subject: [PATCH 0043/1630] Update with master --- src/pygit2.c | 7 +++++ src/repository.c | 37 +++++++++++++++++++++++ test/test_repository.py | 66 +++++++++++++++++++++++++++++++++++++++-- 3 files changed, 107 insertions(+), 3 deletions(-) diff --git a/src/pygit2.c b/src/pygit2.c index 64ff4b6db..4bd37c1df 100644 --- a/src/pygit2.c +++ b/src/pygit2.c @@ -304,6 +304,13 @@ moduleinit(PyObject* m) ADD_CONSTANT_INT(m, GIT_SORT_TIME) ADD_CONSTANT_INT(m, GIT_SORT_REVERSE) + /* + * Reset + */ + ADD_CONSTANT_INT(m, GIT_RESET_SOFT) + ADD_CONSTANT_INT(m, GIT_RESET_MIXED) + ADD_CONSTANT_INT(m, GIT_RESET_HARD) + /* * References */ diff --git a/src/repository.c b/src/repository.c index 7b6595aa4..a5094bca4 100644 --- a/src/repository.c +++ b/src/repository.c @@ -1503,6 +1503,42 @@ PyObject* Repository_blame(Repository *self, PyObject *args, PyObject *kwds) return wrap_blame(blame, self); } +PyDoc_STRVAR(Repository_reset__doc__, + "reset(oid, reset_type)\n" + "\n" + "Resets current head to the provided oid.\n" + "reset_type:\n" + "GIT_RESET_SOFT: resets head to point to oid, but does not modfy working copy, and leaves the changes in the index.\n" + "GIT_RESET_MIXED: resets head to point to oid, but does not modfy working copy. It empties the index too.\n" + "GIT_RESET_HARD: resets head to point to oid, and resets too the working copy and the content of the index.\n"); + +PyObject * +Repository_reset(Repository *self, PyObject* args) +{ + PyObject *py_oid; + git_oid oid; + git_object *target = NULL; + int err, reset_type; + size_t len; + + if (!PyArg_ParseTuple(args, "Oi", + &py_oid, + &reset_type + )) + return NULL; + + len = py_oid_to_git_oid(py_oid, &oid); + if (len == 0) + return NULL; + + err = git_object_lookup_prefix(&target, self->repo, &oid, len, + GIT_OBJ_ANY); + err = err < 0 ? err : git_reset(self->repo, target, reset_type); + git_object_free(target); + if (err < 0) + return Error_set_oid(err, &oid, len); + Py_RETURN_NONE; +} PyMethodDef Repository_methods[] = { METHOD(Repository, create_blob, METH_VARARGS), @@ -1534,6 +1570,7 @@ PyMethodDef Repository_methods[] = { METHOD(Repository, listall_branches, METH_VARARGS), METHOD(Repository, create_branch, METH_VARARGS), METHOD(Repository, blame, METH_VARARGS | METH_KEYWORDS), + METHOD(Repository, reset, METH_VARARGS), {NULL} }; diff --git a/test/test_repository.py b/test/test_repository.py index e783f80c6..43e196be8 100644 --- a/test/test_repository.py +++ b/test/test_repository.py @@ -238,6 +238,69 @@ def test_merge_base(self): self.assertEqual(commit.hex, 'acecd5ea2924a4b900e7e149496e1f4b57976e51') + def test_reset_hard(self): + ref = "5ebeeebb320790caf276b9fc8b24546d63316533" + with open(os.path.join(self.repo.workdir, "hello.txt")) as f: + lines = f.readlines() + self.assertTrue("hola mundo\n" in lines) + self.assertTrue("bonjour le monde\n" in lines) + + self.repo.reset( + ref, + pygit2.GIT_RESET_HARD) + self.assertEqual(self.repo.head.target.hex, ref) + + with open(os.path.join(self.repo.workdir, "hello.txt")) as f: + lines = f.readlines() + #Hard reset will reset the working copy too + self.assertFalse("hola mundo\n" in lines) + self.assertFalse("bonjour le monde\n" in lines) + + def test_reset_soft(self): + ref = "5ebeeebb320790caf276b9fc8b24546d63316533" + with open(os.path.join(self.repo.workdir, "hello.txt")) as f: + lines = f.readlines() + self.assertTrue("hola mundo\n" in lines) + self.assertTrue("bonjour le monde\n" in lines) + + self.repo.reset( + ref, + pygit2.GIT_RESET_SOFT) + self.assertEqual(self.repo.head.target.hex, ref) + with open(os.path.join(self.repo.workdir, "hello.txt")) as f: + lines = f.readlines() + #Soft reset will not reset the working copy + self.assertTrue("hola mundo\n" in lines) + self.assertTrue("bonjour le monde\n" in lines) + + #soft reset will keep changes in the index + diff = self.repo.diff(cached=True) + self.assertRaises(KeyError, lambda: diff[0]) + + def test_reset_mixed(self): + ref = "5ebeeebb320790caf276b9fc8b24546d63316533" + with open(os.path.join(self.repo.workdir, "hello.txt")) as f: + lines = f.readlines() + self.assertTrue("hola mundo\n" in lines) + self.assertTrue("bonjour le monde\n" in lines) + + self.repo.reset( + ref, + pygit2.GIT_RESET_MIXED) + + self.assertEqual(self.repo.head.target.hex, ref) + + with open(os.path.join(self.repo.workdir, "hello.txt")) as f: + lines = f.readlines() + #mixed reset will not reset the working copy + self.assertTrue("hola mundo\n" in lines) + self.assertTrue("bonjour le monde\n" in lines) + + #mixed reset will set the index to match working copy + diff = self.repo.diff(cached=True) + self.assertTrue("hola mundo\n" in diff.patch) + self.assertTrue("bonjour le monde\n" in diff.patch) + class NewRepositoryTest(utils.NoRepoTestCase): @@ -274,7 +337,6 @@ def test_keyword_arg_true(self): self.assertTrue(repo.is_bare) - class DiscoverRepositoryTest(utils.NoRepoTestCase): def test_discover_repo(self): @@ -284,7 +346,6 @@ def test_discover_repo(self): self.assertEqual(repo.path, discover_repository(subdir)) - class EmptyRepositoryTest(utils.EmptyRepoTestCase): def test_is_empty(self): @@ -298,7 +359,6 @@ def test_head(self): self.assertFalse(self.repo.head_is_detached) - class CloneRepositoryTest(utils.NoRepoTestCase): def test_clone_repository(self): From cb3c28fc6a1786d89511e6c3f5af839653ab3d73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Mon, 2 Dec 2013 11:07:36 +0100 Subject: [PATCH 0044/1630] Remove trailing whitespace --- src/repository.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/repository.c b/src/repository.c index a5094bca4..c9b3f816d 100644 --- a/src/repository.c +++ b/src/repository.c @@ -1537,7 +1537,7 @@ Repository_reset(Repository *self, PyObject* args) git_object_free(target); if (err < 0) return Error_set_oid(err, &oid, len); - Py_RETURN_NONE; + Py_RETURN_NONE; } PyMethodDef Repository_methods[] = { From 05798fc5aa97202f077170e733896912b136ad90 Mon Sep 17 00:00:00 2001 From: Jose Plana Date: Mon, 2 Dec 2013 13:40:38 +0100 Subject: [PATCH 0045/1630] Fixed some error exceptions, docs --- docs/remotes.rst | 4 ++++ src/remote.c | 54 +++++++++++++++++++++++++----------------------- 2 files changed, 32 insertions(+), 26 deletions(-) diff --git a/docs/remotes.rst b/docs/remotes.rst index 4b9692fcd..12a1755b2 100644 --- a/docs/remotes.rst +++ b/docs/remotes.rst @@ -13,6 +13,10 @@ The Remote type .. autoattribute:: pygit2.Remote.name .. autoattribute:: pygit2.Remote.url .. autoattribute:: pygit2.Remote.refspec_count +.. automethod:: pygit2.Remote.get_push_refspec +.. automethod:: pygit2.Remote.get_pull_refspec +.. automethod:: pygit2.Remote.set_push_refspec +.. automethod:: pygit2.Remote.set_pull_refspec .. automethod:: pygit2.Remote.get_refspec .. automethod:: pygit2.Remote.fetch .. automethod:: pygit2.Remote.push diff --git a/src/remote.c b/src/remote.c index fbfd1fc26..818bb145c 100644 --- a/src/remote.c +++ b/src/remote.c @@ -102,6 +102,10 @@ PyObject * get_pylist_from_git_strarray(git_strarray *strarray) PyObject *new_list; new_list = PyList_New(strarray->count); + + if (new_list == NULL) + return Error_set(GITERR_NOMEMORY); + for (index = 0; index < strarray->count; (index)++ ) { PyList_SET_ITEM( new_list, @@ -124,10 +128,8 @@ Remote_get_fetch_refspecs(Remote *self) err = git_remote_get_fetch_refspecs(&refspecs, self->remote); - if (err != 0) { - Error_set(err); - return NULL; - } + if (err != GIT_OK) + return Error_set(err); new_list = get_pylist_from_git_strarray(&refspecs); @@ -148,12 +150,11 @@ Remote_get_push_refspecs(Remote *self) err = git_remote_get_push_refspecs(&refspecs, self->remote); - if (err != 0) { - Error_set(err); - return NULL; - } + if (err != GIT_OK) + return Error_set(err); new_list = get_pylist_from_git_strarray(&refspecs); + git_strarray_free(&refspecs); return new_list; } @@ -167,13 +168,13 @@ get_strarraygit_from_pylist(git_strarray *array, PyObject *pylist) n = PyObject_Length(pylist); if (n < 0) - return -1; + goto error; - // allocate new git_strarray + /* allocate new git_strarray */ void *ptr = calloc(n, sizeof(char *)); if (!ptr) - return -1; + goto error; array->strings = ptr; array->count = n; @@ -182,7 +183,12 @@ get_strarraygit_from_pylist(git_strarray *array, PyObject *pylist) item = PyList_GetItem(pylist, index); array->strings[index] = py_str_to_c_str(item, NULL); } - return 0; + + return GIT_OK; + + error: + Error_set(GITERR_NOMEMORY); + return -1; } @@ -199,21 +205,20 @@ Remote_set_fetch_refspecs(Remote *self, PyObject *args) git_strarray fetch_refspecs; if (! PyArg_Parse(args, "O", &pyrefspecs)) - return NULL; + return Error_set(GITERR_INVALID); - if (get_strarraygit_from_pylist(&fetch_refspecs , pyrefspecs) != 0) { + if (get_strarraygit_from_pylist(&fetch_refspecs , pyrefspecs) != GIT_OK) { return NULL; } err = git_remote_set_fetch_refspecs(self->remote, &fetch_refspecs); - if (err != 0) { - Error_set(err); - return NULL; - } - git_strarray_free(&fetch_refspecs); + if (err != GIT_OK) { + return Error_set(err); + } + Py_RETURN_NONE; } @@ -226,13 +231,12 @@ PyDoc_STRVAR(Remote_set_push_refspecs__doc__, PyObject * Remote_set_push_refspecs(Remote *self, PyObject *args) { - int err; PyObject *pyrefspecs; git_strarray push_refspecs; if (! PyArg_Parse(args, "O", &pyrefspecs)) - return NULL; + return Error_set(GITERR_INVALID); if (get_strarraygit_from_pylist(&push_refspecs, pyrefspecs) != 0) { return NULL; @@ -240,13 +244,11 @@ Remote_set_push_refspecs(Remote *self, PyObject *args) err = git_remote_set_push_refspecs(self->remote, &push_refspecs); - if (err != 0) { - Error_set(err); - return NULL; - } - git_strarray_free(&push_refspecs); + if (err != GIT_OK) + return Error_set(err); + Py_RETURN_NONE; } From 6050ae021dad0bc1af053b5fd1b6a431bf160df0 Mon Sep 17 00:00:00 2001 From: Jose Plana Date: Mon, 2 Dec 2013 16:17:51 +0100 Subject: [PATCH 0046/1630] Fix last minute typo --- src/remote.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/remote.c b/src/remote.c index 55539af8f..b3fb89ee3 100644 --- a/src/remote.c +++ b/src/remote.c @@ -207,7 +207,7 @@ Remote_set_fetch_refspecs(Remote *self, PyObject *args) if (! PyArg_Parse(args, "O", &pyrefspecs)) return Error_set(GITERR_INVALID); - if (get_strarraygit_from_pylist(&fetch_refspecs , pyrefspecs) != GIT_OK) { + if (get_strarraygit_from_pylist(&fetch_refspecs , pyrefspecs) != GIT_OK) return NULL; err = git_remote_set_fetch_refspecs(self->remote, &fetch_refspecs); From d7196e7794dd8c0413396cfb3761e99fa04b0dc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Mon, 2 Dec 2013 16:34:30 +0100 Subject: [PATCH 0047/1630] PEP-7: function name in column 1 http://www.python.org/dev/peps/pep-0007/#code-lay-out --- src/branch.c | 15 ++++++++++----- src/error.c | 15 ++++++++++----- src/reference.c | 3 ++- src/remote.c | 3 ++- src/repository.c | 6 ++++-- 5 files changed, 28 insertions(+), 14 deletions(-) diff --git a/src/branch.c b/src/branch.c index 57c51a3ae..ae547fe30 100644 --- a/src/branch.c +++ b/src/branch.c @@ -89,7 +89,8 @@ PyDoc_STRVAR(Branch_rename__doc__, "checked for validity.\n" "Returns the new branch."); -PyObject* Branch_rename(Branch *self, PyObject *args) +PyObject * +Branch_rename(Branch *self, PyObject *args) { int err, force = 0; git_reference *c_out; @@ -111,7 +112,8 @@ PyObject* Branch_rename(Branch *self, PyObject *args) PyDoc_STRVAR(Branch_branch_name__doc__, "The name of the local or remote branch."); -PyObject* Branch_branch_name__get__(Branch *self) +PyObject * +Branch_branch_name__get__(Branch *self) { int err; const char *c_name; @@ -129,7 +131,8 @@ PyObject* Branch_branch_name__get__(Branch *self) PyDoc_STRVAR(Branch_remote_name__doc__, "The name of the remote that the remote tracking branch belongs to."); -PyObject* Branch_remote_name__get__(Branch *self) +PyObject * +Branch_remote_name__get__(Branch *self) { int err; const char *branch_name; @@ -168,7 +171,8 @@ PyDoc_STRVAR(Branch_upstream__doc__, "The branch supporting the remote tracking branch or None if this is not a " "remote tracking branch. Set to None to unset."); -PyObject* Branch_upstream__get__(Branch *self) +PyObject * +Branch_upstream__get__(Branch *self) { int err; git_reference *c_reference; @@ -218,7 +222,8 @@ int Branch_upstream__set__(Branch *self, Reference *py_ref) PyDoc_STRVAR(Branch_upstream_name__doc__, "The name of the reference supporting the remote tracking branch."); -PyObject* Branch_upstream_name__get__(Branch *self) +PyObject * +Branch_upstream_name__get__(Branch *self) { int err; const char *branch_name; diff --git a/src/error.c b/src/error.c index 730c5263b..66587143a 100644 --- a/src/error.c +++ b/src/error.c @@ -29,7 +29,8 @@ PyObject *GitError; -PyObject * Error_type(int type) +PyObject * +Error_type(int type) { const git_error* error; /* Expected */ @@ -79,14 +80,16 @@ PyObject * Error_type(int type) } -PyObject* Error_set(int err) +PyObject * +Error_set(int err) { assert(err < 0); return Error_set_exc(Error_type(err)); } -PyObject* Error_set_exc(PyObject* exception) +PyObject * +Error_set_exc(PyObject* exception) { const git_error* error = giterr_last(); char* message = (error == NULL) ? @@ -97,7 +100,8 @@ PyObject* Error_set_exc(PyObject* exception) } -PyObject* Error_set_str(int err, const char *str) +PyObject * +Error_set_str(int err, const char *str) { const git_error* error; if (err == GIT_ENOTFOUND) { @@ -113,7 +117,8 @@ PyObject* Error_set_str(int err, const char *str) return PyErr_Format(Error_type(err), "%s: %s", str, error->message); } -PyObject* Error_set_oid(int err, const git_oid *oid, size_t len) +PyObject * +Error_set_oid(int err, const git_oid *oid, size_t len) { char hex[GIT_OID_HEXSZ + 1]; diff --git a/src/reference.c b/src/reference.c index 70108af6b..df5e9a6b2 100644 --- a/src/reference.c +++ b/src/reference.c @@ -49,7 +49,8 @@ void RefLogIter_dealloc(RefLogIter *self) PyObject_Del(self); } -PyObject* RefLogIter_iternext(RefLogIter *self) +PyObject * +RefLogIter_iternext(RefLogIter *self) { const git_reflog_entry *entry; RefLogEntry *py_entry; diff --git a/src/remote.c b/src/remote.c index a357fa4e1..c4db92c2d 100644 --- a/src/remote.c +++ b/src/remote.c @@ -96,7 +96,8 @@ Remote_name__set__(Remote *self, PyObject* py_name) } -PyObject * get_pylist_from_git_strarray(git_strarray *strarray) +PyObject * +get_pylist_from_git_strarray(git_strarray *strarray) { int index; PyObject *new_list; diff --git a/src/repository.c b/src/repository.c index c9b3f816d..20e2a7c50 100644 --- a/src/repository.c +++ b/src/repository.c @@ -866,7 +866,8 @@ PyDoc_STRVAR(Repository_create_branch__doc__, "\n" " repo.create_branch('foo', repo.head.hex, force=False)"); -PyObject* Repository_create_branch(Repository *self, PyObject *args) +PyObject * +Repository_create_branch(Repository *self, PyObject *args) { Commit *py_commit; git_reference *c_reference; @@ -1467,7 +1468,8 @@ PyDoc_STRVAR(Repository_blame__doc__, "\n" " repo.blame('foo.c', flags=GIT_BLAME_TRACK_COPIES_SAME_FILE)"); -PyObject* Repository_blame(Repository *self, PyObject *args, PyObject *kwds) +PyObject * +Repository_blame(Repository *self, PyObject *args, PyObject *kwds) { git_blame_options opts = GIT_BLAME_OPTIONS_INIT; git_blame *blame; From 82ac7c83af5329e580262f93043fb59b7d10a3cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Mon, 2 Dec 2013 16:49:12 +0100 Subject: [PATCH 0048/1630] Python API already sets exceptions on error --- src/remote.c | 46 ++++++++++++++++++---------------------------- 1 file changed, 18 insertions(+), 28 deletions(-) diff --git a/src/remote.c b/src/remote.c index c4db92c2d..b5d705998 100644 --- a/src/remote.c +++ b/src/remote.c @@ -103,16 +103,13 @@ get_pylist_from_git_strarray(git_strarray *strarray) PyObject *new_list; new_list = PyList_New(strarray->count); - if (new_list == NULL) - return Error_set(GITERR_NOMEMORY); + return NULL; + + for (index = 0; index < strarray->count; index++) + PyList_SET_ITEM(new_list, index, + to_unicode(strarray->strings[index], NULL, NULL)); - for (index = 0; index < strarray->count; (index)++ ) { - PyList_SET_ITEM( - new_list, - index, - to_unicode(strarray->strings[index], NULL, NULL)); - } return new_list; } @@ -128,7 +125,6 @@ Remote_get_fetch_refspecs(Remote *self) PyObject *new_list; err = git_remote_get_fetch_refspecs(&refspecs, self->remote); - if (err != GIT_OK) return Error_set(err); @@ -150,7 +146,6 @@ Remote_get_push_refspecs(Remote *self) PyObject *new_list; err = git_remote_get_push_refspecs(&refspecs, self->remote); - if (err != GIT_OK) return Error_set(err); @@ -166,16 +161,18 @@ get_strarraygit_from_pylist(git_strarray *array, PyObject *pylist) { long index, n; PyObject *item; + void *ptr; n = PyObject_Length(pylist); if (n < 0) - goto error; + return -1; /* allocate new git_strarray */ - void *ptr = calloc(n, sizeof(char *)); - - if (!ptr) - goto error; + ptr = calloc(n, sizeof(char *)); + if (!ptr) { + PyErr_SetNone(PyExc_MemoryError); + return -1; + } array->strings = ptr; array->count = n; @@ -186,10 +183,6 @@ get_strarraygit_from_pylist(git_strarray *array, PyObject *pylist) } return GIT_OK; - -error: - Error_set(GITERR_NOMEMORY); - return -1; } @@ -206,26 +199,24 @@ Remote_set_fetch_refspecs(Remote *self, PyObject *args) git_strarray fetch_refspecs; if (! PyArg_Parse(args, "O", &pyrefspecs)) - return Error_set(GITERR_INVALID); + return NULL; - if (get_strarraygit_from_pylist(&fetch_refspecs , pyrefspecs) != GIT_OK) + if (get_strarraygit_from_pylist(&fetch_refspecs, pyrefspecs) != GIT_OK) return NULL; err = git_remote_set_fetch_refspecs(self->remote, &fetch_refspecs); - git_strarray_free(&fetch_refspecs); - if (err != GIT_OK) { + if (err != GIT_OK) return Error_set(err); - } Py_RETURN_NONE; } PyDoc_STRVAR(Remote_set_push_refspecs__doc__, - "set_push_refspecs([str])\n" - "\n"); + "set_push_refspecs([str])\n" + "\n"); PyObject * @@ -236,13 +227,12 @@ Remote_set_push_refspecs(Remote *self, PyObject *args) git_strarray push_refspecs; if (! PyArg_Parse(args, "O", &pyrefspecs)) - return Error_set(GITERR_INVALID); + return NULL; if (get_strarraygit_from_pylist(&push_refspecs, pyrefspecs) != 0) return NULL; err = git_remote_set_push_refspecs(self->remote, &push_refspecs); - git_strarray_free(&push_refspecs); if (err != GIT_OK) From 1f5ec810adbdb5d94da7adcc24490106d686bfa2 Mon Sep 17 00:00:00 2001 From: Victor Garcia Date: Wed, 4 Dec 2013 18:17:10 +0100 Subject: [PATCH 0049/1630] 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 000000000..c0ffddd6e --- /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 000000000..7aadac4aa --- /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 4bd37c1df..e1766d26e 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 20e2a7c50..3d4889c65 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 3c6094872..735f77480 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 46062a456..06504fd36 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 43e196be8..bcbb48fa3 100644 --- a/test/test_repository.py +++ b/test/test_repository.py @@ -302,6 +302,63 @@ def test_reset_mixed(self): 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 @@ def test_clone_bare_repository(self): 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 fb7ca58f9..4d941e5c3 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' From 9955b3e67add52465d7201eccf8a7c973f7d5420 Mon Sep 17 00:00:00 2001 From: Victor Garcia Date: Wed, 4 Dec 2013 18:23:13 +0100 Subject: [PATCH 0050/1630] implementing merge with default options --- test/data/testrepoformerging.tar | Bin 0 -> 92160 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 test/data/testrepoformerging.tar diff --git a/test/data/testrepoformerging.tar b/test/data/testrepoformerging.tar new file mode 100644 index 0000000000000000000000000000000000000000..c2b145803d58e7d03505b6b7bbfc8c542ef366ad GIT binary patch literal 92160 zcmeHQ2Yg%A)i+_)0s>{VlmfSk!FFQVZ)iG;Bo4uuki?9T!P6VI5-lV-u}K1@K-nn} zAhZoFWtL4DrIb;2Ll}Vqp@p*A1}LQLJwCqwx$jAq99w$w0@|lv6hFOp$GKV zzKovEr1WIMNTgzVsx=aCZKxRNkAP3W=c~XUG2jp6|D{LtsSqW}8}Lh#DETV{QT9oJ z3g#PVKzeGgJ)Kcfj8z!X#Il~AXYZAIo-Ow+`F*!eI;)m6=>D^f`ED&BBN5cGg9dPCljUl$E;*cS)| z{JKwMQ|noivNWQx=CE@A@APBYn8h0XJ}{C|lw@bUnuv{Kq7({)W zkffkj4VDFDNGK$HHy{s6>2bBqWxMM^p?XM`vWV=F>O^azxk-KdRQKs&UDv~5SrP&v zK~)SX;17okH5it>KCfT%%d+VA$#QuJ4S2J_T2!B)X^c#{NNYTi(wPy7>Mq+|OJN-| zaR`&L<~4$n8juAkEUE%RqMp|m?8;Mf*^^Z;xKlR-)^t=HJaTPnT>HsLXInX#I^0dpzM-*GUzcP9eUdSSOnP{ z5)9EF7JZ5k3W>6=g=IzX2NYRT{Q)B!4r*S}5ZPf7HIqoODN0I>C~QnPr7YDKC!!Jk zz}A=&i53t?7D8DDvJ@T|n`^l&x4UfjYn!{u0)Bw`y9+Xc%4S9o?SyKHxBdmv@-ErynZuSczr!((4~!1ml@8)DHf3bxm4cDuJf+1YK;K8w(R0>v#6 z+nr(tH+kNpCgMgE^KBXPxEPkgitfYOG~|N|K$i`y-{o-7563_tBuRcv(6NRogZnWw zNz4t+n#V0y02v#p#L~nPohfhwn9#ax!!YooK)}kyMWH)x^CY6ABkJoZP}KjM(v5*| z0+WBKr2oIb|Lezc)A9cf1xJ*of19po{o?{#^f%*wqTf&c|A0*XUnl-I_#9xD{Ov^-u?dqLcam{|H@>6hOb>6aq< zU+@MbGyW^evR^=+5VC**UPu3zkLmJj*wOt|hS^?X+HgpVIgdh*YM;^$>@Tu+KWyYioN^7LU4#6QL4-zRu2`S(eJB$51!67s*C{Et2$yUZ#?8#^C1@ctA* zfU`F1{OM5NYv$Pbnz zpDa4^Uk;A@TGo#2QyKg7zirv#e(mgXphx*H&k(Tf8ZHOIGTqaCCBxTN&#wHNs<>}p z68{vF{{WJ`#eDy-gaD2+|Mv~qW!56v*!c{C|7TG0KW%5YmE!^m@IY`{{u(jRoBV5f zT1`cgnMk6~VV~TnXnrX+|BE8_f8@{qhyu!fNB(mN9K?&3cpN#f8uG4_iF5?nmz|@T zu3;xerY({-(v@&x9sMwfL>k1Vdz1f2+(>+#`~PGo{y&IR?`paz z{q+tt+OFwcrTcbOO8+D1(_Q}u5C(GWzrLZntXd-&wg|Ck8ae$@JMN2PWi#ZKyUIt zK&~GScq#c8c>Z7h`7b^k0prO3;8DG+Wr%en)^W)5Hf7rp(JtKlFI@E||A{c!YW>Um z&f<^czhwS@e*9PT3pfVK$^XtGa1j5NqQ9p1-wR-y{@MIL7=#r6bMpTOk?>a2Hs~J; z3$jns1Cmz=>mp(_K~~rG3 zMKC&Wk2H&1>_zhm*O z`y>uLU%TwpfBoykJv=SJe?9-%=d;vnCu^VFG0Gji@{;HNu+@jLn3)!CCDJ^0d3_WB}v(yj+DJ>RFPo%J`JFn3Kl)H=N7 z_3KXh;Xa2we0lWwL)M+vaqaJ(z5ZO`^Rq6w{oNz<(!ve^}kPj9BTge`ETud z#RU_SwLg95wM!4%aqflDOHR7}cZphKarntSs_Oo>{JyC_ed)D-H@>{7V#@y=@q;lt ze6`}u?~Pge{6)XM;NcaYk9qpdP2XMH5IR39-}hqUlE*$;ctPmHKi$>&%)3{x6Q4cy zColY??S-E&Iw#t)`#C$)Fs8Zg6LzooIP~E2J8as2#_H{w=KtZz*sOQXz4M7vzdYp9 zLlT?zy7R&hUG=RO*6tubeZ@2L(x+bFKJ=`sC*LsrfdkhHjSZ_mJZJi@vS-Ym7vFg6 zPODQ>SAR789M81>+#=Vl{^+dsjmZ()j-5K*lf3HXrVT%QW%Iw^fAxEtZ+Q35f1ZEp ztGBwIN>*I;`-gwNao%5l)3p09_es@$@pePwyx+X`eC#J;bkyHp(VK>f4}7oPXXOQY)M@BiZd+cZtM>C~4m{^JREzk1hWPwn`j zcEPx<8h4vrap9N)CQV&E;kCowyyc=h&pLDD;`^3dyyuB`y>i=+)WN zUi{f=edOL}?0m=8d%bfu;bRryW0wPgkImaQU$MG*Yj=Iq6W3LLe|7D3A51yzCois- zJ4B%4r4zTmq3-H?p1#q0epCGHv)84X9{u5L*S|)bGxryNKY87wH@$wuk0)=l*ZFsC z_}gyB&RulJ$YdzFqzLLBSOp<}4YvYQ@19{Co!h zdgcVzFmcC%Mbie~oYL-nZ$AMVG}Ma6K~hte^bf`oJgR9>>3Y%y!1LS4}+i z+^UX!h8a(vcUrJ*cm0xkS8q7E`So3%c=(xB=@VCOzxVCW47;Q1hAYP%-8pj6!ozn9 zy>!QzS)*Pb`?D|Coin2D#wnToMz_dc-nQ)d=jyln(^<>6-gt3n-Zk5Ftd&he=!t0? z>K>lH;gN=^`Y!c5fB5`un_Jh+Y*yd@)o&x$&D-zR;d@P-tCKPG=J|8a6Y6@jhxV`B z=W&0>+Nb_9s=ndnVHMkLz13ZR7}l}&k%J$5_~FBAHZ-hner5f-4b5}cc^;eg@M}}o zZ+K*VgJ<@-=9YDjZJ5@)X;_80|L~Lh3epkN#_aLOyH@;amVEz$Rqelg^YhdHxcu2+ z72j*z=Z!IiFbu2MH?>Xc<$%l@`Z@yeS+W9;xH`G+whur4^+QLTvF@E^hd2Fo=Hy$C z*}CGB$A|yC$MPMw{R(4D(p3H-b&Hn@W z*$Ryf=9l2L``sF4yMMRAy@C2}i?;gn-46A8`fiW=`QPv0H+?I6_)=W^zwx^R#?{+* zM?Cvq{O*Kt57c)!+T!2cmbJ{h`-)Fbugjcp;+^v@IC6mk7KmAEvdf4yo zS?yW1$TfBNh5nP8Ha~ma)k4h<2RyOPg^WIHu*IZG5*p9>RlrMPw`qjP>b$@FdfAr$352)7{JTr0j zrW3}jn)leUAOELvWukJ<@yYK*-ha{)YI$Jx!P?#D-1N80*?luk{``v*76gwx{H>>V z-){Z9pYCw&=4alht-9>?iq~FgZjSFa?eI16L*KY`;=V8Z>-bT#esF1naOTZxhP`vn zwO_7o-~Ex!^LKyx4}0GnnvcJ&_H{LN!rH)&;flldlcydv z>t6q+i|-k6=;tTg+tje|ik-zZznB}?xAV2B*Q`12^5d(Zaukz4w`s)nhfs(A#CIjs z4&-#3Gw|#w+`k0hBvjd%xVISBJNIPNJbm&Nk%bNMJ%78$rBiP2tlanCpFepH>JP}L z)ch|?{q_D-(7Eq6;y>p3FMgZ_Ac%fR#{PfD|34rkF4~9<`pbr*3$o$$3w~V*;rv$3 z2!?S~aab}`B@puIes9nqX7BA&!v4cayZ+qyKb-!O9Pm5#UvKCf(EA-*Oka{sZ*CMGjHQPqpx{y;TJpH@zTogEldon zc!P-#4d57@ruvS7Qu40_hEV_G{eS%Y577@tv}6DGovQY0+MqvP_5!0{@%h4vX7~(E zmh^yO;GX0|;t!H{y`fNmoQS=BO6vc-{9p6@7uo0N|K6}Uu=lh2KcH`^{$F2o_ajX= zAN|mj&WHD!ztvO0#VgmYx%$9T>VK<~L;HL=us}Mk4H^bY$-f>NLj9k$|DgZr{0GPX zGiYSbHEe_a_Fj3BYjMkqQu-e&zi#%w*UA6c5{-j$r(-LmhAiG%+-2LlHrCz}I4S<+ zw)b|twzu}ux${;%x^6e`P3bRR-|@&j#{725rn^4&U;dZ3yxZUW!S!b=yG5_se~;Fw ztDZdeJ1w6NyYTz!eZN?|$9V52w?FW}Mz!gh)32JK-!NhSM|Zh(FK? z@!~08EbgrTd)vPbdQaZz*!R_!ez556i=O-FoR5z?=eH+red_QN$80_9)LXA#|L|8I zKG=|W^F;l%e_lNLjPaSProB5TV|N-Yo&OgaGW$R8{CD#IIQ##Hggr~?f6!l+!+sqm zzdtOiVL`#$Dt$q};-&YLhNYm0SHgsYVXX+XrS!k(>wf+}P6TlL|3xr5aF4R~zgIB2 zZ2@@%Z4Cp~pVhes1$Yi)YRpcf^ykPo8x5b$|NG`_R0m zpZ@6%_kBx8UVYZySJyP$y7u06$L;g}nv?E4U)GP`{P|t~-tgX48^#>BRmB4@2^6k( zKHnq^l#+j+cc|?DLj5m0?|&Z>HnBl}d+(|(0&OY%FVXog1@C`>{>S|9oc~t@sRQ(= zW2?(oi8EpUS6ubw>HirqbKy}0_2-48u!)vE0`KX*p3_IFP{c;I`>o9;d^@YI=C zeDHqfKD!uS34ZyA)R|L_PcQQB`Qfh8W7E$S_S|^SZ^P^7y>Ml8<91b}YTt8jKCM0=RSOX`1a|L4(Pl6`m$oTLAX;B=rKk^g7L z;`@bN4!ZfHT4TSOzjS=C{pFc8duPU9aL`%1J}*uD@Y8E1&pi8??RUQW&u6qhKXv=d zlK-Bu&AE@9KIw_*qxaSRa;>rZn)i=x+ot(9m+mru?eO?b%h+k&&+l1yWTLtHA?>X9 zBZpqLb*F3ilXE`U?W+%$$KII}-Y@;nJ$JbOwZD9F-joGrtoSNEf8)=NxUJ!dn{Rt= z@zdK~KXsIEr^blfqIn9|G!DAd3asj zVKo~befZFtn)MBjY?#?{@Z5tR-!Scwho{%SGOXgA$veI{P@OvMd}ADX6PkrT<*EW6d)km+q`qDdp37qoTn`=Im@89po7fl_KP|yIP^;^?a zDftf!S^j5s|F6jR|2q4>hK7Y~&_5Jb6a!nn3>@($E0UyRLxABmB-K!5zZ#VBvL>Gq zECOyR{g1^#F8=GsUNG4Ie&_t}A_yJ8M_Kz{IvNLmwwPy9O|5IGnd*6D1MLPr@%2hN zAI|ywmtvrl{HsH{|2O;oN5AaEe}{(QY|!7QNs_FEf{LO91Cl?Clb>Z@NHHW`3JAI` zdxMH5_2>Kt?E1^v|K|HYq5lP(|1l&+Upu|Kh_MO-uOKRh@>0)>)%^xP%jJKH+Ljx@ zdOY`VLd*UwSG+-o9?pGy;fJ@r{Lk%2UHMdL0SzxJuM#%Eh!T{Xg2UA^D6ZCBm* zqn9?mbo>ugkNf_Y7JOG3y~i^T?bLGKnt6{Ot^H}$r_(0Bxc=uijsNJ&7e8qI^y$`p z|5OqDZ2Y8eFslA5K`SNy@{r~KTjxKB=J`KP{{PUhn+^IKYS1UEim3Vm{-7XeAsqUn z={`db$~XlmsB40bvw(_#Thji=WS=|#f#^@?|2Y2tA{ZUGN0$A6qgFHPvF67&%xL-7 zunNzy+wL-OJvc3WlQB?A{)Hj)|B(Kdx#O44fAcx}zlMZuY|tOMTYgzF1iy;6GI%w= zqyz+2(?r>)_`^y-lfqt6>zDrrFZ<2<|H<(W{SOC_WB(5Y17!U_-aP-$unPa!ZEqfm z-kmo0X|HpfD+5Vs8O{W~Wa$Rln?N_uUKf2`K zH!ZmJxmAISr%JQasrT+Z{eqD{d@uIpmsg4#-O?8??D5^-&p9;q_ljYUcupRYfjOPn z!a#5G-OF*=1H5@uP-tB;Js2bB%C~U=!j@@8E=HqySi2O+}I!mPxRs zdMaXcvSn>bhDFj$2`Ae5{jknV2Fd7JJzAKgM5DUK!kx_B8p&AC+-zAS)5hWnrleZi zV|qN3u4im+n{M^emS~S^sH}IOid8i3IaW_47@{+ocJyat^b|Tyw@2cwOo=lknM@^; zsfd!%Sxir-l~$ca475QlDCmHIPG=HHmO(cb5A!k3O)x#Kgrhp1U?eooI=lfpuEeZ6 zMueUvn7fPn-SsY4#9%a{l)luHR8nc3c{~!=LT}H$OWHcMBGttfwLL&lg63ak_xJOB= zkqD!a(zw$st%I6`j-?84%8`=*1nqjgi&sJ&US(`mjY-zH5ozVPOV<|^sHJWvr4s2h zOGcH9kx0cDD8*kU%`$pi<8UlZM6{mZ7>%;}Qt$^F0Rm&pNF`z{taGAk8W;~I~7lZ+-n9C~CZDqVn2@eA#MN#Yqkq?8f|mr%S9>|s75N1f3ztn zR0E4CfDlloO*YI#q8d;D(jUd#RRsfevmcFRZbFfpEno*6K!lx{05@j4X6@jSgu%jS zd5KOvrV{OFT>)O12@3EHA#2l9dL65YWUABD2#p`(JElF7(zQAU46_X(nj(u1cC2FlbJ1&Y>QUs;F%!;qegDcoOkwC$8z42b=`#jh(GB$nEq6yQdkDWxL_Gqm7DCRkC!F-{*)@_N1uC_teCZ|$~R3n>^$q@H*Cea92 zh0MyR<9fHNk}8@1GxuELTNQGnDui-y8~8VthB_!<{$+{O600V2b0w416PVIiBF+-P z0nltQY}>L~ruQg}AE@DOiy!2fagAd0kGwNL-UuDj$-;Uy??LX*?XlGK%sBL|<0ZZ6_f?w>u|5NnCCE(=Pqh$p50(Bne&WBZWVAnTdYvka&eTq>Ol8OSL4^9V~fkbX*YXf z>DIo=KCy5K`4{mf=`GiPPW)#Gq`#uVuKb&Z0hx*>Pq1jBHBX?6uKN;kJ-6_yNOQBfO~JaYs}E~ox8+)~2W^2S%3CY;s4dWnwN}hqpOUdhD<^aLp=;Jc z!$L%M^-1HfRGaM;W|w@Ch8e+H;&cb=AC85vPRlh~w{cT=4lHzC^CVzyL(N7bz^b(x z%HjzvG;wQ^FXdp`E2fI20Wh52&mx+xkjdYw$MuvFtz%(Y#RK8cT3FO(ojoM*Xp5_7 z)Qu%zreftys{`~1XAsxmb7oDnmV;O{GSY$*TIL=+Yxbh%rpYtsvK3fVVyzHo)#-+X z^K0ryj+J>TzSxkgu0?OH0D*aqNi+AtDm3keW)0Zib~BRyNdQ^}>RZjjY=07>k!Q)?FA! zdpvRs^QhSo>8!e8;Zdw|!AOz*Z_u!oAvl*?3zBgMNUW(i9F^SDA-Yz64H~~ttrXY) zG?}IJu#(mXPyfU7hr=EV{eMCLM*=$bUm>Xm;rXsCv8CIj#G6~1F)Y1Pz zHWf0EV3XLvN=%6-at0iCk(mtQ&Z&!3dz>vsS~b?pa9-%9BQRUY^1kpzFN8P(zsTZ5 zxQ_c>X#H#Xe{!<``D3^f2(89xp$yeW!b6v|h({abBFOU%s3N}R=QB=% zv^li^od<^kJQwLkKBg)WYhd1Ydom3NoD$`fY)@jvvgZx8Tlvq6p+J9 zA{kMwIsl2!n&d@LqJUQG1V}MG)oLp0>bTyKskRE6SZviZ9Ca3em;iLsYr-cu0xd$m zw`Jsz1-R+mNI7h4dd75IaW5=+MpZxuHP3r+qlzmJfbruSM%KD2M~`;VpW$;&xD7(op{z{T?-x7 zm}zoCPk_Pf!+>YIGmp7UD95{i^F@*PBJ<~1PAzhggF7-14X|w{(>WFvAh%Z*n#FP; z9i8I~_zm4v1#K1h2Wd{V=taJ_o`D$+q{4p)@2Vb4EG0i3cfxU>m&GJ1XKc>wc$~9; zKr6J0_(T%s6U-aSd!8LTT(YUWp%a6T2YCz_2;6Z8NhNt9_Jf}=$9H5Xk&|>0Rahi; zLrxAC3$uJ!wcHY+m1Jtb_BcAPy3|W`mizQjy-Vuy;4rP9t{V_I=pzIxEcA1Fck2x{ z8NcnexbtXNWwz~n%9&;0JLc_iFD>gvx~v9iPkJ&5k4B~%SIj17z*A}2M&%K#(u}QG z;_9jd0nIzsn1}-1hO{Mf<{>bv8!*nCgI$j=Wl231Wgb|7fHT&~Or$`cRZW;Z<*=iK z1*5I|FrdlC9GNXa3mener?muE)p-oX$)45fjq~GftC^bp^T4k~gE1vZW@cq&!@`=Q z1W!ou7z;+#vIZ(&S%cYY>^LUY(yx_>Y#?4x(=b2YP-lTBF5r(SqCl|g@RGBdT$A0} zguz~0Wj0)Sl-Y8X#K+fT+tPyCS`#$zMa_@6DoV4gL=*a#^A0`ZfTfjIQR1PRD&Q*n zyOIZM*qpgdlV{In^n{PBvJ!z4X#TDnD`~)mrQPO;(1EzXLSZEp!A4!chv-pDm6p(btM&zklw~jq%6_XurX1Y zjHxaQih58XjkL0bVM%zQ`C8!(V2msi;ZrhQW`X102-L$r@D7{~sR&0xTvsU$o$BPX ziZy~5^-M$2VfXED_Em z{xOF{J(@E)jg_O7?93QhLP?DRAWJ&oz|=$*(E*;0qqh0&8c( z)PU3^W_A<^qeI)7(POS|jP?{!oYDkH%ijgwuS9H}B|IKa$*(1) zyha1D9X{E=TU=8qdZ?gf`i9vADpObig{Vw^BR!O%Tli#4s%S0uWfRSZ^qjH;=#wp2 z*zA1sAw9PW=9dd|5GV06*^*Yd{9-*d*}$=m`Xv#VEt8u9TrSsyIt+p4uStBRmp`fK zgqI{^}p1P{1rjYx-?nL&(0UIa_21ua~__RbqNcmY= zCD6Mn929@uKsQ>#|C>l>;6_mO$kKjzfJ?>y1UmmMKmS+4MnEV2H_)W+vAsh7?}VHU zXnJ$4{7+16d}eba=`P0)Zl0-T#ey*9BVV%Msa2P>5FEm00;k1FNT5VM6A7rIs(O+p zX@#Fd4Tlry5Sp&LGW@y}V0)7M1(sSw!%3`osBSi?6s9ifBF(Oq{KDyshTr@aR4@_| zagU8jL^UX!acm6M0_Fwu7n!u<@c1AMG&jYfw4!)@vLyO7F)Smml3s#mgo0k2cc}RM zA+KN4RL!qQ0`murfD$&mI-W~XPzVHrA;TwyRlzIkswRu!K*$J+UQ(7>Pp7G2xZgRA zR8JVgFrLF!8UfGyQ!H0-9j&n13u1#@mvNsO5C+J*C!mo&FQ5k$`iOcnTQCjtUT@G6 zY6E37V9BI66n36p(W6uD#@8OF>XCV!oBhT>as6)!Tz_JJ1YimM9}u>T|4E{=|Eu4$ zwbOtt{Z9s8G=Xv7**!FO2HzFJCP8^LWC^dN)fSc8vyb(zO@Y?r>+}G8}d{T zXe7}C&5W2AE})ss2&3&h3X@NfLqVBklXx;LE?~FggA1OSW#*~p4FC@)$piC9IFaD% zcw}~Az1F7l#a>=XmZ708iy&VbYjSP|W7^?<6=x;J4*ur2aAG?AFRBA0$wRXQ!n3_!Ch#P zM7%z4F%&Q{G4-SOG@+=1)3i3G#!ApK%u}=Mu$Vj95G0;1x#oWh%rZR z=GOPl8el#Av-pXa7uCSA;NTd->>AHnq0l7Bh#5vTtruJ)Xk!{MxR%}7igVyr#vTEl ztYz+6;L#Z?LlGMlMiZhC>M?U_nmBF9JmX87thGmeVHBgaS`LhQHwALC6|q~PE4kkE z;+T=ObUS4W^JE~TMDota{41=?ssuI$!-5gk-5NtCjoD3CqB?rQo&gFET9|M%Pr|%m z*6#XkVq}Y9w$SdN#v;9L*=d#zlvZ!$7i0cm0nshA z$dkhmNE45t=u9?;Hm|(9HXEkTtH^`(?v^-71SKfIQsujV5NTV%+^Ax4_Q;VV*&*h( zqkI_5%^6sDnEDuQN~ruo>gR{@xh(R%7n#nao`!US8LLb#BgrMh2L5OIL+$`L>l7|D zP!^k?)2$2l1jD1S&*hQ9a3*aU^ zxv9(|tFf_F%dM*sBf8q4h#Z$_Ydck*tQ731$|@>^gZcg-BIsP`r0%^Uwpq|C=wP~i zx@Kupx#jVSsbcb5Zh`ZtntV|-$3fdD;3iL+EvV+pFdDq+JGa6QV8|rA#~Exz%C%)G ztV)VJR}v2u;LRkDi8qTa&}cVQm=AEjBqEx~r%nceIVqPh;-XNZYZJuobj>GNPS^{e zM5;UAOhmpsb9ZD`TeESj+s??!YZf_D~Mf7KgqEPU}91JY{b_ zDxhRf@aFN`K*4KTQr&e*hW1JlVsbF&B&n-H4@2CtUOvm370MrLw^sW_B+4a&(wq?Q z0cq-ea_FA~tiyIh?(0{ap5C$8xJ|&BpD<7I}pkrB9g6Ih^ zVa*?wI_IX9m0x3Osch03&2$G@Q3n@Kfe;**kDc}Nx4lep{||Qr_S*?W22YXm9|Q@9 z{dDvHOM>J79}?EG>;FL_Cv6b1{PYF3H@A`aW_L;&8i73+u`sqBEAcc1`VkD}+x=|& za`JeNHpS%jDVuDF-FGQ;sx0)^;HLykbj)^8t5kflGp3Re@cn&fIPP=|JnIJ>lcQvq>LTF zREr0C^7j0~EyI%{`4R{sX>vF6k@#UwbnuNOqe#r9`gyKFzPzy<)O^2^RS6EbG?I^0 z9WfYk7E!DP_mb@?7!{NfP3xp?`*+cE!46)YXiWX_JZ%H9Z!3k*Jc}l8qgfrQQb7xF zw2o7;Qk0)iuEk^8R(Vop?gr+uDGmls^@+9v0qBoTg|bo+*4 z(+lX_6bi$(sZ6@jO?%yt7fPA3W^S+vwgoGOk=Bs}KvAt+S6E=SXOOK$wfJdX)-gat zNsqPJ+LLmlqiiTFi^{r!4S<9sUf-Ux2%Ul{j4+47S`!JN4Ewxbsan2NsSW`dU<7G+9MN^PJkhX+Z6uNU zu(AX@q~UwS7U_l&3_CS&F@lX~7|}qk=7xni0yZGgnc+5_kOgd8o0Q=l0i1O!=+Goe zc12$ljO@KHV6vJQK`C>ug&TV%SUuw#w=DV=9`lqV^wF*nl_pex4*tF>&Ud3mv9V*B zkOvTBBWqdShPbbDpZI|2$p7X>O3eS1(Pb%$z8?TtV*VE-?-u!gVj$qy|3krd_U3=m ziBNmiMrt$YkL0v=WbdWZ$bf}PF}Z|F#S}U-uMdlUvk98}8tEuK%HnsH_=C&@y~+Qi zSuL>FFg_j8U8;y|J#o@!9nnG{=3&%|Mw5) zc57Q=3%eLW&866t2crgdAh#MF;j@dyZ*RHYfBdw$=4 zo;HS^bfMXv^M8La(3||z91v;kZ|i3LQt~egIRBHs|HBJK;UoF?`JD4V2L|~iS~-fg ze=MMErEDr4?UYe6h_R)Sw1+b%1UgLvM@3*eIDS!9i>oqBp}?_pf;63I4o4vT>Bv7Y zLv-nEv7^kK4dzbZOsPoRjc(d;e8yPH6l!ds%_nW`;d)pg4bey#zodp_C;cH!f=+d8 z=0o98JbBE@&S~U7vbi8;yA2~9L!|_jdw<-U{Bu0_UahPrB>%gBrHGi;n!4h1PFPVH@&K=Z*6dLi;)>`||IT1ibhU z@-M<9aPt59itsY(>7hW_s~Vb5!~0yrUK~)2Q!ET9;7}l>Dqh{EiE2O%1|0b>1F7Gl zqBi8;6n>-uSh+-HkIuIIXXAf3{Q<9mcI3ZoOqX9l3`=1}_xThxGx;u_gcc=YJtfKz989{lK{Ws!~|@1;h}r zuX&B2qy}U`3X7@`k|iyu`wT(xDuNygI`VHHr{AWMMdhD1z9U-O-w(-udH#RD6oC9Y z{{Q}BzI6Q_=-(B1FJ0YMiPHLi%lHqrggW-0E$sHM5GOVcX-eS*+--QTFpJ?}`GhN+ zh%cri%5h{!47&n!NB#E?&EMGCJ;?u-i9G#;zNGwT^FOfuv+}=uq7(n=C)Vv%A6cJX zmUN`s9Hv4h9?yI2iZ_Vc`D(C517i literal 0 HcmV?d00001 From 1f2fdca2c145179085933ca180f44bd3774f6fd8 Mon Sep 17 00:00:00 2001 From: Victor Garcia Date: Wed, 4 Dec 2013 20:04:02 +0100 Subject: [PATCH 0051/1630] implementing merge with default options - new test --- test/test_repository.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/test_repository.py b/test/test_repository.py index bcbb48fa3..c44c3df31 100644 --- a/test/test_repository.py +++ b/test/test_repository.py @@ -304,6 +304,9 @@ def test_reset_mixed(self): 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 From 6855a599e4faa534e0ce24215519d6099c744b57 Mon Sep 17 00:00:00 2001 From: Victor Garcia Date: Wed, 4 Dec 2013 23:49:26 +0100 Subject: [PATCH 0052/1630] implementing merge with default options - minor change --- src/mergeresult.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mergeresult.c b/src/mergeresult.c index c0ffddd6e..dfad78bc4 100644 --- a/src/mergeresult.c +++ b/src/mergeresult.c @@ -90,7 +90,7 @@ PyDoc_STRVAR(MergeResult_fastforward_oid__doc__, "Fastforward Oid"); PyObject * MergeResult_fastforward_oid__get__(MergeResult *self) { - if (self->is_fastforward == 1) + if (self->is_fastforward) { Py_INCREF(self->fastforward_oid); return self->fastforward_oid; @@ -123,7 +123,7 @@ PyTypeObject MergeResultType = { "_pygit2.MergeResult", /* tp_name */ sizeof(MergeResult), /* tp_basicsize */ 0, /* tp_itemsize */ - 0, /* tp_dealloc */ + 0, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ From 749810ac77601c8b2010ed0c2063be3880ceaf7f Mon Sep 17 00:00:00 2001 From: Adam Spiers Date: Mon, 9 Dec 2013 11:42:13 +0000 Subject: [PATCH 0053/1630] fix blame argument handling The argument handling for the new Repository_blame had several problems: - The call to PyArg_ParseTupleAndKeywords was missing &path, so none of the optional arguments got parsed correctly. - newest_commit and oldest_commit were missing type validation against OidType. - The opts structure was discarded rather than passed to git_blame_file. This commit fixes these issues and adds a test case. --- src/repository.c | 10 ++++++---- test/test_blame.py | 27 +++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/src/repository.c b/src/repository.c index 20e2a7c50..60a687443 100644 --- a/src/repository.c +++ b/src/repository.c @@ -46,6 +46,7 @@ extern PyTypeObject IndexType; extern PyTypeObject WalkerType; extern PyTypeObject SignatureType; extern PyTypeObject ObjectType; +extern PyTypeObject OidType; extern PyTypeObject CommitType; extern PyTypeObject TreeType; extern PyTypeObject TreeBuilderType; @@ -1477,13 +1478,14 @@ Repository_blame(Repository *self, PyObject *args, PyObject *kwds) PyObject *value1 = NULL; PyObject *value2 = NULL; int err; - char *keywords[] = {"flags", "min_match_characters", "newest_commit", + char *keywords[] = {"path", "flags", "min_match_characters", "newest_commit", "oldest_commit", "min_line", "max_line", NULL}; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "s|IHOOII", keywords, + if (!PyArg_ParseTupleAndKeywords(args, kwds, "s|IHO!O!II", keywords, &path, &opts.flags, &opts.min_match_characters, - &value1, &value2, + &OidType, &value1, + &OidType, &value2, &opts.min_line, &opts.max_line)) return NULL; @@ -1498,7 +1500,7 @@ Repository_blame(Repository *self, PyObject *args, PyObject *kwds) return NULL; } - err = git_blame_file(&blame, self->repo, path, NULL); + err = git_blame_file(&blame, self->repo, path, &opts); if (err < 0) return Error_set(err); diff --git a/test/test_blame.py b/test/test_blame.py index 9c04e799b..39dcd863f 100644 --- a/test/test_blame.py +++ b/test/test_blame.py @@ -111,5 +111,32 @@ def test(): self.assertRaises(IndexError, test) + def test_blame_newest(self): + repo = self.repo + + revs = [ + ( 'master^2', 3 ), + ( 'master^2^', 2 ), + ( 'master^2^^', 1 ), + ] + + for rev, num_commits in revs: + commit = repo.revparse_single(rev) + blame = repo.blame(PATH, newest_commit=commit.oid) + + self.assertEqual(len(blame), num_commits) + + for i, hunk in enumerate(tuple(blame)[:num_commits]): + self.assertEqual(hunk.lines_in_hunk, 1) + self.assertEqual(HUNKS[i][0], hunk.final_commit_id) + self.assertEqual(HUNKS[i][1], hunk.final_start_line_number) + self.assertEqualSignature(HUNKS[i][2], hunk.final_committer) + self.assertEqual(hunk.orig_commit_id, + '0000000000000000000000000000000000000000') + self.assertEqual(hunk.orig_path, PATH) + self.assertEqual(HUNKS[i][1], hunk.orig_start_line_number) + self.assertTrue(hunk.orig_committer is None) + self.assertEqual(HUNKS[i][3], hunk.boundary) + if __name__ == '__main__': unittest.main() From 3cd8fed386a3cde6612abc1bd9280991912333a5 Mon Sep 17 00:00:00 2001 From: Victor Garcia Date: Mon, 9 Dec 2013 12:45:51 +0100 Subject: [PATCH 0054/1630] implementing merge - some fixes for the pull request: MergeRestul typedef, pep7 fixes and some error checks --- src/repository.c | 6 ++---- src/types.h | 9 +-------- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/src/repository.c b/src/repository.c index 3d4889c65..81396f9ae 100644 --- a/src/repository.c +++ b/src/repository.c @@ -613,9 +613,8 @@ Repository_merge(Repository *self, PyObject *py_oid) 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); + if (err < 0) { + git_merge_head_free(oid_merge_head); goto error; } @@ -627,7 +626,6 @@ Repository_merge(Repository *self, PyObject *py_oid) return py_merge_result; error: - git_merge_head_free(oid_merge_head); return Error_set(err); } diff --git a/src/types.h b/src/types.h index 06504fd36..e69226b73 100644 --- a/src/types.h +++ b/src/types.h @@ -218,13 +218,6 @@ typedef struct { } BlameHunk; /* git_merge */ -typedef struct { - PyObject_HEAD - int is_uptodate; - int is_fastforward; - PyObject* fastforward_oid; - PyObject* status; - -} MergeResult; +SIMPLE_TYPE(MergeResult, git_merge_result, result) #endif From 7d9d2667e59a81514ae93192bc301374f8151fc1 Mon Sep 17 00:00:00 2001 From: Victor Garcia Date: Mon, 9 Dec 2013 15:36:10 +0100 Subject: [PATCH 0055/1630] implementing merge: merge.rst doc and implementing MergeResult as a simple type that maps git_merge_result to it --- docs/merge.rst | 33 +++++++++++++++++++++++ src/mergeresult.c | 60 +++++++++++++++++++++-------------------- src/repository.c | 1 - test/test_repository.py | 26 ++++++++++++------ 4 files changed, 82 insertions(+), 38 deletions(-) diff --git a/docs/merge.rst b/docs/merge.rst index 55e411ac1..f2ee278af 100644 --- a/docs/merge.rst +++ b/docs/merge.rst @@ -2,4 +2,37 @@ 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 this 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. +- index: represents the repository index after the merge diff --git a/src/mergeresult.c b/src/mergeresult.c index dfad78bc4..1493d6a95 100644 --- a/src/mergeresult.c +++ b/src/mergeresult.c @@ -39,26 +39,14 @@ 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); + if (!py_merge_result) + return NULL; - 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); + py_merge_result->result = merge_result; + py_merge_result->repo = repo; return (PyObject*) py_merge_result; } @@ -68,7 +56,7 @@ PyDoc_STRVAR(MergeResult_is_uptodate__doc__, "Is up to date"); PyObject * MergeResult_is_uptodate__get__(MergeResult *self) { - if (self->is_uptodate) + if (git_merge_result_is_uptodate(self->result)) Py_RETURN_TRUE; else Py_RETURN_FALSE; @@ -79,7 +67,7 @@ PyDoc_STRVAR(MergeResult_is_fastforward__doc__, "Is fastforward"); PyObject * MergeResult_is_fastforward__get__(MergeResult *self) { - if (self->is_fastforward) + if (git_merge_result_is_fastforward(self->result)) Py_RETURN_TRUE; else Py_RETURN_FALSE; @@ -90,29 +78,43 @@ PyDoc_STRVAR(MergeResult_fastforward_oid__doc__, "Fastforward Oid"); PyObject * MergeResult_fastforward_oid__get__(MergeResult *self) { - if (self->is_fastforward) - { - Py_INCREF(self->fastforward_oid); - return self->fastforward_oid; + 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; + else Py_RETURN_NONE; } -PyDoc_STRVAR(MergeResult_status__doc__, "Merge repository status"); +PyDoc_STRVAR(MergeResult_index__doc__, "Merge repository index"); PyObject * -MergeResult_status__get__(MergeResult *self) +MergeResult_index__get__(MergeResult *self) { - Py_INCREF(self->status); - return self->status; + git_index *index; + Index *py_index; + int err; + + err = git_repository_index(&index, self->repo->repo); + if (err < 0) + return NULL; + + py_index = PyObject_GC_New(Index, &IndexType); + if (!py_index) { + return NULL; + } + + py_index->repo = self->repo; + py_index->index = index; + Py_INCREF(py_index); + return (PyObject*) py_index; } PyGetSetDef MergeResult_getseters[] = { GETTER(MergeResult, is_uptodate), GETTER(MergeResult, is_fastforward), GETTER(MergeResult, fastforward_oid), - GETTER(MergeResult, status), + GETTER(MergeResult, index), {NULL}, }; diff --git a/src/repository.c b/src/repository.c index 81396f9ae..f590507d8 100644 --- a/src/repository.c +++ b/src/repository.c @@ -621,7 +621,6 @@ Repository_merge(Repository *self, PyObject *py_oid) 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; diff --git a/test/test_repository.py b/test/test_repository.py index c44c3df31..0ba57663f 100644 --- a/test/test_repository.py +++ b/test/test_repository.py @@ -314,7 +314,7 @@ def test_merge_uptodate(self): self.assertTrue(merge_result.is_uptodate) self.assertFalse(merge_result.is_fastforward) self.assertEquals(None, merge_result.fastforward_oid) - self.assertEquals({}, merge_result.status) + self.assertEquals({}, self.repo.status()) def test_merge_fastforward(self): branch_head_hex = 'e97b4cfd5db0fb4ebabf4f203979ca4e5d1c7c87' @@ -325,7 +325,7 @@ def test_merge_fastforward(self): # 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) + self.assertEquals({}, self.repo.status()) def test_merge_no_fastforward_no_conflicts(self): branch_head_hex = '03490f16b15a09913edb3a067a3dc67fbb8d41f1' @@ -333,10 +333,15 @@ def test_merge_no_fastforward_no_conflicts(self): 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) + 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 + merge_result.index.remove('bye.txt') + merge_result.index.write() + self.assertEquals({'bye.txt': 128}, self.repo.status()) def test_merge_no_fastforward_conflicts(self): branch_head_hex = '1b2bae55ac95a4be3f8983b86cd579226d0eb247' @@ -344,10 +349,15 @@ def test_merge_no_fastforward_conflicts(self): 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) + 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 + merge_result.index.add('.gitignore') + merge_result.index.write() + self.assertEquals({'.gitignore': 2}, self.repo.status()) def test_merge_invalid_hex(self): branch_head_hex = '12345678' From 1cf6e748e539fa3f510e39cbde28174431940855 Mon Sep 17 00:00:00 2001 From: Victor Garcia Date: Mon, 9 Dec 2013 15:51:29 +0100 Subject: [PATCH 0056/1630] implementing merge: some small fixes in merge.rst --- docs/merge.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/merge.rst b/docs/merge.rst index f2ee278af..ec04c09ff 100644 --- a/docs/merge.rst +++ b/docs/merge.rst @@ -28,11 +28,11 @@ Example:: The MergeResult object ====================== -Represents the result of a merge and contains this fields: +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 + 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. + forwarded Oid. - index: represents the repository index after the merge From b8e72fc2721f71605421761f09abe5fff788aed4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Tue, 10 Dec 2013 13:41:51 +0100 Subject: [PATCH 0057/1630] Issue#298: Fix compilation on Windows --- src/branch.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/branch.c b/src/branch.c index ae547fe30..483906b82 100644 --- a/src/branch.c +++ b/src/branch.c @@ -137,6 +137,7 @@ Branch_remote_name__get__(Branch *self) int err; const char *branch_name; char *c_name = NULL; + PyObject *py_name; CHECK_REFERENCE(self); @@ -160,7 +161,7 @@ Branch_remote_name__get__(Branch *self) return Error_set(err); } - PyObject *py_name = to_unicode(c_name, NULL, NULL); + py_name = to_unicode(c_name, NULL, NULL); free(c_name); return py_name; @@ -228,6 +229,7 @@ Branch_upstream_name__get__(Branch *self) int err; const char *branch_name; char *c_name = NULL; + PyObject *py_name; CHECK_REFERENCE(self); @@ -251,7 +253,7 @@ Branch_upstream_name__get__(Branch *self) return Error_set(err); } - PyObject *py_name = to_unicode(c_name, NULL, NULL); + py_name = to_unicode(c_name, NULL, NULL); free(c_name); return py_name; From 0556ab0cd8828d2876307d0cd889dcfa9797d39b Mon Sep 17 00:00:00 2001 From: Victor Garcia Date: Tue, 10 Dec 2013 16:01:48 +0100 Subject: [PATCH 0058/1630] implementing merge: removing index field from MergeResult --- src/mergeresult.c | 25 ------------------------- test/test_repository.py | 8 ++++---- 2 files changed, 4 insertions(+), 29 deletions(-) diff --git a/src/mergeresult.c b/src/mergeresult.c index 1493d6a95..1ce4497e0 100644 --- a/src/mergeresult.c +++ b/src/mergeresult.c @@ -86,35 +86,10 @@ MergeResult_fastforward_oid__get__(MergeResult *self) else Py_RETURN_NONE; } -PyDoc_STRVAR(MergeResult_index__doc__, "Merge repository index"); - -PyObject * -MergeResult_index__get__(MergeResult *self) -{ - git_index *index; - Index *py_index; - int err; - - err = git_repository_index(&index, self->repo->repo); - if (err < 0) - return NULL; - - py_index = PyObject_GC_New(Index, &IndexType); - if (!py_index) { - return NULL; - } - - py_index->repo = self->repo; - py_index->index = index; - Py_INCREF(py_index); - return (PyObject*) py_index; -} - PyGetSetDef MergeResult_getseters[] = { GETTER(MergeResult, is_uptodate), GETTER(MergeResult, is_fastforward), GETTER(MergeResult, fastforward_oid), - GETTER(MergeResult, index), {NULL}, }; diff --git a/test/test_repository.py b/test/test_repository.py index 0ba57663f..11ebc4de3 100644 --- a/test/test_repository.py +++ b/test/test_repository.py @@ -339,8 +339,8 @@ def test_merge_no_fastforward_no_conflicts(self): self.assertEquals({'bye.txt': 1}, self.repo.status()) self.assertEquals({'bye.txt': 1}, self.repo.status()) # Checking the index works as expected - merge_result.index.remove('bye.txt') - merge_result.index.write() + 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): @@ -355,8 +355,8 @@ def test_merge_no_fastforward_conflicts(self): self.assertEquals({'.gitignore': 132}, self.repo.status()) self.assertEquals({'.gitignore': 132}, self.repo.status()) # Checking the index works as expected - merge_result.index.add('.gitignore') - merge_result.index.write() + self.repo.index.add('.gitignore') + self.repo.index.write() self.assertEquals({'.gitignore': 2}, self.repo.status()) def test_merge_invalid_hex(self): From 1938147b3dfc1103ee88ea26af24441ff63019c3 Mon Sep 17 00:00:00 2001 From: Victor Garcia Date: Tue, 10 Dec 2013 16:02:37 +0100 Subject: [PATCH 0059/1630] implementing merge: removing index field from merge.rst --- docs/merge.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/merge.rst b/docs/merge.rst index ec04c09ff..82c243a7d 100644 --- a/docs/merge.rst +++ b/docs/merge.rst @@ -35,4 +35,3 @@ Represents the result of a merge and contains these fields: - 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. -- index: represents the repository index after the merge From 7e2cfd7a70def3ac824513ec2d9843dd2c9790a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Tue, 10 Dec 2013 21:00:44 +0100 Subject: [PATCH 0060/1630] Remove trailing whitespace --- test/test_repository.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_repository.py b/test/test_repository.py index 11ebc4de3..53dc00d15 100644 --- a/test/test_repository.py +++ b/test/test_repository.py @@ -341,7 +341,7 @@ def test_merge_no_fastforward_no_conflicts(self): # Checking the index works as expected self.repo.index.remove('bye.txt') self.repo.index.write() - self.assertEquals({'bye.txt': 128}, self.repo.status()) + self.assertEquals({'bye.txt': 128}, self.repo.status()) def test_merge_no_fastforward_conflicts(self): branch_head_hex = '1b2bae55ac95a4be3f8983b86cd579226d0eb247' @@ -357,7 +357,7 @@ def test_merge_no_fastforward_conflicts(self): # Checking the index works as expected self.repo.index.add('.gitignore') self.repo.index.write() - self.assertEquals({'.gitignore': 2}, self.repo.status()) + self.assertEquals({'.gitignore': 2}, self.repo.status()) def test_merge_invalid_hex(self): branch_head_hex = '12345678' From 937e2822a834a50160b8ca4e56ed4163bd803b31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Tue, 10 Dec 2013 21:05:52 +0100 Subject: [PATCH 0061/1630] merge: remove unused reference to the repository --- src/mergeresult.c | 3 +-- src/mergeresult.h | 2 +- src/repository.c | 15 ++++----------- src/types.h | 5 ++++- 4 files changed, 10 insertions(+), 15 deletions(-) diff --git a/src/mergeresult.c b/src/mergeresult.c index 1ce4497e0..31699c1ca 100644 --- a/src/mergeresult.c +++ b/src/mergeresult.c @@ -37,7 +37,7 @@ extern PyTypeObject MergeResultType; extern PyTypeObject IndexType; PyObject * -git_merge_result_to_python(git_merge_result *merge_result, Repository *repo) +git_merge_result_to_python(git_merge_result *merge_result) { MergeResult *py_merge_result; @@ -46,7 +46,6 @@ git_merge_result_to_python(git_merge_result *merge_result, Repository *repo) return NULL; py_merge_result->result = merge_result; - py_merge_result->repo = repo; return (PyObject*) py_merge_result; } diff --git a/src/mergeresult.h b/src/mergeresult.h index 7aadac4aa..770a4bd40 100644 --- a/src/mergeresult.h +++ b/src/mergeresult.h @@ -32,6 +32,6 @@ #include #include -PyObject* git_merge_result_to_python(git_merge_result *merge_result, Repository *repo); +PyObject* git_merge_result_to_python(git_merge_result *merge_result); #endif diff --git a/src/repository.c b/src/repository.c index 2b15993d2..7cc746b75 100644 --- a/src/repository.c +++ b/src/repository.c @@ -609,24 +609,17 @@ Repository_merge(Repository *self, PyObject *py_oid) err = git_merge_head_from_oid(&oid_merge_head, self->repo, &oid); if (err < 0) - goto error; + return Error_set(err); 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); + if (err < 0) + return Error_set(err); + py_merge_result = git_merge_result_to_python(merge_result); return py_merge_result; - -error: - return Error_set(err); } PyDoc_STRVAR(Repository_walk__doc__, diff --git a/src/types.h b/src/types.h index e69226b73..c86495992 100644 --- a/src/types.h +++ b/src/types.h @@ -218,6 +218,9 @@ typedef struct { } BlameHunk; /* git_merge */ -SIMPLE_TYPE(MergeResult, git_merge_result, result) +typedef struct { + PyObject_HEAD + git_merge_result *result; +} MergeResult; #endif From c6cdbba53dccc50ba801c24c73ebf2261eaa838e Mon Sep 17 00:00:00 2001 From: Eric Schrijver Date: Sat, 14 Dec 2013 16:06:40 +0100 Subject: [PATCH 0062/1630] =?UTF-8?q?Typo=20in=20README.rst:=20Blog=20?= =?UTF-8?q?=E2=86=92=20Blob?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 4345acb22..ed0a3b103 100644 --- a/README.rst +++ b/README.rst @@ -1,4 +1,3 @@ - ###################################################################### pygit2 - libgit2 bindings in Python ###################################################################### @@ -107,7 +106,7 @@ New API: - ``Reference.log_append(...)`` - ``Reference.shorthand`` - - ``Blog.is_binary`` + - ``Blob.is_binary`` - ``len(Diff)`` - ``Patch.additions`` - ``Patch.deletions`` From fafb4b90725bbd86ccb8780819f4679dff257db9 Mon Sep 17 00:00:00 2001 From: XTao Date: Mon, 16 Dec 2013 16:30:33 +0800 Subject: [PATCH 0063/1630] Fix no patch diff. --- src/diff.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/diff.c b/src/diff.c index b7bacd58d..75df6acd9 100644 --- a/src/diff.c +++ b/src/diff.c @@ -290,6 +290,8 @@ Diff_patch__get__(Diff *self) PyObject *py_patch = NULL; num = git_diff_num_deltas(self->list); + if (num == 0) + Py_RETURN_NONE; MALLOC(strings, num * sizeof(char*), cleanup); for (i = 0, len = 1; i < num ; ++i) { From 19b15ed928a26b4323d699dd1a048be30b626b57 Mon Sep 17 00:00:00 2001 From: Petr Hosek Date: Mon, 16 Dec 2013 11:14:06 +0000 Subject: [PATCH 0064/1630] Provide Walker simplify_first_parent method --- src/walker.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/walker.c b/src/walker.c index a02b2f45c..79cf389bf 100644 --- a/src/walker.c +++ b/src/walker.c @@ -122,6 +122,18 @@ Walker_reset(Walker *self) Py_RETURN_NONE; } +PyDoc_STRVAR(Walker_simplify_first_parent__doc__, + "simplify_first_parent()\n" + "\n" + "Simplify the history by first-parent."); + +PyObject * +Walker_simplify_first_parent(Walker *self) +{ + git_revwalk_simplify_first_parent(self->walk); + Py_RETURN_NONE; +} + PyObject * Walker_iter(Walker *self) { @@ -158,6 +170,7 @@ PyMethodDef Walker_methods[] = { METHOD(Walker, hide, METH_O), METHOD(Walker, push, METH_O), METHOD(Walker, reset, METH_NOARGS), + METHOD(Walker, simplify_first_parent, METH_NOARGS), METHOD(Walker, sort, METH_O), {NULL} }; From 3cc0662defac69964751bad7fa273157c32affcf Mon Sep 17 00:00:00 2001 From: Petr Hosek Date: Fri, 20 Dec 2013 13:28:20 +0000 Subject: [PATCH 0065/1630] Test case for Walker simplify_first_parent --- test/test_revwalk.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/test_revwalk.py b/test/test_revwalk.py index 119f22253..654ef47a8 100644 --- a/test/test_revwalk.py +++ b/test/test_revwalk.py @@ -102,6 +102,10 @@ def test_sort(self): walker.sort(GIT_SORT_TIME | GIT_SORT_REVERSE) self.assertEqual([x.hex for x in walker], list(reversed(log))) + def test_simplify_first_parent(self): + walker = self.repo.walk(log[0], GIT_SORT_TIME) + walker.simplify_first_parent() + self.assertEqual(len(list(walker)), 3) if __name__ == '__main__': unittest.main() From 46543a85d32f42de3f73555d5660478f7a13a3ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Fri, 20 Dec 2013 19:00:58 +0100 Subject: [PATCH 0066/1630] Docs: Fix and add missing stuff --- docs/remotes.rst | 8 ++++---- docs/repository.rst | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/remotes.rst b/docs/remotes.rst index 12a1755b2..3542f699b 100644 --- a/docs/remotes.rst +++ b/docs/remotes.rst @@ -13,10 +13,10 @@ The Remote type .. autoattribute:: pygit2.Remote.name .. autoattribute:: pygit2.Remote.url .. autoattribute:: pygit2.Remote.refspec_count -.. automethod:: pygit2.Remote.get_push_refspec -.. automethod:: pygit2.Remote.get_pull_refspec -.. automethod:: pygit2.Remote.set_push_refspec -.. automethod:: pygit2.Remote.set_pull_refspec +.. automethod:: pygit2.Remote.get_push_refspecs +.. automethod:: pygit2.Remote.get_fetch_refspecs +.. automethod:: pygit2.Remote.set_push_refspecs +.. automethod:: pygit2.Remote.set_fetch_refspecs .. automethod:: pygit2.Remote.get_refspec .. automethod:: pygit2.Remote.fetch .. automethod:: pygit2.Remote.push diff --git a/docs/repository.rst b/docs/repository.rst index 034ed5692..ca08f1f4d 100644 --- a/docs/repository.rst +++ b/docs/repository.rst @@ -61,3 +61,4 @@ Below there are some general attributes and methods: .. autoattribute:: pygit2.Repository.is_empty .. automethod:: pygit2.Repository.read .. automethod:: pygit2.Repository.write +.. automethod:: pygit2.Repository.reset From 9a5184ba35148b3e31f0b43e53fa4bde79812ab7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Mon, 23 Dec 2013 17:26:02 +0100 Subject: [PATCH 0067/1630] Add documentation for the Walker type --- docs/log.rst | 7 +++++++ src/pygit2.c | 1 + 2 files changed, 8 insertions(+) diff --git a/docs/log.rst b/docs/log.rst index c63bc41c6..440d54b8c 100644 --- a/docs/log.rst +++ b/docs/log.rst @@ -3,3 +3,10 @@ Commit log ********************************************************************** .. automethod:: pygit2.Repository.walk + + +.. automethod:: pygit2.Walker.hide +.. automethod:: pygit2.Walker.push +.. automethod:: pygit2.Walker.reset +.. automethod:: pygit2.Walker.sort +.. automethod:: pygit2.Walker.simplify_first_parent diff --git a/src/pygit2.c b/src/pygit2.c index e1766d26e..e640ea8e0 100644 --- a/src/pygit2.c +++ b/src/pygit2.c @@ -300,6 +300,7 @@ moduleinit(PyObject* m) * Log */ INIT_TYPE(WalkerType, NULL, PyType_GenericNew) + ADD_TYPE(m, Walker); ADD_CONSTANT_INT(m, GIT_SORT_NONE) ADD_CONSTANT_INT(m, GIT_SORT_TOPOLOGICAL) ADD_CONSTANT_INT(m, GIT_SORT_TIME) From 4bbc59c574869a6dc24276a61fa50b070cbbd5b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Tue, 24 Dec 2013 10:08:36 +0100 Subject: [PATCH 0068/1630] Update changelog --- README.rst | 88 ++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 66 insertions(+), 22 deletions(-) diff --git a/README.rst b/README.rst index ed0a3b103..bd2481384 100644 --- a/README.rst +++ b/README.rst @@ -77,40 +77,84 @@ Authors Changelog ============== +0.20.1 (201X-XX-XX) +------------------- + +- New remote ref-specs API: + `#290 `_ + +- New ``Repository.reset(...)``: + `#292 `_, + `#294 `_ + +- Export ``GIT_DIFF_MINIMAL``: + `#293 `_ + +- New ``Repository.merge(...)``: + `#295 `_ + +- Fix ``Repository.blame`` argument handling: + `#297 `_ + +- Fix build error on Windows: + `#298 `_ + +- Fix typo in the README file, Blog → Blob: + `#301 `_ + +- Now ``Diff.patch`` returns ``None`` if no patch: + `#232 `_, + `#303 `_ + +- New ``Walker.simplify_first_parent()``: + `#304 `_ + 0.20.0 (2013-11-24) ------------------- -API changes: +- Upgrade to libgit2 v0.20.0: + `#288 `_ + + Rename ``Repository.head_is_orphaned`` to ``Repository.head_is_unborn`` + + Prototype of ``pygit2.clone_repository(...)`` changed:: + + # Before + pygit2.clone_repository(url, path, bare=False, remote_name='origin', + push_url=None, fetch_spec=None, push_spec=None, + checkout_branch=None) + + # Now + pygit2.clone_repository(url, path, bare=False, ignore_cert_errors=False, + remote_name='origin', checkout_branch=None) -- Renamed ``Repository.head_is_orphaned`` to ``Repository.head_is_unborn`` +- New ``Patch.additions`` and ``Patch.deletions``: + `#275 `_ -- ``Repository.listall_references`` and ``Repository.listall_branches`` now - return a list, instead of a tuple +- New ``Patch.is_binary``: + `#276 `_ -- The prototype of ``clone_repository`` changed from:: +- New ``Reference.log_append(...)``: + `#277 `_ - # Before - pygit2.clone_repository(url, path, bare=False, remote_name='origin', - push_url=None, fetch_spec=None, push_spec=None, - checkout_branch=None) +- New ``Blob.is_binary``: + `#278 `_ - # Now - pygit2.clone_repository(url, path, bare=False, ignore_cert_errors=False, - remote_name='origin', checkout_branch=None) +- New ``len(Diff)`` shows the number of patches: + `#281 `_ -New API: +- Rewrite ``Repository.status()``: + `#283 `_ -- Added support for blame +- New ``Reference.shorthand``: + `#284 `_ -- New: +- New ``Repository.blame(...)``: + `#285 `_ - - ``Reference.log_append(...)`` - - ``Reference.shorthand`` - - ``Blob.is_binary`` - - ``len(Diff)`` - - ``Patch.additions`` - - ``Patch.deletions`` - - ``Patch.is_binary`` +- Now ``Repository.listall_references()`` and + ``Repository.listall_branches()`` return a list, not a tuple: + `#289 `_ License From d04823c3c87ae50f76842402ad04dc0cd1863029 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Tue, 24 Dec 2013 10:48:39 +0100 Subject: [PATCH 0069/1630] Get ready to release 0.20.1 --- .mailmap | 5 +++++ README.rst | 44 +++++++++++++++++++++++--------------------- docs/conf.py | 2 +- docs/install.rst | 2 +- pygit2/version.py | 2 +- 5 files changed, 31 insertions(+), 24 deletions(-) diff --git a/.mailmap b/.mailmap index 453de866a..710e4ef05 100644 --- a/.mailmap +++ b/.mailmap @@ -1,9 +1,14 @@ Carlos Martín Nieto Christian Boos +Gustavo Di Pietro J. David Ibáñez Martin Lenders +Óscar San José Richo Healey Xavier Delannoy Xu Tao Xu Tao + + + diff --git a/README.rst b/README.rst index bd2481384..5d116a7e0 100644 --- a/README.rst +++ b/README.rst @@ -44,40 +44,42 @@ for the topic), send a pull request. Authors ============== -52 developers have contributed at least 1 commit to pygit2:: +56 developers have contributed at least 1 commit to pygit2:: J. David Ibáñez Andrey Devyatkin Nico von Geyso Ben Davis - Carlos Martín Nieto Hervé Cauwelier - W. Trevor King Huang Huang - Dave Borowitz Jared Flatow - Daniel Rodríguez Troitiño Jiunn Haur Lim - Richo Healey Sarath Lakshman - Christian Boos Vicent Marti - Julien Miotte Zoran Zaric - Martin Lenders Andrew Chin + Carlos Martín Nieto Eric Schrijver + W. Trevor King Hervé Cauwelier + Dave Borowitz Huang Huang + Daniel Rodríguez Troitiño Jared Flatow + Richo Healey Jiunn Haur Lim + Christian Boos Sarath Lakshman + Julien Miotte Vicent Marti + Jose Plana Zoran Zaric + Martin Lenders Adam Spiers + Victor Garcia Andrew Chin Xavier Delannoy András Veres-Szentkirályi Yonggang Luo Benjamin Kircher - Valentin Haenel Benjamin Pollack - Xu Tao Bryan O'Sullivan - Bernardo Heynemann David Fischer - John Szakmeister David Sanders - Brodie Rao Eric Davis - Petr Hosek Eric Schrijver - David Versmisse Erik van Zijst - Rémi Duraffort Ferengee + Petr Hosek Benjamin Pollack + Valentin Haenel Bryan O'Sullivan + Xu Tao David Fischer + Bernardo Heynemann David Sanders + John Szakmeister Eric Davis + Brodie Rao Erik van Zijst + David Versmisse Ferengee + Rémi Duraffort Gustavo Di Pietro Sebastian Thiel Hugh Cole-Baker Fraser Tweedale Josh Bleecher Snyder Han-Wen Nienhuys Jun Omae - Petr Viktorin Ridge Kennedy - Alex Chamberlain Rui Abreu Ferreira - Amit Bakshi pistacchio + Petr Viktorin Óscar San José + Alex Chamberlain Ridge Kennedy + Amit Bakshi Rui Abreu Ferreira Changelog ============== -0.20.1 (201X-XX-XX) +0.20.1 (2013-12-24) ------------------- - New remote ref-specs API: diff --git a/docs/conf.py b/docs/conf.py index 92253aaf7..7ec185961 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -50,7 +50,7 @@ # The short X.Y version. version = '0.20' # The full version, including alpha/beta/rc tags. -release = '0.20.0' +release = '0.20.1' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/docs/install.rst b/docs/install.rst index 95cba2b9a..e237037a2 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -27,7 +27,7 @@ When those are installed, you can install pygit2: .. note:: A minor version of pygit2 must be used with the corresponding minor version of libgit2. For example, pygit2 v0.20.x must be used with libgit2 - v0.20.0. + v0.20.1. Building on \*nix (including OS X) =================================== diff --git a/pygit2/version.py b/pygit2/version.py index 428045a6d..9469822bf 100644 --- a/pygit2/version.py +++ b/pygit2/version.py @@ -23,4 +23,4 @@ # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. -__version__ = '0.20.0' +__version__ = '0.20.1' From cde2456327197ff479add30a40576f427e12618a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Tue, 24 Dec 2013 10:51:02 +0100 Subject: [PATCH 0070/1630] tests: fix deprecation warning --- test/test_repository.py | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/test/test_repository.py b/test/test_repository.py index 53dc00d15..281630d1e 100644 --- a/test/test_repository.py +++ b/test/test_repository.py @@ -313,8 +313,8 @@ def test_merge_uptodate(self): 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()) + self.assertEqual(None, merge_result.fastforward_oid) + self.assertEqual({}, self.repo.status()) def test_merge_fastforward(self): branch_head_hex = 'e97b4cfd5db0fb4ebabf4f203979ca4e5d1c7c87' @@ -323,9 +323,9 @@ def test_merge_fastforward(self): 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()) + self.assertEqual(branch_head_hex, merge_result.fastforward_oid.hex) + self.assertEqual(branch_head_hex, merge_result.fastforward_oid.hex) + self.assertEqual({}, self.repo.status()) def test_merge_no_fastforward_no_conflicts(self): branch_head_hex = '03490f16b15a09913edb3a067a3dc67fbb8d41f1' @@ -334,14 +334,14 @@ def test_merge_no_fastforward_no_conflicts(self): 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()) + self.assertEqual(None, merge_result.fastforward_oid) + self.assertEqual(None, merge_result.fastforward_oid) + self.assertEqual({'bye.txt': 1}, self.repo.status()) + self.assertEqual({'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()) + self.assertEqual({'bye.txt': 128}, self.repo.status()) def test_merge_no_fastforward_conflicts(self): branch_head_hex = '1b2bae55ac95a4be3f8983b86cd579226d0eb247' @@ -350,14 +350,14 @@ def test_merge_no_fastforward_conflicts(self): 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()) + self.assertEqual(None, merge_result.fastforward_oid) + self.assertEqual(None, merge_result.fastforward_oid) + self.assertEqual({'.gitignore': 132}, self.repo.status()) + self.assertEqual({'.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()) + self.assertEqual({'.gitignore': 2}, self.repo.status()) def test_merge_invalid_hex(self): branch_head_hex = '12345678' From 78d134c016f88921dc54c3f237107fbd8534934d Mon Sep 17 00:00:00 2001 From: Brodie Rao Date: Wed, 8 Jan 2014 15:14:27 -0800 Subject: [PATCH 0071/1630] repository: add listall_reference_objects() method This allows for efficient reading of many references and their targets, without incurring the overhead of lookup_reference() (which stats for a loose ref and then reads packed-refs) which can be expensive on NFS with thousands of refs. --- docs/references.rst | 1 + src/repository.c | 51 +++++++++++++++++++++++++++++++++++++++++++++ src/repository.h | 2 ++ test/test_refs.py | 11 ++++++++++ 4 files changed, 65 insertions(+) diff --git a/docs/references.rst b/docs/references.rst index 339139230..0682f0ecd 100644 --- a/docs/references.rst +++ b/docs/references.rst @@ -4,6 +4,7 @@ References .. contents:: +.. automethod:: pygit2.Repository.listall_reference_objects .. automethod:: pygit2.Repository.listall_references .. automethod:: pygit2.Repository.lookup_reference diff --git a/src/repository.c b/src/repository.c index f8d42a7af..e83302c48 100644 --- a/src/repository.c +++ b/src/repository.c @@ -1123,6 +1123,56 @@ Repository_listall_references(Repository *self, PyObject *args) } +PyDoc_STRVAR(Repository_listall_reference_objects__doc__, + "listall_reference_objects() -> [Reference, ...]\n" + "\n" + "Return a list with all the reference objects in the repository."); + +PyObject * +Repository_listall_reference_objects(Repository *self, PyObject *args) +{ + git_reference_iterator *iter; + git_reference *ref = NULL; + int err; + PyObject *list; + + list = PyList_New(0); + if (list == NULL) + return NULL; + + if ((err = git_reference_iterator_new(&iter, self->repo)) < 0) + return Error_set(err); + + while ((err = git_reference_next(&ref, iter)) == 0) { + PyObject *py_ref = wrap_reference(ref, self); + if (py_ref == NULL) + goto error; + + err = PyList_Append(list, py_ref); + Py_DECREF(py_ref); + + if (err < 0) + goto error; + } + + git_reference_iterator_free(iter); + if (err == GIT_ITEROVER) + err = 0; + + if (err < 0) { + Py_CLEAR(list); + return Error_set(err); + } + + return list; + +error: + git_reference_iterator_free(iter); + Py_CLEAR(list); + return NULL; +} + + PyDoc_STRVAR(Repository_listall_branches__doc__, "listall_branches([flag]) -> [str, ...]\n" "\n" @@ -1643,6 +1693,7 @@ PyMethodDef Repository_methods[] = { METHOD(Repository, create_reference_direct, METH_VARARGS), METHOD(Repository, create_reference_symbolic, METH_VARARGS), METHOD(Repository, listall_references, METH_NOARGS), + METHOD(Repository, listall_reference_objects, METH_NOARGS), METHOD(Repository, listall_submodules, METH_NOARGS), METHOD(Repository, lookup_reference, METH_O), METHOD(Repository, revparse_single, METH_O), diff --git a/src/repository.h b/src/repository.h index 694726982..bc6cc38b3 100644 --- a/src/repository.h +++ b/src/repository.h @@ -58,6 +58,8 @@ PyObject* Repository_create_commit(Repository *self, PyObject *args); PyObject* Repository_create_tag(Repository *self, PyObject *args); PyObject* Repository_create_branch(Repository *self, PyObject *args); PyObject* Repository_listall_references(Repository *self, PyObject *args); +PyObject* Repository_listall_reference_objects(Repository *self, + PyObject *args); PyObject* Repository_listall_branches(Repository *self, PyObject *args); PyObject* Repository_lookup_reference(Repository *self, PyObject *py_name); diff --git a/test/test_refs.py b/test/test_refs.py index 53de205e3..9429146c4 100644 --- a/test/test_refs.py +++ b/test/test_refs.py @@ -42,6 +42,17 @@ class ReferencesTest(utils.RepoTestCase): + def test_list_all_reference_objects(self): + repo = self.repo + + refs = [(ref.name, ref.target.hex) + for ref in repo.listall_reference_objects()] + self.assertEqual(sorted(refs), + [('refs/heads/i18n', + '5470a671a80ac3789f1a6a8cefbcf43ce7af0563'), + ('refs/heads/master', + '2be5719152d4f82c7302b1c0932d8e5f0a4a0e98')]) + def test_list_all_references(self): repo = self.repo From dcc9051a8c9978b2351995e5fe90fb20fa1e571e Mon Sep 17 00:00:00 2001 From: Petr Hosek Date: Wed, 15 Jan 2014 15:40:04 +0000 Subject: [PATCH 0072/1630] Support diff for blobs --- src/blob.c | 97 ++++++++++++++++++++++++++++++++++++++++++++++- src/diff.c | 74 ++++++++++++++++++++---------------- src/diff.h | 1 + test/test_blob.py | 11 ++++++ 4 files changed, 149 insertions(+), 34 deletions(-) diff --git a/src/blob.c b/src/blob.c index 83c7ece55..1b2840f71 100644 --- a/src/blob.c +++ b/src/blob.c @@ -27,10 +27,105 @@ #define PY_SSIZE_T_CLEAN #include +#include "diff.h" +#include "error.h" #include "utils.h" #include "object.h" #include "blob.h" +extern PyObject *GitError; + +extern PyTypeObject BlobType; + +PyDoc_STRVAR(Blob_diff__doc__, + "diff([blob, flag, old_as_path, new_as_path] -> Patch\n" + "\n" + "Directly generate a :py:class:`pygit2.Patch` from the difference\n" + " between two blobs.\n" + "\n" + "Arguments:\n" + "\n" + "blob: the :py:class:`~pygit2.Blob` to diff.\n" + "\n" + "flag: a GIT_DIFF_* constant.\n" + "\n" + "old_as_path: treat old blob as if it had this filename.\n" + "\n" + "new_as_path: treat new blob as if it had this filename.\n"); + +PyObject * +Blob_diff(Blob *self, PyObject *args, PyObject *kwds) +{ + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_patch *patch; + char *old_as_path = NULL, *new_as_path = NULL; + Blob *py_blob = NULL; + int err; + char *keywords[] = {"blob", "flag", "old_as_path", "new_as_path", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "|O!ssI", keywords, + &BlobType, &py_blob, &opts.flags, + &old_as_path, &new_as_path)) + return NULL; + + err = git_patch_from_blobs(&patch, self->blob, old_as_path, + py_blob ? py_blob->blob : NULL, new_as_path, + &opts); + if (err < 0) + return Error_set(err); + + return wrap_patch(patch); +} + + +PyDoc_STRVAR(Blob_diff_to_buffer__doc__, + "diff_to_buffer([buffer, flag, old_as_path, buffer_as_path] -> Patch\n" + "\n" + "Directly generate a :py:class:`~pygit2.Patch` from the difference\n" + " between a blob and a buffer.\n" + "\n" + "Arguments:\n" + "\n" + "buffer: Raw data for new side of diff.\n" + "\n" + "flag: a GIT_DIFF_* constant.\n" + "\n" + "old_as_path: treat old blob as if it had this filename.\n" + "\n" + "buffer_as_path: treat buffer as if it had this filename.\n"); + +PyObject * +Blob_diff_to_buffer(Blob *self, PyObject *args, PyObject *kwds) +{ + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_patch *patch; + char *old_as_path = NULL, *buffer_as_path = NULL; + const char *buffer = NULL; + Py_ssize_t buffer_len; + int err; + char *keywords[] = {"buffer", "flag", "old_as_path", "buffer_as_path", + NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "|s#ssI", keywords, + &buffer, &buffer_len, &opts.flags, + &old_as_path, &buffer_as_path)) + return NULL; + + err = git_patch_from_blob_and_buffer(&patch, self->blob, old_as_path, + buffer, buffer_len, buffer_as_path, + &opts); + if (err < 0) + return Error_set(err); + + return wrap_patch(patch); +} + +static PyMethodDef Blob_methods[] = { + METHOD(Blob, diff, METH_VARARGS | METH_KEYWORDS), + METHOD(Blob, diff_to_buffer, METH_VARARGS | METH_KEYWORDS), + {NULL} +}; + PyDoc_STRVAR(Blob_size__doc__, "Size."); @@ -94,7 +189,7 @@ PyTypeObject BlobType = { 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ - 0, /* tp_methods */ + Blob_methods, /* tp_methods */ 0, /* tp_members */ Blob_getseters, /* tp_getset */ 0, /* tp_base */ diff --git a/src/diff.c b/src/diff.c index 75df6acd9..1609a7d65 100644 --- a/src/diff.c +++ b/src/diff.c @@ -57,27 +57,24 @@ wrap_diff(git_diff *diff, Repository *repo) return (PyObject*) py_diff; } -PyObject* -diff_get_patch_byindex(git_diff* diff, size_t idx) +PyObject * +wrap_patch(git_patch *patch) { - const git_diff_delta* delta; - const git_diff_hunk *hunk; - const git_diff_line *line; - git_patch* patch = NULL; - size_t i, j, hunk_amounts, lines_in_hunk, additions, deletions; - int err; - Hunk *py_hunk = NULL; - Patch *py_patch = NULL; - PyObject *py_line_origin=NULL, *py_line=NULL; - - err = git_patch_from_diff(&patch, diff, idx); - if (err < 0) - return Error_set(err); + Patch *py_patch; - delta = git_patch_get_delta(patch); + if (!patch) + Py_RETURN_NONE; py_patch = PyObject_New(Patch, &PatchType); - if (py_patch != NULL) { + if (py_patch) { + size_t i, j, hunk_amounts, lines_in_hunk, additions, deletions; + const git_diff_delta *delta; + const git_diff_hunk *hunk; + const git_diff_line *line; + int err; + + delta = git_patch_get_delta(patch); + py_patch->old_file_path = delta->old_file.path; py_patch->new_file_path = delta->new_file.path; py_patch->status = git_diff_status_char(delta->status); @@ -92,11 +89,12 @@ diff_get_patch_byindex(git_diff* diff, size_t idx) hunk_amounts = git_patch_num_hunks(patch); py_patch->hunks = PyList_New(hunk_amounts); - for (i=0; i < hunk_amounts; ++i) { - err = git_patch_get_hunk(&hunk, &lines_in_hunk, patch, i); + for (i = 0; i < hunk_amounts; ++i) { + Hunk *py_hunk = NULL; + err = git_patch_get_hunk(&hunk, &lines_in_hunk, patch, i); if (err < 0) - goto cleanup; + return Error_set(err); py_hunk = PyObject_New(Hunk, &HunkType); if (py_hunk != NULL) { @@ -106,20 +104,20 @@ diff_get_patch_byindex(git_diff* diff, size_t idx) py_hunk->new_lines = hunk->new_lines; py_hunk->lines = PyList_New(lines_in_hunk); - for (j=0; j < lines_in_hunk; ++j) { - err = git_patch_get_line_in_hunk(&line, patch, i, j); + for (j = 0; j < lines_in_hunk; ++j) { + PyObject *py_line_origin = NULL, *py_line = NULL; + err = git_patch_get_line_in_hunk(&line, patch, i, j); if (err < 0) - goto cleanup; + return Error_set(err); - py_line_origin = to_unicode_n(&line->origin, 1, NULL, NULL); - py_line = to_unicode_n(line->content, line->content_len, NULL, NULL); + py_line_origin = to_unicode_n(&line->origin, 1, + NULL, NULL); + py_line = to_unicode_n(line->content, line->content_len, + NULL, NULL); PyList_SetItem(py_hunk->lines, j, - Py_BuildValue("OO", - py_line_origin, - py_line - ) - ); + Py_BuildValue("OO", py_line_origin, py_line)); + Py_DECREF(py_line_origin); Py_DECREF(py_line); } @@ -130,10 +128,20 @@ diff_get_patch_byindex(git_diff* diff, size_t idx) } } -cleanup: - git_patch_free(patch); + return (PyObject*) py_patch; +} + +PyObject* +diff_get_patch_byindex(git_diff *diff, size_t idx) +{ + git_patch *patch = NULL; + int err; + + err = git_patch_from_diff(&patch, diff, idx); + if (err < 0) + return Error_set(err); - return (err < 0) ? Error_set(err) : (PyObject*) py_patch; + return (PyObject*) wrap_patch(patch); } static void diff --git a/src/diff.h b/src/diff.h index f32c93090..9ba3a9ea0 100644 --- a/src/diff.h +++ b/src/diff.h @@ -42,5 +42,6 @@ PyObject* Diff_changes(Diff *self); PyObject* Diff_patch(Diff *self); PyObject* wrap_diff(git_diff *diff, Repository *repo); +PyObject* wrap_patch(git_patch *patch); #endif diff --git a/test/test_blob.py b/test/test_blob.py index 4e7a6cdc9..30faee4f8 100644 --- a/test/test_blob.py +++ b/test/test_blob.py @@ -105,5 +105,16 @@ def test_create_blob_fromdisk(self): self.assertTrue(isinstance(blob, pygit2.Blob)) self.assertEqual(pygit2.GIT_OBJ_BLOB, blob.type) + def test_diff_blob(self): + blob = self.repo[BLOB_SHA] + old_blob = self.repo['3b18e512dba79e4c8300dd08aeb37f8e728b8dad'] + patch = blob.diff(old_blob, old_as_path="hello.txt") + self.assertEqual(len(patch.hunks), 1) + + def test_diff_blob_to_buffer(self): + blob = self.repo[BLOB_SHA] + patch = blob.diff_to_buffer("hello world") + self.assertEqual(len(patch.hunks), 1) + if __name__ == '__main__': unittest.main() From bf26aaf180ccecad53deebadf9b165b93a8eb164 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sun, 19 Jan 2014 15:26:29 +0100 Subject: [PATCH 0073/1630] Repository: allow retrieving the default signature Make it easier to grab the default signature for a repository by adding a getter at the Repository level. --- src/repository.c | 15 +++++++++++++++ test/test_repository.py | 10 ++++++++++ 2 files changed, 25 insertions(+) diff --git a/src/repository.c b/src/repository.c index 7cc746b75..fa0dd7ee1 100644 --- a/src/repository.c +++ b/src/repository.c @@ -39,6 +39,7 @@ #include "branch.h" #include "blame.h" #include "mergeresult.h" +#include "signature.h" #include extern PyObject *GitError; @@ -1324,6 +1325,19 @@ Repository_remotes__get__(Repository *self) return (PyObject*) py_list; } +PyDoc_STRVAR(Repository_default_signature__doc__, "Return the signature according to the repository's configuration"); + +PyObject * +Repository_default_signature__get__(Repository *self) +{ + git_signature *sig; + int err; + + if ((err = git_signature_default(&sig, self->repo)) < 0) + return Error_set(err); + + return build_signature((Object*) self, sig, "utf-8"); +} PyDoc_STRVAR(Repository_checkout_head__doc__, "checkout_head(strategy)\n" @@ -1633,6 +1647,7 @@ PyGetSetDef Repository_getseters[] = { GETTER(Repository, config), GETTER(Repository, workdir), GETTER(Repository, remotes), + GETTER(Repository, default_signature), {NULL} }; diff --git a/test/test_repository.py b/test/test_repository.py index 281630d1e..081e792b8 100644 --- a/test/test_repository.py +++ b/test/test_repository.py @@ -371,6 +371,16 @@ def test_merge_already_something_in_index(self): self.repo.index.add('inindex.txt') self.assertRaises(pygit2.GitError, self.repo.merge, branch_oid) +class RepositorySignatureTest(utils.RepoTestCase): + + def test_default_signature(self): + config = self.repo.config + config['user.name'] = 'Random J Hacker' + config['user.email'] ='rjh@example.com' + + sig = self.repo.default_signature + self.assertEqual('Random J Hacker', sig.name) + self.assertEqual('rjh@example.com', sig.email) class NewRepositoryTest(utils.NoRepoTestCase): From 5a80091c2d42f2c5598d8e699ce3d1bb9669afcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sun, 19 Jan 2014 15:43:53 +0100 Subject: [PATCH 0074/1630] Commit: allow retrieval of the tree's ID Let the user retrieve the ID of the commit's tree instead of having to load the tree just to retrieve this information. --- src/commit.c | 10 +++++++++- test/test_commit.py | 4 +++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/commit.c b/src/commit.c index 725a3c44e..ef821be5d 100644 --- a/src/commit.c +++ b/src/commit.c @@ -32,6 +32,7 @@ #include "signature.h" #include "commit.h" #include "object.h" +#include "oid.h" extern PyTypeObject TreeType; @@ -120,7 +121,6 @@ Commit_author__get__(Commit *self) return build_signature((Object*)self, signature, encoding); } - PyDoc_STRVAR(Commit_tree__doc__, "The tree object attached to the commit."); PyObject * @@ -146,6 +146,13 @@ Commit_tree__get__(Commit *commit) return (PyObject*)py_tree; } +PyDoc_STRVAR(Commit_tree_id__doc__, "The id of the tree attached to the commit."); + +PyObject * +Commit_tree_id__get__(Commit *commit) +{ + return git_oid_to_python(git_commit_tree_id(commit->commit)); +} PyDoc_STRVAR(Commit_parents__doc__, "The list of parent commits."); @@ -201,6 +208,7 @@ PyGetSetDef Commit_getseters[] = { GETTER(Commit, committer), GETTER(Commit, author), GETTER(Commit, tree), + GETTER(Commit, tree_id), GETTER(Commit, parents), {NULL} }; diff --git a/test/test_commit.py b/test/test_commit.py index 85f3d2350..c9e8fbaa1 100644 --- a/test/test_commit.py +++ b/test/test_commit.py @@ -31,7 +31,7 @@ from __future__ import unicode_literals import unittest -from pygit2 import GIT_OBJ_COMMIT, Signature +from pygit2 import GIT_OBJ_COMMIT, Signature, Oid from . import utils @@ -92,6 +92,7 @@ def test_new_commit(self): self.assertEqualSignature(committer, commit.committer) self.assertEqualSignature(author, commit.author) self.assertEqual(tree, commit.tree.hex) + self.assertEqual(Oid(hex=tree), commit.tree_id) self.assertEqual(1, len(commit.parents)) self.assertEqual(COMMIT_SHA, commit.parents[0].hex) @@ -118,6 +119,7 @@ def test_new_commit_encoding(self): self.assertEqualSignature(committer, commit.committer) self.assertEqualSignature(author, commit.author) self.assertEqual(tree, commit.tree.hex) + self.assertEqual(Oid(hex=tree), commit.tree_id) self.assertEqual(1, len(commit.parents)) self.assertEqual(COMMIT_SHA, commit.parents[0].hex) From 1b473b718313ab81f2d5c1a955f1f4d9d9c85500 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sun, 19 Jan 2014 15:52:01 +0100 Subject: [PATCH 0075/1630] Commit: allow retrieval of the parents' ids Don't force the user to load the parents in order to get their ids, but expose a list of the ids directly. --- src/commit.c | 23 +++++++++++++++++++++++ test/test_commit.py | 2 ++ 2 files changed, 25 insertions(+) diff --git a/src/commit.c b/src/commit.c index ef821be5d..258f082ba 100644 --- a/src/commit.c +++ b/src/commit.c @@ -199,6 +199,28 @@ Commit_parents__get__(Commit *self) return list; } +PyDoc_STRVAR(Commit_parent_ids__doc__, "The list of parent commits' ids."); + +PyObject * +Commit_parent_ids__get__(Commit *self) +{ + unsigned int i, parent_count; + const git_oid *id; + PyObject *list; + + parent_count = git_commit_parentcount(self->commit); + list = PyList_New(parent_count); + if (!list) + return NULL; + + for (i=0; i < parent_count; i++) { + id = git_commit_parent_id(self->commit, i); + PyList_SET_ITEM(list, i, git_oid_to_python(id)); + } + + return list; +} + PyGetSetDef Commit_getseters[] = { GETTER(Commit, message_encoding), GETTER(Commit, message), @@ -210,6 +232,7 @@ PyGetSetDef Commit_getseters[] = { GETTER(Commit, tree), GETTER(Commit, tree_id), GETTER(Commit, parents), + GETTER(Commit, parent_ids), {NULL} }; diff --git a/test/test_commit.py b/test/test_commit.py index c9e8fbaa1..a944c40d6 100644 --- a/test/test_commit.py +++ b/test/test_commit.py @@ -95,6 +95,7 @@ def test_new_commit(self): self.assertEqual(Oid(hex=tree), commit.tree_id) self.assertEqual(1, len(commit.parents)) self.assertEqual(COMMIT_SHA, commit.parents[0].hex) + self.assertEqual(Oid(hex=COMMIT_SHA), commit.parent_ids[0]) def test_new_commit_encoding(self): repo = self.repo @@ -122,6 +123,7 @@ def test_new_commit_encoding(self): self.assertEqual(Oid(hex=tree), commit.tree_id) self.assertEqual(1, len(commit.parents)) self.assertEqual(COMMIT_SHA, commit.parents[0].hex) + self.assertEqual(Oid(hex=COMMIT_SHA), commit.parent_ids[0]) def test_modify_commit(self): message = 'New commit.\n\nMessage.\n' From 4dc90f78a9a3e248b016b4743e41babe5d6b423c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sun, 19 Jan 2014 17:10:40 +0100 Subject: [PATCH 0076/1630] TreeEntry: add rich comparison function Allow direct comparisons between TreeEntry objects, which also allows us to use assertEqual in the sanity check test. This fixes #305. --- src/tree.c | 44 +++++++++++++++++++++++++++++++++++++++++++- test/test_tree.py | 2 +- 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/src/tree.c b/src/tree.c index 7599d659b..a3bfe2132 100644 --- a/src/tree.c +++ b/src/tree.c @@ -36,6 +36,7 @@ #include "diff.h" extern PyTypeObject TreeType; +extern PyTypeObject TreeEntryType; extern PyTypeObject DiffType; extern PyTypeObject TreeIterType; extern PyTypeObject IndexType; @@ -77,6 +78,47 @@ TreeEntry_oid__get__(TreeEntry *self) return git_oid_to_python(oid); } +PyObject * +TreeEntry_richcompare(PyObject *a, PyObject *b, int op) +{ + PyObject *res; + int cmp; + + /* We only support comparing to another tree entry */ + if (!PyObject_TypeCheck(b, &TreeEntryType)) { + Py_INCREF(Py_NotImplemented); + return Py_NotImplemented; + } + + cmp =git_tree_entry_cmp(((TreeEntry*)a)->entry, ((TreeEntry*)b)->entry); + switch (op) { + case Py_LT: + res = (cmp <= 0) ? Py_True: Py_False; + break; + case Py_LE: + res = (cmp < 0) ? Py_True: Py_False; + break; + case Py_EQ: + res = (cmp == 0) ? Py_True: Py_False; + break; + case Py_NE: + res = (cmp != 0) ? Py_True: Py_False; + break; + case Py_GT: + res = (cmp > 0) ? Py_True: Py_False; + break; + case Py_GE: + res = (cmp >= 0) ? Py_True: Py_False; + break; + default: + PyErr_Format(PyExc_RuntimeError, "Unexpected '%d' op", op); + return NULL; + } + + Py_INCREF(res); + return res; +} + PyDoc_STRVAR(TreeEntry_hex__doc__, "Hex oid."); @@ -122,7 +164,7 @@ PyTypeObject TreeEntryType = { TreeEntry__doc__, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ - 0, /* tp_richcompare */ + (richcmpfunc)TreeEntry_richcompare, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ diff --git a/test/test_tree.py b/test/test_tree.py index 16674ec32..d1fc0debe 100644 --- a/test/test_tree.py +++ b/test/test_tree.py @@ -118,7 +118,7 @@ def test_iterate_tree(self): """ tree = self.repo[TREE_SHA] for tree_entry in tree: - self.assertEqual(tree_entry.hex, tree[tree_entry.name].hex) + self.assertEqual(tree_entry, tree[tree_entry.name]) if __name__ == '__main__': From 35386cbec25c6e74a5843266a2f26695fe6f02fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sun, 19 Jan 2014 16:34:53 +0100 Subject: [PATCH 0077/1630] Config: switch from foreach iterator An iterator is much more natural in python, so let's use that. --- docs/config.rst | 10 +++- src/config.c | 140 +++++++++++++++++++++++--------------------- src/pygit2.c | 3 + src/types.h | 5 ++ test/test_config.py | 15 ++--- 5 files changed, 96 insertions(+), 77 deletions(-) diff --git a/docs/config.rst b/docs/config.rst index e69fbca83..ef3a1a54f 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -10,9 +10,17 @@ The Config type .. automethod:: pygit2.Config.get_system_config .. automethod:: pygit2.Config.get_global_config -.. automethod:: pygit2.Config.foreach .. automethod:: pygit2.Config.add_file .. automethod:: pygit2.Config.get_multivar .. automethod:: pygit2.Config.set_multivar The :class:`Config` Mapping interface. + +Iterator +========= + +The :class:`Config` class has an iterator which can be used to loop +through all the entries in the configuration. Each element is a tuple +containing the name and the value of each configuration variable. Be +aware that this may return multiple versions of each entry if they are +set multiple times in the configuration files. diff --git a/src/config.c b/src/config.c index 873ba7363..f7675587c 100644 --- a/src/config.c +++ b/src/config.c @@ -33,6 +33,7 @@ #include "config.h" extern PyTypeObject ConfigType; +extern PyTypeObject ConfigIterType; PyObject * @@ -239,70 +240,6 @@ Config_setitem(Config *self, PyObject *py_key, PyObject *py_value) return 0; } -int -Config_foreach_callback_wrapper(const git_config_entry *entry, void *c_payload) -{ - PyObject *args = (PyObject *)c_payload; - PyObject *py_callback = NULL; - PyObject *py_payload = NULL; - PyObject *py_result = NULL; - int c_result; - - if (!PyArg_ParseTuple(args, "O|O", &py_callback, &py_payload)) - return -1; - - if (py_payload) - args = Py_BuildValue("ssO", entry->name, entry->value, py_payload); - else - args = Py_BuildValue("ss", entry->name, entry->value); - if (!args) - return -1; - - if (!(py_result = PyObject_CallObject(py_callback, args))) - return -1; - - if ((c_result = PyLong_AsLong(py_result)) == -1) - return -1; - - Py_CLEAR(args); - - return c_result; -} - - -PyDoc_STRVAR(Config_foreach__doc__, - "foreach(callback[, payload]) -> int\n" - "\n" - "Perform an operation on each config variable.\n" - "\n" - "The callback must be of type Callable and receives the normalized name\n" - "and value of each variable in the config backend, and an optional payload\n" - "passed to this method. As soon as one of the callbacks returns an integer\n" - "other than 0, this function returns that value."); - -PyObject * -Config_foreach(Config *self, PyObject *args) -{ - int ret; - PyObject *py_callback; - PyObject *py_payload = NULL; - - if (!PyArg_ParseTuple(args, "O|O", &py_callback, &py_payload)) - return NULL; - - if (!PyCallable_Check(py_callback)) { - PyErr_SetString(PyExc_TypeError, - "Argument 'callback' is not callable"); - return NULL; - } - - ret = git_config_foreach(self->config, Config_foreach_callback_wrapper, - (void *)args); - - return PyLong_FromLong((long)ret); -} - - PyDoc_STRVAR(Config_add_file__doc__, "add_file(path, level=0, force=0)\n" "\n" @@ -407,10 +344,28 @@ Config_set_multivar(Config *self, PyObject *args) Py_RETURN_NONE; } +PyObject * +Config_iter(Config *self) +{ + ConfigIter *iter; + int err; + + iter = PyObject_New(ConfigIter, &ConfigIterType); + if (!iter) + return NULL; + + if ((err = git_config_iterator_new(&iter->iter, self->config)) < 0) + return Error_set(err); + + Py_INCREF(self); + iter->owner = self; + + return (PyObject*)iter; +} + PyMethodDef Config_methods[] = { METHOD(Config, get_system_config, METH_NOARGS | METH_STATIC), METHOD(Config, get_global_config, METH_NOARGS | METH_STATIC), - METHOD(Config, foreach, METH_VARARGS), METHOD(Config, add_file, METH_VARARGS | METH_KEYWORDS), METHOD(Config, get_multivar, METH_VARARGS), METHOD(Config, set_multivar, METH_VARARGS), @@ -463,7 +418,7 @@ PyTypeObject ConfigType = { 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ - 0, /* tp_iter */ + (getiterfunc)Config_iter, /* tp_iter */ 0, /* tp_iternext */ Config_methods, /* tp_methods */ 0, /* tp_members */ @@ -477,3 +432,56 @@ PyTypeObject ConfigType = { 0, /* tp_alloc */ 0, /* tp_new */ }; + +void +ConfigIter_dealloc(ConfigIter *self) +{ + Py_CLEAR(self->owner); + git_config_iterator_free(self->iter); + PyObject_Del(self); +} + +PyObject * +ConfigIter_iternext(ConfigIter *self) +{ + int err; + git_config_entry *entry; + + if ((err = git_config_next(&entry, self->iter)) < 0) + return Error_set(err); + + return Py_BuildValue("ss", entry->name, entry->value); +} + +PyDoc_STRVAR(ConfigIter__doc__, "Configuration iterator."); + +PyTypeObject ConfigIterType = { + PyVarObject_HEAD_INIT(NULL, 0) + "_pygit2.ConfigIter", /* tp_name */ + sizeof(ConfigIter), /* tp_basicsize */ + 0, /* tp_itemsize */ + (destructor)ConfigIter_dealloc , /* 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 | Py_TPFLAGS_BASETYPE, /* tp_flags */ + ConfigIter__doc__, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + PyObject_SelfIter, /* tp_iter */ + (iternextfunc)ConfigIter_iternext, /* tp_iternext */ + +}; diff --git a/src/pygit2.c b/src/pygit2.c index e640ea8e0..97eb421ee 100644 --- a/src/pygit2.c +++ b/src/pygit2.c @@ -56,6 +56,7 @@ extern PyTypeObject IndexEntryType; extern PyTypeObject IndexIterType; extern PyTypeObject WalkerType; extern PyTypeObject ConfigType; +extern PyTypeObject ConfigIterType; extern PyTypeObject ReferenceType; extern PyTypeObject RefLogIterType; extern PyTypeObject RefLogEntryType; @@ -412,7 +413,9 @@ moduleinit(PyObject* m) /* Config */ INIT_TYPE(ConfigType, NULL, PyType_GenericNew) + INIT_TYPE(ConfigIterType, NULL, PyType_GenericNew) ADD_TYPE(m, Config) + ADD_TYPE(m, ConfigIter) /* Remotes */ INIT_TYPE(RemoteType, NULL, NULL) diff --git a/src/types.h b/src/types.h index c86495992..94ce79057 100644 --- a/src/types.h +++ b/src/types.h @@ -77,6 +77,11 @@ typedef struct { git_config* config; } Config; +typedef struct { + PyObject_HEAD + Config *owner; + git_config_iterator *iter; +} ConfigIter; /* git_note */ typedef struct { diff --git a/test/test_config.py b/test/test_config.py index 8740a64c9..4a22f407e 100644 --- a/test/test_config.py +++ b/test/test_config.py @@ -36,13 +36,6 @@ CONFIG_FILENAME = "test_config" - -def foreach_test_wrapper(key, name, lst): - lst[key] = name - return 0 -foreach_test_wrapper.__test__ = False - - class ConfigTest(utils.RepoTestCase): def tearDown(self): @@ -175,13 +168,15 @@ def test_write(self): for i in l: self.assertEqual(i, 'foo-123456') - def test_foreach(self): + def test_iterator(self): config = self.repo.config lst = {} - config.foreach(foreach_test_wrapper, lst) + + for name, value in config: + lst[name] = value + self.assertTrue('core.bare' in lst) self.assertTrue(lst['core.bare']) - if __name__ == '__main__': unittest.main() From e681a47245a60bfbef83bfe5eeeeaa4ef4d84dba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sun, 19 Jan 2014 23:05:41 +0100 Subject: [PATCH 0078/1630] Refspec: create the type to wrap refspecs --- src/pygit2.c | 6 ++ src/remote.c | 178 +++++++++++++++++++++++++++++++++++++++++++- src/types.h | 6 ++ test/test_remote.py | 13 +++- 4 files changed, 197 insertions(+), 6 deletions(-) diff --git a/src/pygit2.c b/src/pygit2.c index 97eb421ee..7c72e509c 100644 --- a/src/pygit2.c +++ b/src/pygit2.c @@ -63,6 +63,7 @@ extern PyTypeObject RefLogEntryType; extern PyTypeObject BranchType; extern PyTypeObject SignatureType; extern PyTypeObject RemoteType; +extern PyTypeObject RefspecType; extern PyTypeObject NoteType; extern PyTypeObject NoteIterType; extern PyTypeObject BlameType; @@ -420,6 +421,11 @@ moduleinit(PyObject* m) /* Remotes */ INIT_TYPE(RemoteType, NULL, NULL) ADD_TYPE(m, Remote) + INIT_TYPE(RefspecType, NULL, NULL) + ADD_TYPE(m, Refspec) + /* Direction for the refspec */ + ADD_CONSTANT_INT(m, GIT_DIRECTION_FETCH) + ADD_CONSTANT_INT(m, GIT_DIRECTION_PUSH) /* Blame */ INIT_TYPE(BlameType, NULL, NULL) diff --git a/src/remote.c b/src/remote.c index b5d705998..265828dd1 100644 --- a/src/remote.c +++ b/src/remote.c @@ -36,6 +36,181 @@ extern PyObject *GitError; extern PyTypeObject RepositoryType; +extern PyTypeObject RefspecType; + +Refspec * +wrap_refspec(const Remote *owner, const git_refspec *refspec) +{ + Refspec *spec; + + spec = PyObject_New(Refspec, &RefspecType); + if (!spec) + return NULL; + + Py_INCREF(owner); + spec->owner = owner; + spec->refspec = refspec; + + return spec; +} + +PyDoc_STRVAR(Refspec_direction__doc__, + "The direction of this refspec (fetch or push)"); + +PyObject * +Refspec_direction__get__(Refspec *self) +{ + return Py_BuildValue("i", git_refspec_direction(self->refspec)); +} + +PyDoc_STRVAR(Refspec_src__doc__, "Source or lhs of the refspec"); + +PyObject * +Refspec_src__get__(Refspec *self) +{ + return to_unicode(git_refspec_src(self->refspec), NULL, NULL); +} + +PyDoc_STRVAR(Refspec_dst__doc__, "Destination or rhs of the refspec"); + +PyObject * +Refspec_dst__get__(Refspec *self) +{ + return to_unicode(git_refspec_dst(self->refspec), NULL, NULL); +} + +PyDoc_STRVAR(Refspec_string__doc__, "String used to create this refspec"); + +PyObject * +Refspec_string__get__(Refspec *self) +{ + return to_unicode(git_refspec_string(self->refspec), NULL, NULL); +} + +PyDoc_STRVAR(Refspec_force__doc__, + "Whether this refspec allows non-fast-forward updates"); + +PyObject * +Refspec_force__get__(Refspec *self) +{ + if (git_refspec_force(self->refspec)) + Py_RETURN_TRUE; + + Py_RETURN_FALSE; +} + +PyDoc_STRVAR(Refspec_src_matches__doc__, + "src_matches(str) -> Bool\n" + "\n" + "Returns whether the string matches the source refspec\n"); + +PyObject * +Refspec_src_matches(Refspec *self, PyObject *py_str) +{ + char *str; + int res; + + str = py_str_to_c_str(py_str, NULL); + if (!str) + return NULL; + + res = git_refspec_src_matches(self->refspec, str); + free(str); + + if (res) + Py_RETURN_TRUE; + + Py_RETURN_FALSE; +} + +PyDoc_STRVAR(Refspec_dst_matches__doc__, + "dst_matches(str) -> Bool\n" + "\n" + "Returns whether the string matches the destination refspec\n"); + +PyObject * +Refspec_dst_matches(Refspec *self, PyObject *py_str) +{ + char *str; + int res; + + str = py_str_to_c_str(py_str, NULL); + if (!str) + return NULL; + + res = git_refspec_dst_matches(self->refspec, str); + free(str); + + if (res) + Py_RETURN_TRUE; + + Py_RETURN_FALSE; +} + +PyMethodDef Refspec_methods[] = { + METHOD(Refspec, src_matches, METH_O), + METHOD(Refspec, dst_matches, METH_O), + {NULL} +}; + +PyGetSetDef Refspec_getseters[] = { + GETTER(Refspec, direction), + GETTER(Refspec, src), + GETTER(Refspec, dst), + GETTER(Refspec, string), + GETTER(Refspec, force), + {NULL} +}; + +static void +Refspec_dealloc(Refspec *self) +{ + Py_CLEAR(self->owner); + PyObject_Del(self); +} + +PyDoc_STRVAR(Refspec__doc__, "Refspec object."); + +PyTypeObject RefspecType = { + PyVarObject_HEAD_INIT(NULL, 0) + "_pygit2.Refspec", /* tp_name */ + sizeof(Remote), /* tp_basicsize */ + 0, /* tp_itemsize */ + (destructor)Refspec_dealloc, /* 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 | Py_TPFLAGS_BASETYPE, /* tp_flags */ + Refspec__doc__, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + Refspec_methods, /* tp_methods */ + 0, /* tp_members */ + Refspec_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 */ +}; PyObject * Remote_init(Remote *self, PyObject *args, PyObject *kwds) @@ -306,8 +481,7 @@ Remote_get_refspec(Remote *self, PyObject *value) return NULL; } - return Py_BuildValue("(ss)", git_refspec_src(refspec), - git_refspec_dst(refspec)); + return (PyObject*) wrap_refspec(self, refspec); } diff --git a/src/types.h b/src/types.h index 94ce79057..63a5672d5 100644 --- a/src/types.h +++ b/src/types.h @@ -198,6 +198,12 @@ typedef struct { /* git_remote */ SIMPLE_TYPE(Remote, git_remote, remote) +/* git_refspec */ +typedef struct { + PyObject_HEAD + const Remote *owner; + const git_refspec *refspec; +} Refspec; /* git_blame */ SIMPLE_TYPE(Blame, git_blame, blame) diff --git a/test/test_remote.py b/test/test_remote.py index 741cd504d..981e3bd3e 100644 --- a/test/test_remote.py +++ b/test/test_remote.py @@ -39,6 +39,7 @@ REMOTE_REPO_OBJECTS = 30 REMOTE_REPO_BYTES = 2758 +ORIGIN_REFSPEC = '+refs/heads/*:refs/remotes/origin/*' class RepositoryTest(utils.RepoTestCase): def test_remote_create(self): @@ -80,13 +81,17 @@ def test_refspec(self): self.assertEqual(remote.refspec_count, 1) refspec = remote.get_refspec(0) - self.assertEqual(refspec[0], REMOTE_FETCHSPEC_SRC) - self.assertEqual(refspec[1], REMOTE_FETCHSPEC_DST) + self.assertEqual(refspec.src, REMOTE_FETCHSPEC_SRC) + self.assertEqual(refspec.dst, REMOTE_FETCHSPEC_DST) + self.assertEqual(True, refspec.force) + self.assertEqual(ORIGIN_REFSPEC, refspec.string) self.assertEqual(list, type(remote.get_fetch_refspecs())) self.assertEqual(1, len(remote.get_fetch_refspecs())) - self.assertEqual('+refs/heads/*:refs/remotes/origin/*', - remote.get_fetch_refspecs()[0]) + self.assertEqual(ORIGIN_REFSPEC, remote.get_fetch_refspecs()[0]) + + self.assertTrue(refspec.src_matches('refs/heads/master')) + self.assertTrue(refspec.dst_matches('refs/remotes/origin/master')) self.assertEqual(list, type(remote.get_push_refspecs())) self.assertEqual(0, len(remote.get_push_refspecs())) From 17a49bb417182fb4e0735bf614e916f61fb615cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sun, 19 Jan 2014 23:53:56 +0100 Subject: [PATCH 0079/1630] Refspec: implement transform functions --- src/remote.c | 82 +++++++++++++++++++++++++++++++++++++++++++++ test/test_remote.py | 2 ++ 2 files changed, 84 insertions(+) diff --git a/src/remote.c b/src/remote.c index 265828dd1..524d01138 100644 --- a/src/remote.c +++ b/src/remote.c @@ -147,9 +147,91 @@ Refspec_dst_matches(Refspec *self, PyObject *py_str) Py_RETURN_FALSE; } +PyDoc_STRVAR(Refspec_transform__doc__, + "transform(str) -> str\n" + "\n" + "Transform a reference according to the refspec\n"); + +PyObject * +Refspec_transform(Refspec *self, PyObject *py_str) +{ + char *str, *trans; + int err, len, alen; + PyObject *py_trans; + + str = py_str_to_c_str(py_str, NULL); + alen = len = strlen(str); + + do { + alen *= alen; + trans = malloc(alen); + if (!trans) { + free(str); + return PyErr_NoMemory(); + } + + err = git_refspec_transform(trans, alen, self->refspec, str); + } while(err == GIT_EBUFS); + free(str); + + if (err < 0) { + free(trans); + Error_set(err); + return NULL; + } + + py_trans = to_unicode(trans, NULL, NULL); + + free(trans); + + return py_trans; +} + +PyDoc_STRVAR(Refspec_rtransform__doc__, + "rtransform(str) -> str\n" + "\n" + "Transform a reference according to the refspec in reverse\n"); + +PyObject * +Refspec_rtransform(Refspec *self, PyObject *py_str) +{ + char *str, *trans; + int err, len, alen; + PyObject *py_trans; + + str = py_str_to_c_str(py_str, NULL); + alen = len = strlen(str); + + do { + alen *= alen; + trans = malloc(alen); + if (!trans) { + free(str); + return PyErr_NoMemory(); + } + + err = git_refspec_rtransform(trans, alen, self->refspec, str); + } while(err == GIT_EBUFS); + free(str); + + if (err < 0) { + free(trans); + Error_set(err); + return NULL; + } + + py_trans = to_unicode(trans, NULL, NULL); + + free(trans); + + return py_trans; +} + PyMethodDef Refspec_methods[] = { METHOD(Refspec, src_matches, METH_O), METHOD(Refspec, dst_matches, METH_O), + METHOD(Refspec, transform, METH_O), + METHOD(Refspec, rtransform, METH_O), {NULL} }; diff --git a/test/test_remote.py b/test/test_remote.py index 981e3bd3e..59ebcdc18 100644 --- a/test/test_remote.py +++ b/test/test_remote.py @@ -92,6 +92,8 @@ def test_refspec(self): self.assertTrue(refspec.src_matches('refs/heads/master')) self.assertTrue(refspec.dst_matches('refs/remotes/origin/master')) + self.assertEqual('refs/remotes/origin/master', refspec.transform('refs/heads/master')) + self.assertEqual('refs/heads/master', refspec.rtransform('refs/remotes/origin/master')) self.assertEqual(list, type(remote.get_push_refspecs())) self.assertEqual(0, len(remote.get_push_refspecs())) From de5244d7c78b3d13b28e8084c17972d364a1e4af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sun, 19 Jan 2014 23:58:33 +0100 Subject: [PATCH 0080/1630] Refspec: add documentation --- docs/remotes.rst | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/remotes.rst b/docs/remotes.rst index 3542f699b..9a9d7bfaa 100644 --- a/docs/remotes.rst +++ b/docs/remotes.rst @@ -21,3 +21,16 @@ The Remote type .. automethod:: pygit2.Remote.fetch .. automethod:: pygit2.Remote.push .. automethod:: pygit2.Remote.save + +The Refspec type +=================== + +.. autoattribute:: pygit2.Refspec.direction +.. autoattribute:: pygit2.Refspec.src +.. autoattribute:: pygit2.Refspec.dst +.. autoattribute:: pygit2.Refspec.force +.. autoattribute:: pygit2.Refspec.string +.. automethod:: pygit2.Refspec.src_matches +.. automethod:: pygit2.Refspec.dst_matches +.. automethod:: pygit2.Refspec.transform +.. automethod:: pygit2.Refspec.rtransform From 7ef23780cd3d0d19c506831f89be75935980f0cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Mon, 20 Jan 2014 03:15:56 +0100 Subject: [PATCH 0081/1630] Remote: handle the case of a missing url --- src/remote.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/remote.c b/src/remote.c index b5d705998..a1a5eac2e 100644 --- a/src/remote.c +++ b/src/remote.c @@ -248,7 +248,13 @@ PyDoc_STRVAR(Remote_url__doc__, "Url of the remote"); PyObject * Remote_url__get__(Remote *self) { - return to_unicode(git_remote_url(self->remote), NULL, NULL); + const char *url; + + url = git_remote_url(self->remote); + if (!url) + Py_RETURN_NONE; + + return to_unicode(url, NULL, NULL); } From e28c06fc1de1fe826d28f508d3354aa23477854e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Mon, 20 Jan 2014 03:17:34 +0100 Subject: [PATCH 0082/1630] Remote: allow access to the push url --- src/remote.c | 37 +++++++++++++++++++++++++++++++++++++ test/test_remote.py | 5 +++++ 2 files changed, 42 insertions(+) diff --git a/src/remote.c b/src/remote.c index a1a5eac2e..4d32de157 100644 --- a/src/remote.c +++ b/src/remote.c @@ -278,6 +278,42 @@ Remote_url__set__(Remote *self, PyObject* py_url) return -1; } +PyDoc_STRVAR(Remote_push_url__doc__, "Push url of the remote"); + + +PyObject * +Remote_push_url__get__(Remote *self) +{ + const char *url; + + url = git_remote_pushurl(self->remote); + if (!url) + Py_RETURN_NONE; + + return to_unicode(url, NULL, NULL); +} + + +int +Remote_push_url__set__(Remote *self, PyObject* py_url) +{ + int err; + char* url = NULL; + + url = py_str_to_c_str(py_url, NULL); + if (url != NULL) { + err = git_remote_set_pushurl(self->remote, url); + free(url); + + if (err == GIT_OK) + return 0; + + Error_set(err); + } + + return -1; +} + PyDoc_STRVAR(Remote_refspec_count__doc__, "Number of refspecs."); @@ -452,6 +488,7 @@ PyMethodDef Remote_methods[] = { PyGetSetDef Remote_getseters[] = { GETSET(Remote, name), GETSET(Remote, url), + GETSET(Remote, push_url), GETTER(Remote, refspec_count), {NULL} }; diff --git a/test/test_remote.py b/test/test_remote.py index 741cd504d..85478c843 100644 --- a/test/test_remote.py +++ b/test/test_remote.py @@ -50,6 +50,7 @@ def test_remote_create(self): self.assertEqual(type(remote), pygit2.Remote) self.assertEqual(name, remote.name) self.assertEqual(url, remote.url) + self.assertEqual(None, remote.push_url) self.assertRaises(ValueError, self.repo.create_remote, *(name, url)) @@ -74,6 +75,10 @@ def test_remote_set_url(self): self.assertRaisesAssign(ValueError, remote, 'url', '') + remote.push_url = new_url + self.assertEqual(new_url, remote.push_url) + self.assertRaisesAssign(ValueError, remote, 'push_url', '') + def test_refspec(self): remote = self.repo.remotes[0] From 5baaf287d20cb7ddb8b9269a5a308ba95e97baaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Mon, 20 Jan 2014 03:37:59 +0100 Subject: [PATCH 0083/1630] Tree: let contains look in subtrees Allow looking in subtrees as a convenience in the 'contains' function. As slashes are not allowed to be part of the name of an entry, there is no ambiguity in what they mean. --- src/tree.c | 18 +++++++++++++++--- test/test_tree.py | 7 +++++++ 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/tree.c b/src/tree.c index a3bfe2132..ca91cc04f 100644 --- a/src/tree.c +++ b/src/tree.c @@ -191,14 +191,26 @@ Tree_len(Tree *self) int Tree_contains(Tree *self, PyObject *py_name) { - int result = 0; + int err; + git_tree_entry *entry; char *name = py_path_to_c_str(py_name); if (name == NULL) return -1; - result = git_tree_entry_byname(self->tree, name) ? 1 : 0; + err = git_tree_entry_bypath(&entry, self->tree, name); free(name); - return result; + + if (err == GIT_ENOTFOUND) + return 0; + + if (err < 0) { + Error_set(err); + return -1; + } + + git_tree_entry_free(entry); + + return 1; } TreeEntry * diff --git a/test/test_tree.py b/test/test_tree.py index d1fc0debe..507a22bdd 100644 --- a/test/test_tree.py +++ b/test/test_tree.py @@ -120,6 +120,13 @@ def test_iterate_tree(self): for tree_entry in tree: self.assertEqual(tree_entry, tree[tree_entry.name]) + def test_deep_contains(self): + tree = self.repo[TREE_SHA] + self.assertTrue('a' in tree) + self.assertTrue('c' in tree) + self.assertTrue('c/d' in tree) + self.assertFalse('c/e' in tree) + self.assertFalse('d' in tree) if __name__ == '__main__': unittest.main() From 9ef75d846eabe575c53653fc9aeb109f5b2d7010 Mon Sep 17 00:00:00 2001 From: XTao Date: Mon, 20 Jan 2014 18:18:22 +0800 Subject: [PATCH 0084/1630] Add fetch & push refspec. --- src/remote.c | 49 +++++++++++++++++++++++++++++++++++++++++++++ test/test_remote.py | 9 +++++++++ 2 files changed, 58 insertions(+) diff --git a/src/remote.c b/src/remote.c index 823ec6818..d77996626 100644 --- a/src/remote.c +++ b/src/remote.c @@ -729,11 +729,60 @@ Remote_push(Remote *self, PyObject *args) } +PyDoc_STRVAR(Remote_add_push__doc__, + "add_push(refspec)\n" + "\n" + "Add a push refspec to the remote."); + +PyObject * +Remote_add_push(Remote *self, PyObject *args) +{ + git_remote *remote; + char *refspec = NULL; + int err = 0; + + if (!PyArg_ParseTuple(args, "s", &refspec)) + return NULL; + + remote = self->remote; + err = git_remote_add_push(remote, refspec); + if (err < 0) + return Error_set(err); + + Py_RETURN_NONE; +} + + +PyDoc_STRVAR(Remote_add_fetch__doc__, + "add_fetch(refspec)\n" + "\n" + "Add a fetch refspec to the remote."); + +PyObject * +Remote_add_fetch(Remote *self, PyObject *args) +{ + git_remote *remote; + char *refspec = NULL; + int err = 0; + + if (!PyArg_ParseTuple(args, "s", &refspec)) + return NULL; + + remote = self->remote; + err = git_remote_add_fetch(remote, refspec); + if (err < 0) + return Error_set(err); + + Py_RETURN_NONE; +} + PyMethodDef Remote_methods[] = { METHOD(Remote, fetch, METH_NOARGS), METHOD(Remote, save, METH_NOARGS), METHOD(Remote, get_refspec, METH_O), METHOD(Remote, push, METH_VARARGS), + METHOD(Remote, add_push, METH_VARARGS), + METHOD(Remote, add_fetch, METH_VARARGS), METHOD(Remote, get_fetch_refspecs, METH_NOARGS), METHOD(Remote, set_fetch_refspecs, METH_O), METHOD(Remote, get_push_refspecs, METH_NOARGS), diff --git a/test/test_remote.py b/test/test_remote.py index 69b980203..75fdb4fe8 100644 --- a/test/test_remote.py +++ b/test/test_remote.py @@ -151,6 +151,15 @@ def test_remote_save(self): self.assertEqual('http://example.com/test.git', self.repo.remotes[0].url) + def test_add_refspec(self): + remote = self.repo.create_remote('test_add_refspec', REMOTE_URL) + remote.add_push('refs/heads/*:refs/heads/test_refspec/*') + self.assertEqual('refs/heads/*:refs/heads/test_refspec/*', + remote.get_push_refspecs()[0]) + remote.add_fetch('+refs/heads/*:refs/remotes/test_refspec/*') + self.assertEqual('+refs/heads/*:refs/remotes/test_refspec/*', + remote.get_fetch_refspecs()[1]) + class EmptyRepositoryTest(utils.EmptyRepoTestCase): def test_fetch(self): From 1040a6330ac474e51b7b4808b922dfcb6597c53e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Wed, 22 Jan 2014 23:06:52 +0100 Subject: [PATCH 0085/1630] Remote: make fetch/push_refspecs attributes This is a lot more pythonic than two sets of getter-setter functions. The old ones are left for backwards compatibility but they should be removed in the next release. --- src/remote.c | 142 ++++++++++++++++++++++++++++---------------- test/test_remote.py | 9 +++ 2 files changed, 101 insertions(+), 50 deletions(-) diff --git a/src/remote.c b/src/remote.c index d77996626..14cd8f410 100644 --- a/src/remote.c +++ b/src/remote.c @@ -370,12 +370,39 @@ get_pylist_from_git_strarray(git_strarray *strarray) return new_list; } +int +get_strarraygit_from_pylist(git_strarray *array, PyObject *pylist) +{ + long index, n; + PyObject *item; + void *ptr; -PyDoc_STRVAR(Remote_get_fetch_refspecs__doc__, "Fetch refspecs"); + n = PyObject_Length(pylist); + if (n < 0) + return -1; + /* allocate new git_strarray */ + ptr = calloc(n, sizeof(char *)); + if (!ptr) { + PyErr_SetNone(PyExc_MemoryError); + return -1; + } + + array->strings = ptr; + array->count = n; + + for (index = 0; index < n; index++) { + item = PyList_GetItem(pylist, index); + array->strings[index] = py_str_to_c_str(item, NULL); + } + + return GIT_OK; +} + +PyDoc_STRVAR(Remote_fetch_refspecs__doc__, "Fetch refspecs"); PyObject * -Remote_get_fetch_refspecs(Remote *self) +Remote_fetch_refspecs__get__(Remote *self) { int err; git_strarray refspecs; @@ -391,12 +418,34 @@ Remote_get_fetch_refspecs(Remote *self) return new_list; } +int +Remote_fetch_refspecs__set__(Remote *self, PyObject *args) +{ + int err; + PyObject *pyrefspecs; + git_strarray fetch_refspecs; -PyDoc_STRVAR(Remote_get_push_refspecs__doc__, "Push refspecs"); + if (! PyArg_Parse(args, "O", &pyrefspecs)) + return -1; + + if (get_strarraygit_from_pylist(&fetch_refspecs, pyrefspecs) < 0) + return -1; + + err = git_remote_set_fetch_refspecs(self->remote, &fetch_refspecs); + git_strarray_free(&fetch_refspecs); + + if (err < 0) { + Error_set(err); + return -1; + } + return 0; +} + +PyDoc_STRVAR(Remote_push_refspecs__doc__, "Push refspecs"); PyObject * -Remote_get_push_refspecs(Remote *self) +Remote_push_refspecs__get__(Remote *self) { int err; git_strarray refspecs; @@ -412,89 +461,80 @@ Remote_get_push_refspecs(Remote *self) return new_list; } - int -get_strarraygit_from_pylist(git_strarray *array, PyObject *pylist) +Remote_push_refspecs__set__(Remote *self, PyObject *args) { - long index, n; - PyObject *item; - void *ptr; + int err; + PyObject *pyrefspecs; + git_strarray push_refspecs; - n = PyObject_Length(pylist); - if (n < 0) + if (! PyArg_Parse(args, "O", &pyrefspecs)) return -1; - /* allocate new git_strarray */ - ptr = calloc(n, sizeof(char *)); - if (!ptr) { - PyErr_SetNone(PyExc_MemoryError); + if (get_strarraygit_from_pylist(&push_refspecs, pyrefspecs) != 0) return -1; - } - array->strings = ptr; - array->count = n; + err = git_remote_set_push_refspecs(self->remote, &push_refspecs); + git_strarray_free(&push_refspecs); - for (index = 0; index < n; index++) { - item = PyList_GetItem(pylist, index); - array->strings[index] = py_str_to_c_str(item, NULL); + if (err < 0) { + Error_set(err); + return -1; } - return GIT_OK; + return 0; } +PyDoc_STRVAR(Remote_get_fetch_refspecs__doc__, + "Fetch refspecs.\n" + "This function is deprecated, please use the fetch_refspecs attribute" + "\n"); + + +PyObject * +Remote_get_fetch_refspecs(Remote *self) +{ + return Remote_fetch_refspecs__get__(self); +} + + +PyDoc_STRVAR(Remote_get_push_refspecs__doc__, "Push refspecs"); + + +PyObject * +Remote_get_push_refspecs(Remote *self) +{ + return Remote_push_refspecs__get__(self); +} PyDoc_STRVAR(Remote_set_fetch_refspecs__doc__, "set_fetch_refspecs([str])\n" + "This function is deprecated, please use the push_refspecs attribute" "\n"); PyObject * Remote_set_fetch_refspecs(Remote *self, PyObject *args) { - int err; - PyObject *pyrefspecs; - git_strarray fetch_refspecs; - - if (! PyArg_Parse(args, "O", &pyrefspecs)) + if (Remote_fetch_refspecs__set__(self, args) < 0) return NULL; - if (get_strarraygit_from_pylist(&fetch_refspecs, pyrefspecs) != GIT_OK) - return NULL; - - err = git_remote_set_fetch_refspecs(self->remote, &fetch_refspecs); - git_strarray_free(&fetch_refspecs); - - if (err != GIT_OK) - return Error_set(err); - Py_RETURN_NONE; } PyDoc_STRVAR(Remote_set_push_refspecs__doc__, "set_push_refspecs([str])\n" + "This function is deprecated, please use the push_refspecs attribute" "\n"); PyObject * Remote_set_push_refspecs(Remote *self, PyObject *args) { - int err; - PyObject *pyrefspecs; - git_strarray push_refspecs; - - if (! PyArg_Parse(args, "O", &pyrefspecs)) + if (Remote_push_refspecs__set__(self, args) < 0) return NULL; - if (get_strarraygit_from_pylist(&push_refspecs, pyrefspecs) != 0) - return NULL; - - err = git_remote_set_push_refspecs(self->remote, &push_refspecs); - git_strarray_free(&push_refspecs); - - if (err != GIT_OK) - return Error_set(err); - Py_RETURN_NONE; } @@ -795,6 +835,8 @@ PyGetSetDef Remote_getseters[] = { GETSET(Remote, url), GETSET(Remote, push_url), GETTER(Remote, refspec_count), + GETSET(Remote, fetch_refspecs), + GETSET(Remote, push_refspecs), {NULL} }; diff --git a/test/test_remote.py b/test/test_remote.py index 75fdb4fe8..dc1a85025 100644 --- a/test/test_remote.py +++ b/test/test_remote.py @@ -103,10 +103,19 @@ def test_refspec(self): self.assertEqual(list, type(remote.get_push_refspecs())) self.assertEqual(0, len(remote.get_push_refspecs())) + push_specs = remote.push_refspecs + self.assertEqual(list, type(push_specs)) + self.assertEqual(0, len(push_specs)) + remote.set_fetch_refspecs(['+refs/*:refs/remotes/*']) self.assertEqual('+refs/*:refs/remotes/*', remote.get_fetch_refspecs()[0]) + fetch_specs = remote.fetch_refspecs + self.assertEqual(list, type(fetch_specs)) + self.assertEqual(1, len(fetch_specs)) + self.assertEqual('+refs/*:refs/remotes/*', fetch_specs[0]) + remote.set_fetch_refspecs([ '+refs/*:refs/remotes/*', '+refs/test/*:refs/test/remotes/*' From 0c3a700e30e132b4a9e8c87d5635965d44ac510e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Wed, 22 Jan 2014 23:30:02 +0100 Subject: [PATCH 0086/1630] Remote: harden the resfpec setters The object passed must be a list of strings, so make sure we fail by raising an error instead of segfaulting. --- src/remote.c | 42 +++++++++++++++++++++++++----------------- test/test_remote.py | 4 ++++ 2 files changed, 29 insertions(+), 17 deletions(-) diff --git a/src/remote.c b/src/remote.c index 14cd8f410..cafa64819 100644 --- a/src/remote.c +++ b/src/remote.c @@ -373,13 +373,16 @@ get_pylist_from_git_strarray(git_strarray *strarray) int get_strarraygit_from_pylist(git_strarray *array, PyObject *pylist) { - long index, n; + Py_ssize_t index, n; PyObject *item; void *ptr; - n = PyObject_Length(pylist); - if (n < 0) + if (!PyList_Check(pylist)) { + PyErr_SetString(PyExc_TypeError, "Value must be a list"); return -1; + } + + n = PyList_Size(pylist); /* allocate new git_strarray */ ptr = calloc(n, sizeof(char *)); @@ -393,10 +396,23 @@ get_strarraygit_from_pylist(git_strarray *array, PyObject *pylist) for (index = 0; index < n; index++) { item = PyList_GetItem(pylist, index); - array->strings[index] = py_str_to_c_str(item, NULL); + char *str = py_str_to_c_str(item, NULL); + if (!str) + goto on_error; + + array->strings[index] = str; } - return GIT_OK; + return 0; + +on_error: + n = index; + for (index = 0; index < n; index++) { + free(array->strings[index]); + } + free(array->strings); + + return -1; } PyDoc_STRVAR(Remote_fetch_refspecs__doc__, "Fetch refspecs"); @@ -419,16 +435,12 @@ Remote_fetch_refspecs__get__(Remote *self) } int -Remote_fetch_refspecs__set__(Remote *self, PyObject *args) +Remote_fetch_refspecs__set__(Remote *self, PyObject *py_list) { int err; - PyObject *pyrefspecs; git_strarray fetch_refspecs; - if (! PyArg_Parse(args, "O", &pyrefspecs)) - return -1; - - if (get_strarraygit_from_pylist(&fetch_refspecs, pyrefspecs) < 0) + if (get_strarraygit_from_pylist(&fetch_refspecs, py_list) < 0) return -1; err = git_remote_set_fetch_refspecs(self->remote, &fetch_refspecs); @@ -462,16 +474,12 @@ Remote_push_refspecs__get__(Remote *self) } int -Remote_push_refspecs__set__(Remote *self, PyObject *args) +Remote_push_refspecs__set__(Remote *self, PyObject *py_list) { int err; - PyObject *pyrefspecs; git_strarray push_refspecs; - if (! PyArg_Parse(args, "O", &pyrefspecs)) - return -1; - - if (get_strarraygit_from_pylist(&push_refspecs, pyrefspecs) != 0) + if (get_strarraygit_from_pylist(&push_refspecs, py_list) != 0) return -1; err = git_remote_set_push_refspecs(self->remote, &push_refspecs); diff --git a/test/test_remote.py b/test/test_remote.py index dc1a85025..13fc93ce3 100644 --- a/test/test_remote.py +++ b/test/test_remote.py @@ -130,6 +130,10 @@ def test_refspec(self): '+refs/test/*:refs/test/remotes/*' ]) + self.assertRaises(TypeError, setattr, remote, 'push_refspecs', '+refs/*:refs/*') + self.assertRaises(TypeError, setattr, remote, 'fetch_refspecs', '+refs/*:refs/*') + self.assertRaises(TypeError, setattr, remote, 'fetch_refspecs', ['+refs/*:refs/*', 5]) + self.assertEqual('+refs/*:refs/remotes/*', remote.get_push_refspecs()[0]) self.assertEqual('+refs/test/*:refs/test/remotes/*', From 3a83cb44b66a261a6234186b830e9d1a5586a452 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Fri, 24 Jan 2014 08:17:49 +0100 Subject: [PATCH 0087/1630] Oid: Deprecate 'hex' in favour of str() or unicode() Python has built-in functions to get a string representation of an object id, so let's use that instead of using a custom attribute. --- src/oid.c | 14 ++++++++++---- test/test_oid.py | 6 +++--- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/oid.c b/src/oid.c index 40a5d79e4..81749e51d 100644 --- a/src/oid.c +++ b/src/oid.c @@ -257,6 +257,11 @@ Oid_richcompare(PyObject *o1, PyObject *o2, int op) return res; } +PyObject * +Oid__str__(Oid *self) +{ + return git_oid_to_py_str(&self->oid); +} PyDoc_STRVAR(Oid_raw__doc__, "Raw oid, a 20 bytes string."); @@ -267,12 +272,13 @@ Oid_raw__get__(Oid *self) } -PyDoc_STRVAR(Oid_hex__doc__, "Hex oid, a 40 chars long string (type str)."); +PyDoc_STRVAR(Oid_hex__doc__, "Hex oid, a 40 chars long string (type str).\n" + "This attribute is deprecated, please use the built-int str() or unicode()\n"); PyObject * Oid_hex__get__(Oid *self) { - return git_oid_to_py_str(&self->oid); + return Oid__str__(self); } PyGetSetDef Oid_getseters[] = { @@ -293,13 +299,13 @@ PyTypeObject OidType = { 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_compare */ - 0, /* tp_repr */ + (reprfunc)Oid__str__, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ (hashfunc)Oid_hash, /* tp_hash */ 0, /* tp_call */ - 0, /* tp_str */ + (reprfunc)Oid__str__, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ diff --git a/test/test_oid.py b/test/test_oid.py index e631399b2..d2d7a9205 100644 --- a/test/test_oid.py +++ b/test/test_oid.py @@ -50,19 +50,19 @@ class OidTest(utils.BareRepoTestCase): def test_raw(self): oid = Oid(raw=RAW) self.assertEqual(oid.raw, RAW) - self.assertEqual(oid.hex, HEX) + self.assertEqual(str(oid), HEX) def test_hex(self): oid = Oid(hex=HEX) self.assertEqual(oid.raw, RAW) - self.assertEqual(oid.hex, HEX) + self.assertEqual(str(oid), HEX) def test_hex_bytes(self): if version_info[0] == 2: hex = bytes(HEX) oid = Oid(hex=hex) self.assertEqual(oid.raw, RAW) - self.assertEqual(oid.hex, HEX) + self.assertEqual(str(oid), HEX) else: hex = bytes(HEX, "ascii") self.assertRaises(TypeError, Oid, hex=hex) From 500a6793c43a7370b1197bec21e1faf030cc080e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Fri, 24 Jan 2014 10:46:55 +0100 Subject: [PATCH 0088/1630] Object: move to use an 'id' attribute instead of 'oid' This looks like a left-over from the libgit2 misnaming. The current consensus is that 'oid' is the data type and 'id' is the name of the attribute. --- src/object.c | 17 ++++++++++++++--- test/test_blame.py | 2 +- test/test_blob.py | 6 +++--- test/test_commit.py | 6 +++--- test/test_refs.py | 2 +- test/test_remote.py | 12 ++++++------ test/test_repository.py | 10 +++++----- test/test_tag.py | 2 +- test/test_tree.py | 4 ++-- test/test_treebuilder.py | 6 +++--- 10 files changed, 39 insertions(+), 28 deletions(-) diff --git a/src/object.c b/src/object.c index 6e018e564..d608e0c53 100644 --- a/src/object.c +++ b/src/object.c @@ -49,11 +49,11 @@ Object_dealloc(Object* self) } -PyDoc_STRVAR(Object_oid__doc__, +PyDoc_STRVAR(Object_id__doc__, "The object id, an instance of the Oid type."); PyObject * -Object_oid__get__(Object *self) +Object_id__get__(Object *self) { const git_oid *oid; @@ -63,10 +63,20 @@ Object_oid__get__(Object *self) return git_oid_to_python(oid); } +PyDoc_STRVAR(Object_oid__doc__, + "The object id, an instance of the Oid type.\n" + "This attribute is deprecated, please use 'id'\n"); + +PyObject * +Object_oid__get__(Object *self) +{ + return Object_id__get__(self); +} PyDoc_STRVAR(Object_hex__doc__, "Hexadecimal representation of the object id. This is a shortcut for\n" - "Object.oid.hex"); + "Object.oid.hex\n" + "This attribute is deprecated, please use 'id'\n"); PyObject * Object_hex__get__(Object *self) @@ -119,6 +129,7 @@ Object_read_raw(Object *self) PyGetSetDef Object_getseters[] = { GETTER(Object, oid), + GETTER(Object, id), GETTER(Object, hex), GETTER(Object, type), {NULL} diff --git a/test/test_blame.py b/test/test_blame.py index 39dcd863f..1f907740b 100644 --- a/test/test_blame.py +++ b/test/test_blame.py @@ -122,7 +122,7 @@ def test_blame_newest(self): for rev, num_commits in revs: commit = repo.revparse_single(rev) - blame = repo.blame(PATH, newest_commit=commit.oid) + blame = repo.blame(PATH, newest_commit=commit.id) self.assertEqual(len(blame), num_commits) diff --git a/test/test_blob.py b/test/test_blob.py index 30faee4f8..cc6bc0aba 100644 --- a/test/test_blob.py +++ b/test/test_blob.py @@ -50,7 +50,7 @@ class BlobTest(utils.RepoTestCase): def test_read_blob(self): blob = self.repo[BLOB_SHA] self.assertEqual(blob.hex, BLOB_SHA) - sha = blob.oid.hex + sha = blob.id.hex self.assertEqual(sha, BLOB_SHA) self.assertTrue(isinstance(blob, pygit2.Blob)) self.assertFalse(blob.is_binary) @@ -66,7 +66,7 @@ def test_create_blob(self): self.assertTrue(isinstance(blob, pygit2.Blob)) self.assertEqual(pygit2.GIT_OBJ_BLOB, blob.type) - self.assertEqual(blob_oid, blob.oid) + self.assertEqual(blob_oid, blob.id) self.assertEqual( utils.gen_blob_sha1(BLOB_NEW_CONTENT), blob_oid.hex) @@ -83,7 +83,7 @@ def test_create_blob_fromworkdir(self): self.assertTrue(isinstance(blob, pygit2.Blob)) self.assertEqual(pygit2.GIT_OBJ_BLOB, blob.type) - self.assertEqual(blob_oid, blob.oid) + self.assertEqual(blob_oid, blob.id) self.assertEqual( utils.gen_blob_sha1(BLOB_FILE_CONTENT), blob_oid.hex) diff --git a/test/test_commit.py b/test/test_commit.py index a944c40d6..f462f7878 100644 --- a/test/test_commit.py +++ b/test/test_commit.py @@ -42,11 +42,11 @@ class CommitTest(utils.BareRepoTestCase): def test_read_commit(self): commit = self.repo[COMMIT_SHA] - self.assertEqual(COMMIT_SHA, commit.hex) + self.assertEqual(COMMIT_SHA, str(commit.id)) parents = commit.parents self.assertEqual(1, len(parents)) self.assertEqual('c2792cfa289ae6321ecf2cd5806c2194b0fd070c', - parents[0].hex) + str(parents[0].id)) self.assertEqual(None, commit.message_encoding) self.assertEqual(('Second test data commit.\n\n' 'This commit has some additional text.\n'), @@ -62,7 +62,7 @@ def test_read_commit(self): Signature('Dave Borowitz', 'dborowitz@google.com', 1288477363, -420)) self.assertEqual( - '967fce8df97cc71722d3c2a5930ef3e6f1d27b12', commit.tree.hex) + '967fce8df97cc71722d3c2a5930ef3e6f1d27b12', str(commit.tree.id)) def test_new_commit(self): repo = self.repo diff --git a/test/test_refs.py b/test/test_refs.py index 347d38114..bac89db6f 100644 --- a/test/test_refs.py +++ b/test/test_refs.py @@ -213,7 +213,7 @@ def test_create_symbolic_reference(self): def test_get_object(self): repo = self.repo ref = repo.lookup_reference('refs/heads/master') - self.assertEqual(repo[ref.target].oid, ref.get_object().oid) + self.assertEqual(repo[ref.target].id, ref.get_object().id) if __name__ == '__main__': diff --git a/test/test_remote.py b/test/test_remote.py index 13fc93ce3..267c22e97 100644 --- a/test/test_remote.py +++ b/test/test_remote.py @@ -199,27 +199,27 @@ def test_push_fast_forward_commits_to_remote_succeeds(self): tip = self.clone[self.clone.head.target] oid = self.clone.create_commit( 'refs/heads/master', tip.author, tip.author, 'empty commit', - tip.tree.oid, [tip.oid] + tip.tree.id, [tip.id] ) self.remote.push('refs/heads/master') - self.assertEqual(self.origin[self.origin.head.target].oid, oid) + self.assertEqual(self.origin[self.origin.head.target].id, oid) def test_push_when_up_to_date_succeeds(self): self.remote.push('refs/heads/master') - origin_tip = self.origin[self.origin.head.target].oid - clone_tip = self.clone[self.clone.head.target].oid + origin_tip = self.origin[self.origin.head.target].id + clone_tip = self.clone[self.clone.head.target].id self.assertEqual(origin_tip, clone_tip) def test_push_non_fast_forward_commits_to_remote_fails(self): tip = self.origin[self.origin.head.target] oid = self.origin.create_commit( 'refs/heads/master', tip.author, tip.author, 'some commit', - tip.tree.oid, [tip.oid] + tip.tree.id, [tip.id] ) tip = self.clone[self.clone.head.target] oid = self.clone.create_commit( 'refs/heads/master', tip.author, tip.author, 'other commit', - tip.tree.oid, [tip.oid] + tip.tree.id, [tip.id] ) self.assertRaises(pygit2.GitError, self.remote.push, 'refs/heads/master') diff --git a/test/test_repository.py b/test/test_repository.py index 081e792b8..acd54e4b4 100644 --- a/test/test_repository.py +++ b/test/test_repository.py @@ -309,7 +309,7 @@ def test_merge_none(self): def test_merge_uptodate(self): branch_head_hex = '5ebeeebb320790caf276b9fc8b24546d63316533' - branch_oid = self.repo.get(branch_head_hex).oid + branch_oid = self.repo.get(branch_head_hex).id merge_result = self.repo.merge(branch_oid) self.assertTrue(merge_result.is_uptodate) self.assertFalse(merge_result.is_fastforward) @@ -318,7 +318,7 @@ def test_merge_uptodate(self): def test_merge_fastforward(self): branch_head_hex = 'e97b4cfd5db0fb4ebabf4f203979ca4e5d1c7c87' - branch_oid = self.repo.get(branch_head_hex).oid + branch_oid = self.repo.get(branch_head_hex).id merge_result = self.repo.merge(branch_oid) self.assertFalse(merge_result.is_uptodate) self.assertTrue(merge_result.is_fastforward) @@ -329,7 +329,7 @@ def test_merge_fastforward(self): def test_merge_no_fastforward_no_conflicts(self): branch_head_hex = '03490f16b15a09913edb3a067a3dc67fbb8d41f1' - branch_oid = self.repo.get(branch_head_hex).oid + branch_oid = self.repo.get(branch_head_hex).id merge_result = self.repo.merge(branch_oid) self.assertFalse(merge_result.is_uptodate) self.assertFalse(merge_result.is_fastforward) @@ -345,7 +345,7 @@ def test_merge_no_fastforward_no_conflicts(self): def test_merge_no_fastforward_conflicts(self): branch_head_hex = '1b2bae55ac95a4be3f8983b86cd579226d0eb247' - branch_oid = self.repo.get(branch_head_hex).oid + branch_oid = self.repo.get(branch_head_hex).id merge_result = self.repo.merge(branch_oid) self.assertFalse(merge_result.is_uptodate) self.assertFalse(merge_result.is_fastforward) @@ -365,7 +365,7 @@ def test_merge_invalid_hex(self): def test_merge_already_something_in_index(self): branch_head_hex = '03490f16b15a09913edb3a067a3dc67fbb8d41f1' - branch_oid = self.repo.get(branch_head_hex).oid + branch_oid = self.repo.get(branch_head_hex).id with open(os.path.join(self.repo.workdir, 'inindex.txt'), 'w') as f: f.write('new content') self.repo.index.add('inindex.txt') diff --git a/test/test_tag.py b/test/test_tag.py index 9530eb5cc..7acfd3069 100644 --- a/test/test_tag.py +++ b/test/test_tag.py @@ -92,7 +92,7 @@ def test_modify_tag(self): def test_get_object(self): repo = self.repo tag = repo[TAG_SHA] - self.assertEqual(repo[tag.target].oid, tag.get_object().oid) + self.assertEqual(repo[tag.target].id, tag.get_object().id) if __name__ == '__main__': diff --git a/test/test_tree.py b/test/test_tree.py index 507a22bdd..e5838ac88 100644 --- a/test/test_tree.py +++ b/test/test_tree.py @@ -100,8 +100,8 @@ def test_new_tree(self): self.assertEqual(x.filemode, 0o0100644) self.assertEqual(y.filemode, 0o0100755) - self.assertEqual(repo[x.oid].oid, b0) - self.assertEqual(repo[y.oid].oid, b1) + self.assertEqual(repo[x.oid].id, b0) + self.assertEqual(repo[y.oid].id, b1) def test_modify_tree(self): diff --git a/test/test_treebuilder.py b/test/test_treebuilder.py index 6b2d99adc..c10c4f322 100644 --- a/test/test_treebuilder.py +++ b/test/test_treebuilder.py @@ -49,7 +49,7 @@ def test_noop_treebuilder(self): result = bld.write() self.assertEqual(len(bld), len(tree)) - self.assertEqual(tree.oid, result) + self.assertEqual(tree.id, result) def test_noop_treebuilder_from_tree(self): @@ -58,7 +58,7 @@ def test_noop_treebuilder_from_tree(self): result = bld.write() self.assertEqual(len(bld), len(tree)) - self.assertEqual(tree.oid, result) + self.assertEqual(tree.id, result) def test_rebuild_treebuilder(self): @@ -72,7 +72,7 @@ def test_rebuild_treebuilder(self): result = bld.write() self.assertEqual(len(bld), len(tree)) - self.assertEqual(tree.oid, result) + self.assertEqual(tree.id, result) if __name__ == '__main__': From 541012818750dc2a549028bf3b4c15a31729d242 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Fri, 24 Jan 2014 11:25:39 +0100 Subject: [PATCH 0089/1630] TreeEntry: move to use 'id' attribute for the object's id Similar to the Object change, we should be using 'id' when referring to the target's id.x --- src/tree.c | 14 ++++++++++++-- test/test_tree.py | 6 +++--- test/test_treebuilder.py | 2 +- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/tree.c b/src/tree.c index ca91cc04f..bd2c0870e 100644 --- a/src/tree.c +++ b/src/tree.c @@ -67,10 +67,10 @@ TreeEntry_name__get__(TreeEntry *self) } -PyDoc_STRVAR(TreeEntry_oid__doc__, "Object id."); +PyDoc_STRVAR(TreeEntry_id__doc__, "Object id."); PyObject * -TreeEntry_oid__get__(TreeEntry *self) +TreeEntry_id__get__(TreeEntry *self) { const git_oid *oid; @@ -78,6 +78,15 @@ TreeEntry_oid__get__(TreeEntry *self) return git_oid_to_python(oid); } +PyDoc_STRVAR(TreeEntry_oid__doc__, "Object id.\n" + "This attribute is deprecated. Please use 'id'"); + +PyObject * +TreeEntry_oid__get__(TreeEntry *self) +{ + return TreeEntry_id__get__(self); +} + PyObject * TreeEntry_richcompare(PyObject *a, PyObject *b, int op) { @@ -133,6 +142,7 @@ PyGetSetDef TreeEntry_getseters[] = { GETTER(TreeEntry, filemode), GETTER(TreeEntry, name), GETTER(TreeEntry, oid), + GETTER(TreeEntry, id), GETTER(TreeEntry, hex), {NULL} }; diff --git a/test/test_tree.py b/test/test_tree.py index e5838ac88..25ba7ed66 100644 --- a/test/test_tree.py +++ b/test/test_tree.py @@ -77,7 +77,7 @@ def test_read_subtree(self): subtree_entry = tree['c'] self.assertTreeEntryEqual(subtree_entry, SUBTREE_SHA, 'c', 0o0040000) - subtree = self.repo[subtree_entry.oid] + subtree = self.repo[subtree_entry.id] self.assertEqual(1, len(subtree)) sha = '297efb891a47de80be0cfe9c639e4b8c9b450989' self.assertTreeEntryEqual(subtree[0], sha, 'd', 0o0100644) @@ -100,8 +100,8 @@ def test_new_tree(self): self.assertEqual(x.filemode, 0o0100644) self.assertEqual(y.filemode, 0o0100755) - self.assertEqual(repo[x.oid].id, b0) - self.assertEqual(repo[y.oid].id, b1) + self.assertEqual(repo[x.id].id, b0) + self.assertEqual(repo[y.id].id, b1) def test_modify_tree(self): diff --git a/test/test_treebuilder.py b/test/test_treebuilder.py index c10c4f322..5c7fa67ac 100644 --- a/test/test_treebuilder.py +++ b/test/test_treebuilder.py @@ -68,7 +68,7 @@ def test_rebuild_treebuilder(self): name = entry.name self.assertTrue(bld.get(name) is None) bld.insert(name, entry.hex, entry.filemode) - self.assertEqual(bld.get(name).oid, entry.oid) + self.assertEqual(bld.get(name).id, entry.id) result = bld.write() self.assertEqual(len(bld), len(tree)) From 1cc112c32f3706f2caa24262bfd6082641eab059 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sat, 25 Jan 2014 04:22:54 +0100 Subject: [PATCH 0090/1630] docs: adjust to recent changes It seems I have been forgetting to update the documentation with the last few changes, so adjust to the oid -> id renaming and add missing attributes to the listings. --- docs/merge.rst | 2 +- docs/objects.rst | 10 ++++++++-- docs/oid.rst | 5 +++-- docs/recipes/git-log.rst | 2 +- docs/remotes.rst | 3 +++ docs/repository.rst | 1 + docs/working-copy.rst | 2 +- 7 files changed, 18 insertions(+), 7 deletions(-) diff --git a/docs/merge.rst b/docs/merge.rst index 82c243a7d..4d98590aa 100644 --- a/docs/merge.rst +++ b/docs/merge.rst @@ -22,7 +22,7 @@ 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 + >>> branch_oid = self.repo.get(branch_head_hex).id >>> merge_result = self.repo.merge(branch_oid) The MergeResult object diff --git a/docs/objects.rst b/docs/objects.rst index 377829e1d..4c868c95a 100644 --- a/docs/objects.rst +++ b/docs/objects.rst @@ -78,6 +78,7 @@ New objects are created using an specific API we will see later. This is the common interface for all Git objects: +.. autoattribute:: pygit2.Object.id .. autoattribute:: pygit2.Object.oid .. autoattribute:: pygit2.Object.hex .. autoattribute:: pygit2.Object.type @@ -170,10 +171,13 @@ Tree entries ------------ .. autoattribute:: pygit2.TreeEntry.name +.. autoattribute:: pygit2.TreeEntry.id .. autoattribute:: pygit2.TreeEntry.oid .. autoattribute:: pygit2.TreeEntry.hex .. autoattribute:: pygit2.TreeEntry.filemode +:class:`TreeEntry` supports comparison against other tree entries. + Example:: >>> tree = commit.tree @@ -181,7 +185,7 @@ Example:: 6 >>> for entry in tree: # Iteration - ... print(entry.hex, entry.name) + ... print(entry.id, entry.name) ... 7151ca7cd3e59f3eab19c485cfbf3cb30928d7fa .gitignore c36f4cf1e38ec1bb9d9ad146ed572b89ecfc9f18 COPYING @@ -194,7 +198,7 @@ Example:: >>> entry - >>> blob = repo[entry.oid] # Get the object the entry points to + >>> blob = repo[entry.id] # Get the object the entry points to >>> blob @@ -221,7 +225,9 @@ committer and others. .. autoattribute:: pygit2.Commit.message_encoding .. autoattribute:: pygit2.Commit.raw_message .. autoattribute:: pygit2.Commit.tree +.. autoattribute:: pygit2.Commit.tree_id .. autoattribute:: pygit2.Commit.parents +.. autoattribute:: pygit2.Commit.parent_ids .. autoattribute:: pygit2.Commit.commit_time .. autoattribute:: pygit2.Commit.commit_time_offset diff --git a/docs/oid.rst b/docs/oid.rst index 1ffc08c61..c01e1afae 100644 --- a/docs/oid.rst +++ b/docs/oid.rst @@ -61,8 +61,9 @@ The Oid type >>> raw = unhexlify("cff3ceaefc955f0dbe1957017db181bc49913781") >>> oid2 = Oid(raw=raw) -An the other way around, from an Oid object we can get the hexadecimal and raw -forms. +And the other way around, from an Oid object we can get the hexadecimal and raw +forms. You can use the built-in `str()` (or `unicode()` in python 2) to get the +hexadecimal representation of the Oid. .. autoattribute:: pygit2.Oid.hex .. autoattribute:: pygit2.Oid.raw diff --git a/docs/recipes/git-log.rst b/docs/recipes/git-log.rst index b234e0f61..df645a6e6 100644 --- a/docs/recipes/git-log.rst +++ b/docs/recipes/git-log.rst @@ -31,7 +31,7 @@ Traverse commit history .. code-block:: python >>> last = repo[repo.head.target] - >>> for commit in repo.walk(last.oid, pygit2.GIT_SORT_TIME): + >>> for commit in repo.walk(last.id, pygit2.GIT_SORT_TIME): >>> print(commit.message) # or some other operation ---------------------------------------------------------------------- diff --git a/docs/remotes.rst b/docs/remotes.rst index 9a9d7bfaa..089ab4a4e 100644 --- a/docs/remotes.rst +++ b/docs/remotes.rst @@ -12,7 +12,10 @@ The Remote type .. autoattribute:: pygit2.Remote.name .. autoattribute:: pygit2.Remote.url +.. autoattribute:: pygit2.Remote.push_url .. autoattribute:: pygit2.Remote.refspec_count +.. autoattribute:: pygit2.Remote.push_refspecs +.. autoattribute:: pygit2.Remote.fetch_refspecs .. automethod:: pygit2.Remote.get_push_refspecs .. automethod:: pygit2.Remote.get_fetch_refspecs .. automethod:: pygit2.Remote.set_push_refspecs diff --git a/docs/repository.rst b/docs/repository.rst index ca08f1f4d..c2bd0c20c 100644 --- a/docs/repository.rst +++ b/docs/repository.rst @@ -59,6 +59,7 @@ Below there are some general attributes and methods: .. autoattribute:: pygit2.Repository.workdir .. autoattribute:: pygit2.Repository.is_bare .. autoattribute:: pygit2.Repository.is_empty +.. autoattribute:: pygit2.Repository.default_signature .. automethod:: pygit2.Repository.read .. automethod:: pygit2.Repository.write .. automethod:: pygit2.Repository.reset diff --git a/docs/working-copy.rst b/docs/working-copy.rst index bb10f7156..ab8c6fe23 100644 --- a/docs/working-copy.rst +++ b/docs/working-copy.rst @@ -8,7 +8,7 @@ Index read:: >>> index = repo.index >>> index.read() - >>> oid = index['path/to/file'].oid # from path to object id + >>> oid = index['path/to/file'].id # from path to object id >>> blob = repo[oid] # from object id to object Iterate over all entries of the index:: From c2b2c5dd16f62b4dd94f8d891987530e729e1b7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Mon, 4 Nov 2013 01:11:20 +0100 Subject: [PATCH 0091/1630] remote: call user-provided callbacks The user can set 'progress', 'transfer_progress' and 'update_tips' to be notified whenever one of those happen. --- docs/remotes.rst | 3 + src/remote.c | 146 ++++++++++++++++++++++++++++++++++++++------ src/remote.h | 2 + src/repository.c | 1 + src/types.h | 10 ++- test/test_remote.py | 41 +++++++++++++ 6 files changed, 185 insertions(+), 18 deletions(-) diff --git a/docs/remotes.rst b/docs/remotes.rst index 089ab4a4e..b136fee06 100644 --- a/docs/remotes.rst +++ b/docs/remotes.rst @@ -16,6 +16,9 @@ The Remote type .. autoattribute:: pygit2.Remote.refspec_count .. autoattribute:: pygit2.Remote.push_refspecs .. autoattribute:: pygit2.Remote.fetch_refspecs +.. autoattribute:: pygit2.Remote.progress +.. autoattribute:: pygit2.Remote.transfer_progress +.. autoattribute:: pygit2.Remote.update_tips .. automethod:: pygit2.Remote.get_push_refspecs .. automethod:: pygit2.Remote.get_fetch_refspecs .. automethod:: pygit2.Remote.set_push_refspecs diff --git a/src/remote.c b/src/remote.c index cafa64819..1b825860c 100644 --- a/src/remote.c +++ b/src/remote.c @@ -32,6 +32,7 @@ #include "utils.h" #include "types.h" #include "remote.h" +#include "oid.h" extern PyObject *GitError; @@ -294,6 +295,93 @@ PyTypeObject RefspecType = { 0, /* tp_new */ }; +static int +progress_cb(const char *str, int len, void *data) +{ + Remote *remote = (Remote *) data; + PyObject *arglist, *ret; + + if (remote->progress == NULL) + return 0; + + if (!PyCallable_Check(remote->progress)) { + PyErr_SetString(PyExc_TypeError, "progress callback is not callable"); + return -1; + } + + arglist = Py_BuildValue("(s#)", str, len); + ret = PyObject_CallObject(remote->progress, arglist); + Py_DECREF(arglist); + + if (!ret) + return -1; + + Py_DECREF(ret); + + return 0; +} + +static int +transfer_progress_cb(const git_transfer_progress *stats, void *data) +{ + Remote *remote = (Remote *) data; + PyObject *arglist, *ret; + + if (remote->transfer_progress == NULL) + return 0; + + if (!PyCallable_Check(remote->transfer_progress)) { + PyErr_SetString(PyExc_TypeError, "transfer progress callback is not callable"); + return -1; + } + + arglist = Py_BuildValue("({s:I,s:I,s:n})", + "indexed_objects", stats->indexed_objects, + "received_objects", stats->received_objects, + "received_bytes", stats->received_bytes); + + ret = PyObject_CallObject(remote->transfer_progress, arglist); + Py_DECREF(arglist); + + if (!ret) + return -1; + + Py_DECREF(ret); + + return 0; +} + +static int +update_tips_cb(const char *refname, const git_oid *a, const git_oid *b, void *data) +{ + Remote *remote = (Remote *) data; + PyObject *ret; + PyObject *old, *new; + + if (remote->update_tips == NULL) + return 0; + + if (!PyCallable_Check(remote->update_tips)) { + PyErr_SetString(PyExc_TypeError, "update tips callback is not callable"); + return -1; + } + + old = git_oid_to_python(a); + new = git_oid_to_python(b); + + ret = PyObject_CallFunction(remote->update_tips, "(s,O,O)", refname, old ,new); + + Py_DECREF(old); + Py_DECREF(new); + + if (!ret) + return -1; + + Py_DECREF(ret); + + return 0; +} + PyObject * Remote_init(Remote *self, PyObject *args, PyObject *kwds) { @@ -311,19 +399,37 @@ Remote_init(Remote *self, PyObject *args, PyObject *kwds) if (err < 0) return Error_set(err); + self->progress = NULL; + self->transfer_progress = NULL; + self->update_tips = NULL; + + Remote_set_callbacks(self); return (PyObject*) self; } +void +Remote_set_callbacks(Remote *self) +{ + git_remote_callbacks callbacks = GIT_REMOTE_CALLBACKS_INIT; + + self->progress = NULL; + + callbacks.progress = progress_cb; + callbacks.transfer_progress = transfer_progress_cb; + callbacks.update_tips = update_tips_cb; + callbacks.payload = self; + git_remote_set_callbacks(self->remote, &callbacks); +} static void Remote_dealloc(Remote *self) { Py_CLEAR(self->repo); + Py_CLEAR(self->progress); git_remote_free(self->remote); PyObject_Del(self); } - PyDoc_STRVAR(Remote_name__doc__, "Name of the remote refspec"); PyObject * @@ -671,24 +777,23 @@ Remote_fetch(Remote *self, PyObject *args) const git_transfer_progress *stats; int err; - err = git_remote_connect(self->remote, GIT_DIRECTION_FETCH); - if (err == GIT_OK) { - err = git_remote_download(self->remote); - if (err == GIT_OK) { - stats = git_remote_stats(self->remote); - py_stats = Py_BuildValue("{s:I,s:I,s:n}", - "indexed_objects", stats->indexed_objects, - "received_objects", stats->received_objects, - "received_bytes", stats->received_bytes); - - err = git_remote_update_tips(self->remote); - } - git_remote_disconnect(self->remote); - } - + PyErr_Clear(); + err = git_remote_fetch(self->remote); + /* + * XXX: We should be checking for GIT_EUSER, but on v0.20, this does not + * make it all the way to us for update_tips + */ + if (err < 0 && PyErr_Occurred()) + return NULL; if (err < 0) return Error_set(err); + stats = git_remote_stats(self->remote); + py_stats = Py_BuildValue("{s:I,s:I,s:n}", + "indexed_objects", stats->indexed_objects, + "received_objects", stats->received_objects, + "received_bytes", stats->received_bytes); + return (PyObject*) py_stats; } @@ -848,6 +953,13 @@ PyGetSetDef Remote_getseters[] = { {NULL} }; +PyMemberDef Remote_members[] = { + MEMBER(Remote, progress, T_OBJECT_EX, "Progress output callback"), + MEMBER(Remote, transfer_progress, T_OBJECT_EX, "Transfer progress callback"), + MEMBER(Remote, update_tips, T_OBJECT_EX, "update tips callback"), + {NULL}, +}; + PyDoc_STRVAR(Remote__doc__, "Remote object."); PyTypeObject RemoteType = { @@ -879,7 +991,7 @@ PyTypeObject RemoteType = { 0, /* tp_iter */ 0, /* tp_iternext */ Remote_methods, /* tp_methods */ - 0, /* tp_members */ + Remote_members, /* tp_members */ Remote_getseters, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ diff --git a/src/remote.h b/src/remote.h index 7f6e131de..0ab41a511 100644 --- a/src/remote.h +++ b/src/remote.h @@ -36,4 +36,6 @@ PyObject* Remote_init(Remote *self, PyObject *args, PyObject *kwds); PyObject* Remote_fetch(Remote *self, PyObject *args); +void Remote_set_callbacks(Remote *self); + #endif diff --git a/src/repository.c b/src/repository.c index fa0dd7ee1..f52dd66d7 100644 --- a/src/repository.c +++ b/src/repository.c @@ -1295,6 +1295,7 @@ Repository_create_remote(Repository *self, PyObject *args) Py_INCREF(self); py_remote->repo = self; py_remote->remote = remote; + Remote_set_callbacks(py_remote); return (PyObject*) py_remote; } diff --git a/src/types.h b/src/types.h index 63a5672d5..c8f7a6f98 100644 --- a/src/types.h +++ b/src/types.h @@ -196,7 +196,15 @@ typedef struct { /* git_remote */ -SIMPLE_TYPE(Remote, git_remote, remote) +typedef struct { + PyObject_HEAD + Repository *repo; + git_remote *remote; + /* Callbacks for network events */ + PyObject *progress; + PyObject *transfer_progress; + PyObject *update_tips; +} Remote; /* git_refspec */ typedef struct { diff --git a/test/test_remote.py b/test/test_remote.py index 267c22e97..ce146e0ed 100644 --- a/test/test_remote.py +++ b/test/test_remote.py @@ -30,6 +30,7 @@ import unittest import pygit2 +from pygit2 import Oid from . import utils REMOTE_NAME = 'origin' @@ -173,6 +174,20 @@ def test_add_refspec(self): self.assertEqual('+refs/heads/*:refs/remotes/test_refspec/*', remote.get_fetch_refspecs()[1]) + def test_remote_callback_typecheck(self): + remote = self.repo.remotes[0] + remote.progress = 5 + self.assertRaises(TypeError, remote, 'fetch') + + remote = self.repo.remotes[0] + remote.transfer_progress = 5 + self.assertRaises(TypeError, remote, 'fetch') + + remote = self.repo.remotes[0] + remote.update_tips = 5 + self.assertRaises(TypeError, remote, 'fetch') + + class EmptyRepositoryTest(utils.EmptyRepoTestCase): def test_fetch(self): @@ -182,6 +197,32 @@ def test_fetch(self): self.assertEqual(stats['indexed_objects'], REMOTE_REPO_OBJECTS) self.assertEqual(stats['received_objects'], REMOTE_REPO_OBJECTS) + def test_transfer_progress(self): + self.tp = None + def tp_cb(stats): + self.tp = stats + + remote = self.repo.remotes[0] + remote.transfer_progress = tp_cb + stats = remote.fetch() + self.assertEqual(stats['received_bytes'], self.tp.received_bytes) + self.assertEqual(stats['indexed_objects'], self.tp.indexed_objects) + self.assertEqual(stats['received_objects'], self.tp.received_objects) + + def test_update_tips(self): + remote = self.repo.remotes[0] + self.i = 0 + self.tips = [('refs/remotes/origin/master', Oid(hex='0'*40), + Oid(hex='784855caf26449a1914d2cf62d12b9374d76ae78')), + ('refs/tags/root', Oid(hex='0'*40), + Oid(hex='3d2962987c695a29f1f80b6c3aa4ec046ef44369'))] + + def ut_cb(name, old, new): + self.assertEqual(self.tips[self.i], (name, old, new)) + self.i += 1 + + remote.update_tips = ut_cb + remote.fetch() class PushTestCase(unittest.TestCase): def setUp(self): From 0bbd15b4f1412a8d08fadc3e06c0384c5c1bf1ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sat, 25 Jan 2014 10:20:31 +0100 Subject: [PATCH 0092/1630] TransferProgress: create this type for transfer progress reporting This gets passed to the transfer progress callback, instead of the stripped-down version which Remote.fetch() returns. --- docs/remotes.rst | 14 +++++++ src/pygit2.c | 5 ++- src/remote.c | 95 ++++++++++++++++++++++++++++++++++++++++++++---- src/types.h | 12 ++++++ src/utils.h | 3 ++ 5 files changed, 120 insertions(+), 9 deletions(-) diff --git a/docs/remotes.rst b/docs/remotes.rst index b136fee06..e7c512977 100644 --- a/docs/remotes.rst +++ b/docs/remotes.rst @@ -28,6 +28,20 @@ The Remote type .. automethod:: pygit2.Remote.push .. automethod:: pygit2.Remote.save +The TransferProgress type +=========================== + +This class contains the data which is available to us during a fetch. + +.. autoattribute:: pygit2.TransferProgress.total_objects +.. autoattribute:: pygit2.TransferProgress.indexed_objects +.. autoattribute:: pygit2.TransferProgress.received_objects +.. autoattribute:: pygit2.TransferProgress.local_objects +.. autoattribute:: pygit2.TransferProgress.total_deltas +.. autoattribute:: pygit2.TransferProgress.indexed_deltas +.. autoattribute:: pygit2.TransferProgress.received_bytes + + The Refspec type =================== diff --git a/src/pygit2.c b/src/pygit2.c index 7c72e509c..e324a64b1 100644 --- a/src/pygit2.c +++ b/src/pygit2.c @@ -64,6 +64,7 @@ extern PyTypeObject BranchType; extern PyTypeObject SignatureType; extern PyTypeObject RemoteType; extern PyTypeObject RefspecType; +extern PyTypeObject TransferProgressType; extern PyTypeObject NoteType; extern PyTypeObject NoteIterType; extern PyTypeObject BlameType; @@ -420,9 +421,11 @@ moduleinit(PyObject* m) /* Remotes */ INIT_TYPE(RemoteType, NULL, NULL) - ADD_TYPE(m, Remote) INIT_TYPE(RefspecType, NULL, NULL) + INIT_TYPE(TransferProgressType, NULL, NULL) + ADD_TYPE(m, Remote) ADD_TYPE(m, Refspec) + ADD_TYPE(m, TransferProgress) /* Direction for the refspec */ ADD_CONSTANT_INT(m, GIT_DIRECTION_FETCH) ADD_CONSTANT_INT(m, GIT_DIRECTION_PUSH) diff --git a/src/remote.c b/src/remote.c index 1b825860c..3ee0ab704 100644 --- a/src/remote.c +++ b/src/remote.c @@ -38,6 +38,7 @@ extern PyObject *GitError; extern PyTypeObject RepositoryType; extern PyTypeObject RefspecType; +extern PyTypeObject TransferProgressType; Refspec * wrap_refspec(const Remote *owner, const git_refspec *refspec) @@ -295,6 +296,87 @@ PyTypeObject RefspecType = { 0, /* tp_new */ }; +PyObject * +wrap_transfer_progress(const git_transfer_progress *stats) +{ + TransferProgress *py_stats; + + py_stats = PyObject_New(TransferProgress, &TransferProgressType); + if (!py_stats) + return NULL; + + py_stats->total_objects = stats->total_objects; + py_stats->indexed_objects = stats->indexed_objects; + py_stats->received_objects = stats->received_objects; + py_stats->local_objects = stats->local_objects; + py_stats->total_deltas = stats->total_deltas; + py_stats->indexed_deltas = stats->indexed_deltas; + py_stats->received_bytes = stats->received_bytes; + + return (PyObject *) py_stats; +} + +void +TransferProgress_dealloc(TransferProgress *self) +{ + PyObject_Del(self); +} + +PyMemberDef TransferProgress_members[] = { + RMEMBER(TransferProgress, total_objects, T_UINT, "Total number objects to download"), + RMEMBER(TransferProgress, indexed_objects, T_UINT, "Objects which have been indexed"), + RMEMBER(TransferProgress, received_objects, T_UINT, "Objects which have been received up to now"), + RMEMBER(TransferProgress, local_objects, T_UINT, "Local objects which were used to fix the thin pack"), + RMEMBER(TransferProgress, total_deltas, T_UINT, "Total number of deltas in the pack"), + RMEMBER(TransferProgress, indexed_deltas, T_UINT, "Deltas which have been indexed"), + /* FIXME: technically this is unsigned, but there's no value for size_t here. */ + RMEMBER(TransferProgress, received_bytes, T_PYSSIZET, "Number of bytes received up to now"), + {NULL}, +}; + +PyDoc_STRVAR(TransferProgress__doc__, "Progress downloading and indexing data during a fetch"); + +PyTypeObject TransferProgressType = { + PyVarObject_HEAD_INIT(NULL, 0) + "_pygit2.TransferProgress", /* tp_name */ + sizeof(TransferProgress), /* tp_basicsize */ + 0, /* tp_itemsize */ + (destructor)TransferProgress_dealloc, /* 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 */ + TransferProgress__doc__, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + 0, /* tp_methods */ + TransferProgress_members, /* tp_members */ + 0, /* 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 */ +}; + static int progress_cb(const char *str, int len, void *data) { @@ -325,7 +407,7 @@ static int transfer_progress_cb(const git_transfer_progress *stats, void *data) { Remote *remote = (Remote *) data; - PyObject *arglist, *ret; + PyObject *py_stats, *ret; if (remote->transfer_progress == NULL) return 0; @@ -335,14 +417,11 @@ transfer_progress_cb(const git_transfer_progress *stats, void *data) return -1; } - arglist = Py_BuildValue("({s:I,s:I,s:n})", - "indexed_objects", stats->indexed_objects, - "received_objects", stats->received_objects, - "received_bytes", stats->received_bytes); - - ret = PyObject_CallObject(remote->transfer_progress, arglist); - Py_DECREF(arglist); + py_stats = wrap_transfer_progress(stats); + if (!py_stats) + return -1; + ret = PyObject_CallFunctionObjArgs(remote->transfer_progress, py_stats, NULL); if (!ret) return -1; diff --git a/src/types.h b/src/types.h index c8f7a6f98..95d2bd2b3 100644 --- a/src/types.h +++ b/src/types.h @@ -213,6 +213,18 @@ typedef struct { const git_refspec *refspec; } Refspec; +/* git_transfer_progress */ +typedef struct { + PyObject_HEAD + unsigned int total_objects; + unsigned int indexed_objects; + unsigned int received_objects; + unsigned int local_objects; + unsigned int total_deltas; + unsigned int indexed_deltas; + size_t received_bytes; +} TransferProgress; + /* git_blame */ SIMPLE_TYPE(Blame, git_blame, blame) diff --git a/src/utils.h b/src/utils.h index 62699e835..66d923453 100644 --- a/src/utils.h +++ b/src/utils.h @@ -133,6 +133,9 @@ char * py_str_to_c_str(PyObject *value, const char *encoding); #define MEMBER(type, attr, attr_type, docstr)\ {#attr, attr_type, offsetof(type, attr), 0, PyDoc_STR(docstr)} +#define RMEMBER(type, attr, attr_type, docstr)\ + {#attr, attr_type, offsetof(type, attr), READONLY, PyDoc_STR(docstr)} + /* Helpers for memory allocation */ #define CALLOC(ptr, num, size, label) \ From 9a428f985c29b1872f723af2e06173e6c809e7dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sat, 25 Jan 2014 10:23:41 +0100 Subject: [PATCH 0093/1630] Refspec: fix copy-paste error --- src/remote.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/remote.c b/src/remote.c index 3ee0ab704..6f473746d 100644 --- a/src/remote.c +++ b/src/remote.c @@ -258,7 +258,7 @@ PyDoc_STRVAR(Refspec__doc__, "Refspec object."); PyTypeObject RefspecType = { PyVarObject_HEAD_INIT(NULL, 0) "_pygit2.Refspec", /* tp_name */ - sizeof(Remote), /* tp_basicsize */ + sizeof(Refspec), /* tp_basicsize */ 0, /* tp_itemsize */ (destructor)Refspec_dealloc, /* tp_dealloc */ 0, /* tp_print */ From 30084e00c4a2f941780ea38fe28c2a268098140a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sun, 26 Jan 2014 09:47:42 +0100 Subject: [PATCH 0094/1630] Add support for pypy Fortunately pypy provides support for a lot of the CPython API, so the changes are minimal. The most important changes are: - constructors always get a keyword argument dictionary, even if no keyword arguments are passed - trying to assign to a read-only attribute raises TypeError instead of AttributeError Apart from that, pypy does not provide MAXPATHLEN. There is a hack in place currently, but there is only place that's using that macro, and there shouldn't be a need for it much longer. This fixes #209. --- src/config.c | 2 +- src/index.c | 2 +- src/pygit2.c | 12 +++++++++++- src/repository.c | 2 +- test/test_commit.py | 19 +++++++++++++------ test/test_tag.py | 15 +++++++++++---- 6 files changed, 38 insertions(+), 14 deletions(-) diff --git a/src/config.c b/src/config.c index f7675587c..fdb2593c2 100644 --- a/src/config.c +++ b/src/config.c @@ -59,7 +59,7 @@ Config_init(Config *self, PyObject *args, PyObject *kwds) char *path = NULL; int err; - if (kwds) { + if (kwds && PyDict_Size(kwds) > 0) { PyErr_SetString(PyExc_TypeError, "Config takes no keyword arguments"); return -1; diff --git a/src/index.c b/src/index.c index ce47f13a0..34daf1d1d 100644 --- a/src/index.c +++ b/src/index.c @@ -46,7 +46,7 @@ Index_init(Index *self, PyObject *args, PyObject *kwds) char *path; int err; - if (kwds) { + if (kwds && PyDict_Size(kwds) > 0) { PyErr_SetString(PyExc_TypeError, "Index takes no keyword arguments"); return -1; } diff --git a/src/pygit2.c b/src/pygit2.c index 7c72e509c..466527709 100644 --- a/src/pygit2.c +++ b/src/pygit2.c @@ -27,7 +27,12 @@ #define PY_SSIZE_T_CLEAN #include -#include + +/* Pypy does not provide this header */ +#ifndef PYPY_VERSION +# include +#endif + #include #include "error.h" #include "types.h" @@ -35,6 +40,11 @@ #include "repository.h" #include "oid.h" +/* FIXME: This is for pypy */ +#ifndef MAXPATHLEN +# define MAXPATHLEN 1024 +#endif + extern PyObject *GitError; extern PyTypeObject RepositoryType; diff --git a/src/repository.c b/src/repository.c index fa0dd7ee1..b0464d41f 100644 --- a/src/repository.c +++ b/src/repository.c @@ -77,7 +77,7 @@ Repository_init(Repository *self, PyObject *args, PyObject *kwds) char *path; int err; - if (kwds) { + if (kwds && PyDict_Size(kwds) > 0) { PyErr_SetString(PyExc_TypeError, "Repository takes no keyword arguments"); return -1; diff --git a/test/test_commit.py b/test/test_commit.py index f462f7878..e7d8c5c6f 100644 --- a/test/test_commit.py +++ b/test/test_commit.py @@ -34,6 +34,12 @@ from pygit2 import GIT_OBJ_COMMIT, Signature, Oid from . import utils +# pypy raises TypeError on writing to read-only, so we need to check +# and change the test accordingly +try: + import __pypy__ +except ImportError: + __pypy__ = None COMMIT_SHA = '5fe808e8953c12735680c257f56600cb0de44b10' @@ -131,12 +137,13 @@ def test_modify_commit(self): author = ('Jane Doe', 'jdoe2@example.com', 12345) commit = self.repo[COMMIT_SHA] - self.assertRaises(AttributeError, setattr, commit, 'message', message) - self.assertRaises(AttributeError, setattr, commit, 'committer', - committer) - self.assertRaises(AttributeError, setattr, commit, 'author', author) - self.assertRaises(AttributeError, setattr, commit, 'tree', None) - self.assertRaises(AttributeError, setattr, commit, 'parents', None) + + error_type = AttributeError if not __pypy__ else TypeError + self.assertRaises(error_type, setattr, commit, 'message', message) + self.assertRaises(error_type, setattr, commit, 'committer', committer) + self.assertRaises(error_type, setattr, commit, 'author', author) + self.assertRaises(error_type, setattr, commit, 'tree', None) + self.assertRaises(error_type, setattr, commit, 'parents', None) if __name__ == '__main__': diff --git a/test/test_tag.py b/test/test_tag.py index 7acfd3069..e31f7b0f2 100644 --- a/test/test_tag.py +++ b/test/test_tag.py @@ -34,6 +34,12 @@ import pygit2 from . import utils +# pypy raises TypeError on writing to read-only, so we need to check +# and change the test accordingly +try: + import __pypy__ +except ImportError: + __pypy__ = None TAG_SHA = '3d2962987c695a29f1f80b6c3aa4ec046ef44369' @@ -84,10 +90,11 @@ def test_modify_tag(self): tagger = ('John Doe', 'jdoe@example.com', 12347) tag = self.repo[TAG_SHA] - self.assertRaises(AttributeError, setattr, tag, 'name', name) - self.assertRaises(AttributeError, setattr, tag, 'target', target) - self.assertRaises(AttributeError, setattr, tag, 'tagger', tagger) - self.assertRaises(AttributeError, setattr, tag, 'message', message) + error_type = AttributeError if not __pypy__ else TypeError + self.assertRaises(error_type, setattr, tag, 'name', name) + self.assertRaises(error_type, setattr, tag, 'target', target) + self.assertRaises(error_type, setattr, tag, 'tagger', tagger) + self.assertRaises(error_type, setattr, tag, 'message', message) def test_get_object(self): repo = self.repo From 94e841bfb2eda428477bb47f7b6025539c4e3ceb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Tue, 28 Jan 2014 18:35:56 +0100 Subject: [PATCH 0095/1630] Remove useless constructors The Reference, Branch, ConfigIter and Walker types were allowed to be created by the user, but no constructor was set, and the default values are either meaningless (in the case of Reference/Branch) or would cause a segfault as soon as one tried to use them (ConfigIter and Walker). Remove the constructor from these types, as they don't serve a purpose and can only be used by mistake. --- src/pygit2.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/pygit2.c b/src/pygit2.c index a33efc640..4cb9b3921 100644 --- a/src/pygit2.c +++ b/src/pygit2.c @@ -312,7 +312,7 @@ moduleinit(PyObject* m) /* * Log */ - INIT_TYPE(WalkerType, NULL, PyType_GenericNew) + INIT_TYPE(WalkerType, NULL, NULL) ADD_TYPE(m, Walker); ADD_CONSTANT_INT(m, GIT_SORT_NONE) ADD_CONSTANT_INT(m, GIT_SORT_TOPOLOGICAL) @@ -329,7 +329,7 @@ moduleinit(PyObject* m) /* * References */ - INIT_TYPE(ReferenceType, NULL, PyType_GenericNew) + INIT_TYPE(ReferenceType, NULL, NULL) INIT_TYPE(RefLogEntryType, NULL, NULL) INIT_TYPE(RefLogIterType, NULL, NULL) INIT_TYPE(NoteType, NULL, NULL) @@ -345,7 +345,7 @@ moduleinit(PyObject* m) /* * Branches */ - INIT_TYPE(BranchType, &ReferenceType, PyType_GenericNew); + INIT_TYPE(BranchType, &ReferenceType, NULL); ADD_TYPE(m, Branch) ADD_CONSTANT_INT(m, GIT_BRANCH_LOCAL) ADD_CONSTANT_INT(m, GIT_BRANCH_REMOTE) @@ -425,7 +425,7 @@ moduleinit(PyObject* m) /* Config */ INIT_TYPE(ConfigType, NULL, PyType_GenericNew) - INIT_TYPE(ConfigIterType, NULL, PyType_GenericNew) + INIT_TYPE(ConfigIterType, NULL, NULL) ADD_TYPE(m, Config) ADD_TYPE(m, ConfigIter) From 5b4b7f39d57f51ae5893dd8f3f66b52701e390e9 Mon Sep 17 00:00:00 2001 From: XTao Date: Wed, 29 Jan 2014 15:14:57 +0800 Subject: [PATCH 0096/1630] Add wrap remote. --- src/remote.c | 20 +++++++++++++++++++- src/remote.h | 1 + src/repository.c | 20 +++++++------------- 3 files changed, 27 insertions(+), 14 deletions(-) diff --git a/src/remote.c b/src/remote.c index 6f473746d..8201a9035 100644 --- a/src/remote.c +++ b/src/remote.c @@ -1036,7 +1036,7 @@ PyMemberDef Remote_members[] = { MEMBER(Remote, progress, T_OBJECT_EX, "Progress output callback"), MEMBER(Remote, transfer_progress, T_OBJECT_EX, "Transfer progress callback"), MEMBER(Remote, update_tips, T_OBJECT_EX, "update tips callback"), - {NULL}, + {NULL}, }; PyDoc_STRVAR(Remote__doc__, "Remote object."); @@ -1081,3 +1081,21 @@ PyTypeObject RemoteType = { 0, /* tp_alloc */ 0, /* tp_new */ }; + +PyObject * +wrap_remote(git_remote *c_remote, Repository *repo) +{ + Remote *py_remote = NULL; + py_remote = PyObject_New(Remote, &RemoteType); + if (py_remote) { + Py_INCREF(repo); + py_remote->repo = repo; + py_remote->remote = c_remote; + py_remote->progress = NULL; + py_remote->transfer_progress = NULL; + py_remote->update_tips = NULL; + Remote_set_callbacks(py_remote); + } + + return (PyObject *)py_remote; +} diff --git a/src/remote.h b/src/remote.h index 0ab41a511..1deedcd50 100644 --- a/src/remote.h +++ b/src/remote.h @@ -37,5 +37,6 @@ PyObject* Remote_init(Remote *self, PyObject *args, PyObject *kwds); PyObject* Remote_fetch(Remote *self, PyObject *args); void Remote_set_callbacks(Remote *self); +PyObject *wrap_remote(git_remote *c_remote, Repository *repo); #endif diff --git a/src/repository.c b/src/repository.c index 19ee57da7..c1fa44ccd 100644 --- a/src/repository.c +++ b/src/repository.c @@ -1279,7 +1279,6 @@ PyDoc_STRVAR(Repository_create_remote__doc__, PyObject * Repository_create_remote(Repository *self, PyObject *args) { - Remote *py_remote; git_remote *remote; char *name = NULL, *url = NULL; int err; @@ -1291,13 +1290,7 @@ Repository_create_remote(Repository *self, PyObject *args) if (err < 0) return Error_set(err); - py_remote = PyObject_New(Remote, &RemoteType); - Py_INCREF(self); - py_remote->repo = self; - py_remote->remote = remote; - Remote_set_callbacks(py_remote); - - return (PyObject*) py_remote; + return (PyObject*) wrap_remote(remote, self); } @@ -1307,18 +1300,19 @@ PyObject * Repository_remotes__get__(Repository *self) { git_strarray remotes; + git_remote *remote = NULL; PyObject* py_list = NULL, *py_args = NULL; - Remote *py_remote; size_t i; + int err; git_remote_list(&remotes, self->repo); py_list = PyList_New(remotes.count); for (i=0; i < remotes.count; ++i) { - py_remote = PyObject_New(Remote, &RemoteType); - py_args = Py_BuildValue("Os", self, remotes.strings[i]); - Remote_init(py_remote, py_args, NULL); - PyList_SetItem(py_list, i, (PyObject*) py_remote); + err = git_remote_load(&remote, self->repo, remotes.strings[i]); + if (err < 0) + return Error_set(err); + PyList_SetItem(py_list, i, wrap_remote(remote, self)); } git_strarray_free(&remotes); From 9c95cb056048c53efbcaf955c648d397770e227f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Tue, 21 Jan 2014 02:58:30 +0100 Subject: [PATCH 0097/1630] IndexEntry: keep a copy of the underlying git_index_entry The tree entries exist more or less independently of the index they were retrieved from. The idea behind them is that they can be kept by a binding without needing to refer to the original anymore, which may disappear at any moment if the index is modified. Keep a copy of the data in TreeEntry instead of pointing to the one retrieved from the index, which is not safe to keep around. --- src/index.c | 19 +++++++++++++------ src/types.h | 2 +- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/index.c b/src/index.c index ce47f13a0..d9a12d84d 100644 --- a/src/index.c +++ b/src/index.c @@ -314,8 +314,15 @@ wrap_index_entry(const git_index_entry *entry, Index *index) IndexEntry *py_entry; py_entry = PyObject_New(IndexEntry, &IndexEntryType); - if (py_entry) - py_entry->entry = entry; + if (!py_entry) + return NULL; + + memcpy(&py_entry->entry, entry, sizeof(struct git_index_entry)); + py_entry->entry.path = strdup(entry->path); + if (!py_entry->entry.path) { + Py_CLEAR(py_entry); + return NULL; + } return (PyObject*)py_entry; } @@ -569,7 +576,7 @@ PyDoc_STRVAR(IndexEntry_mode__doc__, "Mode."); PyObject * IndexEntry_mode__get__(IndexEntry *self) { - return PyLong_FromLong(self->entry->mode); + return PyLong_FromLong(self->entry.mode); } @@ -578,7 +585,7 @@ PyDoc_STRVAR(IndexEntry_path__doc__, "Path."); PyObject * IndexEntry_path__get__(IndexEntry *self) { - return to_path(self->entry->path); + return to_path(self->entry.path); } @@ -587,7 +594,7 @@ PyDoc_STRVAR(IndexEntry_oid__doc__, "Object id."); PyObject * IndexEntry_oid__get__(IndexEntry *self) { - return git_oid_to_python(&self->entry->oid); + return git_oid_to_python(&self->entry.oid); } @@ -596,7 +603,7 @@ PyDoc_STRVAR(IndexEntry_hex__doc__, "Hex id."); PyObject * IndexEntry_hex__get__(IndexEntry *self) { - return git_oid_to_py_str(&self->entry->oid); + return git_oid_to_py_str(&self->entry.oid); } PyGetSetDef IndexEntry_getseters[] = { diff --git a/src/types.h b/src/types.h index 63a5672d5..1918c9d24 100644 --- a/src/types.h +++ b/src/types.h @@ -153,7 +153,7 @@ SIMPLE_TYPE(Index, git_index, index) typedef struct { PyObject_HEAD - const git_index_entry *entry; + git_index_entry entry; } IndexEntry; typedef struct { From d98a7014772e453f16fc9c5b2820b592e0ea780d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Tue, 21 Jan 2014 03:38:22 +0100 Subject: [PATCH 0098/1630] IndexEntry: allow creation of this object They are not required to belong to a particular index, so it should be possible to create them at runtime in order to insert them. --- src/index.c | 28 +++++++++++++++++++++++++++- src/pygit2.c | 2 +- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/src/index.c b/src/index.c index d9a12d84d..9add4a912 100644 --- a/src/index.c +++ b/src/index.c @@ -39,6 +39,7 @@ extern PyTypeObject TreeType; extern PyTypeObject DiffType; extern PyTypeObject IndexIterType; extern PyTypeObject IndexEntryType; +extern PyTypeObject OidType; int Index_init(Index *self, PyObject *args, PyObject *kwds) @@ -564,6 +565,31 @@ PyTypeObject IndexIterType = { (iternextfunc)IndexIter_iternext, /* tp_iternext */ }; +int +IndexEntry_init(IndexEntry *self, PyObject *args, PyObject *kwds) +{ + char *c_path = NULL; + Oid *id = NULL; + unsigned int mode; + char *keywords[] = {"path", "oid", "mode", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "sO!I", keywords, + &c_path, &OidType, &id, &mode)) + return -1; + + memset(&self->entry, 0, sizeof(struct git_index_entry)); + if (c_path) + self->entry.path = c_path; + + if (id) + git_oid_cpy(&self->entry.oid, &id->oid); + + if (mode) + self->entry.mode = mode; + + return 0; +} + void IndexEntry_dealloc(IndexEntry *self) { @@ -652,7 +678,7 @@ PyTypeObject IndexEntryType = { 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ - 0, /* tp_init */ + (initproc)IndexEntry_init, /* tp_init */ 0, /* tp_alloc */ 0, /* tp_new */ }; diff --git a/src/pygit2.c b/src/pygit2.c index 7c72e509c..aeb5d8f67 100644 --- a/src/pygit2.c +++ b/src/pygit2.c @@ -343,7 +343,7 @@ moduleinit(PyObject* m) * Index & Working copy */ INIT_TYPE(IndexType, NULL, PyType_GenericNew) - INIT_TYPE(IndexEntryType, NULL, NULL) + INIT_TYPE(IndexEntryType, NULL, PyType_GenericNew) INIT_TYPE(IndexIterType, NULL, NULL) ADD_TYPE(m, Index) ADD_TYPE(m, IndexEntry) From f79ae6b42166638788e0747fcb57957d7910ddf5 Mon Sep 17 00:00:00 2001 From: XTao Date: Wed, 29 Jan 2014 18:09:55 +0800 Subject: [PATCH 0099/1630] Drop Remote_init. --- src/remote.c | 50 +++++++++--------------------------------------- src/remote.h | 1 - src/repository.c | 19 ++++++++++++++---- 3 files changed, 24 insertions(+), 46 deletions(-) diff --git a/src/remote.c b/src/remote.c index 8201a9035..3f2388cb4 100644 --- a/src/remote.c +++ b/src/remote.c @@ -461,45 +461,6 @@ update_tips_cb(const char *refname, const git_oid *a, const git_oid *b, void *da return 0; } -PyObject * -Remote_init(Remote *self, PyObject *args, PyObject *kwds) -{ - Repository* py_repo = NULL; - char *name = NULL; - int err; - - if (!PyArg_ParseTuple(args, "O!s", &RepositoryType, &py_repo, &name)) - return NULL; - - self->repo = py_repo; - Py_INCREF(self->repo); - err = git_remote_load(&self->remote, py_repo->repo, name); - - if (err < 0) - return Error_set(err); - - self->progress = NULL; - self->transfer_progress = NULL; - self->update_tips = NULL; - - Remote_set_callbacks(self); - return (PyObject*) self; -} - -void -Remote_set_callbacks(Remote *self) -{ - git_remote_callbacks callbacks = GIT_REMOTE_CALLBACKS_INIT; - - self->progress = NULL; - - callbacks.progress = progress_cb; - callbacks.transfer_progress = transfer_progress_cb; - callbacks.update_tips = update_tips_cb; - callbacks.payload = self; - git_remote_set_callbacks(self->remote, &callbacks); -} - static void Remote_dealloc(Remote *self) { @@ -1077,7 +1038,7 @@ PyTypeObject RemoteType = { 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ - (initproc)Remote_init, /* tp_init */ + 0, /* tp_init */ 0, /* tp_alloc */ 0, /* tp_new */ }; @@ -1086,6 +1047,8 @@ PyObject * wrap_remote(git_remote *c_remote, Repository *repo) { Remote *py_remote = NULL; + git_remote_callbacks callbacks = GIT_REMOTE_CALLBACKS_INIT; + py_remote = PyObject_New(Remote, &RemoteType); if (py_remote) { Py_INCREF(repo); @@ -1094,7 +1057,12 @@ wrap_remote(git_remote *c_remote, Repository *repo) py_remote->progress = NULL; py_remote->transfer_progress = NULL; py_remote->update_tips = NULL; - Remote_set_callbacks(py_remote); + + callbacks.progress = progress_cb; + callbacks.transfer_progress = transfer_progress_cb; + callbacks.update_tips = update_tips_cb; + callbacks.payload = py_remote; + git_remote_set_callbacks(c_remote, &callbacks); } return (PyObject *)py_remote; diff --git a/src/remote.h b/src/remote.h index 1deedcd50..426314038 100644 --- a/src/remote.h +++ b/src/remote.h @@ -33,7 +33,6 @@ #include #include -PyObject* Remote_init(Remote *self, PyObject *args, PyObject *kwds); PyObject* Remote_fetch(Remote *self, PyObject *args); void Remote_set_callbacks(Remote *self); diff --git a/src/repository.c b/src/repository.c index c1fa44ccd..00d9cc55e 100644 --- a/src/repository.c +++ b/src/repository.c @@ -1301,7 +1301,8 @@ Repository_remotes__get__(Repository *self) { git_strarray remotes; git_remote *remote = NULL; - PyObject* py_list = NULL, *py_args = NULL; + PyObject *py_list = NULL; + PyObject *py_remote = NULL; size_t i; int err; @@ -1311,13 +1312,23 @@ Repository_remotes__get__(Repository *self) for (i=0; i < remotes.count; ++i) { err = git_remote_load(&remote, self->repo, remotes.strings[i]); if (err < 0) - return Error_set(err); - PyList_SetItem(py_list, i, wrap_remote(remote, self)); + goto cleanup; + py_remote = wrap_remote(remote, self); + if (py_remote == NULL) + goto cleanup; + PyList_SetItem(py_list, i, py_remote); } git_strarray_free(&remotes); - return (PyObject*) py_list; + +cleanup: + git_strarray_free(&remotes); + if (py_list) + Py_DECREF(py_list); + if (err < 0) + return Error_set(err); + return NULL; } PyDoc_STRVAR(Repository_default_signature__doc__, "Return the signature according to the repository's configuration"); From 5a785ba976bd3b74c43a6d14fef2dfe15ddb62b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Wed, 29 Jan 2014 11:26:13 +0100 Subject: [PATCH 0100/1630] Remove left over declaration of Remote_set_callbacks --- src/remote.h | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/remote.h b/src/remote.h index 426314038..1c8605f8e 100644 --- a/src/remote.h +++ b/src/remote.h @@ -34,8 +34,6 @@ #include PyObject* Remote_fetch(Remote *self, PyObject *args); - -void Remote_set_callbacks(Remote *self); -PyObject *wrap_remote(git_remote *c_remote, Repository *repo); +PyObject* wrap_remote(git_remote *c_remote, Repository *repo); #endif From f6389ee2c30ea0807a86742736e6adfc39f6f7b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Tue, 21 Jan 2014 04:06:31 +0100 Subject: [PATCH 0101/1630] IndexEntry: make the attributes writable When updating entries in an index, it is necessary to modify the attributes of tree entries. make it possible to do so. --- src/index.c | 47 +++++++++++++++++++++++++++++++++++++++++++--- test/test_index.py | 13 +++++++++++++ 2 files changed, 57 insertions(+), 3 deletions(-) diff --git a/src/index.c b/src/index.c index 9add4a912..ac08898a3 100644 --- a/src/index.c +++ b/src/index.c @@ -605,6 +605,19 @@ IndexEntry_mode__get__(IndexEntry *self) return PyLong_FromLong(self->entry.mode); } +int +IndexEntry_mode__set__(IndexEntry *self, PyObject *py_mode) +{ + long c_val; + + c_val = PyLong_AsLong(py_mode); + if (c_val == -1 && PyErr_Occurred()) + return -1; + + self->entry.mode = (unsigned int) c_val; + + return 0; +} PyDoc_STRVAR(IndexEntry_path__doc__, "Path."); @@ -614,6 +627,26 @@ IndexEntry_path__get__(IndexEntry *self) return to_path(self->entry.path); } +int +IndexEntry_path__set__(IndexEntry *self, PyObject *py_path) +{ + char *c_inner, *c_path; + + c_inner = py_str_to_c_str(py_path, NULL); + if (!c_inner) + return -1; + + c_path = strdup(c_inner); + if (!c_path) { + PyErr_NoMemory(); + return -1; + } + + free(self->entry.path); + self->entry.path = c_path; + + return 0; +} PyDoc_STRVAR(IndexEntry_oid__doc__, "Object id."); @@ -623,6 +656,14 @@ IndexEntry_oid__get__(IndexEntry *self) return git_oid_to_python(&self->entry.oid); } +int +IndexEntry_oid__set__(IndexEntry *self, PyObject *py_id) +{ + if (!py_oid_to_git_oid(py_id, &self->entry.oid)) + return -1; + + return 0; +} PyDoc_STRVAR(IndexEntry_hex__doc__, "Hex id."); @@ -633,9 +674,9 @@ IndexEntry_hex__get__(IndexEntry *self) } PyGetSetDef IndexEntry_getseters[] = { - GETTER(IndexEntry, mode), - GETTER(IndexEntry, path), - GETTER(IndexEntry, oid), + GETSET(IndexEntry, mode), + GETSET(IndexEntry, path), + GETSET(IndexEntry, oid), GETTER(IndexEntry, hex), {NULL}, }; diff --git a/test/test_index.py b/test/test_index.py index 8613020e4..47b98b090 100644 --- a/test/test_index.py +++ b/test/test_index.py @@ -139,6 +139,19 @@ def test_remove(self): index.remove('hello.txt') self.assertFalse('hello.txt' in index) + def test_change_attributes(self): + index = self.repo.index + entry = index['hello.txt'] + ign_entry = index['.gitignore'] + self.assertNotEqual(ign_entry.oid, entry.oid) + self.assertNotEqual(entry.mode, pygit2.GIT_FILEMODE_BLOB_EXECUTABLE) + entry.path = 'foo.txt' + entry.oid = ign_entry.oid + entry.mode = pygit2.GIT_FILEMODE_BLOB_EXECUTABLE + self.assertEqual('foo.txt', entry.path) + self.assertEqual(ign_entry.oid, entry.oid) + self.assertEqual(pygit2.GIT_FILEMODE_BLOB_EXECUTABLE, entry.mode) + if __name__ == '__main__': unittest.main() From c43c320c3e099901efc5591ec21535a47a06dfc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Tue, 21 Jan 2014 04:47:57 +0100 Subject: [PATCH 0102/1630] Index: accept adding either a path or an IndexEntry A path is only useful if we have the file on the worktree. Passing an IndexEntry allows us to add an entry with arbitrary attributes. --- docs/working-copy.rst | 3 +++ src/index.c | 12 +++++++++++- test/test_index.py | 9 +++++++++ 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/docs/working-copy.rst b/docs/working-copy.rst index ab8c6fe23..76bcada87 100644 --- a/docs/working-copy.rst +++ b/docs/working-copy.rst @@ -22,6 +22,9 @@ Index write:: >>> del index['path/to/file'] # git rm >>> index.write() # don't forget to save the changes +Custom entries:: + >>> entry = pygit2.IndexEntry('README.md', blob_id, blob_filemode) + >>> repo.index.add(entry) The Index type ==================== diff --git a/src/index.c b/src/index.c index ac08898a3..e0b0ed4c4 100644 --- a/src/index.c +++ b/src/index.c @@ -82,7 +82,7 @@ Index_traverse(Index *self, visitproc visit, void *arg) PyDoc_STRVAR(Index_add__doc__, - "add(path)\n" + "add([path|entry])\n" "\n" "Add or update an index entry from a file in disk."); @@ -91,6 +91,16 @@ Index_add(Index *self, PyObject *args) { int err; const char *path; + IndexEntry *py_entry; + + if (PyArg_ParseTuple(args, "O!", &IndexEntryType, &py_entry)) { + err = git_index_add(self->index, &py_entry->entry); + if (err < 0) + return Error_set(err); + + Py_RETURN_NONE; + } + PyErr_Clear(); if (!PyArg_ParseTuple(args, "s", &path)) return NULL; diff --git a/test/test_index.py b/test/test_index.py index 47b98b090..6242efa79 100644 --- a/test/test_index.py +++ b/test/test_index.py @@ -152,6 +152,15 @@ def test_change_attributes(self): self.assertEqual(ign_entry.oid, entry.oid) self.assertEqual(pygit2.GIT_FILEMODE_BLOB_EXECUTABLE, entry.mode) +class IndexEntryTest(utils.RepoTestCase): + + def test_create_entry(self): + index = self.repo.index + hello_entry = index['hello.txt'] + entry = pygit2.IndexEntry('README.md', hello_entry.oid, hello_entry.mode) + index.add(entry) + tree_id = index.write_tree() + self.assertEqual('60e769e57ae1d6a2ab75d8d253139e6260e1f912', str(tree_id)) if __name__ == '__main__': unittest.main() From f3f3d28637b1a183f31cc920d93ec22227674770 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sun, 26 Jan 2014 05:00:45 +0100 Subject: [PATCH 0103/1630] Index: allow writing the tree to a particular repository Take an optional repository in Index.write_tree() to serialize to a tree into a particular repository's odb. --- src/index.c | 20 +++++++++++++++----- src/index.h | 2 +- test/test_index.py | 9 +++++++++ 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/src/index.c b/src/index.c index e0b0ed4c4..2149a70e2 100644 --- a/src/index.c +++ b/src/index.c @@ -40,6 +40,7 @@ extern PyTypeObject DiffType; extern PyTypeObject IndexIterType; extern PyTypeObject IndexEntryType; extern PyTypeObject OidType; +extern PyTypeObject RepositoryType; int Index_init(Index *self, PyObject *args, PyObject *kwds) @@ -428,17 +429,26 @@ Index_read_tree(Index *self, PyObject *value) PyDoc_STRVAR(Index_write_tree__doc__, - "write_tree() -> Oid\n" + "write_tree([repo]) -> Oid\n" "\n" - "Create a tree object from the index file, return its oid."); + "Create a tree object from the index file, return its oid.\n" + "If 'repo' is passed, write to that repository's odb."); PyObject * -Index_write_tree(Index *self) +Index_write_tree(Index *self, PyObject *args) { git_oid oid; + Repository *repo = NULL; int err; - err = git_index_write_tree(&oid, self->index); + if (!PyArg_ParseTuple(args, "|O!", &RepositoryType, &repo)) + return NULL; + + if (repo) + err = git_index_write_tree_to(&oid, self->index, repo->repo); + else + err = git_index_write_tree(&oid, self->index); + if (err < 0) return Error_set(err); @@ -455,7 +465,7 @@ PyMethodDef Index_methods[] = { METHOD(Index, read, METH_VARARGS), METHOD(Index, write, METH_NOARGS), METHOD(Index, read_tree, METH_O), - METHOD(Index, write_tree, METH_NOARGS), + METHOD(Index, write_tree, METH_VARARGS), {NULL} }; diff --git a/src/index.h b/src/index.h index f38cb4a04..e59262ca6 100644 --- a/src/index.h +++ b/src/index.h @@ -40,7 +40,7 @@ PyObject* Index_write(Index *self); PyObject* Index_iter(Index *self); PyObject* Index_getitem(Index *self, PyObject *value); PyObject* Index_read_tree(Index *self, PyObject *value); -PyObject* Index_write_tree(Index *self); +PyObject* Index_write_tree(Index *self, PyObject *args); Py_ssize_t Index_len(Index *self); int Index_setitem(Index *self, PyObject *key, PyObject *value); diff --git a/test/test_index.py b/test/test_index.py index 6242efa79..1afdf56ce 100644 --- a/test/test_index.py +++ b/test/test_index.py @@ -31,6 +31,7 @@ from __future__ import unicode_literals import os import unittest +import tempfile import pygit2 from . import utils @@ -152,6 +153,14 @@ def test_change_attributes(self): self.assertEqual(ign_entry.oid, entry.oid) self.assertEqual(pygit2.GIT_FILEMODE_BLOB_EXECUTABLE, entry.mode) + def test_write_tree_to(self): + path = tempfile.mkdtemp() + pygit2.init_repository(path) + nrepo = pygit2.Repository(path) + + id = self.repo.index.write_tree(nrepo) + self.assertNotEqual(None, nrepo[id]) + class IndexEntryTest(utils.RepoTestCase): def test_create_entry(self): From b9bf1175e266d387c028caf485c587e9c72b15cd Mon Sep 17 00:00:00 2001 From: Alexander Bayandin Date: Sun, 2 Feb 2014 12:11:19 +0300 Subject: [PATCH 0104/1630] Run tests on travis CI for pypy Because #209 is done --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 2279b1581..f2ffa47a8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,7 @@ python: - "2.7" - "3.2" - "3.3" + - "pypy" env: LIBGIT2=~/libgit2/_install/ LD_LIBRARY_PATH=~/libgit2/_install/lib From 977c315c21a81db5da319336b9296eff7cd12d63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Sun, 2 Feb 2014 14:05:43 +0100 Subject: [PATCH 0105/1630] Preparing for release --- README.rst | 51 +++++++++++++++++++++++++-------- docs/config.rst | 15 +++++----- docs/objects.rst | 10 ++++--- docs/oid.rst | 2 +- docs/remotes.rst | 6 ++-- src/remote.c | 69 ++++----------------------------------------- src/tree.c | 6 ++-- test/test_remote.py | 50 +++++++++++++++----------------- 8 files changed, 89 insertions(+), 120 deletions(-) diff --git a/README.rst b/README.rst index 5d116a7e0..53d69ab59 100644 --- a/README.rst +++ b/README.rst @@ -79,6 +79,43 @@ Authors Changelog ============== +0.20.2 (2014-02-XX) +------------------- + +- New ``Blob.diff(...)`` and ``Blob.diff_to_buffer(...)`` + `#307 `_ + +- New ``Repository.default_signature`` + `#310 `_ + +- New ``Commit.tree_id`` and ``Commit.parent_ids`` + `#730 `_ + + +- New rich comparison between tree entries +- New ``Config`` iterator replaces ``Config.foreach`` +- New type ``Refspec`` +- New ``Remote.push_url`` +- New ``Remote.add_push`` and ``Remote.add_fetch`` +- New ``Remote.fetch_refspecs`` replaces ``Remote.get_fetch_refspecs()`` and + ``Remote.set_fetch_refspecs(...)`` +- New ``Remote.push_refspecs`` replaces ``Remote.get_push_refspecs()`` and + ``Remote.set_push_refspecs(...)`` +- Now *path* in ``Tree`` works +- New ``str(Oid)`` deprecates ``Oid.hex`` +- New ``Object.id`` deprecates ``Object.oid`` +- New ``TreeEntry.id`` deprecates ``TreeEntry.oid`` +- New ``Remote.progress``, ``Remote.transfer_progress`` and + ``Remote.update_tips`` +- New type ``TransferProgress`` +- Now possible to create ``IndexEntry(...)`` +- Now ``IndexEntry.path``, ``IndexEntry.oid`` and ``IndexEntry.mode`` are + writable +- Now ``Index.add(...)`` accepts an ``IndexEntry`` too +- Now ``Index.write_tree(...)`` is able to write to a different repository +- Support pypy + + 0.20.1 (2013-12-24) ------------------- @@ -117,18 +154,10 @@ Changelog - Upgrade to libgit2 v0.20.0: `#288 `_ - Rename ``Repository.head_is_orphaned`` to ``Repository.head_is_unborn`` - - Prototype of ``pygit2.clone_repository(...)`` changed:: - - # Before - pygit2.clone_repository(url, path, bare=False, remote_name='origin', - push_url=None, fetch_spec=None, push_spec=None, - checkout_branch=None) +- New ``Repository.head_is_unborn`` replaces ``Repository.head_is_orphaned`` - # Now - pygit2.clone_repository(url, path, bare=False, ignore_cert_errors=False, - remote_name='origin', checkout_branch=None) +- Changed ``pygit2.clone_repository(...)``. Drop ``push_url``, ``fetch_spec`` + and ``push_spec`` parameters. Add ``ignore_cert_errors``. - New ``Patch.additions`` and ``Patch.deletions``: `#275 `_ diff --git a/docs/config.rst b/docs/config.rst index ef3a1a54f..e9b5c647b 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -14,13 +14,12 @@ The Config type .. automethod:: pygit2.Config.get_multivar .. automethod:: pygit2.Config.set_multivar -The :class:`Config` Mapping interface. +.. method:: for (name, value) in Config -Iterator -========= + The :class:`Config` class has an iterator which can be used to loop + through all the entries in the configuration. Each element is a tuple + containing the name and the value of each configuration variable. Be + aware that this may return multiple versions of each entry if they are + set multiple times in the configuration files. -The :class:`Config` class has an iterator which can be used to loop -through all the entries in the configuration. Each element is a tuple -containing the name and the value of each configuration variable. Be -aware that this may return multiple versions of each entry if they are -set multiple times in the configuration files. +The :class:`Config` Mapping interface. diff --git a/docs/objects.rst b/docs/objects.rst index 4c868c95a..432481b4f 100644 --- a/docs/objects.rst +++ b/docs/objects.rst @@ -79,8 +79,6 @@ New objects are created using an specific API we will see later. This is the common interface for all Git objects: .. autoattribute:: pygit2.Object.id -.. autoattribute:: pygit2.Object.oid -.. autoattribute:: pygit2.Object.hex .. autoattribute:: pygit2.Object.type .. automethod:: pygit2.Object.read_raw @@ -112,6 +110,9 @@ This is their API: .. autoattribute:: pygit2.Blob.is_binary +.. automethod:: pygit2.Blob.diff +.. automethod:: pygit2.Blob.diff_to_buffer + Creating blobs -------------- @@ -172,11 +173,12 @@ Tree entries .. autoattribute:: pygit2.TreeEntry.name .. autoattribute:: pygit2.TreeEntry.id -.. autoattribute:: pygit2.TreeEntry.oid .. autoattribute:: pygit2.TreeEntry.hex .. autoattribute:: pygit2.TreeEntry.filemode -:class:`TreeEntry` supports comparison against other tree entries. +.. method:: cmp(TreeEntry, TreeEntry) + + Rich comparison between tree entries. Example:: diff --git a/docs/oid.rst b/docs/oid.rst index c01e1afae..66adcdb42 100644 --- a/docs/oid.rst +++ b/docs/oid.rst @@ -65,7 +65,7 @@ And the other way around, from an Oid object we can get the hexadecimal and raw forms. You can use the built-in `str()` (or `unicode()` in python 2) to get the hexadecimal representation of the Oid. -.. autoattribute:: pygit2.Oid.hex +.. automethod:: str(pygit2.Oid) .. autoattribute:: pygit2.Oid.raw The Oid type supports: diff --git a/docs/remotes.rst b/docs/remotes.rst index e7c512977..f77bd7393 100644 --- a/docs/remotes.rst +++ b/docs/remotes.rst @@ -19,14 +19,12 @@ The Remote type .. autoattribute:: pygit2.Remote.progress .. autoattribute:: pygit2.Remote.transfer_progress .. autoattribute:: pygit2.Remote.update_tips -.. automethod:: pygit2.Remote.get_push_refspecs -.. automethod:: pygit2.Remote.get_fetch_refspecs -.. automethod:: pygit2.Remote.set_push_refspecs -.. automethod:: pygit2.Remote.set_fetch_refspecs .. automethod:: pygit2.Remote.get_refspec .. automethod:: pygit2.Remote.fetch .. automethod:: pygit2.Remote.push .. automethod:: pygit2.Remote.save +.. automethod:: pygit2.Remote.add_push +.. automethod:: pygit2.Remote.add_fetch The TransferProgress type =========================== diff --git a/src/remote.c b/src/remote.c index 3f2388cb4..63d11c8ca 100644 --- a/src/remote.c +++ b/src/remote.c @@ -43,9 +43,9 @@ extern PyTypeObject TransferProgressType; Refspec * wrap_refspec(const Remote *owner, const git_refspec *refspec) { - Refspec *spec; + Refspec *spec; - spec = PyObject_New(Refspec, &RefspecType); + spec = PyObject_New(Refspec, &RefspecType); if (!spec) return NULL; @@ -258,7 +258,7 @@ PyDoc_STRVAR(Refspec__doc__, "Refspec object."); PyTypeObject RefspecType = { PyVarObject_HEAD_INIT(NULL, 0) "_pygit2.Refspec", /* tp_name */ - sizeof(Refspec), /* tp_basicsize */ + sizeof(Refspec), /* tp_basicsize */ 0, /* tp_itemsize */ (destructor)Refspec_dealloc, /* tp_dealloc */ 0, /* tp_print */ @@ -331,7 +331,7 @@ PyMemberDef TransferProgress_members[] = { RMEMBER(TransferProgress, indexed_deltas, T_UINT, "Deltas which have been indexed"), /* FIXME: technically this is unsigned, but there's no value for size_t here. */ RMEMBER(TransferProgress, received_bytes, T_PYSSIZET, "Number of bytes received up to now"), - {NULL}, + {NULL}, }; PyDoc_STRVAR(TransferProgress__doc__, "Progress downloading and indexing data during a fetch"); @@ -639,59 +639,6 @@ Remote_push_refspecs__set__(Remote *self, PyObject *py_list) return 0; } -PyDoc_STRVAR(Remote_get_fetch_refspecs__doc__, - "Fetch refspecs.\n" - "This function is deprecated, please use the fetch_refspecs attribute" - "\n"); - - -PyObject * -Remote_get_fetch_refspecs(Remote *self) -{ - return Remote_fetch_refspecs__get__(self); -} - - -PyDoc_STRVAR(Remote_get_push_refspecs__doc__, "Push refspecs"); - - -PyObject * -Remote_get_push_refspecs(Remote *self) -{ - return Remote_push_refspecs__get__(self); -} - -PyDoc_STRVAR(Remote_set_fetch_refspecs__doc__, - "set_fetch_refspecs([str])\n" - "This function is deprecated, please use the push_refspecs attribute" - "\n"); - - -PyObject * -Remote_set_fetch_refspecs(Remote *self, PyObject *args) -{ - if (Remote_fetch_refspecs__set__(self, args) < 0) - return NULL; - - Py_RETURN_NONE; -} - - -PyDoc_STRVAR(Remote_set_push_refspecs__doc__, - "set_push_refspecs([str])\n" - "This function is deprecated, please use the push_refspecs attribute" - "\n"); - - -PyObject * -Remote_set_push_refspecs(Remote *self, PyObject *args) -{ - if (Remote_push_refspecs__set__(self, args) < 0) - return NULL; - - Py_RETURN_NONE; -} - PyDoc_STRVAR(Remote_url__doc__, "Url of the remote"); @@ -699,7 +646,7 @@ PyDoc_STRVAR(Remote_url__doc__, "Url of the remote"); PyObject * Remote_url__get__(Remote *self) { - const char *url; + const char *url; url = git_remote_url(self->remote); if (!url) @@ -735,7 +682,7 @@ PyDoc_STRVAR(Remote_push_url__doc__, "Push url of the remote"); PyObject * Remote_push_url__get__(Remote *self) { - const char *url; + const char *url; url = git_remote_pushurl(self->remote); if (!url) @@ -976,10 +923,6 @@ PyMethodDef Remote_methods[] = { METHOD(Remote, push, METH_VARARGS), METHOD(Remote, add_push, METH_VARARGS), METHOD(Remote, add_fetch, METH_VARARGS), - METHOD(Remote, get_fetch_refspecs, METH_NOARGS), - METHOD(Remote, set_fetch_refspecs, METH_O), - METHOD(Remote, get_push_refspecs, METH_NOARGS), - METHOD(Remote, set_push_refspecs, METH_O), {NULL} }; diff --git a/src/tree.c b/src/tree.c index bd2c0870e..dae77dcd2 100644 --- a/src/tree.c +++ b/src/tree.c @@ -201,9 +201,11 @@ Tree_len(Tree *self) int Tree_contains(Tree *self, PyObject *py_name) { - int err; + int err; git_tree_entry *entry; - char *name = py_path_to_c_str(py_name); + char *name; + + name = py_path_to_c_str(py_name); if (name == NULL) return -1; diff --git a/test/test_remote.py b/test/test_remote.py index ce146e0ed..d98bfc5c9 100644 --- a/test/test_remote.py +++ b/test/test_remote.py @@ -92,53 +92,49 @@ def test_refspec(self): self.assertEqual(True, refspec.force) self.assertEqual(ORIGIN_REFSPEC, refspec.string) - self.assertEqual(list, type(remote.get_fetch_refspecs())) - self.assertEqual(1, len(remote.get_fetch_refspecs())) - self.assertEqual(ORIGIN_REFSPEC, remote.get_fetch_refspecs()[0]) + self.assertEqual(list, type(remote.fetch_refspecs)) + self.assertEqual(1, len(remote.fetch_refspecs)) + self.assertEqual(ORIGIN_REFSPEC, remote.fetch_refspecs[0]) self.assertTrue(refspec.src_matches('refs/heads/master')) self.assertTrue(refspec.dst_matches('refs/remotes/origin/master')) self.assertEqual('refs/remotes/origin/master', refspec.transform('refs/heads/master')) self.assertEqual('refs/heads/master', refspec.rtransform('refs/remotes/origin/master')) - self.assertEqual(list, type(remote.get_push_refspecs())) - self.assertEqual(0, len(remote.get_push_refspecs())) + self.assertEqual(list, type(remote.push_refspecs)) + self.assertEqual(0, len(remote.push_refspecs)) push_specs = remote.push_refspecs self.assertEqual(list, type(push_specs)) self.assertEqual(0, len(push_specs)) - remote.set_fetch_refspecs(['+refs/*:refs/remotes/*']) - self.assertEqual('+refs/*:refs/remotes/*', - remote.get_fetch_refspecs()[0]) + remote.fetch_refspecs = ['+refs/*:refs/remotes/*'] + self.assertEqual('+refs/*:refs/remotes/*', remote.fetch_refspecs[0]) fetch_specs = remote.fetch_refspecs self.assertEqual(list, type(fetch_specs)) self.assertEqual(1, len(fetch_specs)) self.assertEqual('+refs/*:refs/remotes/*', fetch_specs[0]) - remote.set_fetch_refspecs([ - '+refs/*:refs/remotes/*', - '+refs/test/*:refs/test/remotes/*' - ]) - self.assertEqual('+refs/*:refs/remotes/*', - remote.get_fetch_refspecs()[0]) + remote.fetch_refspecs = ['+refs/*:refs/remotes/*', + '+refs/test/*:refs/test/remotes/*'] + self.assertEqual('+refs/*:refs/remotes/*', remote.fetch_refspecs[0]) self.assertEqual('+refs/test/*:refs/test/remotes/*', - remote.get_fetch_refspecs()[1]) + remote.fetch_refspecs[1]) - remote.set_push_refspecs([ - '+refs/*:refs/remotes/*', - '+refs/test/*:refs/test/remotes/*' - ]) + remote.push_refspecs = ['+refs/*:refs/remotes/*', + '+refs/test/*:refs/test/remotes/*'] - self.assertRaises(TypeError, setattr, remote, 'push_refspecs', '+refs/*:refs/*') - self.assertRaises(TypeError, setattr, remote, 'fetch_refspecs', '+refs/*:refs/*') - self.assertRaises(TypeError, setattr, remote, 'fetch_refspecs', ['+refs/*:refs/*', 5]) + self.assertRaises(TypeError, setattr, remote, 'push_refspecs', + '+refs/*:refs/*') + self.assertRaises(TypeError, setattr, remote, 'fetch_refspecs', + '+refs/*:refs/*') + self.assertRaises(TypeError, setattr, remote, 'fetch_refspecs', + ['+refs/*:refs/*', 5]) - self.assertEqual('+refs/*:refs/remotes/*', - remote.get_push_refspecs()[0]) + self.assertEqual('+refs/*:refs/remotes/*', remote.push_refspecs[0]) self.assertEqual('+refs/test/*:refs/test/remotes/*', - remote.get_push_refspecs()[1]) + remote.push_refspecs[1]) def test_remote_list(self): @@ -169,10 +165,10 @@ def test_add_refspec(self): remote = self.repo.create_remote('test_add_refspec', REMOTE_URL) remote.add_push('refs/heads/*:refs/heads/test_refspec/*') self.assertEqual('refs/heads/*:refs/heads/test_refspec/*', - remote.get_push_refspecs()[0]) + remote.push_refspecs[0]) remote.add_fetch('+refs/heads/*:refs/remotes/test_refspec/*') self.assertEqual('+refs/heads/*:refs/remotes/test_refspec/*', - remote.get_fetch_refspecs()[1]) + remote.fetch_refspecs[1]) def test_remote_callback_typecheck(self): remote = self.repo.remotes[0] From b95ee3758c4eb44b195758d3e83bc061936c080a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Sun, 2 Feb 2014 23:32:21 +0100 Subject: [PATCH 0106/1630] Preparing release --- README.rst | 56 ++++++++++++++++++++++++++++++++++++++++++++++++---- docs/oid.rst | 2 +- 2 files changed, 53 insertions(+), 5 deletions(-) diff --git a/README.rst b/README.rst index 53d69ab59..1be1203de 100644 --- a/README.rst +++ b/README.rst @@ -82,6 +82,11 @@ Changelog 0.20.2 (2014-02-XX) ------------------- +- Support pypy + `#209 `_ + `#327 `_ + `#333 `_ + - New ``Blob.diff(...)`` and ``Blob.diff_to_buffer(...)`` `#307 `_ @@ -89,31 +94,74 @@ Changelog `#310 `_ - New ``Commit.tree_id`` and ``Commit.parent_ids`` - `#730 `_ + `#73 `_ + `#311 `_ +- New ``Config`` iterator replaces ``Config.foreach`` + `#183 `_ + `#312 `_ - New rich comparison between tree entries -- New ``Config`` iterator replaces ``Config.foreach`` + `#305 `_ + `#313 `_ + - New type ``Refspec`` + `#314 `_ + - New ``Remote.push_url`` + `#315 `_ + +- Now ``path in Tree`` works + `#306 `_ + `#316 `_ + - New ``Remote.add_push`` and ``Remote.add_fetch`` + `#255 `_ + `#318 `_ + - New ``Remote.fetch_refspecs`` replaces ``Remote.get_fetch_refspecs()`` and ``Remote.set_fetch_refspecs(...)`` + `#319 `_ + - New ``Remote.push_refspecs`` replaces ``Remote.get_push_refspecs()`` and ``Remote.set_push_refspecs(...)`` -- Now *path* in ``Tree`` works + `#319 `_ + - New ``str(Oid)`` deprecates ``Oid.hex`` + `#322 `_ + - New ``Object.id`` deprecates ``Object.oid`` + `#322 `_ + - New ``TreeEntry.id`` deprecates ``TreeEntry.oid`` + `#322 `_ + - New ``Remote.progress``, ``Remote.transfer_progress`` and ``Remote.update_tips`` + `#274 `_ + `#324 `_ + - New type ``TransferProgress`` + `#274 `_ + `#324 `_ + - Now possible to create ``IndexEntry(...)`` + `#325 `_ + - Now ``IndexEntry.path``, ``IndexEntry.oid`` and ``IndexEntry.mode`` are writable + `#325 `_ + - Now ``Index.add(...)`` accepts an ``IndexEntry`` too + `#325 `_ + - Now ``Index.write_tree(...)`` is able to write to a different repository -- Support pypy + `#325 `_ + +- Other non user visible changes: + `#331 `_ + and + `#332 `_ 0.20.1 (2013-12-24) diff --git a/docs/oid.rst b/docs/oid.rst index 66adcdb42..ff5cad803 100644 --- a/docs/oid.rst +++ b/docs/oid.rst @@ -65,7 +65,7 @@ And the other way around, from an Oid object we can get the hexadecimal and raw forms. You can use the built-in `str()` (or `unicode()` in python 2) to get the hexadecimal representation of the Oid. -.. automethod:: str(pygit2.Oid) +.. method:: str(pygit2.Oid) .. autoattribute:: pygit2.Oid.raw The Oid type supports: From 873b38bbc3b1f56618c8ba796ccfc62af4325da4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Mon, 3 Feb 2014 22:39:58 +0100 Subject: [PATCH 0107/1630] Update README --- README.rst | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 1be1203de..3b6767a86 100644 --- a/README.rst +++ b/README.rst @@ -158,10 +158,12 @@ Changelog - Now ``Index.write_tree(...)`` is able to write to a different repository `#325 `_ +- Fix refcount leak in ``Repository.remotes`` + `#321 `_ + `#332 `_ + - Other non user visible changes: `#331 `_ - and - `#332 `_ 0.20.1 (2013-12-24) From c04db1f66dcc2c88b9066ec2d73d0588a61b3090 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Tue, 4 Feb 2014 07:25:00 +0100 Subject: [PATCH 0108/1630] Split the Refspec type to its own file --- src/refspec.c | 293 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/refspec.h | 38 +++++++ src/remote.c | 262 +------------------------------------------- 3 files changed, 334 insertions(+), 259 deletions(-) create mode 100644 src/refspec.c create mode 100644 src/refspec.h diff --git a/src/refspec.c b/src/refspec.c new file mode 100644 index 000000000..ce679df38 --- /dev/null +++ b/src/refspec.c @@ -0,0 +1,293 @@ +/* + * 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 +#include "error.h" +#include "types.h" +#include "utils.h" +#include "refspec.h" + + +extern PyTypeObject RefspecType; + +Refspec * +wrap_refspec(const Remote *owner, const git_refspec *refspec) +{ + Refspec *spec; + + spec = PyObject_New(Refspec, &RefspecType); + if (!spec) + return NULL; + + Py_INCREF(owner); + spec->owner = owner; + spec->refspec = refspec; + + return spec; +} + +PyDoc_STRVAR(Refspec_direction__doc__, + "The direction of this refspec (fetch or push)"); + +PyObject * +Refspec_direction__get__(Refspec *self) +{ + return Py_BuildValue("i", git_refspec_direction(self->refspec)); +} + +PyDoc_STRVAR(Refspec_src__doc__, "Source or lhs of the refspec"); + +PyObject * +Refspec_src__get__(Refspec *self) +{ + return to_unicode(git_refspec_src(self->refspec), NULL, NULL); +} + +PyDoc_STRVAR(Refspec_dst__doc__, "Destination or rhs of the refspec"); + +PyObject * +Refspec_dst__get__(Refspec *self) +{ + return to_unicode(git_refspec_dst(self->refspec), NULL, NULL); +} + +PyDoc_STRVAR(Refspec_string__doc__, "String used to create this refspec"); + +PyObject * +Refspec_string__get__(Refspec *self) +{ + return to_unicode(git_refspec_string(self->refspec), NULL, NULL); +} + +PyDoc_STRVAR(Refspec_force__doc__, + "Whether this refspec allows non-fast-forward updates"); + +PyObject * +Refspec_force__get__(Refspec *self) +{ + if (git_refspec_force(self->refspec)) + Py_RETURN_TRUE; + + Py_RETURN_FALSE; +} + +PyDoc_STRVAR(Refspec_src_matches__doc__, + "src_matches(str) -> Bool\n" + "\n" + "Returns whether the string matches the source refspec\n"); + +PyObject * +Refspec_src_matches(Refspec *self, PyObject *py_str) +{ + char *str; + int res; + + str = py_str_to_c_str(py_str, NULL); + if (!str) + return NULL; + + res = git_refspec_src_matches(self->refspec, str); + free(str); + + if (res) + Py_RETURN_TRUE; + + Py_RETURN_FALSE; +} + +PyDoc_STRVAR(Refspec_dst_matches__doc__, + "dst_matches(str) -> Bool\n" + "\n" + "Returns whether the string matches the destination refspec\n"); + +PyObject * +Refspec_dst_matches(Refspec *self, PyObject *py_str) +{ + char *str; + int res; + + str = py_str_to_c_str(py_str, NULL); + if (!str) + return NULL; + + res = git_refspec_dst_matches(self->refspec, str); + free(str); + + if (res) + Py_RETURN_TRUE; + + Py_RETURN_FALSE; +} + +PyDoc_STRVAR(Refspec_transform__doc__, + "transform(str) -> str\n" + "\n" + "Transform a reference according to the refspec\n"); + +PyObject * +Refspec_transform(Refspec *self, PyObject *py_str) +{ + char *str, *trans; + int err, len, alen; + PyObject *py_trans; + + str = py_str_to_c_str(py_str, NULL); + alen = len = strlen(str); + + do { + alen *= alen; + trans = malloc(alen); + if (!trans) { + free(str); + return PyErr_NoMemory(); + } + + err = git_refspec_transform(trans, alen, self->refspec, str); + } while(err == GIT_EBUFS); + free(str); + + if (err < 0) { + free(trans); + Error_set(err); + return NULL; + } + + py_trans = to_unicode(trans, NULL, NULL); + + free(trans); + + return py_trans; +} + +PyDoc_STRVAR(Refspec_rtransform__doc__, + "rtransform(str) -> str\n" + "\n" + "Transform a reference according to the refspec in reverse\n"); + +PyObject * +Refspec_rtransform(Refspec *self, PyObject *py_str) +{ + char *str, *trans; + int err, len, alen; + PyObject *py_trans; + + str = py_str_to_c_str(py_str, NULL); + alen = len = strlen(str); + + do { + alen *= alen; + trans = malloc(alen); + if (!trans) { + free(str); + return PyErr_NoMemory(); + } + + err = git_refspec_rtransform(trans, alen, self->refspec, str); + } while(err == GIT_EBUFS); + free(str); + + if (err < 0) { + free(trans); + Error_set(err); + return NULL; + } + + py_trans = to_unicode(trans, NULL, NULL); + + free(trans); + + return py_trans; +} + +PyMethodDef Refspec_methods[] = { + METHOD(Refspec, src_matches, METH_O), + METHOD(Refspec, dst_matches, METH_O), + METHOD(Refspec, transform, METH_O), + METHOD(Refspec, rtransform, METH_O), + {NULL} +}; + +PyGetSetDef Refspec_getseters[] = { + GETTER(Refspec, direction), + GETTER(Refspec, src), + GETTER(Refspec, dst), + GETTER(Refspec, string), + GETTER(Refspec, force), + {NULL} +}; + +static void +Refspec_dealloc(Refspec *self) +{ + Py_CLEAR(self->owner); + PyObject_Del(self); +} + +PyDoc_STRVAR(Refspec__doc__, "Refspec object."); + +PyTypeObject RefspecType = { + PyVarObject_HEAD_INIT(NULL, 0) + "_pygit2.Refspec", /* tp_name */ + sizeof(Refspec), /* tp_basicsize */ + 0, /* tp_itemsize */ + (destructor)Refspec_dealloc, /* 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 | Py_TPFLAGS_BASETYPE, /* tp_flags */ + Refspec__doc__, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + Refspec_methods, /* tp_methods */ + 0, /* tp_members */ + Refspec_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/refspec.h b/src/refspec.h new file mode 100644 index 000000000..e653bf59b --- /dev/null +++ b/src/refspec.h @@ -0,0 +1,38 @@ +/* + * 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_refspec_h +#define INCLUDE_pygit2_refspec_h + +#define PY_SSIZE_T_CLEAN +#include +#include +#include + +Refspec* wrap_refspec(const Remote *owner, const git_refspec *refspec); + +#endif diff --git a/src/remote.c b/src/remote.c index 63d11c8ca..00072b7bb 100644 --- a/src/remote.c +++ b/src/remote.c @@ -29,273 +29,17 @@ #include #include #include "error.h" -#include "utils.h" #include "types.h" -#include "remote.h" +#include "utils.h" #include "oid.h" +#include "refspec.h" +#include "remote.h" extern PyObject *GitError; extern PyTypeObject RepositoryType; -extern PyTypeObject RefspecType; extern PyTypeObject TransferProgressType; -Refspec * -wrap_refspec(const Remote *owner, const git_refspec *refspec) -{ - Refspec *spec; - - spec = PyObject_New(Refspec, &RefspecType); - if (!spec) - return NULL; - - Py_INCREF(owner); - spec->owner = owner; - spec->refspec = refspec; - - return spec; -} - -PyDoc_STRVAR(Refspec_direction__doc__, - "The direction of this refspec (fetch or push)"); - -PyObject * -Refspec_direction__get__(Refspec *self) -{ - return Py_BuildValue("i", git_refspec_direction(self->refspec)); -} - -PyDoc_STRVAR(Refspec_src__doc__, "Source or lhs of the refspec"); - -PyObject * -Refspec_src__get__(Refspec *self) -{ - return to_unicode(git_refspec_src(self->refspec), NULL, NULL); -} - -PyDoc_STRVAR(Refspec_dst__doc__, "Destination or rhs of the refspec"); - -PyObject * -Refspec_dst__get__(Refspec *self) -{ - return to_unicode(git_refspec_dst(self->refspec), NULL, NULL); -} - -PyDoc_STRVAR(Refspec_string__doc__, "String used to create this refspec"); - -PyObject * -Refspec_string__get__(Refspec *self) -{ - return to_unicode(git_refspec_string(self->refspec), NULL, NULL); -} - -PyDoc_STRVAR(Refspec_force__doc__, - "Whether this refspec allows non-fast-forward updates"); - -PyObject * -Refspec_force__get__(Refspec *self) -{ - if (git_refspec_force(self->refspec)) - Py_RETURN_TRUE; - - Py_RETURN_FALSE; -} - -PyDoc_STRVAR(Refspec_src_matches__doc__, - "src_matches(str) -> Bool\n" - "\n" - "Returns whether the string matches the source refspec\n"); - -PyObject * -Refspec_src_matches(Refspec *self, PyObject *py_str) -{ - char *str; - int res; - - str = py_str_to_c_str(py_str, NULL); - if (!str) - return NULL; - - res = git_refspec_src_matches(self->refspec, str); - free(str); - - if (res) - Py_RETURN_TRUE; - - Py_RETURN_FALSE; -} - -PyDoc_STRVAR(Refspec_dst_matches__doc__, - "dst_matches(str) -> Bool\n" - "\n" - "Returns whether the string matches the destination refspec\n"); - -PyObject * -Refspec_dst_matches(Refspec *self, PyObject *py_str) -{ - char *str; - int res; - - str = py_str_to_c_str(py_str, NULL); - if (!str) - return NULL; - - res = git_refspec_dst_matches(self->refspec, str); - free(str); - - if (res) - Py_RETURN_TRUE; - - Py_RETURN_FALSE; -} - -PyDoc_STRVAR(Refspec_transform__doc__, - "transform(str) -> str\n" - "\n" - "Transform a reference according to the refspec\n"); - -PyObject * -Refspec_transform(Refspec *self, PyObject *py_str) -{ - char *str, *trans; - int err, len, alen; - PyObject *py_trans; - - str = py_str_to_c_str(py_str, NULL); - alen = len = strlen(str); - - do { - alen *= alen; - trans = malloc(alen); - if (!trans) { - free(str); - return PyErr_NoMemory(); - } - - err = git_refspec_transform(trans, alen, self->refspec, str); - } while(err == GIT_EBUFS); - free(str); - - if (err < 0) { - free(trans); - Error_set(err); - return NULL; - } - - py_trans = to_unicode(trans, NULL, NULL); - - free(trans); - - return py_trans; -} - -PyDoc_STRVAR(Refspec_rtransform__doc__, - "rtransform(str) -> str\n" - "\n" - "Transform a reference according to the refspec in reverse\n"); - -PyObject * -Refspec_rtransform(Refspec *self, PyObject *py_str) -{ - char *str, *trans; - int err, len, alen; - PyObject *py_trans; - - str = py_str_to_c_str(py_str, NULL); - alen = len = strlen(str); - - do { - alen *= alen; - trans = malloc(alen); - if (!trans) { - free(str); - return PyErr_NoMemory(); - } - - err = git_refspec_rtransform(trans, alen, self->refspec, str); - } while(err == GIT_EBUFS); - free(str); - - if (err < 0) { - free(trans); - Error_set(err); - return NULL; - } - - py_trans = to_unicode(trans, NULL, NULL); - - free(trans); - - return py_trans; -} - -PyMethodDef Refspec_methods[] = { - METHOD(Refspec, src_matches, METH_O), - METHOD(Refspec, dst_matches, METH_O), - METHOD(Refspec, transform, METH_O), - METHOD(Refspec, rtransform, METH_O), - {NULL} -}; - -PyGetSetDef Refspec_getseters[] = { - GETTER(Refspec, direction), - GETTER(Refspec, src), - GETTER(Refspec, dst), - GETTER(Refspec, string), - GETTER(Refspec, force), - {NULL} -}; - -static void -Refspec_dealloc(Refspec *self) -{ - Py_CLEAR(self->owner); - PyObject_Del(self); -} - -PyDoc_STRVAR(Refspec__doc__, "Refspec object."); - -PyTypeObject RefspecType = { - PyVarObject_HEAD_INIT(NULL, 0) - "_pygit2.Refspec", /* tp_name */ - sizeof(Refspec), /* tp_basicsize */ - 0, /* tp_itemsize */ - (destructor)Refspec_dealloc, /* 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 | Py_TPFLAGS_BASETYPE, /* tp_flags */ - Refspec__doc__, /* tp_doc */ - 0, /* tp_traverse */ - 0, /* tp_clear */ - 0, /* tp_richcompare */ - 0, /* tp_weaklistoffset */ - 0, /* tp_iter */ - 0, /* tp_iternext */ - Refspec_methods, /* tp_methods */ - 0, /* tp_members */ - Refspec_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 */ -}; - PyObject * wrap_transfer_progress(const git_transfer_progress *stats) { From 7b1310f31b749109fd11d0055a9528539f0e1a10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Tue, 4 Feb 2014 07:52:58 +0100 Subject: [PATCH 0109/1630] docs: fix warnings --- docs/blame.rst | 6 +++--- docs/config.rst | 2 +- docs/diff.rst | 2 +- docs/objects.rst | 16 ++++++++-------- docs/oid.rst | 2 +- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/blame.rst b/docs/blame.rst index b0c7381bb..fc052e0e1 100644 --- a/docs/blame.rst +++ b/docs/blame.rst @@ -12,9 +12,9 @@ The Blame type ============== .. automethod:: pygit2.Blame.for_line -.. method:: iter(Blame) -.. method:: len(Blame) -.. method:: Blame[n] +.. method:: Blame.__iter__() +.. method:: Blame.__len__() +.. method:: Blame.__getitem__(n) The BlameHunk type diff --git a/docs/config.rst b/docs/config.rst index e9b5c647b..348c34743 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -14,7 +14,7 @@ The Config type .. automethod:: pygit2.Config.get_multivar .. automethod:: pygit2.Config.set_multivar -.. method:: for (name, value) in Config +.. method:: Config.__iter__() The :class:`Config` class has an iterator which can be used to loop through all the entries in the configuration. Each element is a tuple diff --git a/docs/diff.rst b/docs/diff.rst index 0e5fbaffa..09fc9da21 100644 --- a/docs/diff.rst +++ b/docs/diff.rst @@ -35,7 +35,7 @@ The Diff type ==================== .. autoattribute:: pygit2.Diff.patch -.. method:: len(Diff) +.. method:: Diff.__len__() Returns the number of deltas/patches in this diff. diff --git a/docs/objects.rst b/docs/objects.rst index 432481b4f..ecb39e998 100644 --- a/docs/objects.rst +++ b/docs/objects.rst @@ -18,7 +18,7 @@ In the previous chapter we learnt about Object IDs. With an oid we can ask the repository to get the associated object. To do that the ``Repository`` class implementes a subset of the mapping interface. -.. method:: Repository.get(oid, default=None) +.. automethod:: pygit2.Repository.get Return the Git object for the given *oid*, returns the *default* value if there's no object in the repository with that oid. The oid can be an Oid @@ -32,13 +32,13 @@ implementes a subset of the mapping interface. >>> obj <_pygit2.Commit object at 0x7ff27a6b60f0> -.. method:: Repository[oid] +.. method:: Repository.__getitem__(oid) Return the Git object for the given oid, raise ``KeyError`` if there's no object in the repository with that oid. The oid can be an Oid object, or an hexadecimal string. -.. method:: oid in Repository +.. method:: Repository.__contains__(oid) Returns True if there is an object in the Repository with that oid, False if there is not. The oid can be an Oid object, or an hexadecimal string. @@ -147,20 +147,20 @@ directory in a file system. Each entry points to another tree or a blob. A tree can be iterated, and partially implements the sequence and mapping interfaces. -.. method:: Tree[name] +.. method:: Tree.__getitem__(name) Return the TreeEntry object for the given *name*. Raise ``KeyError`` if there is not a tree entry with that name. -.. method:: name in Tree +.. method:: Tree.__contains__(name) Return True if there is a tree entry with the given name, False otherwise. -.. method:: len(Tree) +.. method:: Tree.__len__() Return the number of entries in the tree. -.. method:: iter(Tree) +.. method:: Tree.__iter__() Return an iterator over the entries of the tree. @@ -176,7 +176,7 @@ Tree entries .. autoattribute:: pygit2.TreeEntry.hex .. autoattribute:: pygit2.TreeEntry.filemode -.. method:: cmp(TreeEntry, TreeEntry) +.. method:: TreeEntry.__cmp__(TreeEntry) Rich comparison between tree entries. diff --git a/docs/oid.rst b/docs/oid.rst index ff5cad803..2d66bde72 100644 --- a/docs/oid.rst +++ b/docs/oid.rst @@ -65,7 +65,7 @@ And the other way around, from an Oid object we can get the hexadecimal and raw forms. You can use the built-in `str()` (or `unicode()` in python 2) to get the hexadecimal representation of the Oid. -.. method:: str(pygit2.Oid) +.. method:: Oid.__str__() .. autoattribute:: pygit2.Oid.raw The Oid type supports: From d7071b88cd87d3e34a414d92e3b8949929b37472 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Tue, 4 Feb 2014 08:02:12 +0100 Subject: [PATCH 0110/1630] Update copyright year --- docs/conf.py | 2 +- pygit2/__init__.py | 2 +- pygit2/repository.py | 2 +- pygit2/version.py | 2 +- setup.py | 2 +- src/blame.c | 2 +- src/blame.h | 2 +- src/blob.c | 2 +- src/blob.h | 2 +- src/branch.c | 2 +- src/branch.h | 2 +- src/commit.c | 2 +- src/commit.h | 2 +- src/config.c | 2 +- src/config.h | 2 +- src/diff.c | 2 +- src/diff.h | 2 +- src/error.c | 2 +- src/error.h | 2 +- src/index.c | 2 +- src/index.h | 2 +- src/mergeresult.c | 2 +- src/mergeresult.h | 2 +- src/note.c | 2 +- src/note.h | 2 +- src/object.c | 2 +- src/object.h | 2 +- src/oid.c | 2 +- src/oid.h | 2 +- src/pygit2.c | 2 +- src/reference.c | 2 +- src/reference.h | 2 +- src/refspec.c | 2 +- src/refspec.h | 2 +- src/remote.c | 2 +- src/remote.h | 2 +- src/repository.c | 2 +- src/repository.h | 2 +- src/signature.c | 2 +- src/signature.h | 2 +- src/tag.c | 2 +- src/tag.h | 2 +- src/tree.c | 2 +- src/tree.h | 2 +- src/treebuilder.c | 2 +- src/treebuilder.h | 2 +- src/types.h | 2 +- src/utils.c | 2 +- src/utils.h | 2 +- src/walker.c | 2 +- src/walker.h | 2 +- test/__init__.py | 2 +- test/test_blame.py | 2 +- test/test_blob.py | 2 +- test/test_branch.py | 2 +- test/test_commit.py | 2 +- test/test_config.py | 2 +- test/test_diff.py | 2 +- test/test_index.py | 2 +- test/test_note.py | 2 +- test/test_oid.py | 2 +- test/test_reflog.py | 2 +- test/test_refs.py | 2 +- test/test_remote.py | 2 +- test/test_repository.py | 2 +- test/test_revwalk.py | 2 +- test/test_signature.py | 2 +- test/test_status.py | 2 +- test/test_tag.py | 2 +- test/test_tree.py | 2 +- test/test_treebuilder.py | 2 +- test/utils.py | 2 +- 72 files changed, 72 insertions(+), 72 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 7ec185961..addf93dd3 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -41,7 +41,7 @@ # General information about the project. project = u'pygit2' -copyright = u'2013, J. David Ibáñez' +copyright = u'2010-2014 The pygit2 contributors' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the diff --git a/pygit2/__init__.py b/pygit2/__init__.py index e2d52467c..244fdad1e 100644 --- a/pygit2/__init__.py +++ b/pygit2/__init__.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright 2010-2013 The pygit2 contributors +# Copyright 2010-2014 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, diff --git a/pygit2/repository.py b/pygit2/repository.py index fbb77ced3..4b7876579 100644 --- a/pygit2/repository.py +++ b/pygit2/repository.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright 2010-2013 The pygit2 contributors +# Copyright 2010-2014 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, diff --git a/pygit2/version.py b/pygit2/version.py index 9469822bf..71fbd6f5f 100644 --- a/pygit2/version.py +++ b/pygit2/version.py @@ -1,4 +1,4 @@ -# Copyright 2010-2013 The pygit2 contributors +# Copyright 2010-2014 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, diff --git a/setup.py b/setup.py index 0717839a8..eff54e184 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # coding: UTF-8 # -# Copyright 2010-2013 The pygit2 contributors +# Copyright 2010-2014 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, diff --git a/src/blame.c b/src/blame.c index d5545bf83..f0ee18f2d 100644 --- a/src/blame.c +++ b/src/blame.c @@ -1,5 +1,5 @@ /* - * Copyright 2010-2013 The pygit2 contributors + * Copyright 2010-2014 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, diff --git a/src/blame.h b/src/blame.h index a6f1e53e4..20bff683e 100644 --- a/src/blame.h +++ b/src/blame.h @@ -1,5 +1,5 @@ /* - * Copyright 2010-2013 The pygit2 contributors + * Copyright 2010-2014 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, diff --git a/src/blob.c b/src/blob.c index 1b2840f71..94352dfda 100644 --- a/src/blob.c +++ b/src/blob.c @@ -1,5 +1,5 @@ /* - * Copyright 2010-2013 The pygit2 contributors + * Copyright 2010-2014 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, diff --git a/src/blob.h b/src/blob.h index 5de1dee00..7af6537cb 100644 --- a/src/blob.h +++ b/src/blob.h @@ -1,5 +1,5 @@ /* - * Copyright 2010-2013 The pygit2 contributors + * Copyright 2010-2014 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, diff --git a/src/branch.c b/src/branch.c index 483906b82..8f78de8bb 100644 --- a/src/branch.c +++ b/src/branch.c @@ -1,5 +1,5 @@ /* - * Copyright 2010-2013 The pygit2 contributors + * Copyright 2010-2014 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, diff --git a/src/branch.h b/src/branch.h index ee1185e09..cad0c2dc0 100644 --- a/src/branch.h +++ b/src/branch.h @@ -1,5 +1,5 @@ /* - * Copyright 2010-2013 The pygit2 contributors + * Copyright 2010-2014 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, diff --git a/src/commit.c b/src/commit.c index 258f082ba..8a61d29e7 100644 --- a/src/commit.c +++ b/src/commit.c @@ -1,5 +1,5 @@ /* - * Copyright 2010-2013 The pygit2 contributors + * Copyright 2010-2014 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, diff --git a/src/commit.h b/src/commit.h index b94e30370..eeb0a92aa 100644 --- a/src/commit.h +++ b/src/commit.h @@ -1,5 +1,5 @@ /* - * Copyright 2010-2013 The pygit2 contributors + * Copyright 2010-2014 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, diff --git a/src/config.c b/src/config.c index fdb2593c2..441abb127 100644 --- a/src/config.c +++ b/src/config.c @@ -1,5 +1,5 @@ /* - * Copyright 2010-2013 The pygit2 contributors + * Copyright 2010-2014 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, diff --git a/src/config.h b/src/config.h index b0eba9353..ffa0733bb 100644 --- a/src/config.h +++ b/src/config.h @@ -1,5 +1,5 @@ /* - * Copyright 2010-2013 The pygit2 contributors + * Copyright 2010-2014 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, diff --git a/src/diff.c b/src/diff.c index 1609a7d65..8e59ebaa0 100644 --- a/src/diff.c +++ b/src/diff.c @@ -1,5 +1,5 @@ /* - * Copyright 2010-2013 The pygit2 contributors + * Copyright 2010-2014 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, diff --git a/src/diff.h b/src/diff.h index 9ba3a9ea0..4ae78ddff 100644 --- a/src/diff.h +++ b/src/diff.h @@ -1,5 +1,5 @@ /* - * Copyright 2010-2013 The pygit2 contributors + * Copyright 2010-2014 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, diff --git a/src/error.c b/src/error.c index 66587143a..848178c7e 100644 --- a/src/error.c +++ b/src/error.c @@ -1,5 +1,5 @@ /* - * Copyright 2010-2013 The pygit2 contributors + * Copyright 2010-2014 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, diff --git a/src/error.h b/src/error.h index f487763bf..cc52790b3 100644 --- a/src/error.h +++ b/src/error.h @@ -1,5 +1,5 @@ /* - * Copyright 2010-2013 The pygit2 contributors + * Copyright 2010-2014 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, diff --git a/src/index.c b/src/index.c index fcd36128c..5d70a1dcf 100644 --- a/src/index.c +++ b/src/index.c @@ -1,5 +1,5 @@ /* - * Copyright 2010-2013 The pygit2 contributors + * Copyright 2010-2014 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, diff --git a/src/index.h b/src/index.h index e59262ca6..cd570aa0c 100644 --- a/src/index.h +++ b/src/index.h @@ -1,5 +1,5 @@ /* - * Copyright 2010-2013 The pygit2 contributors + * Copyright 2010-2014 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, diff --git a/src/mergeresult.c b/src/mergeresult.c index 31699c1ca..ada872fb4 100644 --- a/src/mergeresult.c +++ b/src/mergeresult.c @@ -1,5 +1,5 @@ /* - * Copyright 2010-2013 The pygit2 contributors + * Copyright 2010-2014 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, diff --git a/src/mergeresult.h b/src/mergeresult.h index 770a4bd40..74161ea9d 100644 --- a/src/mergeresult.h +++ b/src/mergeresult.h @@ -1,5 +1,5 @@ /* - * Copyright 2010-2013 The pygit2 contributors + * Copyright 2010-2014 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, diff --git a/src/note.c b/src/note.c index c515d8bd4..2e5c9b149 100644 --- a/src/note.c +++ b/src/note.c @@ -1,5 +1,5 @@ /* - * Copyright 2010-2013 The pygit2 contributors + * Copyright 2010-2014 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, diff --git a/src/note.h b/src/note.h index a4ded369a..18858f053 100644 --- a/src/note.h +++ b/src/note.h @@ -1,5 +1,5 @@ /* - * Copyright 2010-2013 The pygit2 contributors + * Copyright 2010-2014 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, diff --git a/src/object.c b/src/object.c index d608e0c53..e17ef3f12 100644 --- a/src/object.c +++ b/src/object.c @@ -1,5 +1,5 @@ /* - * Copyright 2010-2013 The pygit2 contributors + * Copyright 2010-2014 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, diff --git a/src/object.h b/src/object.h index 7ab68a4a9..5cf01afa0 100644 --- a/src/object.h +++ b/src/object.h @@ -1,5 +1,5 @@ /* - * Copyright 2010-2013 The pygit2 contributors + * Copyright 2010-2014 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, diff --git a/src/oid.c b/src/oid.c index 81749e51d..b9769f78d 100644 --- a/src/oid.c +++ b/src/oid.c @@ -1,5 +1,5 @@ /* - * Copyright 2010-2013 The pygit2 contributors + * Copyright 2010-2014 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, diff --git a/src/oid.h b/src/oid.h index 871f26a47..4d4825b07 100644 --- a/src/oid.h +++ b/src/oid.h @@ -1,5 +1,5 @@ /* - * Copyright 2010-2013 The pygit2 contributors + * Copyright 2010-2014 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, diff --git a/src/pygit2.c b/src/pygit2.c index 0ec81676c..77535c735 100644 --- a/src/pygit2.c +++ b/src/pygit2.c @@ -1,5 +1,5 @@ /* - * Copyright 2010-2013 The pygit2 contributors + * Copyright 2010-2014 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, diff --git a/src/reference.c b/src/reference.c index df5e9a6b2..2efbca3c5 100644 --- a/src/reference.c +++ b/src/reference.c @@ -1,5 +1,5 @@ /* - * Copyright 2010-2013 The pygit2 contributors + * Copyright 2010-2014 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, diff --git a/src/reference.h b/src/reference.h index 5cbd13453..324866aff 100644 --- a/src/reference.h +++ b/src/reference.h @@ -1,5 +1,5 @@ /* - * Copyright 2010-2013 The pygit2 contributors + * Copyright 2010-2014 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, diff --git a/src/refspec.c b/src/refspec.c index ce679df38..a59350afd 100644 --- a/src/refspec.c +++ b/src/refspec.c @@ -1,5 +1,5 @@ /* - * Copyright 2010-2013 The pygit2 contributors + * Copyright 2010-2014 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, diff --git a/src/refspec.h b/src/refspec.h index e653bf59b..829823f94 100644 --- a/src/refspec.h +++ b/src/refspec.h @@ -1,5 +1,5 @@ /* - * Copyright 2010-2013 The pygit2 contributors + * Copyright 2010-2014 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, diff --git a/src/remote.c b/src/remote.c index 00072b7bb..f6ff1e3e5 100644 --- a/src/remote.c +++ b/src/remote.c @@ -1,5 +1,5 @@ /* - * Copyright 2010-2013 The pygit2 contributors + * Copyright 2010-2014 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, diff --git a/src/remote.h b/src/remote.h index 1c8605f8e..ce6ee47d5 100644 --- a/src/remote.h +++ b/src/remote.h @@ -1,5 +1,5 @@ /* - * Copyright 2010-2013 The pygit2 contributors + * Copyright 2010-2014 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, diff --git a/src/repository.c b/src/repository.c index 00d9cc55e..95e2e3459 100644 --- a/src/repository.c +++ b/src/repository.c @@ -1,5 +1,5 @@ /* - * Copyright 2010-2013 The pygit2 contributors + * Copyright 2010-2014 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, diff --git a/src/repository.h b/src/repository.h index 735f77480..53a760e83 100644 --- a/src/repository.h +++ b/src/repository.h @@ -1,5 +1,5 @@ /* - * Copyright 2010-2013 The pygit2 contributors + * Copyright 2010-2014 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, diff --git a/src/signature.c b/src/signature.c index 61fd0746c..687343b89 100644 --- a/src/signature.c +++ b/src/signature.c @@ -1,5 +1,5 @@ /* - * Copyright 2010-2013 The pygit2 contributors + * Copyright 2010-2014 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, diff --git a/src/signature.h b/src/signature.h index a0028a9d3..425b3dda3 100644 --- a/src/signature.h +++ b/src/signature.h @@ -1,5 +1,5 @@ /* - * Copyright 2010-2013 The pygit2 contributors + * Copyright 2010-2014 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, diff --git a/src/tag.c b/src/tag.c index c5fdd0511..6224d4c80 100644 --- a/src/tag.c +++ b/src/tag.c @@ -1,5 +1,5 @@ /* - * Copyright 2010-2013 The pygit2 contributors + * Copyright 2010-2014 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, diff --git a/src/tag.h b/src/tag.h index 04d3a1092..ada402c59 100644 --- a/src/tag.h +++ b/src/tag.h @@ -1,5 +1,5 @@ /* - * Copyright 2010-2013 The pygit2 contributors + * Copyright 2010-2014 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, diff --git a/src/tree.c b/src/tree.c index dae77dcd2..8c83d3aa7 100644 --- a/src/tree.c +++ b/src/tree.c @@ -1,5 +1,5 @@ /* - * Copyright 2010-2013 The pygit2 contributors + * Copyright 2010-2014 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, diff --git a/src/tree.h b/src/tree.h index 94d0ff184..6392cf9f0 100644 --- a/src/tree.h +++ b/src/tree.h @@ -1,5 +1,5 @@ /* - * Copyright 2010-2013 The pygit2 contributors + * Copyright 2010-2014 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, diff --git a/src/treebuilder.c b/src/treebuilder.c index 8981e98c3..8354b1b5e 100644 --- a/src/treebuilder.c +++ b/src/treebuilder.c @@ -1,5 +1,5 @@ /* - * Copyright 2010-2013 The pygit2 contributors + * Copyright 2010-2014 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, diff --git a/src/treebuilder.h b/src/treebuilder.h index ad000dfff..16a5af03a 100644 --- a/src/treebuilder.h +++ b/src/treebuilder.h @@ -1,5 +1,5 @@ /* - * Copyright 2010-2013 The pygit2 contributors + * Copyright 2010-2014 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, diff --git a/src/types.h b/src/types.h index 7a573dd96..e6a318994 100644 --- a/src/types.h +++ b/src/types.h @@ -1,5 +1,5 @@ /* - * Copyright 2010-2013 The pygit2 contributors + * Copyright 2010-2014 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, diff --git a/src/utils.c b/src/utils.c index 31f0472aa..6a20b01bf 100644 --- a/src/utils.c +++ b/src/utils.c @@ -1,5 +1,5 @@ /* - * Copyright 2010-2013 The pygit2 contributors + * Copyright 2010-2014 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, diff --git a/src/utils.h b/src/utils.h index 66d923453..05ddcd21b 100644 --- a/src/utils.h +++ b/src/utils.h @@ -1,5 +1,5 @@ /* - * Copyright 2010-2013 The pygit2 contributors + * Copyright 2010-2014 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, diff --git a/src/walker.c b/src/walker.c index 79cf389bf..7c76560bf 100644 --- a/src/walker.c +++ b/src/walker.c @@ -1,5 +1,5 @@ /* - * Copyright 2010-2013 The pygit2 contributors + * Copyright 2010-2014 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, diff --git a/src/walker.h b/src/walker.h index 9742ea97a..1f4b801d6 100644 --- a/src/walker.h +++ b/src/walker.h @@ -1,5 +1,5 @@ /* - * Copyright 2010-2013 The pygit2 contributors + * Copyright 2010-2014 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, diff --git a/test/__init__.py b/test/__init__.py index 1cbb37d02..f065d8b39 100644 --- a/test/__init__.py +++ b/test/__init__.py @@ -1,6 +1,6 @@ # -*- coding: UTF-8 -*- # -# Copyright 2010-2013 The pygit2 contributors +# Copyright 2010-2014 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, diff --git a/test/test_blame.py b/test/test_blame.py index 1f907740b..dbe9e6bf0 100644 --- a/test/test_blame.py +++ b/test/test_blame.py @@ -1,6 +1,6 @@ # -*- coding: UTF-8 -*- # -# Copyright 2010-2013 The pygit2 contributors +# Copyright 2010-2014 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, diff --git a/test/test_blob.py b/test/test_blob.py index cc6bc0aba..da3db3e8f 100644 --- a/test/test_blob.py +++ b/test/test_blob.py @@ -1,6 +1,6 @@ # -*- coding: UTF-8 -*- # -# Copyright 2010-2013 The pygit2 contributors +# Copyright 2010-2014 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, diff --git a/test/test_branch.py b/test/test_branch.py index 8dc62cb1a..87e3a57df 100644 --- a/test/test_branch.py +++ b/test/test_branch.py @@ -1,6 +1,6 @@ # -*- coding: UTF-8 -*- # -# Copyright 2010-2013 The pygit2 contributors +# Copyright 2010-2014 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, diff --git a/test/test_commit.py b/test/test_commit.py index e7d8c5c6f..f7a42b680 100644 --- a/test/test_commit.py +++ b/test/test_commit.py @@ -1,6 +1,6 @@ # -*- coding: UTF-8 -*- # -# Copyright 2010-2013 The pygit2 contributors +# Copyright 2010-2014 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, diff --git a/test/test_config.py b/test/test_config.py index 4a22f407e..9f4d460a7 100644 --- a/test/test_config.py +++ b/test/test_config.py @@ -1,6 +1,6 @@ # -*- coding: UTF-8 -*- # -# Copyright 2010-2013 The pygit2 contributors +# Copyright 2010-2014 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, diff --git a/test/test_diff.py b/test/test_diff.py index 60078ac24..f982d0194 100644 --- a/test/test_diff.py +++ b/test/test_diff.py @@ -1,6 +1,6 @@ # -*- coding: UTF-8 -*- # -# Copyright 2010-2013 The pygit2 contributors +# Copyright 2010-2014 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, diff --git a/test/test_index.py b/test/test_index.py index 1afdf56ce..7076e2e12 100644 --- a/test/test_index.py +++ b/test/test_index.py @@ -1,6 +1,6 @@ # -*- coding: UTF-8 -*- # -# Copyright 2010-2013 The pygit2 contributors +# Copyright 2010-2014 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, diff --git a/test/test_note.py b/test/test_note.py index a0ef44385..74f08940d 100644 --- a/test/test_note.py +++ b/test/test_note.py @@ -1,6 +1,6 @@ # -*- coding: UTF-8 -*- # -# Copyright 2010-2013 The pygit2 contributors +# Copyright 2010-2014 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, diff --git a/test/test_oid.py b/test/test_oid.py index d2d7a9205..6c9a71e25 100644 --- a/test/test_oid.py +++ b/test/test_oid.py @@ -1,6 +1,6 @@ # -*- coding: UTF-8 -*- # -# Copyright 2010-2013 The pygit2 contributors +# Copyright 2010-2014 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, diff --git a/test/test_reflog.py b/test/test_reflog.py index 84473c8ee..1251c2bde 100644 --- a/test/test_reflog.py +++ b/test/test_reflog.py @@ -1,6 +1,6 @@ # -*- coding: UTF-8 -*- # -# Copyright 2010-2013 The pygit2 contributors +# Copyright 2010-2014 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, diff --git a/test/test_refs.py b/test/test_refs.py index bac89db6f..0757b7a90 100644 --- a/test/test_refs.py +++ b/test/test_refs.py @@ -1,6 +1,6 @@ # -*- coding: UTF-8 -*- # -# Copyright 2010-2013 The pygit2 contributors +# Copyright 2010-2014 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, diff --git a/test/test_remote.py b/test/test_remote.py index d98bfc5c9..797474c62 100644 --- a/test/test_remote.py +++ b/test/test_remote.py @@ -1,6 +1,6 @@ # -*- coding: UTF-8 -*- # -# Copyright 2010-2013 The pygit2 contributors +# Copyright 2010-2014 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, diff --git a/test/test_repository.py b/test/test_repository.py index acd54e4b4..bba6d76be 100644 --- a/test/test_repository.py +++ b/test/test_repository.py @@ -1,6 +1,6 @@ # -*- coding: UTF-8 -*- # -# Copyright 2010-2013 The pygit2 contributors +# Copyright 2010-2014 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, diff --git a/test/test_revwalk.py b/test/test_revwalk.py index 654ef47a8..be4a60882 100644 --- a/test/test_revwalk.py +++ b/test/test_revwalk.py @@ -1,6 +1,6 @@ # -*- coding: UTF-8 -*- # -# Copyright 2010-2013 The pygit2 contributors +# Copyright 2010-2014 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, diff --git a/test/test_signature.py b/test/test_signature.py index 4131c6c83..13a2a2598 100644 --- a/test/test_signature.py +++ b/test/test_signature.py @@ -1,6 +1,6 @@ # -*- coding: UTF-8 -*- # -# Copyright 2010-2013 The pygit2 contributors +# Copyright 2010-2014 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, diff --git a/test/test_status.py b/test/test_status.py index 98a7a7099..70ee5bf92 100644 --- a/test/test_status.py +++ b/test/test_status.py @@ -1,6 +1,6 @@ # -*- coding: UTF-8 -*- # -# Copyright 2010-2013 The pygit2 contributors +# Copyright 2010-2014 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, diff --git a/test/test_tag.py b/test/test_tag.py index e31f7b0f2..c8448686d 100644 --- a/test/test_tag.py +++ b/test/test_tag.py @@ -1,6 +1,6 @@ # -*- coding: UTF-8 -*- # -# Copyright 2010-2013 The pygit2 contributors +# Copyright 2010-2014 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, diff --git a/test/test_tree.py b/test/test_tree.py index 25ba7ed66..128fc75c8 100644 --- a/test/test_tree.py +++ b/test/test_tree.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright 2010-2013 The pygit2 contributors +# Copyright 2010-2014 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, diff --git a/test/test_treebuilder.py b/test/test_treebuilder.py index 5c7fa67ac..6a513e4c5 100644 --- a/test/test_treebuilder.py +++ b/test/test_treebuilder.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright 2010-2013 The pygit2 contributors +# Copyright 2010-2014 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, diff --git a/test/utils.py b/test/utils.py index 4d941e5c3..07952a189 100644 --- a/test/utils.py +++ b/test/utils.py @@ -1,6 +1,6 @@ # -*- coding: UTF-8 -*- # -# Copyright 2010-2013 The pygit2 contributors +# Copyright 2010-2014 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, From 74d091d6097f7792bac72dbf8e3da22560714b8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Mon, 3 Feb 2014 17:45:49 +0100 Subject: [PATCH 0111/1630] utils: allow borrowing a C string We don't always need our own copy of a C string; sometimes we just need to parse it. Create py_str_borrow_c_str() which returns a char pointer to python's internal value string, with which we can avoid an extra copy. --- src/utils.c | 43 +++++++++++++++++++++++++++++++++---------- src/utils.h | 2 ++ 2 files changed, 35 insertions(+), 10 deletions(-) diff --git a/src/utils.c b/src/utils.c index 6a20b01bf..e63ea9dbf 100644 --- a/src/utils.c +++ b/src/utils.c @@ -32,27 +32,50 @@ extern PyTypeObject ReferenceType; -/* py_str_to_c_str() returns a newly allocated C string holding - * the string contained in the value argument. */ +/** + * py_str_to_c_str() returns a newly allocated C string holding the string + * contained in the 'value' argument. + */ char * py_str_to_c_str(PyObject *value, const char *encoding) { + const char *borrowed; char *c_str = NULL; + PyObject *tmp = NULL; + + borrowed = py_str_borrow_c_str(&tmp, value, encoding); + if (!borrowed) + return NULL; + + c_str = strdup(borrowed); + + Py_DECREF(tmp); + return c_str; +} + +/** + * Return a pointer to the underlying C string in 'value'. The pointer is + * guaranteed by 'tvalue'. Decrease its refcount when done with the string. + */ +const char * +py_str_borrow_c_str(PyObject **tvalue, PyObject *value, const char *encoding) +{ /* Case 1: byte string */ - if (PyBytes_Check(value)) - return strdup(PyBytes_AsString(value)); + if (PyBytes_Check(value)) { + Py_INCREF(value); + *tvalue = value; + return PyBytes_AsString(value); + } /* Case 2: text string */ if (PyUnicode_Check(value)) { if (encoding == NULL) - value = PyUnicode_AsUTF8String(value); + *tvalue = PyUnicode_AsUTF8String(value); else - value = PyUnicode_AsEncodedString(value, encoding, "strict"); - if (value == NULL) + *tvalue = PyUnicode_AsEncodedString(value, encoding, "strict"); + if (*tvalue == NULL) return NULL; - c_str = strdup(PyBytes_AsString(value)); - Py_DECREF(value); - return c_str; + return PyBytes_AsString(*tvalue); } /* Type error */ diff --git a/src/utils.h b/src/utils.h index 05ddcd21b..3b3c42a07 100644 --- a/src/utils.h +++ b/src/utils.h @@ -108,6 +108,8 @@ to_bytes(const char * value) } char * py_str_to_c_str(PyObject *value, const char *encoding); +const char *py_str_borrow_c_str(PyObject **tvaue, PyObject *value, const char *encoding); + #define py_path_to_c_str(py_path) \ py_str_to_c_str(py_path, Py_FileSystemDefaultEncoding) From 1c74676ed405a5ccd199080618417158589f39c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Mon, 3 Feb 2014 17:56:59 +0100 Subject: [PATCH 0112/1630] config: borrow the string for lookup and setting We don't need our own copy of the string, so use the new borrowing mechanism to use python's underlying string for the key to get/set. --- src/config.c | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/config.c b/src/config.c index 441abb127..8cbb05451 100644 --- a/src/config.c +++ b/src/config.c @@ -147,15 +147,15 @@ Config_get_system_config(void) int Config_contains(Config *self, PyObject *py_key) { int err; - const char *c_value; - char *c_key; + const char *c_value, *c_key; + PyObject *tkey; - c_key = py_str_to_c_str(py_key, NULL); + c_key = py_str_borrow_c_str(&tkey, py_key, NULL); if (c_key == NULL) return -1; err = git_config_get_string(&c_value, self->config, c_key); - free(c_key); + Py_DECREF(tkey); if (err < 0) { if (err == GIT_ENOTFOUND) @@ -175,14 +175,15 @@ Config_getitem(Config *self, PyObject *py_key) int64_t value_int; int err, value_bool; const char *value_str; - char *key; - PyObject* py_value; + const char *key; + PyObject* py_value, *tmp; - key = py_str_to_c_str(py_key, NULL); + key = py_str_borrow_c_str(&tmp, py_key, NULL); if (key == NULL) return NULL; err = git_config_get_string(&value_str, self->config, key); + Py_CLEAR(tmp); if (err < 0) goto cleanup; @@ -194,8 +195,6 @@ Config_getitem(Config *self, PyObject *py_key) py_value = to_unicode(value_str, NULL, NULL); cleanup: - free(key); - if (err < 0) { if (err == GIT_ENOTFOUND) { PyErr_SetObject(PyExc_KeyError, py_key); @@ -212,9 +211,10 @@ int Config_setitem(Config *self, PyObject *py_key, PyObject *py_value) { int err; - char *key, *value; + const char *key, *value; + PyObject *tkey, *tvalue; - key = py_str_to_c_str(py_key, NULL); + key = py_str_borrow_c_str(&tkey, py_key, NULL); if (key == NULL) return -1; @@ -227,12 +227,12 @@ Config_setitem(Config *self, PyObject *py_key, PyObject *py_value) err = git_config_set_int64(self->config, key, (int64_t)PyLong_AsLong(py_value)); } else { - value = py_str_to_c_str(py_value, NULL); + value = py_str_borrow_c_str(&tvalue, py_value, NULL); err = git_config_set_string(self->config, key, value); - free(value); + Py_DECREF(tvalue); } - free(key); + Py_DECREF(tkey); if (err < 0) { Error_set(err); return -1; From b4827ba0815092b5008a4b4b211bfc5e5da63a1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Tue, 4 Feb 2014 12:32:08 +0100 Subject: [PATCH 0113/1630] repository: borrow C strings where possible --- src/repository.c | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/repository.c b/src/repository.c index 95e2e3459..750517859 100644 --- a/src/repository.c +++ b/src/repository.c @@ -181,14 +181,15 @@ int Repository_head__set__(Repository *self, PyObject *py_refname) { int err; - char *refname; + const char *refname; + PyObject *trefname; - refname = py_str_to_c_str(py_refname, NULL); + refname = py_str_borrow_c_str(&trefname, py_refname, NULL); if (refname == NULL) return -1; err = git_repository_set_head(self->repo, refname); - free(refname); + Py_DECREF(trefname); if (err < 0) { Error_set_str(err, refname); return -1; @@ -318,11 +319,12 @@ PyObject * Repository_revparse_single(Repository *self, PyObject *py_spec) { git_object *c_obj; - char *c_spec; + const char *c_spec; + PyObject *tspec; int err; /* 1- Get the C revision spec */ - c_spec = py_str_to_c_str(py_spec, NULL); + c_spec = py_str_borrow_c_str(&tspec, py_spec, NULL); if (c_spec == NULL) return NULL; @@ -331,10 +333,10 @@ Repository_revparse_single(Repository *self, PyObject *py_spec) if (err < 0) { PyObject *err_obj = Error_set_str(err, c_spec); - free(c_spec); + Py_DECREF(tspec); return err_obj; } - free(c_spec); + Py_DECREF(tspec); return wrap_object(c_obj, self); } @@ -782,7 +784,8 @@ Repository_create_commit(Repository *self, PyObject *args) Signature *py_author, *py_committer; PyObject *py_oid, *py_message, *py_parents, *py_parent; PyObject *py_result = NULL; - char *message = NULL; + PyObject *tmessage; + const char *message = NULL; char *update_ref = NULL; char *encoding = NULL; git_oid oid; @@ -806,7 +809,7 @@ Repository_create_commit(Repository *self, PyObject *args) if (len == 0) goto out; - message = py_str_to_c_str(py_message, encoding); + message = py_str_borrow_c_str(&tmessage, py_message, encoding); if (message == NULL) goto out; @@ -846,7 +849,7 @@ Repository_create_commit(Repository *self, PyObject *args) py_result = git_oid_to_python(&oid); out: - free(message); + Py_DECREF(tmessage); git_tree_free(tree); while (i > 0) { i--; @@ -1048,7 +1051,7 @@ Repository_lookup_reference(Repository *self, PyObject *py_name) err = git_reference_lookup(&c_reference, self->repo, c_name); if (err < 0) { PyObject *err_obj = Error_set_str(err, c_name); - free(c_name); + free(c_name); return err_obj; } free(c_name); From c8a4027affef36dc9caf4398961991e3c5f4bf75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Tue, 4 Feb 2014 12:35:07 +0100 Subject: [PATCH 0114/1630] signature: borrow the name's C string --- src/signature.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/signature.c b/src/signature.c index 687343b89..809e80285 100644 --- a/src/signature.c +++ b/src/signature.c @@ -37,8 +37,9 @@ int Signature_init(Signature *self, PyObject *args, PyObject *kwds) { char *keywords[] = {"name", "email", "time", "offset", "encoding", NULL}; - PyObject *py_name; - char *name, *email, *encoding = "ascii"; + PyObject *py_name, *tname; + char *email, *encoding = "ascii"; + const char *name; long long time = -1; int offset = 0; int err; @@ -49,7 +50,7 @@ Signature_init(Signature *self, PyObject *args, PyObject *kwds) &py_name, &email, &time, &offset, &encoding)) return -1; - name = py_str_to_c_str(py_name, encoding); + name = py_str_borrow_c_str(&tname, py_name, encoding); if (name == NULL) return -1; @@ -58,7 +59,8 @@ Signature_init(Signature *self, PyObject *args, PyObject *kwds) } else { err = git_signature_new(&signature, name, email, time, offset); } - free(name); + Py_DECREF(tname); + if (err < 0) { Error_set(err); return -1; From 659749510f17cc5cce03b3c585c687a131c880d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Tue, 4 Feb 2014 12:41:46 +0100 Subject: [PATCH 0115/1630] refspec: borrow the C string --- src/refspec.c | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/src/refspec.c b/src/refspec.c index a59350afd..3053fe60b 100644 --- a/src/refspec.c +++ b/src/refspec.c @@ -105,15 +105,16 @@ PyDoc_STRVAR(Refspec_src_matches__doc__, PyObject * Refspec_src_matches(Refspec *self, PyObject *py_str) { - char *str; + const char *str; + PyObject *tstr; int res; - str = py_str_to_c_str(py_str, NULL); + str = py_str_borrow_c_str(&tstr, py_str, NULL); if (!str) return NULL; res = git_refspec_src_matches(self->refspec, str); - free(str); + Py_DECREF(tstr); if (res) Py_RETURN_TRUE; @@ -129,15 +130,16 @@ PyDoc_STRVAR(Refspec_dst_matches__doc__, PyObject * Refspec_dst_matches(Refspec *self, PyObject *py_str) { - char *str; + const char *str; + PyObject *tstr; int res; - str = py_str_to_c_str(py_str, NULL); + str = py_str_borrow_c_str(&tstr, py_str, NULL); if (!str) return NULL; res = git_refspec_dst_matches(self->refspec, str); - free(str); + Py_DECREF(tstr); if (res) Py_RETURN_TRUE; @@ -153,24 +155,25 @@ PyDoc_STRVAR(Refspec_transform__doc__, PyObject * Refspec_transform(Refspec *self, PyObject *py_str) { - char *str, *trans; + const char *str; + char *trans; int err, len, alen; - PyObject *py_trans; + PyObject *py_trans, *tstr; - str = py_str_to_c_str(py_str, NULL); + str = py_str_borrow_c_str(&tstr, py_str, NULL); alen = len = strlen(str); do { alen *= alen; trans = malloc(alen); if (!trans) { - free(str); + Py_DECREF(tstr); return PyErr_NoMemory(); } err = git_refspec_transform(trans, alen, self->refspec, str); } while(err == GIT_EBUFS); - free(str); + Py_DECREF(tstr); if (err < 0) { free(trans); @@ -193,24 +196,25 @@ PyDoc_STRVAR(Refspec_rtransform__doc__, PyObject * Refspec_rtransform(Refspec *self, PyObject *py_str) { - char *str, *trans; + const char *str; + char *trans; int err, len, alen; - PyObject *py_trans; + PyObject *py_trans, *tstr; - str = py_str_to_c_str(py_str, NULL); + str = py_str_borrow_c_str(&tstr, py_str, NULL); alen = len = strlen(str); do { alen *= alen; trans = malloc(alen); if (!trans) { - free(str); + Py_DECREF(tstr); return PyErr_NoMemory(); } err = git_refspec_rtransform(trans, alen, self->refspec, str); } while(err == GIT_EBUFS); - free(str); + Py_DECREF(tstr); if (err < 0) { free(trans); From 824ac672c19c4de83a0fe41ea82714a0ef03ab6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Tue, 4 Feb 2014 12:43:54 +0100 Subject: [PATCH 0116/1630] remote: borrow the C string where possible --- src/remote.c | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/remote.c b/src/remote.c index f6ff1e3e5..89c5dda55 100644 --- a/src/remote.c +++ b/src/remote.c @@ -226,12 +226,13 @@ int Remote_name__set__(Remote *self, PyObject* py_name) { int err; - char* name; + const char* name; + PyObject *tname; - name = py_str_to_c_str(py_name, NULL); + name = py_str_borrow_c_str(&tname, py_name, NULL); if (name != NULL) { err = git_remote_rename(self->remote, name, NULL, NULL); - free(name); + Py_DECREF(tname); if (err == GIT_OK) return 0; @@ -404,12 +405,13 @@ int Remote_url__set__(Remote *self, PyObject* py_url) { int err; - char* url = NULL; + const char* url = NULL; + PyObject *turl; - url = py_str_to_c_str(py_url, NULL); + url = py_str_borrow_c_str(&turl, py_url, NULL); if (url != NULL) { err = git_remote_set_url(self->remote, url); - free(url); + Py_DECREF(turl); if (err == GIT_OK) return 0; @@ -440,12 +442,13 @@ int Remote_push_url__set__(Remote *self, PyObject* py_url) { int err; - char* url = NULL; + const char* url = NULL; + PyObject *turl; - url = py_str_to_c_str(py_url, NULL); + url = py_str_borrow_c_str(&turl, py_url, NULL); if (url != NULL) { err = git_remote_set_pushurl(self->remote, url); - free(url); + Py_DECREF(turl); if (err == GIT_OK) return 0; From dcd5acc34eacb2024a2b9e7e206717119eea0240 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Tue, 4 Feb 2014 12:45:50 +0100 Subject: [PATCH 0117/1630] index entry: avoid extra copy --- src/index.c | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/index.c b/src/index.c index 5d70a1dcf..90fae7628 100644 --- a/src/index.c +++ b/src/index.c @@ -650,18 +650,12 @@ IndexEntry_path__get__(IndexEntry *self) int IndexEntry_path__set__(IndexEntry *self, PyObject *py_path) { - char *c_inner, *c_path; + char *c_path; - c_inner = py_str_to_c_str(py_path, NULL); - if (!c_inner) + c_path = py_str_to_c_str(py_path, NULL); + if (!c_path) return -1; - c_path = strdup(c_inner); - if (!c_path) { - PyErr_NoMemory(); - return -1; - } - free(self->entry.path); self->entry.path = c_path; From 140305e41011dc7ba37286052927509a93505ec1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Tue, 4 Feb 2014 22:26:46 +0100 Subject: [PATCH 0118/1630] Get ready to release 0.20.2 --- README.rst | 133 ++++++++++++++++++++++++---------------------- docs/conf.py | 2 +- docs/install.rst | 2 +- pygit2/version.py | 2 +- 4 files changed, 72 insertions(+), 67 deletions(-) diff --git a/README.rst b/README.rst index 3b6767a86..a76fee96e 100644 --- a/README.rst +++ b/README.rst @@ -44,42 +44,33 @@ for the topic), send a pull request. Authors ============== -56 developers have contributed at least 1 commit to pygit2:: - - J. David Ibáñez Andrey Devyatkin - Nico von Geyso Ben Davis - Carlos Martín Nieto Eric Schrijver - W. Trevor King Hervé Cauwelier - Dave Borowitz Huang Huang - Daniel Rodríguez Troitiño Jared Flatow - Richo Healey Jiunn Haur Lim - Christian Boos Sarath Lakshman - Julien Miotte Vicent Marti - Jose Plana Zoran Zaric - Martin Lenders Adam Spiers - Victor Garcia Andrew Chin - Xavier Delannoy András Veres-Szentkirályi - Yonggang Luo Benjamin Kircher - Petr Hosek Benjamin Pollack - Valentin Haenel Bryan O'Sullivan - Xu Tao David Fischer - Bernardo Heynemann David Sanders - John Szakmeister Eric Davis - Brodie Rao Erik van Zijst - David Versmisse Ferengee - Rémi Duraffort Gustavo Di Pietro - Sebastian Thiel Hugh Cole-Baker - Fraser Tweedale Josh Bleecher Snyder - Han-Wen Nienhuys Jun Omae - Petr Viktorin Óscar San José - Alex Chamberlain Ridge Kennedy - Amit Bakshi Rui Abreu Ferreira +57 developers have contributed at least 1 commit to pygit2:: + + J. David Ibáñez Brodie Rao Adam Spiers + Nico von Geyso David Versmisse Alexander Bayandin + Carlos Martín Nieto Rémi Duraffort Andrew Chin + W. Trevor King Sebastian Thiel András Veres-Szentkirályi + Dave Borowitz Fraser Tweedale Benjamin Kircher + Daniel Rodríguez Troitiño Han-Wen Nienhuys Benjamin Pollack + Richo Healey Petr Viktorin Bryan O'Sullivan + Christian Boos Alex Chamberlain David Fischer + Julien Miotte Amit Bakshi David Sanders + Xu Tao Andrey Devyatkin Eric Davis + Jose Plana Ben Davis Erik van Zijst + Martin Lenders Eric Schrijver Ferengee + Petr Hosek Hervé Cauwelier Gustavo Di Pietro + Victor Garcia Huang Huang Hugh Cole-Baker + Xavier Delannoy Jared Flatow Josh Bleecher Snyder + Yonggang Luo Jiunn Haur Lim Jun Omae + Valentin Haenel Sarath Lakshman Óscar San José + Bernardo Heynemann Vicent Marti Ridge Kennedy + John Szakmeister Zoran Zaric Rui Abreu Ferreira Changelog ============== -0.20.2 (2014-02-XX) +0.20.2 (2014-02-04) ------------------- - Support pypy @@ -87,23 +78,64 @@ Changelog `#327 `_ `#333 `_ -- New ``Blob.diff(...)`` and ``Blob.diff_to_buffer(...)`` - `#307 `_ +Repository: - New ``Repository.default_signature`` `#310 `_ +Oid: + +- New ``str(Oid)`` deprecates ``Oid.hex`` + `#322 `_ + +Object: + +- New ``Object.id`` deprecates ``Object.oid`` + `#322 `_ + +- New ``TreeEntry.id`` deprecates ``TreeEntry.oid`` + `#322 `_ + +- New ``Blob.diff(...)`` and ``Blob.diff_to_buffer(...)`` + `#307 `_ + - New ``Commit.tree_id`` and ``Commit.parent_ids`` `#73 `_ `#311 `_ +- New rich comparison between tree entries + `#305 `_ + `#313 `_ + +- Now ``Tree.__contains__(key)`` supports paths + `#306 `_ + `#316 `_ + +Index: + +- Now possible to create ``IndexEntry(...)`` + `#325 `_ + +- Now ``IndexEntry.path``, ``IndexEntry.oid`` and ``IndexEntry.mode`` are + writable + `#325 `_ + +- Now ``Index.add(...)`` accepts an ``IndexEntry`` too + `#325 `_ + +- Now ``Index.write_tree(...)`` is able to write to a different repository + `#325 `_ + +- Fix memory leak in ``IndexEntry.path`` setter + `#335 `_ + +Config: + - New ``Config`` iterator replaces ``Config.foreach`` `#183 `_ `#312 `_ -- New rich comparison between tree entries - `#305 `_ - `#313 `_ +Remote: - New type ``Refspec`` `#314 `_ @@ -111,10 +143,6 @@ Changelog - New ``Remote.push_url`` `#315 `_ -- Now ``path in Tree`` works - `#306 `_ - `#316 `_ - - New ``Remote.add_push`` and ``Remote.add_fetch`` `#255 `_ `#318 `_ @@ -127,15 +155,6 @@ Changelog ``Remote.set_push_refspecs(...)`` `#319 `_ -- New ``str(Oid)`` deprecates ``Oid.hex`` - `#322 `_ - -- New ``Object.id`` deprecates ``Object.oid`` - `#322 `_ - -- New ``TreeEntry.id`` deprecates ``TreeEntry.oid`` - `#322 `_ - - New ``Remote.progress``, ``Remote.transfer_progress`` and ``Remote.update_tips`` `#274 `_ @@ -145,25 +164,11 @@ Changelog `#274 `_ `#324 `_ -- Now possible to create ``IndexEntry(...)`` - `#325 `_ - -- Now ``IndexEntry.path``, ``IndexEntry.oid`` and ``IndexEntry.mode`` are - writable - `#325 `_ - -- Now ``Index.add(...)`` accepts an ``IndexEntry`` too - `#325 `_ - -- Now ``Index.write_tree(...)`` is able to write to a different repository - `#325 `_ - - Fix refcount leak in ``Repository.remotes`` `#321 `_ `#332 `_ -- Other non user visible changes: - `#331 `_ +Other: `#331 `_ 0.20.1 (2013-12-24) diff --git a/docs/conf.py b/docs/conf.py index addf93dd3..d7f2799c3 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -50,7 +50,7 @@ # The short X.Y version. version = '0.20' # The full version, including alpha/beta/rc tags. -release = '0.20.1' +release = '0.20.2' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/docs/install.rst b/docs/install.rst index e237037a2..f74e2d7ac 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -27,7 +27,7 @@ When those are installed, you can install pygit2: .. note:: A minor version of pygit2 must be used with the corresponding minor version of libgit2. For example, pygit2 v0.20.x must be used with libgit2 - v0.20.1. + v0.20.0 Building on \*nix (including OS X) =================================== diff --git a/pygit2/version.py b/pygit2/version.py index 71fbd6f5f..af8676feb 100644 --- a/pygit2/version.py +++ b/pygit2/version.py @@ -23,4 +23,4 @@ # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. -__version__ = '0.20.1' +__version__ = '0.20.2' From 73170cc104aa2114e824fdece3b513f03866d26b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Thu, 6 Feb 2014 10:27:21 +0100 Subject: [PATCH 0119/1630] walk: make the sorting mode optional Since the libgit2 has a default sorting method, which we also mention as default in the documentation, there is no particular need to make the user choose a sorting method when the order does not matter. We use sorting NONE in that case. --- src/repository.c | 8 ++++---- test/test_revwalk.py | 10 +++++++++- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/repository.c b/src/repository.c index 750517859..fa1d13d51 100644 --- a/src/repository.c +++ b/src/repository.c @@ -626,13 +626,13 @@ Repository_merge(Repository *self, PyObject *py_oid) } PyDoc_STRVAR(Repository_walk__doc__, - "walk(oid, sort_mode) -> iterator\n" + "walk(oid[, sort_mode]) -> iterator\n" "\n" "Generator that traverses the history starting from the given commit.\n" "The following types of sorting could be used to control traversing\n" "direction:\n" "\n" - "* GIT_SORT_NONE. This is the default sorting for new walkers\n" + "* GIT_SORT_NONE. This is the default sorting for new walkers.\n" " Sort the repository contents in no particular ordering\n" "* GIT_SORT_TOPOLOGICAL. Sort the repository contents in topological order\n" " (parents before children); this sorting mode can be combined with\n" @@ -656,13 +656,13 @@ PyObject * Repository_walk(Repository *self, PyObject *args) { PyObject *value; - unsigned int sort; + unsigned int sort = GIT_SORT_NONE; int err; git_oid oid; git_revwalk *walk; Walker *py_walker; - if (!PyArg_ParseTuple(args, "OI", &value, &sort)) + if (!PyArg_ParseTuple(args, "O|I", &value, &sort)) return NULL; err = git_revwalk_new(&walk, self->repo); diff --git a/test/test_revwalk.py b/test/test_revwalk.py index be4a60882..db08c65f9 100644 --- a/test/test_revwalk.py +++ b/test/test_revwalk.py @@ -31,7 +31,7 @@ from __future__ import unicode_literals import unittest -from pygit2 import GIT_SORT_TIME, GIT_SORT_REVERSE +from pygit2 import GIT_SORT_NONE, GIT_SORT_TIME, GIT_SORT_REVERSE from . import utils @@ -107,5 +107,13 @@ def test_simplify_first_parent(self): walker.simplify_first_parent() self.assertEqual(len(list(walker)), 3) + def test_default_sorting(self): + walker = self.repo.walk(log[0], GIT_SORT_NONE) + list1 = list([x.id for x in walker]) + walker = self.repo.walk(log[0]) + list2 = list([x.id for x in walker]) + + self.assertEqual(list1, list2) + if __name__ == '__main__': unittest.main() From fa7d24005bd8173d7e392d8fcf2c70da8084e7a0 Mon Sep 17 00:00:00 2001 From: Devaev Maxim Date: Fri, 7 Feb 2014 17:11:06 +0400 Subject: [PATCH 0120/1630] Fixed `undefined symbol: PyLong_AsSize_t` on PyPy --- src/utils.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/utils.h b/src/utils.h index 3b3c42a07..60575b1ea 100644 --- a/src/utils.h +++ b/src/utils.h @@ -61,6 +61,10 @@ #define to_encoding(x) PyUnicode_DecodeASCII(x, strlen(x), "strict") #endif +#ifdef PYPY_VERSION + #define PyLong_AsSize_t (size_t)PyLong_AsUnsignedLong +#endif + #ifndef Py_hash_t #define Py_hash_t long #endif From 1f3c0170aa0bd3a84e112fe16f7010147e41e992 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Tue, 11 Feb 2014 17:37:42 +0100 Subject: [PATCH 0121/1630] test: clean up Remove the temporary files and directories we create as part of running the test suite. --- test/test_index.py | 11 +++++------ test/test_repository.py | 1 + 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/test/test_index.py b/test/test_index.py index 7076e2e12..ac8230b78 100644 --- a/test/test_index.py +++ b/test/test_index.py @@ -34,6 +34,7 @@ import tempfile import pygit2 +from pygit2 import Repository from . import utils @@ -154,12 +155,10 @@ def test_change_attributes(self): self.assertEqual(pygit2.GIT_FILEMODE_BLOB_EXECUTABLE, entry.mode) def test_write_tree_to(self): - path = tempfile.mkdtemp() - pygit2.init_repository(path) - nrepo = pygit2.Repository(path) - - id = self.repo.index.write_tree(nrepo) - self.assertNotEqual(None, nrepo[id]) + with utils.TemporaryRepository(('tar', 'emptyrepo')) as path: + nrepo = Repository(path) + id = self.repo.index.write_tree(nrepo) + self.assertNotEqual(None, nrepo[id]) class IndexEntryTest(utils.RepoTestCase): diff --git a/test/test_repository.py b/test/test_repository.py index bba6d76be..8e8101c66 100644 --- a/test/test_repository.py +++ b/test/test_repository.py @@ -165,6 +165,7 @@ def test_hashfile(self): with open(tempfile_path, 'w') as fh: fh.write(data) hashed_sha1 = hashfile(tempfile_path) + os.unlink(tempfile_path) written_sha1 = self.repo.create_blob(data) self.assertEqual(hashed_sha1, written_sha1) From de3dba668ada59d3ea8e87e7d61d05aa2fe5b3a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Tue, 11 Feb 2014 17:33:18 +0100 Subject: [PATCH 0122/1630] object: implement peel() This simplifies getting one type of object from a higher-level one as well as accepting a commit-ish or tree-ish. --- docs/objects.rst | 1 + src/object.c | 23 ++++++++++++++++ test/test_object.py | 67 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 91 insertions(+) create mode 100644 test/test_object.py diff --git a/docs/objects.rst b/docs/objects.rst index ecb39e998..1515dc770 100644 --- a/docs/objects.rst +++ b/docs/objects.rst @@ -81,6 +81,7 @@ This is the common interface for all Git objects: .. autoattribute:: pygit2.Object.id .. autoattribute:: pygit2.Object.type .. automethod:: pygit2.Object.read_raw +.. automethod:: pygit2.Object.peel Blobs diff --git a/src/object.c b/src/object.c index e17ef3f12..56e9e4b87 100644 --- a/src/object.c +++ b/src/object.c @@ -127,6 +127,28 @@ Object_read_raw(Object *self) return aux; } +PyDoc_STRVAR(Object_peel__doc__, + "peel(target_type) -> Object\n" + "\n" + "Peel the current object and returns the first object of the given type\n"); + +PyObject * +Object_peel(Object *self, PyObject *py_type) +{ + int type, err; + git_object *peeled; + + type = PyLong_AsLong(py_type); + if (type == -1 && PyErr_Occurred()) + return NULL; + + err = git_object_peel(&peeled, self->obj, (git_otype)type); + if (err < 0) + return Error_set(err); + + return wrap_object(peeled, self->repo); +} + PyGetSetDef Object_getseters[] = { GETTER(Object, oid), GETTER(Object, id), @@ -137,6 +159,7 @@ PyGetSetDef Object_getseters[] = { PyMethodDef Object_methods[] = { METHOD(Object, read_raw, METH_NOARGS), + METHOD(Object, peel, METH_O), {NULL} }; diff --git a/test/test_object.py b/test/test_object.py new file mode 100644 index 000000000..495b17273 --- /dev/null +++ b/test/test_object.py @@ -0,0 +1,67 @@ +# -*- coding: UTF-8 -*- +# +# Copyright 2010-2014 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. + +"""Tests for Object objects.""" + +from __future__ import absolute_import +from __future__ import unicode_literals +from os.path import dirname, join +import unittest + +import pygit2 +from pygit2 import GIT_OBJ_TREE, GIT_OBJ_TAG +from . import utils + + +BLOB_SHA = 'a520c24d85fbfc815d385957eed41406ca5a860b' +BLOB_CONTENT = """hello world +hola mundo +bonjour le monde +""".encode() +BLOB_NEW_CONTENT = b'foo bar\n' +BLOB_FILE_CONTENT = b'bye world\n' + +class ObjectTest(utils.RepoTestCase): + + def test_peel_commit(self): + # start by looking up the commit + commit_id = self.repo.lookup_reference('refs/heads/master').target + commit = self.repo[commit_id] + # and peel to the tree + tree = commit.peel(GIT_OBJ_TREE) + + self.assertEqual(type(tree), pygit2.Tree) + self.assertEqual(str(tree.id), 'fd937514cb799514d4b81bb24c5fcfeb6472b245') + + def test_invalid(self): + commit_id = self.repo.lookup_reference('refs/heads/master').target + commit = self.repo[commit_id] + + self.assertRaises(ValueError, commit.peel, GIT_OBJ_TAG) + +if __name__ == '__main__': + unittest.main() From b2cd25cf002bf201eee5e127430703e7f6d46493 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Tue, 11 Feb 2014 18:20:00 +0100 Subject: [PATCH 0123/1630] object: allow passing a type object to peel() Allow usage of a python type instead of having to use the libgit2 constants. --- src/object.c | 33 ++++++++++++++++++++++++++++++--- test/test_object.py | 19 +++++++++++++++++-- 2 files changed, 47 insertions(+), 5 deletions(-) diff --git a/src/object.c b/src/object.c index 56e9e4b87..74c380129 100644 --- a/src/object.c +++ b/src/object.c @@ -127,6 +127,24 @@ Object_read_raw(Object *self) return aux; } +static git_otype +py_type_to_git_type(PyTypeObject *py_type) +{ + git_otype type = GIT_OBJ_BAD; + + if (py_type == &CommitType) { + type = GIT_OBJ_COMMIT; + } else if (py_type == &TreeType) { + type = GIT_OBJ_TREE; + } else if (py_type == &BlobType) { + type = GIT_OBJ_BLOB; + } else if (py_type == &TagType) { + type = GIT_OBJ_TAG; + } + + return type; +} + PyDoc_STRVAR(Object_peel__doc__, "peel(target_type) -> Object\n" "\n" @@ -135,12 +153,21 @@ PyDoc_STRVAR(Object_peel__doc__, PyObject * Object_peel(Object *self, PyObject *py_type) { - int type, err; + int type = -1, err; git_object *peeled; - type = PyLong_AsLong(py_type); - if (type == -1 && PyErr_Occurred()) + if (PyLong_Check(py_type)) { + type = PyLong_AsLong(py_type); + if (type == -1 && PyErr_Occurred()) + return NULL; + } else if (PyType_Check(py_type)) { + type = py_type_to_git_type((PyTypeObject *) py_type); + } + + if (type == -1) { + PyErr_SetString(PyExc_ValueError, "invalid target type"); return NULL; + } err = git_object_peel(&peeled, self->obj, (git_otype)type); if (err < 0) diff --git a/test/test_object.py b/test/test_object.py index 495b17273..663040418 100644 --- a/test/test_object.py +++ b/test/test_object.py @@ -33,7 +33,7 @@ import unittest import pygit2 -from pygit2 import GIT_OBJ_TREE, GIT_OBJ_TAG +from pygit2 import GIT_OBJ_TREE, GIT_OBJ_TAG, Tree, Tag from . import utils @@ -54,14 +54,29 @@ def test_peel_commit(self): # and peel to the tree tree = commit.peel(GIT_OBJ_TREE) - self.assertEqual(type(tree), pygit2.Tree) + self.assertEqual(type(tree), Tree) self.assertEqual(str(tree.id), 'fd937514cb799514d4b81bb24c5fcfeb6472b245') + def test_peel_commit_type(self): + commit_id = self.repo.lookup_reference('refs/heads/master').target + commit = self.repo[commit_id] + tree = commit.peel(Tree) + + self.assertEqual(type(tree), Tree) + self.assertEqual(str(tree.id), 'fd937514cb799514d4b81bb24c5fcfeb6472b245') + + def test_invalid(self): commit_id = self.repo.lookup_reference('refs/heads/master').target commit = self.repo[commit_id] self.assertRaises(ValueError, commit.peel, GIT_OBJ_TAG) + def test_invalid_type(self): + commit_id = self.repo.lookup_reference('refs/heads/master').target + commit = self.repo[commit_id] + + self.assertRaises(ValueError, commit.peel, Tag) + if __name__ == '__main__': unittest.main() From 92547db18dc17f60a39f9f77397286b1e75a603f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Tue, 11 Feb 2014 22:03:23 +0100 Subject: [PATCH 0124/1630] docs: clarify git-init recipe Fixes #336 --- docs/recipes/git-init.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/recipes/git-init.rst b/docs/recipes/git-init.rst index 75211a99a..9af6af0a8 100644 --- a/docs/recipes/git-init.rst +++ b/docs/recipes/git-init.rst @@ -12,11 +12,11 @@ Create bare repository .. code-block:: bash - $> git init --bare relative/path + $ git init --bare path/to/git .. code-block:: python - >>> pygit2.init_repository('relative/path', True) + >>> pygit2.init_repository('path/to/git', True) ====================================================================== @@ -25,17 +25,17 @@ Create standard repository .. code-block:: bash - $> git init relative/path + $ git init path/to/git .. code-block:: python - >>> pygit2.init_repository('relative/path', False) + >>> pygit2.init_repository('path/to/git', False) ---------------------------------------------------------------------- References ---------------------------------------------------------------------- -- git-init_. +- git-init_ .. _git-init: https://www.kernel.org/pub/software/scm/git/docs/git-init.html From a794d66558e390d2e0cbe85ac65bde9dcca37fc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Tue, 11 Feb 2014 22:48:34 +0100 Subject: [PATCH 0125/1630] docs: document TreeBuilder.get Fixes #302 --- docs/objects.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/objects.rst b/docs/objects.rst index 1515dc770..3583c17ec 100644 --- a/docs/objects.rst +++ b/docs/objects.rst @@ -214,6 +214,7 @@ Creating trees .. automethod:: pygit2.TreeBuilder.remove .. automethod:: pygit2.TreeBuilder.clear .. automethod:: pygit2.TreeBuilder.write +.. automethod:: pygit2.TreeBuilder.get Commits From db940931facf01ae24b42ec7efd2f3b559e19b78 Mon Sep 17 00:00:00 2001 From: Erik Meusel Date: Wed, 12 Feb 2014 07:46:53 +0100 Subject: [PATCH 0126/1630] make remote.c build with VS compiler 2008 --- src/remote.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/remote.c b/src/remote.c index 89c5dda55..39a593d1b 100644 --- a/src/remote.c +++ b/src/remote.c @@ -267,6 +267,7 @@ get_strarraygit_from_pylist(git_strarray *array, PyObject *pylist) Py_ssize_t index, n; PyObject *item; void *ptr; + char *str; if (!PyList_Check(pylist)) { PyErr_SetString(PyExc_TypeError, "Value must be a list"); @@ -287,7 +288,7 @@ get_strarraygit_from_pylist(git_strarray *array, PyObject *pylist) for (index = 0; index < n; index++) { item = PyList_GetItem(pylist, index); - char *str = py_str_to_c_str(item, NULL); + str = py_str_to_c_str(item, NULL); if (!str) goto on_error; From 1e6062932ced54d6ac171a27cf463cba929cb784 Mon Sep 17 00:00:00 2001 From: Leonardo Rhodes Date: Wed, 12 Feb 2014 21:47:41 +0100 Subject: [PATCH 0127/1630] Added git_add_all Moved git strarray conversion to utils --- src/index.c | 26 ++++++++++++++++++- src/index.h | 1 + src/remote.c | 64 ----------------------------------------------- src/utils.c | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/utils.h | 2 ++ 5 files changed, 98 insertions(+), 65 deletions(-) diff --git a/src/index.c b/src/index.c index 90fae7628..6e6f1e113 100644 --- a/src/index.c +++ b/src/index.c @@ -81,7 +81,6 @@ Index_traverse(Index *self, visitproc visit, void *arg) return 0; } - PyDoc_STRVAR(Index_add__doc__, "add([path|entry])\n" "\n" @@ -114,6 +113,31 @@ Index_add(Index *self, PyObject *args) } +PyDoc_STRVAR(Index_add_all__doc__, + "add_all([file names|glob pattern])\n" + "\n" + "Add or update index entries matching files in the working directory."); + +PyObject * +Index_add_all(Index *self, PyObject *pylist) +{ + int err; + git_strarray pathspec; + + if (get_strarraygit_from_pylist(&pathspec, pylist) < 0) + return -1; + + err = git_index_add_all(self->index, &pathspec, 0, NULL, NULL); + git_strarray_free(&pathspec); + + if (err < 0) { + Error_set(err); + return -1; + } + + return 0; +} + PyDoc_STRVAR(Index_clear__doc__, "clear()\n" "\n" diff --git a/src/index.h b/src/index.h index cd570aa0c..44c150d5f 100644 --- a/src/index.h +++ b/src/index.h @@ -33,6 +33,7 @@ #include PyObject* Index_add(Index *self, PyObject *args); +PyObject* Index_add_all(Index *self, PyObject *pylist); PyObject* Index_clear(Index *self); PyObject* Index_find(Index *self, PyObject *py_path); PyObject* Index_read(Index *self, PyObject *args); diff --git a/src/remote.c b/src/remote.c index 39a593d1b..ead4b1495 100644 --- a/src/remote.c +++ b/src/remote.c @@ -243,70 +243,6 @@ Remote_name__set__(Remote *self, PyObject* py_name) return -1; } - -PyObject * -get_pylist_from_git_strarray(git_strarray *strarray) -{ - int index; - PyObject *new_list; - - new_list = PyList_New(strarray->count); - if (new_list == NULL) - return NULL; - - for (index = 0; index < strarray->count; index++) - PyList_SET_ITEM(new_list, index, - to_unicode(strarray->strings[index], NULL, NULL)); - - return new_list; -} - -int -get_strarraygit_from_pylist(git_strarray *array, PyObject *pylist) -{ - Py_ssize_t index, n; - PyObject *item; - void *ptr; - char *str; - - if (!PyList_Check(pylist)) { - PyErr_SetString(PyExc_TypeError, "Value must be a list"); - return -1; - } - - n = PyList_Size(pylist); - - /* allocate new git_strarray */ - ptr = calloc(n, sizeof(char *)); - if (!ptr) { - PyErr_SetNone(PyExc_MemoryError); - return -1; - } - - array->strings = ptr; - array->count = n; - - for (index = 0; index < n; index++) { - item = PyList_GetItem(pylist, index); - str = py_str_to_c_str(item, NULL); - if (!str) - goto on_error; - - array->strings[index] = str; - } - - return 0; - -on_error: - n = index; - for (index = 0; index < n; index++) { - free(array->strings[index]); - } - free(array->strings); - - return -1; -} - PyDoc_STRVAR(Remote_fetch_refspecs__doc__, "Fetch refspecs"); PyObject * diff --git a/src/utils.c b/src/utils.c index e63ea9dbf..44acf5c6a 100644 --- a/src/utils.c +++ b/src/utils.c @@ -83,3 +83,73 @@ py_str_borrow_c_str(PyObject **tvalue, PyObject *value, const char *encoding) Py_TYPE(value)->tp_name); return NULL; } + +/** + * Converts the (struct) git_strarray to a Python list + */ +PyObject * +get_pylist_from_git_strarray(git_strarray *strarray) +{ + int index; + PyObject *new_list; + + new_list = PyList_New(strarray->count); + if (new_list == NULL) + return NULL; + + for (index = 0; index < strarray->count; index++) + PyList_SET_ITEM(new_list, index, + to_unicode(strarray->strings[index], NULL, NULL)); + + return new_list; +} + +/** + * Converts the Python list to struct git_strarray + * returns -1 if conversion failed + */ +int +get_strarraygit_from_pylist(git_strarray *array, PyObject *pylist) +{ + Py_ssize_t index, n; + PyObject *item; + void *ptr; + char *str; + + if (!PyList_Check(pylist)) { + PyErr_SetString(PyExc_TypeError, "Value must be a list"); + return -1; + } + + n = PyList_Size(pylist); + + /* allocate new git_strarray */ + ptr = calloc(n, sizeof(char *)); + if (!ptr) { + PyErr_SetNone(PyExc_MemoryError); + return -1; + } + + array->strings = ptr; + array->count = n; + + for (index = 0; index < n; index++) { + item = PyList_GetItem(pylist, index); + str = py_str_to_c_str(item, NULL); + if (!str) + goto on_error; + + array->strings[index] = str; + } + + return 0; + +on_error: + n = index; + for (index = 0; index < n; index++) { + free(array->strings[index]); + } + free(array->strings); + + return -1; +} diff --git a/src/utils.h b/src/utils.h index 60575b1ea..81744f686 100644 --- a/src/utils.h +++ b/src/utils.h @@ -114,6 +114,8 @@ to_bytes(const char * value) char * py_str_to_c_str(PyObject *value, const char *encoding); const char *py_str_borrow_c_str(PyObject **tvaue, PyObject *value, const char *encoding); +PyObject * get_pylist_from_git_strarray(git_strarray *strarray); +int get_strarraygit_from_pylist(git_strarray *array, PyObject *pylist); #define py_path_to_c_str(py_path) \ py_str_to_c_str(py_path, Py_FileSystemDefaultEncoding) From 7fd633239f27b77f602478fa9a4ae8da639d81c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Wed, 12 Feb 2014 22:11:10 +0100 Subject: [PATCH 0128/1630] Some coding-style fixes, like not using tabs --- src/remote.c | 37 +++++++++++++++++++++++-------------- src/repository.c | 4 ++-- 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/src/remote.c b/src/remote.c index 39a593d1b..964cc0703 100644 --- a/src/remote.c +++ b/src/remote.c @@ -67,23 +67,32 @@ TransferProgress_dealloc(TransferProgress *self) } PyMemberDef TransferProgress_members[] = { - RMEMBER(TransferProgress, total_objects, T_UINT, "Total number objects to download"), - RMEMBER(TransferProgress, indexed_objects, T_UINT, "Objects which have been indexed"), - RMEMBER(TransferProgress, received_objects, T_UINT, "Objects which have been received up to now"), - RMEMBER(TransferProgress, local_objects, T_UINT, "Local objects which were used to fix the thin pack"), - RMEMBER(TransferProgress, total_deltas, T_UINT, "Total number of deltas in the pack"), - RMEMBER(TransferProgress, indexed_deltas, T_UINT, "Deltas which have been indexed"), - /* FIXME: technically this is unsigned, but there's no value for size_t here. */ - RMEMBER(TransferProgress, received_bytes, T_PYSSIZET, "Number of bytes received up to now"), + RMEMBER(TransferProgress, total_objects, T_UINT, + "Total number objects to download"), + RMEMBER(TransferProgress, indexed_objects, T_UINT, + "Objects which have been indexed"), + RMEMBER(TransferProgress, received_objects, T_UINT, + "Objects which have been received up to now"), + RMEMBER(TransferProgress, local_objects, T_UINT, + "Local objects which were used to fix the thin pack"), + RMEMBER(TransferProgress, total_deltas, T_UINT, + "Total number of deltas in the pack"), + RMEMBER(TransferProgress, indexed_deltas, T_UINT, + "Deltas which have been indexed"), + /* FIXME: technically this is unsigned, but there's no value for size_t + * here. */ + RMEMBER(TransferProgress, received_bytes, T_PYSSIZET, + "Number of bytes received up to now"), {NULL}, }; -PyDoc_STRVAR(TransferProgress__doc__, "Progress downloading and indexing data during a fetch"); +PyDoc_STRVAR(TransferProgress__doc__, + "Progress downloading and indexing data during a fetch"); PyTypeObject TransferProgressType = { PyVarObject_HEAD_INIT(NULL, 0) "_pygit2.TransferProgress", /* tp_name */ - sizeof(TransferProgress), /* tp_basicsize */ + sizeof(TransferProgress), /* tp_basicsize */ 0, /* tp_itemsize */ (destructor)TransferProgress_dealloc, /* tp_dealloc */ 0, /* tp_print */ @@ -101,7 +110,7 @@ PyTypeObject TransferProgressType = { 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT, /* tp_flags */ - TransferProgress__doc__, /* tp_doc */ + TransferProgress__doc__, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ @@ -232,7 +241,7 @@ Remote_name__set__(Remote *self, PyObject* py_name) name = py_str_borrow_c_str(&tname, py_name, NULL); if (name != NULL) { err = git_remote_rename(self->remote, name, NULL, NULL); - Py_DECREF(tname); + Py_DECREF(tname); if (err == GIT_OK) return 0; @@ -412,7 +421,7 @@ Remote_url__set__(Remote *self, PyObject* py_url) url = py_str_borrow_c_str(&turl, py_url, NULL); if (url != NULL) { err = git_remote_set_url(self->remote, url); - Py_DECREF(turl); + Py_DECREF(turl); if (err == GIT_OK) return 0; @@ -449,7 +458,7 @@ Remote_push_url__set__(Remote *self, PyObject* py_url) url = py_str_borrow_c_str(&turl, py_url, NULL); if (url != NULL) { err = git_remote_set_pushurl(self->remote, url); - Py_DECREF(turl); + Py_DECREF(turl); if (err == GIT_OK) return 0; diff --git a/src/repository.c b/src/repository.c index fa1d13d51..1e654e28b 100644 --- a/src/repository.c +++ b/src/repository.c @@ -333,7 +333,7 @@ Repository_revparse_single(Repository *self, PyObject *py_spec) if (err < 0) { PyObject *err_obj = Error_set_str(err, c_spec); - Py_DECREF(tspec); + Py_DECREF(tspec); return err_obj; } Py_DECREF(tspec); @@ -1051,7 +1051,7 @@ Repository_lookup_reference(Repository *self, PyObject *py_name) err = git_reference_lookup(&c_reference, self->repo, c_name); if (err < 0) { PyObject *err_obj = Error_set_str(err, c_name); - free(c_name); + free(c_name); return err_obj; } free(c_name); From 9223d75bb0ebff8ef263158978c4829eeec5aacb Mon Sep 17 00:00:00 2001 From: Leonardo Rhodes Date: Wed, 12 Feb 2014 22:20:09 +0100 Subject: [PATCH 0129/1630] fixed wrong return value --- src/index.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/index.c b/src/index.c index 6e6f1e113..cc1b365df 100644 --- a/src/index.c +++ b/src/index.c @@ -125,17 +125,17 @@ Index_add_all(Index *self, PyObject *pylist) git_strarray pathspec; if (get_strarraygit_from_pylist(&pathspec, pylist) < 0) - return -1; + return NULL; err = git_index_add_all(self->index, &pathspec, 0, NULL, NULL); git_strarray_free(&pathspec); if (err < 0) { Error_set(err); - return -1; + return NULL; } - return 0; + Py_RETURN_NONE; } PyDoc_STRVAR(Index_clear__doc__, @@ -481,6 +481,7 @@ Index_write_tree(Index *self, PyObject *args) PyMethodDef Index_methods[] = { METHOD(Index, add, METH_VARARGS), + METHOD(Index, add_all, METH_O), METHOD(Index, remove, METH_VARARGS), METHOD(Index, clear, METH_NOARGS), METHOD(Index, diff_to_workdir, METH_VARARGS), From e322a3a678903fb9b801079ff4216e877519ba29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Wed, 12 Feb 2014 23:05:51 +0100 Subject: [PATCH 0130/1630] Silence a couple of valgrind warnings --- src/branch.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/branch.c b/src/branch.c index 8f78de8bb..bcf763677 100644 --- a/src/branch.c +++ b/src/branch.c @@ -161,7 +161,7 @@ Branch_remote_name__get__(Branch *self) return Error_set(err); } - py_name = to_unicode(c_name, NULL, NULL); + py_name = to_unicode_n(c_name, err - 1, NULL, NULL); free(c_name); return py_name; @@ -253,7 +253,7 @@ Branch_upstream_name__get__(Branch *self) return Error_set(err); } - py_name = to_unicode(c_name, NULL, NULL); + py_name = to_unicode_n(c_name, err - 1, NULL, NULL); free(c_name); return py_name; From 1a17256ee31f9954eeb3f2726c6cc512b1c69ab1 Mon Sep 17 00:00:00 2001 From: Leonardo Rhodes Date: Thu, 13 Feb 2014 23:09:40 +0100 Subject: [PATCH 0131/1630] added unit test --- test/test_index.py | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/test/test_index.py b/test/test_index.py index ac8230b78..7bed86bb3 100644 --- a/test/test_index.py +++ b/test/test_index.py @@ -74,6 +74,42 @@ def test_add(self): self.assertEqual(len(index), 3) self.assertEqual(index['bye.txt'].hex, sha) + def test_add_all(self): + self.test_clear() + + index = self.repo.index + + sha_bye = '0907563af06c7464d62a70cdd135a6ba7d2b41d8' + sha_hello = 'a520c24d85fbfc815d385957eed41406ca5a860b' + + index.add_all(['*.txt']) + + self.assertTrue('bye.txt' in index) + self.assertTrue('hello.txt' in index) + + self.assertEqual(index['bye.txt'].hex, sha_bye) + self.assertEqual(index['hello.txt'].hex, sha_hello) + + self.test_clear() + + index.add_all(['bye.t??', 'hello.*']) + + self.assertTrue('bye.txt' in index) + self.assertTrue('hello.txt' in index) + + self.assertEqual(index['bye.txt'].hex, sha_bye) + self.assertEqual(index['hello.txt'].hex, sha_hello) + + self.test_clear() + + index.add_all(['[byehlo]*.txt']) + + self.assertTrue('bye.txt' in index) + self.assertTrue('hello.txt' in index) + + self.assertEqual(index['bye.txt'].hex, sha_bye) + self.assertEqual(index['hello.txt'].hex, sha_hello) + def test_clear(self): index = self.repo.index self.assertEqual(len(index), 2) From 0e4a27284f64a390911316cc48f2c4b344099a49 Mon Sep 17 00:00:00 2001 From: earl Date: Fri, 21 Feb 2014 10:21:32 +0100 Subject: [PATCH 0132/1630] diff: Fix for memory leak in git_patch --- src/diff.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/diff.c b/src/diff.c index 8e59ebaa0..b3a32ddc2 100644 --- a/src/diff.c +++ b/src/diff.c @@ -127,6 +127,7 @@ wrap_patch(git_patch *patch) } } } + git_patch_free(patch); return (PyObject*) py_patch; } From ab441967302b0d2eafb84a1c0a7bad90edc99156 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sun, 23 Mar 2014 12:55:12 +0100 Subject: [PATCH 0133/1630] Introduce libgit2 options Allow setting and getting the mwindow size and search paths. --- src/options.c | 185 +++++++++++++++++++++++++++++++++++++++++++ src/options.h | 51 ++++++++++++ src/pygit2.c | 13 +++ test/test_options.py | 57 +++++++++++++ 4 files changed, 306 insertions(+) create mode 100644 src/options.c create mode 100644 src/options.h create mode 100644 test/test_options.py diff --git a/src/options.c b/src/options.c new file mode 100644 index 000000000..abfa4656a --- /dev/null +++ b/src/options.c @@ -0,0 +1,185 @@ +/* + * Copyright 2010-2014 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 +#include "error.h" +#include "types.h" +#include "utils.h" + +extern PyObject *GitError; + +static PyObject * +get_search_path(long level) +{ + char *buf = NULL; + size_t len = 64; + PyObject *py_path; + int error; + + do { + len *= 2; + char *tmp = realloc(buf, len); + if (!tmp) { + free(buf); + PyErr_NoMemory(); + return NULL; + } + buf = tmp; + + error = git_libgit2_opts(GIT_OPT_GET_SEARCH_PATH, level, buf, len); + } while(error == GIT_EBUFS); + + if (error < 0) { + free(buf); + Error_set(error); + return NULL; + } + + if (!buf) + return NULL; + + py_path = to_unicode(buf, NULL, NULL); + free(buf); + + if (!py_path) + return NULL; + + return py_path; +} + +PyObject * +option(PyObject *self, PyObject *args) +{ + long option; + int error; + PyObject *py_option; + + py_option = PyTuple_GetItem(args, 0); + if (!py_option) + return NULL; + + if (!PyLong_Check(py_option)) + goto on_non_integer; + + option = PyLong_AsLong(py_option); + + switch (option) { + case GIT_OPT_GET_SEARCH_PATH: + { + PyObject *py_level; + + py_level = PyTuple_GetItem(args, 1); + if (!py_level) + return NULL; + + if (!PyLong_Check(py_level)) + goto on_non_integer; + + return get_search_path(PyLong_AsLong(py_level)); + break; + } + + case GIT_OPT_SET_SEARCH_PATH: + { + PyObject *py_level, *py_path, *tpath; + const char *path; + int err; + + py_level = PyTuple_GetItem(args, 1); + if (!py_level) + return NULL; + + py_path = PyTuple_GetItem(args, 2); + if (!py_path) + return NULL; + + if (!PyLong_Check(py_level)) + goto on_non_integer; + + path = py_str_borrow_c_str(&tpath, py_path, NULL); + if (!path) + return NULL; + + err = git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, PyLong_AsLong(py_level), path); + Py_DECREF(tpath); + + if (err < 0) { + Error_set(err); + return NULL; + } + + Py_RETURN_NONE; + break; + } + + case GIT_OPT_GET_MWINDOW_SIZE: + { + size_t size; + + if ((error = git_libgit2_opts(GIT_OPT_GET_MWINDOW_SIZE, &size)) < 0) { + Error_set(error); + return NULL; + } + + return PyLong_FromSize_t(size); + + break; + } + + case GIT_OPT_SET_MWINDOW_SIZE: + { + size_t size; + PyObject *py_size; + + py_size = PyTuple_GetItem(args, 1); + if (!py_size) + return NULL; + + if (!PyLong_Check(py_size)) + goto on_non_integer; + + + size = PyLong_AsSize_t(py_size); + if ((error = git_libgit2_opts(GIT_OPT_SET_MWINDOW_SIZE, size)) < 0) { + Error_set(error); + return NULL; + } + + Py_RETURN_NONE; + break; + } + } + + PyErr_SetString(PyExc_ValueError, "unknown/unsupported option value"); + return NULL; + +on_non_integer: + PyErr_SetString(PyExc_TypeError, "option is not an integer"); + return NULL; +} diff --git a/src/options.h b/src/options.h new file mode 100644 index 000000000..8dccda11c --- /dev/null +++ b/src/options.h @@ -0,0 +1,51 @@ +/* + * Copyright 2010-2014 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_blame_h +#define INCLUDE_pygit2_blame_h + +#define PY_SSIZE_T_CLEAN +#include +#include +#include "types.h" + +PyDoc_STRVAR(option__doc__, + "Get or set a libgit2 option\n" + "Arguments:\n" + " (GIT_OPT_GET_SEARCH_PATH, level)\n" + " Get the config search path for the given level\n" + " (GIT_OPT_SET_SEARCH_PATH, level, path)\n" + " Set the config search path for the given level\n" + " (GIT_OPT_GET_MWINDOW_SIZE)\n" + " Get the maximum mmap window size\n" + " (GIT_OPT_SET_MWINDOW_SIZE, size)\n" + " Set the maximum mmap window size\n"); + + +PyObject *option(PyObject *self, PyObject *args); + +#endif diff --git a/src/pygit2.c b/src/pygit2.c index 77535c735..9b6319e6d 100644 --- a/src/pygit2.c +++ b/src/pygit2.c @@ -39,6 +39,7 @@ #include "utils.h" #include "repository.h" #include "oid.h" +#include "options.h" /* FIXME: This is for pypy */ #ifndef MAXPATHLEN @@ -244,6 +245,7 @@ PyMethodDef module_methods[] = { discover_repository__doc__}, {"hashfile", hashfile, METH_VARARGS, hashfile__doc__}, {"hash", hash, METH_VARARGS, hash__doc__}, + {"option", option, METH_VARARGS, option__doc__}, {NULL} }; @@ -259,6 +261,12 @@ moduleinit(PyObject* m) ADD_CONSTANT_INT(m, LIBGIT2_VER_REVISION) ADD_CONSTANT_STR(m, LIBGIT2_VERSION) + /* libgit2 options */ + ADD_CONSTANT_INT(m, GIT_OPT_GET_SEARCH_PATH); + ADD_CONSTANT_INT(m, GIT_OPT_SET_SEARCH_PATH); + ADD_CONSTANT_INT(m, GIT_OPT_GET_MWINDOW_SIZE); + ADD_CONSTANT_INT(m, GIT_OPT_SET_MWINDOW_SIZE); + /* Errors */ GitError = PyErr_NewException("_pygit2.GitError", NULL, NULL); Py_INCREF(GitError); @@ -424,6 +432,11 @@ moduleinit(PyObject* m) ADD_CONSTANT_INT(m, GIT_DIFF_FIND_AND_BREAK_REWRITES) /* Config */ + ADD_CONSTANT_INT(m, GIT_CONFIG_LEVEL_LOCAL); + ADD_CONSTANT_INT(m, GIT_CONFIG_LEVEL_GLOBAL); + ADD_CONSTANT_INT(m, GIT_CONFIG_LEVEL_XDG); + ADD_CONSTANT_INT(m, GIT_CONFIG_LEVEL_SYSTEM); + INIT_TYPE(ConfigType, NULL, PyType_GenericNew) INIT_TYPE(ConfigIterType, NULL, NULL) ADD_TYPE(m, Config) diff --git a/test/test_options.py b/test/test_options.py new file mode 100644 index 000000000..e35f52008 --- /dev/null +++ b/test/test_options.py @@ -0,0 +1,57 @@ +# -*- coding: UTF-8 -*- +# +# Copyright 2010-2014 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. + +"""Tests for Blame objects.""" + +from __future__ import absolute_import +from __future__ import unicode_literals +import unittest +import pygit2 +from pygit2 import GIT_OPT_GET_MWINDOW_SIZE, GIT_OPT_SET_MWINDOW_SIZE +from pygit2 import GIT_OPT_GET_SEARCH_PATH, GIT_OPT_SET_SEARCH_PATH +from pygit2 import GIT_CONFIG_LEVEL_SYSTEM, GIT_CONFIG_LEVEL_XDG, GIT_CONFIG_LEVEL_GLOBAL +from pygit2 import option +from . import utils + +class OptionsTest(utils.NoRepoTestCase): + + def test_mwindow_size(self): + new_size = 200 * 1024 + option(GIT_OPT_SET_MWINDOW_SIZE, new_size) + self.assertEqual(new_size, option(GIT_OPT_GET_MWINDOW_SIZE)) + + def test_search_path(self): + paths = [(GIT_CONFIG_LEVEL_GLOBAL, '/tmp/global'), + (GIT_CONFIG_LEVEL_XDG, '/tmp/xdg'), + (GIT_CONFIG_LEVEL_SYSTEM, '/tmp/etc')] + + for level, path in paths: + option(GIT_OPT_SET_SEARCH_PATH, level, path) + self.assertEqual(path, option(GIT_OPT_GET_SEARCH_PATH, level)) + +if __name__ == '__main__': + unittest.main() From 48ff3a8983467fd86f64636b8751ef2e2beda0f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sun, 23 Mar 2014 15:53:32 +0100 Subject: [PATCH 0134/1630] Create proxy object for libgit2 options pygit2.settings proxies though the lower-level varargs option() via getters and setters which provide a more natural abstraction. --- pygit2/__init__.py | 3 +++ pygit2/settings.py | 52 ++++++++++++++++++++++++++++++++++++++++++++ test/test_options.py | 15 +++++++++++++ 3 files changed, 70 insertions(+) create mode 100644 pygit2/settings.py diff --git a/pygit2/__init__.py b/pygit2/__init__.py index 244fdad1e..8d40fc487 100644 --- a/pygit2/__init__.py +++ b/pygit2/__init__.py @@ -35,6 +35,7 @@ # High level API from .repository import Repository from .version import __version__ +from .settings import Settings def init_repository(path, bare=False): @@ -75,3 +76,5 @@ def clone_repository( _pygit2.clone_repository( url, path, bare, ignore_cert_errors, remote_name, checkout_branch) return Repository(path) + +settings = Settings() diff --git a/pygit2/settings.py b/pygit2/settings.py new file mode 100644 index 000000000..d6e41998c --- /dev/null +++ b/pygit2/settings.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- +# +# Copyright 2010-2014 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. + +from _pygit2 import option +from _pygit2 import GIT_OPT_GET_SEARCH_PATH, GIT_OPT_SET_SEARCH_PATH +from _pygit2 import GIT_OPT_GET_MWINDOW_SIZE, GIT_OPT_SET_MWINDOW_SIZE + +class SearchPathList(object): + + def __getitem__(self, key): + return option(GIT_OPT_GET_SEARCH_PATH, key) + + def __setitem__(self, key, value): + option(GIT_OPT_SET_SEARCH_PATH, key, value) + +class Settings(object): + + __slots__ = ['mwindow_size', 'search_path'] + + search_path = SearchPathList() + + @property + def mwindow_size(self): + return option(GIT_OPT_GET_MWINDOW_SIZE) + + @mwindow_size.setter + def mwindow_size(self, value): + option(GIT_OPT_SET_MWINDOW_SIZE, value) diff --git a/test/test_options.py b/test/test_options.py index e35f52008..a6bc22b46 100644 --- a/test/test_options.py +++ b/test/test_options.py @@ -44,6 +44,12 @@ def test_mwindow_size(self): option(GIT_OPT_SET_MWINDOW_SIZE, new_size) self.assertEqual(new_size, option(GIT_OPT_GET_MWINDOW_SIZE)) + def test_mwindow_size_proxy(self): + new_size = 300 * 1024 + pygit2.settings.mwindow_size = new_size + + self.assertEqual(new_size, pygit2.settings.mwindow_size) + def test_search_path(self): paths = [(GIT_CONFIG_LEVEL_GLOBAL, '/tmp/global'), (GIT_CONFIG_LEVEL_XDG, '/tmp/xdg'), @@ -53,5 +59,14 @@ def test_search_path(self): option(GIT_OPT_SET_SEARCH_PATH, level, path) self.assertEqual(path, option(GIT_OPT_GET_SEARCH_PATH, level)) + def test_search_path_proxy(self): + paths = [(GIT_CONFIG_LEVEL_GLOBAL, '/tmp2/global'), + (GIT_CONFIG_LEVEL_XDG, '/tmp2/xdg'), + (GIT_CONFIG_LEVEL_SYSTEM, '/tmp2/etc')] + + for level, path in paths: + pygit2.settings.search_path[level] = path + self.assertEqual(path, pygit2.settings.search_path[level]) + if __name__ == '__main__': unittest.main() From 6bdb0135924fe8370f5382b3d01648b5b265ee90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sun, 23 Mar 2014 21:21:01 +0100 Subject: [PATCH 0135/1630] settings: python 3.3 compat Python 3.3 doesn't like us overriding properties in __slots__, so set it to an empty list. --- pygit2/settings.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pygit2/settings.py b/pygit2/settings.py index d6e41998c..6455de17e 100644 --- a/pygit2/settings.py +++ b/pygit2/settings.py @@ -39,9 +39,13 @@ def __setitem__(self, key, value): class Settings(object): - __slots__ = ['mwindow_size', 'search_path'] + __slots__ = [] - search_path = SearchPathList() + _search_path = SearchPathList() + + @property + def search_path(self): + return self._search_path @property def mwindow_size(self): From 87c8aef7d999ac52302c76373a66317f1e194803 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sun, 23 Mar 2014 21:33:10 +0100 Subject: [PATCH 0136/1630] Settings: add documentation The search_path attribute is now inside __init__() so it shows properly in the generated documentation. --- docs/index.rst | 1 + docs/settings.rst | 8 ++++++++ pygit2/settings.py | 8 ++++++++ 3 files changed, 17 insertions(+) create mode 100644 docs/settings.rst diff --git a/docs/index.rst b/docs/index.rst index f838b3518..6eccbd564 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -45,6 +45,7 @@ Usage guide: config remotes blame + settings Indices and tables diff --git a/docs/settings.rst b/docs/settings.rst new file mode 100644 index 000000000..f1d7a9871 --- /dev/null +++ b/docs/settings.rst @@ -0,0 +1,8 @@ +********************************************************************** +Settings +********************************************************************** + +.. contents:: + +.. autoclass:: pygit2.Settings + :members: diff --git a/pygit2/settings.py b/pygit2/settings.py index 6455de17e..b5805637b 100644 --- a/pygit2/settings.py +++ b/pygit2/settings.py @@ -38,6 +38,7 @@ def __setitem__(self, key, value): option(GIT_OPT_SET_SEARCH_PATH, key, value) class Settings(object): + """Library-wide settings""" __slots__ = [] @@ -45,10 +46,17 @@ class Settings(object): @property def search_path(self): + """Configuration file search path. + + This behaves like an array whose indices correspond to the + GIT_CONFIG_LEVEL_* values. The local search path cannot be + changed. + """ return self._search_path @property def mwindow_size(self): + """Maximum mmap window size""" return option(GIT_OPT_GET_MWINDOW_SIZE) @mwindow_size.setter From af2528418a1bb627ea066c1534230b036c915426 Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Tue, 25 Mar 2014 15:13:10 -0700 Subject: [PATCH 0137/1630] More informative repr for Repository objects --- pygit2/repository.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pygit2/repository.py b/pygit2/repository.py index 4b7876579..e6c750f31 100644 --- a/pygit2/repository.py +++ b/pygit2/repository.py @@ -56,6 +56,8 @@ def __getitem__(self, key): def __contains__(self, key): return self.git_object_lookup_prefix(key) is not None + def __repr__(self): + return "pygit2.Repository(%r)" % self.path # # References From b49da5396228526a1a9ca7400437c28027662200 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Tue, 25 Mar 2014 18:31:43 +0100 Subject: [PATCH 0138/1630] Introduce credentials --- pygit2/__init__.py | 2 +- pygit2/credentials.py | 52 +++++++++ src/credentials.c | 225 +++++++++++++++++++++++++++++++++++++++ src/pygit2.c | 9 ++ src/remote.c | 77 ++++++++++++++ src/types.h | 20 ++++ test/test_credentials.py | 98 +++++++++++++++++ 7 files changed, 482 insertions(+), 1 deletion(-) create mode 100644 pygit2/credentials.py create mode 100644 src/credentials.c create mode 100644 test/test_credentials.py diff --git a/pygit2/__init__.py b/pygit2/__init__.py index 8d40fc487..60e8942c3 100644 --- a/pygit2/__init__.py +++ b/pygit2/__init__.py @@ -36,7 +36,7 @@ from .repository import Repository from .version import __version__ from .settings import Settings - +from .credentials import * def init_repository(path, bare=False): """ diff --git a/pygit2/credentials.py b/pygit2/credentials.py new file mode 100644 index 000000000..a3a3c25c1 --- /dev/null +++ b/pygit2/credentials.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- +# +# Copyright 2010-2014 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. + +# Import from pygit2 +from _pygit2 import CredUsernamePassword, CredSshKey +from _pygit2 import GIT_CREDTYPE_USERPASS_PLAINTEXT + +class UserPass(CredUsernamePassword): + """Username/Password credentials + + This is an object suitable for passing to a remote's credentials + callback. + + """ + + def __call__(self, _url, _username, _allowed): + return self + +class Keypair(CredSshKey): + """SSH key pair credentials + + This is an object suitable for passing to a remote's credentials + callback. + + """ + + def __call__(self, _url, _username, _allowed): + return self diff --git a/src/credentials.c b/src/credentials.c new file mode 100644 index 000000000..5711f044b --- /dev/null +++ b/src/credentials.c @@ -0,0 +1,225 @@ +/* + * Copyright 2010-2014 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 +#include "error.h" +#include "types.h" +#include "utils.h" +#include "oid.h" +#include "refspec.h" +#include "remote.h" + +int +CredUsernamePassword_init(CredUsernamePassword *self, PyObject *args, PyObject *kwds) +{ + char *username, *password; + + if (kwds && PyDict_Size(kwds) > 0) { + PyErr_SetString(PyExc_TypeError, "CredUsernamePassword takes no keyword arguments"); + return -1; + } + + if (!PyArg_ParseTuple(args, "ss", &username, &password)) + return -1; + + self->parent.credtype = GIT_CREDTYPE_USERPASS_PLAINTEXT; + + self->username = strdup(username); + if (!self->username) { + PyErr_NoMemory(); + return -1; + } + + self->password = strdup(password); + if (!self->password) { + free(self->username); + PyErr_NoMemory(); + return -1; + } + + return 0; +} + +void +CredUsernamePassword_dealloc(CredUsernamePassword *self) +{ + free(self->username); + free(self->password); + + PyObject_Del(self); +} + +PyMemberDef CredUsernamePassword_members[] = { + MEMBER(CredUsernamePassword, username, T_STRING, "username"), + MEMBER(CredUsernamePassword, password, T_STRING, "password"), + {NULL}, +}; + +PyDoc_STRVAR(CredUsernamePassword__doc__, + "Credential type for username/password combination"); + +PyTypeObject CredUsernamePasswordType = { + PyVarObject_HEAD_INIT(NULL, 0) + "_pygit2.CredUsernamePassword", /* tp_name */ + sizeof(CredUsernamePassword), /* tp_basicsize */ + 0, /* tp_itemsize */ + (destructor)CredUsernamePassword_dealloc, /* 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 | Py_TPFLAGS_BASETYPE, /* tp_flags */ + CredUsernamePassword__doc__, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + 0, /* tp_methods */ + CredUsernamePassword_members, /* tp_members */ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + (initproc)CredUsernamePassword_init, /* tp_init */ + 0, /* tp_alloc */ + 0, /* tp_new */ +}; + +int +CredSshKey_init(CredSshKey *self, PyObject *args, PyObject *kwds) +{ + char *username, *pubkey, *privkey, *passphrase; + + if (kwds && PyDict_Size(kwds) > 0) { + PyErr_SetString(PyExc_TypeError, "CredSshKey takes no keyword arguments"); + return -1; + } + + if (!PyArg_ParseTuple(args, "ssss", &username, &pubkey, + &privkey, &passphrase)) + return -1; + + self->parent.credtype = GIT_CREDTYPE_SSH_KEY; + self->username = self->pubkey = self->privkey = self->passphrase = NULL; + + self->username = strdup(username); + self->pubkey = strdup(pubkey); + self->privkey = strdup(privkey); + self->passphrase = strdup(passphrase); + + if (!(self->username && self->pubkey && self->privkey && self->passphrase)) + goto on_oom; + + return 0; + + on_oom: + free(self->username); + free(self->pubkey); + free(self->privkey); + free(self->passphrase); + PyErr_NoMemory(); + return -1; +} + +void +CredSshKey_dealloc(CredSshKey *self) +{ + free(self->username); + free(self->pubkey); + free(self->privkey); + free(self->passphrase); + + PyObject_Del(self); +} + +PyMemberDef CredSshKey_members[] = { + MEMBER(CredSshKey, username, T_STRING, "username"), + MEMBER(CredSshKey, pubkey, T_STRING, "pubkey"), + MEMBER(CredSshKey, privkey, T_STRING, "privkey"), + MEMBER(CredSshKey, passphrase, T_STRING, "passphrase"), + {NULL}, +}; + +PyDoc_STRVAR(CredSshKey__doc__, + "Credential type for an SSH keypair"); + +PyTypeObject CredSshKeyType = { + PyVarObject_HEAD_INIT(NULL, 0) + "_pygit2.CredSshKey", /* tp_name */ + sizeof(CredSshKey), /* tp_basicsize */ + 0, /* tp_itemsize */ + (destructor)CredSshKey_dealloc, /* 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 | Py_TPFLAGS_BASETYPE, /* tp_flags */ + CredSshKey__doc__, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + 0, /* tp_methods */ + CredSshKey_members, /* tp_members */ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + (initproc)CredSshKey_init, /* tp_init */ + 0, /* tp_alloc */ + 0, /* tp_new */ +}; diff --git a/src/pygit2.c b/src/pygit2.c index 9b6319e6d..ea6435435 100644 --- a/src/pygit2.c +++ b/src/pygit2.c @@ -74,6 +74,8 @@ extern PyTypeObject RefLogEntryType; extern PyTypeObject BranchType; extern PyTypeObject SignatureType; extern PyTypeObject RemoteType; +extern PyTypeObject CredUsernamePasswordType; +extern PyTypeObject CredSshKeyType; extern PyTypeObject RefspecType; extern PyTypeObject TransferProgressType; extern PyTypeObject NoteType; @@ -444,14 +446,21 @@ moduleinit(PyObject* m) /* Remotes */ INIT_TYPE(RemoteType, NULL, NULL) + INIT_TYPE(CredUsernamePasswordType, NULL, PyType_GenericNew) + INIT_TYPE(CredSshKeyType, NULL, PyType_GenericNew) INIT_TYPE(RefspecType, NULL, NULL) INIT_TYPE(TransferProgressType, NULL, NULL) ADD_TYPE(m, Remote) + ADD_TYPE(m, CredUsernamePassword) + ADD_TYPE(m, CredSshKey) ADD_TYPE(m, Refspec) ADD_TYPE(m, TransferProgress) /* Direction for the refspec */ ADD_CONSTANT_INT(m, GIT_DIRECTION_FETCH) ADD_CONSTANT_INT(m, GIT_DIRECTION_PUSH) + /* Credential types */ + ADD_CONSTANT_INT(m, GIT_CREDTYPE_USERPASS_PLAINTEXT) + ADD_CONSTANT_INT(m, GIT_CREDTYPE_SSH_KEY) /* Blame */ INIT_TYPE(BlameType, NULL, NULL) diff --git a/src/remote.c b/src/remote.c index c73878423..9c7cce275 100644 --- a/src/remote.c +++ b/src/remote.c @@ -39,6 +39,8 @@ extern PyObject *GitError; extern PyTypeObject RepositoryType; extern PyTypeObject TransferProgressType; +extern PyTypeObject CredUsernamePasswordType; +extern PyTypeObject CredSshKeyType; PyObject * wrap_transfer_progress(const git_transfer_progress *stats) @@ -156,6 +158,78 @@ progress_cb(const char *str, int len, void *data) return 0; } +static int +py_cred_to_git_cred(git_cred **out, PyObject *py_cred, unsigned int allowed) +{ + Cred *base_cred; + int err; + + if (!PyObject_TypeCheck(py_cred, &CredUsernamePasswordType) && + !PyObject_TypeCheck(py_cred, &CredSshKeyType)) { + PyErr_SetString(PyExc_TypeError, "unkown credential type"); + return -1; + } + + base_cred = (Cred *) py_cred; + + /* Sanity check, make sure we're given credentials we can use */ + if (!(allowed & base_cred->credtype)) { + PyErr_SetString(PyExc_TypeError, "invalid credential type"); + return -1; + } + + switch (base_cred->credtype) { + case GIT_CREDTYPE_USERPASS_PLAINTEXT: + { + CredUsernamePassword *cred = (CredUsernamePassword *) base_cred; + err = git_cred_userpass_plaintext_new(out, cred->username, cred->password); + break; + } + case GIT_CREDTYPE_SSH_KEY: + { + CredSshKey *cred = (CredSshKey *) base_cred; + err = git_cred_ssh_key_new(out, cred->username, cred->pubkey, cred->privkey, cred->passphrase); + break; + } + default: + PyErr_SetString(PyExc_TypeError, "unsupported credential type"); + err = -1; + break; + } + + return err; +} + +static int +credentials_cb(git_cred **out, const char *url, const char *username_from_url, unsigned int allowed_types, void *data) +{ + Remote *remote = (Remote *) data; + PyObject *arglist, *py_cred; + int err; + + if (remote->credentials == NULL) + return 0; + + if (!PyCallable_Check(remote->credentials)) { + PyErr_SetString(PyExc_TypeError, "credentials callback is not callable"); + return -1; + } + + arglist = Py_BuildValue("(szI)", url, username_from_url, allowed_types); + py_cred = PyObject_CallObject(remote->credentials, arglist); + Py_DECREF(arglist); + + if (!py_cred) + return -1; + + err = py_cred_to_git_cred(out, py_cred, allowed_types); + + + Py_DECREF(py_cred); + + return err; +} + static int transfer_progress_cb(const git_transfer_progress *stats, void *data) { @@ -631,6 +705,7 @@ PyGetSetDef Remote_getseters[] = { PyMemberDef Remote_members[] = { MEMBER(Remote, progress, T_OBJECT_EX, "Progress output callback"), + MEMBER(Remote, credentials, T_OBJECT_EX, "Credentials callback"), MEMBER(Remote, transfer_progress, T_OBJECT_EX, "Transfer progress callback"), MEMBER(Remote, update_tips, T_OBJECT_EX, "update tips callback"), {NULL}, @@ -691,10 +766,12 @@ wrap_remote(git_remote *c_remote, Repository *repo) py_remote->repo = repo; py_remote->remote = c_remote; py_remote->progress = NULL; + py_remote->credentials = NULL; py_remote->transfer_progress = NULL; py_remote->update_tips = NULL; callbacks.progress = progress_cb; + callbacks.credentials = credentials_cb; callbacks.transfer_progress = transfer_progress_cb; callbacks.update_tips = update_tips_cb; callbacks.payload = py_remote; diff --git a/src/types.h b/src/types.h index e6a318994..1a73fc5ea 100644 --- a/src/types.h +++ b/src/types.h @@ -202,10 +202,30 @@ typedef struct { git_remote *remote; /* Callbacks for network events */ PyObject *progress; + PyObject *credentials; PyObject *transfer_progress; PyObject *update_tips; } Remote; +typedef struct { + PyObject_HEAD + git_credtype_t credtype; +} Cred; + +typedef struct { + Cred parent; + char *username; + char *password; +} CredUsernamePassword; + +typedef struct { + Cred parent; + char *username; + char *pubkey; + char *privkey; + char *passphrase; +} CredSshKey; + /* git_refspec */ typedef struct { PyObject_HEAD diff --git a/test/test_credentials.py b/test/test_credentials.py new file mode 100644 index 000000000..c1b95e548 --- /dev/null +++ b/test/test_credentials.py @@ -0,0 +1,98 @@ +# -*- coding: UTF-8 -*- +# +# Copyright 2010-2014 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. + +"""Tests for credentials""" + + +import unittest +import pygit2 +from pygit2 import CredUsernamePassword, CredSshKey +from pygit2 import GIT_CREDTYPE_USERPASS_PLAINTEXT +from pygit2 import UserPass, Keypair +from . import utils + +REMOTE_NAME = 'origin' +REMOTE_URL = 'git://github.com/libgit2/pygit2.git' +REMOTE_FETCHSPEC_SRC = 'refs/heads/*' +REMOTE_FETCHSPEC_DST = 'refs/remotes/origin/*' +REMOTE_REPO_OBJECTS = 30 +REMOTE_REPO_BYTES = 2758 + +ORIGIN_REFSPEC = '+refs/heads/*:refs/remotes/origin/*' + +class CredentialCreateTest(utils.NoRepoTestCase): + def test_userpass(self): + username = "git" + password = "sekkrit" + + cred = CredUsernamePassword(username, password) + self.assertEqual(username, cred.username) + self.assertEqual(password, cred.password) + + def test_ssh_key(self): + username = "git" + pubkey = "id_rsa.pub" + privkey = "id_rsa" + passphrase = "bad wolf" + + cred = CredSshKey(username, pubkey, privkey, passphrase) + self.assertEqual(username, cred.username) + self.assertEqual(pubkey, cred.pubkey) + self.assertEqual(privkey, cred.privkey) + self.assertEqual(passphrase, cred.passphrase) + +class CredentialCallback(utils.RepoTestCase): + def test_callback(self): + def credentials_cb(url, username, allowed): + self.assertTrue(allowed & GIT_CREDTYPE_USERPASS_PLAINTEXT) + raise Exception("I don't know the password") + + remote = self.repo.create_remote("github", "https://github.com/github/github") + remote.credentials = credentials_cb + + self.assertRaises(Exception, remote.fetch) + + def test_bad_cred_type(self): + def credentials_cb(url, username, allowed): + self.assertTrue(allowed & GIT_CREDTYPE_USERPASS_PLAINTEXT) + return CredSshKey("git", "foo.pub", "foo", "sekkrit") + + remote = self.repo.create_remote("github", "https://github.com/github/github") + remote.credentials = credentials_cb + + self.assertRaises(TypeError, remote.fetch) + +class CallableCredentialTest(utils.RepoTestCase): + + def test_user_pass(self): + remote = self.repo.create_remote("bb", "https://bitbucket.org/libgit2/testgitrepository.git") + remote.credentials = UserPass("libgit2", "libgit2") + + remote.fetch() + +if __name__ == '__main__': + unittest.main() From 4c3c706cb9e4ae2cb8934a876c85f70cff20b431 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Wed, 26 Mar 2014 12:37:28 +0100 Subject: [PATCH 0139/1630] Repository: don't jump to cleanup unecessarily Until we have successfully borrowed the message, we have not used up any resources, we can skip jumping to 'out' and we can return NULL directly. This also avoid dereferencing an uninitialised 'tmessage'. --- src/repository.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/repository.c b/src/repository.c index 1e654e28b..031e6e0ec 100644 --- a/src/repository.c +++ b/src/repository.c @@ -807,11 +807,11 @@ Repository_create_commit(Repository *self, PyObject *args) len = py_oid_to_git_oid(py_oid, &oid); if (len == 0) - goto out; + return NULL; message = py_str_borrow_c_str(&tmessage, py_message, encoding); if (message == NULL) - goto out; + return NULL; err = git_tree_lookup_prefix(&tree, self->repo, &oid, len); if (err < 0) { From fc0cdaebd6a4ecc92cc3ca59db755a394bde145d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Wed, 26 Mar 2014 14:03:29 +0100 Subject: [PATCH 0140/1630] Remote: add documentation for credentials --- docs/remotes.rst | 16 ++++++++++++++++ pygit2/credentials.py | 4 ++-- src/remote.c | 13 ++++++++++++- 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/docs/remotes.rst b/docs/remotes.rst index f77bd7393..58ab8ef5d 100644 --- a/docs/remotes.rst +++ b/docs/remotes.rst @@ -52,3 +52,19 @@ The Refspec type .. automethod:: pygit2.Refspec.dst_matches .. automethod:: pygit2.Refspec.transform .. automethod:: pygit2.Refspec.rtransform + +Credentials +================ + +.. automethod:: pygit2.Remote.credentials + +There are two types of credentials: username/password and SSH key +pairs. Both :py:class:`pygit2.UserPass` and :py:class:`pygit2.Keypair` +are callable objects, with the appropriate signature for the +credentials callback. They will ignore all the arguments and return +themselves. This is useful for scripts where the credentials are known +ahead of time. More complete interfaces would want to look up in their +keychain or ask the user for the data to use in the credentials. + +.. autoclass:: pygit2.UserPass +.. autoclass:: pygit2.Keypair diff --git a/pygit2/credentials.py b/pygit2/credentials.py index a3a3c25c1..dad966b3e 100644 --- a/pygit2/credentials.py +++ b/pygit2/credentials.py @@ -33,7 +33,7 @@ class UserPass(CredUsernamePassword): """Username/Password credentials This is an object suitable for passing to a remote's credentials - callback. + callback and for returning from said callback. """ @@ -44,7 +44,7 @@ class Keypair(CredSshKey): """SSH key pair credentials This is an object suitable for passing to a remote's credentials - callback. + callback and for returning from said callback. """ diff --git a/src/remote.c b/src/remote.c index 9c7cce275..65a195ef6 100644 --- a/src/remote.c +++ b/src/remote.c @@ -705,7 +705,18 @@ PyGetSetDef Remote_getseters[] = { PyMemberDef Remote_members[] = { MEMBER(Remote, progress, T_OBJECT_EX, "Progress output callback"), - MEMBER(Remote, credentials, T_OBJECT_EX, "Credentials callback"), + MEMBER(Remote, credentials, T_OBJECT_EX, + "credentials(url, username_from_url, allowed_types) -> credential\n" + "\n" + "Credentials callback\n" + "\n" + "If the remote server requires authentication, this function will\n" + "be called and its return value used for authentication.\n" + "\n" + ":param str url: The url of the remote\n" + ":param username_from_url: Username extracted from the url, if any\n" + ":type username_from_url: str or None\n" + ":param int allowed_types: credential types supported by the remote "), MEMBER(Remote, transfer_progress, T_OBJECT_EX, "Transfer progress callback"), MEMBER(Remote, update_tips, T_OBJECT_EX, "update tips callback"), {NULL}, From 75f9f883352b1ad8cff069a538607adf79bd7fe1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Wed, 26 Mar 2014 14:19:38 +0100 Subject: [PATCH 0141/1630] Update clone_repository's docs It claims you need to checkout a branch after clone, which is not the case currently (the clone function will do it for you). While here, format the docstring for sphinx to make it pretty. --- pygit2/__init__.py | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/pygit2/__init__.py b/pygit2/__init__.py index 60e8942c3..f4f471ec8 100644 --- a/pygit2/__init__.py +++ b/pygit2/__init__.py @@ -52,24 +52,20 @@ def init_repository(path, bare=False): def clone_repository( url, path, bare=False, ignore_cert_errors=False, remote_name="origin", checkout_branch=None): - """ - Clones a new Git repository from *url* in the given *path*. + """Clones a new Git repository from *url* in the given *path*. - **bare** indicates whether a bare git repository should be created. + Returns a Repository class pointing to the newly cloned repository. - **remote_name** is the name given to the "origin" remote. - The default is "origin". + :param str url: URL of the repository to clone - **checkout_branch** gives the name of the branch to checkout. - None means use the remote's *HEAD*. + :param str path: Local path to clone into - Returns a Repository class pointing to the newly cloned repository. + :param bool bare: Whether the local repository should be bare - If you wish to use the repo, you need to do a checkout for one of - the available branches, like this: + :param str remote_name: Name to give the remote at *url*. - >>> repo = repo.clone_repository("url", "path") - >>> repo.checkout(branch) # i.e.: refs/heads/master + :param str checkout_branch: Branch to checkout after the + clone. The default is to use the remote's default branch. """ From b3ce1b5da6200a027a38d70a845cb79ab1ff9787 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Wed, 26 Mar 2014 14:50:25 +0100 Subject: [PATCH 0142/1630] Add credentials support to clone_repository() --- pygit2/__init__.py | 9 ++++-- src/pygit2.c | 16 +++++++++-- src/remote.c | 66 +----------------------------------------- src/utils.c | 71 ++++++++++++++++++++++++++++++++++++++++++++++ src/utils.h | 2 ++ 5 files changed, 95 insertions(+), 69 deletions(-) diff --git a/pygit2/__init__.py b/pygit2/__init__.py index f4f471ec8..fc2e69366 100644 --- a/pygit2/__init__.py +++ b/pygit2/__init__.py @@ -51,7 +51,7 @@ def init_repository(path, bare=False): def clone_repository( url, path, bare=False, ignore_cert_errors=False, - remote_name="origin", checkout_branch=None): + remote_name="origin", checkout_branch=None, credentials=None): """Clones a new Git repository from *url* in the given *path*. Returns a Repository class pointing to the newly cloned repository. @@ -67,10 +67,15 @@ def clone_repository( :param str checkout_branch: Branch to checkout after the clone. The default is to use the remote's default branch. + :param callable credentials: authentication to use if the remote + requires it + + :rtype: Repository + """ _pygit2.clone_repository( - url, path, bare, ignore_cert_errors, remote_name, checkout_branch) + url, path, bare, ignore_cert_errors, remote_name, checkout_branch, credentials) return Repository(path) settings = Settings() diff --git a/src/pygit2.c b/src/pygit2.c index ea6435435..0126dc8d9 100644 --- a/src/pygit2.c +++ b/src/pygit2.c @@ -118,6 +118,14 @@ init_repository(PyObject *self, PyObject *args) { Py_RETURN_NONE; }; +static int +credentials_cb(git_cred **out, const char *url, const char *username_from_url, unsigned int allowed_types, void *data) +{ + PyObject *credentials = (PyObject *) data; + + return callable_to_credentials(out, url, username_from_url, allowed_types, credentials); +} + PyDoc_STRVAR(clone_repository__doc__, "clone_repository(url, path, bare, remote_name, checkout_branch)\n" "\n" @@ -146,11 +154,12 @@ clone_repository(PyObject *self, PyObject *args) { const char *path; unsigned int bare, ignore_cert_errors; const char *remote_name, *checkout_branch; + PyObject *credentials; int err; git_clone_options opts = GIT_CLONE_OPTIONS_INIT; - if (!PyArg_ParseTuple(args, "zzIIzz", - &url, &path, &bare, &ignore_cert_errors, &remote_name, &checkout_branch)) + if (!PyArg_ParseTuple(args, "zzIIzzO", + &url, &path, &bare, &ignore_cert_errors, &remote_name, &checkout_branch, &credentials)) return NULL; opts.bare = bare; @@ -158,6 +167,9 @@ clone_repository(PyObject *self, PyObject *args) { opts.remote_name = remote_name; opts.checkout_branch = checkout_branch; + opts.remote_callbacks.credentials = credentials_cb; + opts.remote_callbacks.payload = credentials; + err = git_clone(&repo, url, path, &opts); if (err < 0) return Error_set(err); diff --git a/src/remote.c b/src/remote.c index 65a195ef6..b654a8cbf 100644 --- a/src/remote.c +++ b/src/remote.c @@ -158,76 +158,12 @@ progress_cb(const char *str, int len, void *data) return 0; } -static int -py_cred_to_git_cred(git_cred **out, PyObject *py_cred, unsigned int allowed) -{ - Cred *base_cred; - int err; - - if (!PyObject_TypeCheck(py_cred, &CredUsernamePasswordType) && - !PyObject_TypeCheck(py_cred, &CredSshKeyType)) { - PyErr_SetString(PyExc_TypeError, "unkown credential type"); - return -1; - } - - base_cred = (Cred *) py_cred; - - /* Sanity check, make sure we're given credentials we can use */ - if (!(allowed & base_cred->credtype)) { - PyErr_SetString(PyExc_TypeError, "invalid credential type"); - return -1; - } - - switch (base_cred->credtype) { - case GIT_CREDTYPE_USERPASS_PLAINTEXT: - { - CredUsernamePassword *cred = (CredUsernamePassword *) base_cred; - err = git_cred_userpass_plaintext_new(out, cred->username, cred->password); - break; - } - case GIT_CREDTYPE_SSH_KEY: - { - CredSshKey *cred = (CredSshKey *) base_cred; - err = git_cred_ssh_key_new(out, cred->username, cred->pubkey, cred->privkey, cred->passphrase); - break; - } - default: - PyErr_SetString(PyExc_TypeError, "unsupported credential type"); - err = -1; - break; - } - - return err; -} - static int credentials_cb(git_cred **out, const char *url, const char *username_from_url, unsigned int allowed_types, void *data) { Remote *remote = (Remote *) data; - PyObject *arglist, *py_cred; - int err; - - if (remote->credentials == NULL) - return 0; - - if (!PyCallable_Check(remote->credentials)) { - PyErr_SetString(PyExc_TypeError, "credentials callback is not callable"); - return -1; - } - - arglist = Py_BuildValue("(szI)", url, username_from_url, allowed_types); - py_cred = PyObject_CallObject(remote->credentials, arglist); - Py_DECREF(arglist); - - if (!py_cred) - return -1; - - err = py_cred_to_git_cred(out, py_cred, allowed_types); - - - Py_DECREF(py_cred); - return err; + return callable_to_credentials(out, url, username_from_url, allowed_types, remote->credentials); } static int diff --git a/src/utils.c b/src/utils.c index 44acf5c6a..66e546d09 100644 --- a/src/utils.c +++ b/src/utils.c @@ -31,6 +31,8 @@ #include "utils.h" extern PyTypeObject ReferenceType; +extern PyTypeObject CredUsernamePasswordType; +extern PyTypeObject CredSshKeyType; /** * py_str_to_c_str() returns a newly allocated C string holding the string @@ -153,3 +155,72 @@ get_strarraygit_from_pylist(git_strarray *array, PyObject *pylist) return -1; } + +static int +py_cred_to_git_cred(git_cred **out, PyObject *py_cred, unsigned int allowed) +{ + Cred *base_cred; + int err; + + if (!PyObject_TypeCheck(py_cred, &CredUsernamePasswordType) && + !PyObject_TypeCheck(py_cred, &CredSshKeyType)) { + PyErr_SetString(PyExc_TypeError, "unkown credential type"); + return -1; + } + + base_cred = (Cred *) py_cred; + + /* Sanity check, make sure we're given credentials we can use */ + if (!(allowed & base_cred->credtype)) { + PyErr_SetString(PyExc_TypeError, "invalid credential type"); + return -1; + } + + switch (base_cred->credtype) { + case GIT_CREDTYPE_USERPASS_PLAINTEXT: + { + CredUsernamePassword *cred = (CredUsernamePassword *) base_cred; + err = git_cred_userpass_plaintext_new(out, cred->username, cred->password); + break; + } + case GIT_CREDTYPE_SSH_KEY: + { + CredSshKey *cred = (CredSshKey *) base_cred; + err = git_cred_ssh_key_new(out, cred->username, cred->pubkey, cred->privkey, cred->passphrase); + break; + } + default: + PyErr_SetString(PyExc_TypeError, "unsupported credential type"); + err = -1; + break; + } + + return err; +} + +int +callable_to_credentials(git_cred **out, const char *url, const char *username_from_url, unsigned int allowed_types, PyObject *credentials) +{ + int err; + PyObject *py_cred, *arglist; + + if (credentials == NULL) + return 0; + + if (!PyCallable_Check(credentials)) { + PyErr_SetString(PyExc_TypeError, "credentials callback is not callable"); + return -1; + } + + arglist = Py_BuildValue("(szI)", url, username_from_url, allowed_types); + py_cred = PyObject_CallObject(credentials, arglist); + Py_DECREF(arglist); + + if (!py_cred) + return -1; + + err = py_cred_to_git_cred(out, py_cred, allowed_types); + Py_DECREF(py_cred); + + return err; +} diff --git a/src/utils.h b/src/utils.h index 81744f686..7f95d73ee 100644 --- a/src/utils.h +++ b/src/utils.h @@ -117,6 +117,8 @@ const char *py_str_borrow_c_str(PyObject **tvaue, PyObject *value, const char *e PyObject * get_pylist_from_git_strarray(git_strarray *strarray); int get_strarraygit_from_pylist(git_strarray *array, PyObject *pylist); +int callable_to_credentials(git_cred **out, const char *url, const char *username_from_url, unsigned int allowed_types, PyObject *credentials); + #define py_path_to_c_str(py_path) \ py_str_to_c_str(py_path, Py_FileSystemDefaultEncoding) From 77acb11cd0bb5d9c2be8fc046e71296b501de874 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Wed, 26 Mar 2014 19:14:57 +0100 Subject: [PATCH 0143/1630] credentials: memory safety The docs say to use tp_free() to free the memory, and even though we use PyObject_Del() everywhere else, using this in the credentials does cause issues. --- src/credentials.c | 4 ++-- src/pygit2.c | 8 +++++--- src/utils.c | 4 ++-- test/test_repository.py | 7 +++++++ 4 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/credentials.c b/src/credentials.c index 5711f044b..c3127ae1d 100644 --- a/src/credentials.c +++ b/src/credentials.c @@ -72,7 +72,7 @@ CredUsernamePassword_dealloc(CredUsernamePassword *self) free(self->username); free(self->password); - PyObject_Del(self); + Py_TYPE(self)->tp_free(self); } PyMemberDef CredUsernamePassword_members[] = { @@ -169,7 +169,7 @@ CredSshKey_dealloc(CredSshKey *self) free(self->privkey); free(self->passphrase); - PyObject_Del(self); + Py_TYPE(self)->tp_free(self); } PyMemberDef CredSshKey_members[] = { diff --git a/src/pygit2.c b/src/pygit2.c index 0126dc8d9..199bbac11 100644 --- a/src/pygit2.c +++ b/src/pygit2.c @@ -154,7 +154,7 @@ clone_repository(PyObject *self, PyObject *args) { const char *path; unsigned int bare, ignore_cert_errors; const char *remote_name, *checkout_branch; - PyObject *credentials; + PyObject *credentials = NULL; int err; git_clone_options opts = GIT_CLONE_OPTIONS_INIT; @@ -167,8 +167,10 @@ clone_repository(PyObject *self, PyObject *args) { opts.remote_name = remote_name; opts.checkout_branch = checkout_branch; - opts.remote_callbacks.credentials = credentials_cb; - opts.remote_callbacks.payload = credentials; + if (credentials != Py_None) { + opts.remote_callbacks.credentials = credentials_cb; + opts.remote_callbacks.payload = credentials; + } err = git_clone(&repo, url, path, &opts); if (err < 0) diff --git a/src/utils.c b/src/utils.c index 66e546d09..59e7ad5f7 100644 --- a/src/utils.c +++ b/src/utils.c @@ -202,9 +202,9 @@ int callable_to_credentials(git_cred **out, const char *url, const char *username_from_url, unsigned int allowed_types, PyObject *credentials) { int err; - PyObject *py_cred, *arglist; + PyObject *py_cred = NULL, *arglist = NULL; - if (credentials == NULL) + if (credentials == NULL || credentials == Py_None) return 0; if (!PyCallable_Check(credentials)) { diff --git a/test/test_repository.py b/test/test_repository.py index 8e8101c66..71214ccef 100644 --- a/test/test_repository.py +++ b/test/test_repository.py @@ -461,6 +461,13 @@ def test_clone_remote_name(self): self.assertFalse(repo.is_empty) self.assertEqual(repo.remotes[0].name, "custom_remote") + def test_clone_with_credentials(self): + credentials = pygit2.UserPass("libgit2", "libgit2") + repo = clone_repository( + "https://bitbucket.org/libgit2/testgitrepository.git", + self._temp_dir, credentials=credentials) + + self.assertFalse(repo.is_empty) # FIXME The tests below are commented because they are broken: # From 82d88191bb450a153a217ed8227dd1865c48016d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Wed, 26 Mar 2014 20:23:04 +0100 Subject: [PATCH 0144/1630] credentials: use more ducks Instead of making everyone inherit from our credential types, use an interface with two attributes, which makes the C code much shorter and simpler. --- pygit2/credentials.py | 33 +++++- src/credentials.c | 225 --------------------------------------- src/pygit2.c | 6 -- src/remote.c | 2 - src/types.h | 19 ---- src/utils.c | 52 ++++++--- test/test_credentials.py | 15 +-- 7 files changed, 69 insertions(+), 283 deletions(-) delete mode 100644 src/credentials.c diff --git a/pygit2/credentials.py b/pygit2/credentials.py index dad966b3e..1789022d6 100644 --- a/pygit2/credentials.py +++ b/pygit2/credentials.py @@ -26,10 +26,9 @@ # Boston, MA 02110-1301, USA. # Import from pygit2 -from _pygit2 import CredUsernamePassword, CredSshKey -from _pygit2 import GIT_CREDTYPE_USERPASS_PLAINTEXT +from _pygit2 import GIT_CREDTYPE_USERPASS_PLAINTEXT, GIT_CREDTYPE_SSH_KEY -class UserPass(CredUsernamePassword): +class UserPass: """Username/Password credentials This is an object suitable for passing to a remote's credentials @@ -37,10 +36,22 @@ class UserPass(CredUsernamePassword): """ + def __init__(self, username, password): + self._username = username + self._password = password + + @property + def credential_type(self): + return GIT_CREDTYPE_USERPASS_PLAINTEXT + + @property + def credential_tuple(self): + return (self._username, self._password) + def __call__(self, _url, _username, _allowed): return self -class Keypair(CredSshKey): +class Keypair: """SSH key pair credentials This is an object suitable for passing to a remote's credentials @@ -48,5 +59,19 @@ class Keypair(CredSshKey): """ + def __init__(self, username, pubkey, privkey, passphrase): + self._username = username + self._pubkey = pubkey + self._privkey = privkey + self._passphrase = passphrase + + @property + def credential_type(self): + return GIT_CREDTYPE_SSH_KEY + + @property + def credential_tuple(self): + return (self._username, self._pubkey, self._privkey, self._passphrase) + def __call__(self, _url, _username, _allowed): return self diff --git a/src/credentials.c b/src/credentials.c deleted file mode 100644 index c3127ae1d..000000000 --- a/src/credentials.c +++ /dev/null @@ -1,225 +0,0 @@ -/* - * Copyright 2010-2014 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 -#include "error.h" -#include "types.h" -#include "utils.h" -#include "oid.h" -#include "refspec.h" -#include "remote.h" - -int -CredUsernamePassword_init(CredUsernamePassword *self, PyObject *args, PyObject *kwds) -{ - char *username, *password; - - if (kwds && PyDict_Size(kwds) > 0) { - PyErr_SetString(PyExc_TypeError, "CredUsernamePassword takes no keyword arguments"); - return -1; - } - - if (!PyArg_ParseTuple(args, "ss", &username, &password)) - return -1; - - self->parent.credtype = GIT_CREDTYPE_USERPASS_PLAINTEXT; - - self->username = strdup(username); - if (!self->username) { - PyErr_NoMemory(); - return -1; - } - - self->password = strdup(password); - if (!self->password) { - free(self->username); - PyErr_NoMemory(); - return -1; - } - - return 0; -} - -void -CredUsernamePassword_dealloc(CredUsernamePassword *self) -{ - free(self->username); - free(self->password); - - Py_TYPE(self)->tp_free(self); -} - -PyMemberDef CredUsernamePassword_members[] = { - MEMBER(CredUsernamePassword, username, T_STRING, "username"), - MEMBER(CredUsernamePassword, password, T_STRING, "password"), - {NULL}, -}; - -PyDoc_STRVAR(CredUsernamePassword__doc__, - "Credential type for username/password combination"); - -PyTypeObject CredUsernamePasswordType = { - PyVarObject_HEAD_INIT(NULL, 0) - "_pygit2.CredUsernamePassword", /* tp_name */ - sizeof(CredUsernamePassword), /* tp_basicsize */ - 0, /* tp_itemsize */ - (destructor)CredUsernamePassword_dealloc, /* 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 | Py_TPFLAGS_BASETYPE, /* tp_flags */ - CredUsernamePassword__doc__, /* tp_doc */ - 0, /* tp_traverse */ - 0, /* tp_clear */ - 0, /* tp_richcompare */ - 0, /* tp_weaklistoffset */ - 0, /* tp_iter */ - 0, /* tp_iternext */ - 0, /* tp_methods */ - CredUsernamePassword_members, /* tp_members */ - 0, /* tp_getset */ - 0, /* tp_base */ - 0, /* tp_dict */ - 0, /* tp_descr_get */ - 0, /* tp_descr_set */ - 0, /* tp_dictoffset */ - (initproc)CredUsernamePassword_init, /* tp_init */ - 0, /* tp_alloc */ - 0, /* tp_new */ -}; - -int -CredSshKey_init(CredSshKey *self, PyObject *args, PyObject *kwds) -{ - char *username, *pubkey, *privkey, *passphrase; - - if (kwds && PyDict_Size(kwds) > 0) { - PyErr_SetString(PyExc_TypeError, "CredSshKey takes no keyword arguments"); - return -1; - } - - if (!PyArg_ParseTuple(args, "ssss", &username, &pubkey, - &privkey, &passphrase)) - return -1; - - self->parent.credtype = GIT_CREDTYPE_SSH_KEY; - self->username = self->pubkey = self->privkey = self->passphrase = NULL; - - self->username = strdup(username); - self->pubkey = strdup(pubkey); - self->privkey = strdup(privkey); - self->passphrase = strdup(passphrase); - - if (!(self->username && self->pubkey && self->privkey && self->passphrase)) - goto on_oom; - - return 0; - - on_oom: - free(self->username); - free(self->pubkey); - free(self->privkey); - free(self->passphrase); - PyErr_NoMemory(); - return -1; -} - -void -CredSshKey_dealloc(CredSshKey *self) -{ - free(self->username); - free(self->pubkey); - free(self->privkey); - free(self->passphrase); - - Py_TYPE(self)->tp_free(self); -} - -PyMemberDef CredSshKey_members[] = { - MEMBER(CredSshKey, username, T_STRING, "username"), - MEMBER(CredSshKey, pubkey, T_STRING, "pubkey"), - MEMBER(CredSshKey, privkey, T_STRING, "privkey"), - MEMBER(CredSshKey, passphrase, T_STRING, "passphrase"), - {NULL}, -}; - -PyDoc_STRVAR(CredSshKey__doc__, - "Credential type for an SSH keypair"); - -PyTypeObject CredSshKeyType = { - PyVarObject_HEAD_INIT(NULL, 0) - "_pygit2.CredSshKey", /* tp_name */ - sizeof(CredSshKey), /* tp_basicsize */ - 0, /* tp_itemsize */ - (destructor)CredSshKey_dealloc, /* 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 | Py_TPFLAGS_BASETYPE, /* tp_flags */ - CredSshKey__doc__, /* tp_doc */ - 0, /* tp_traverse */ - 0, /* tp_clear */ - 0, /* tp_richcompare */ - 0, /* tp_weaklistoffset */ - 0, /* tp_iter */ - 0, /* tp_iternext */ - 0, /* tp_methods */ - CredSshKey_members, /* tp_members */ - 0, /* tp_getset */ - 0, /* tp_base */ - 0, /* tp_dict */ - 0, /* tp_descr_get */ - 0, /* tp_descr_set */ - 0, /* tp_dictoffset */ - (initproc)CredSshKey_init, /* tp_init */ - 0, /* tp_alloc */ - 0, /* tp_new */ -}; diff --git a/src/pygit2.c b/src/pygit2.c index 199bbac11..bda9545ec 100644 --- a/src/pygit2.c +++ b/src/pygit2.c @@ -74,8 +74,6 @@ extern PyTypeObject RefLogEntryType; extern PyTypeObject BranchType; extern PyTypeObject SignatureType; extern PyTypeObject RemoteType; -extern PyTypeObject CredUsernamePasswordType; -extern PyTypeObject CredSshKeyType; extern PyTypeObject RefspecType; extern PyTypeObject TransferProgressType; extern PyTypeObject NoteType; @@ -460,13 +458,9 @@ moduleinit(PyObject* m) /* Remotes */ INIT_TYPE(RemoteType, NULL, NULL) - INIT_TYPE(CredUsernamePasswordType, NULL, PyType_GenericNew) - INIT_TYPE(CredSshKeyType, NULL, PyType_GenericNew) INIT_TYPE(RefspecType, NULL, NULL) INIT_TYPE(TransferProgressType, NULL, NULL) ADD_TYPE(m, Remote) - ADD_TYPE(m, CredUsernamePassword) - ADD_TYPE(m, CredSshKey) ADD_TYPE(m, Refspec) ADD_TYPE(m, TransferProgress) /* Direction for the refspec */ diff --git a/src/remote.c b/src/remote.c index b654a8cbf..ffbe17d7b 100644 --- a/src/remote.c +++ b/src/remote.c @@ -39,8 +39,6 @@ extern PyObject *GitError; extern PyTypeObject RepositoryType; extern PyTypeObject TransferProgressType; -extern PyTypeObject CredUsernamePasswordType; -extern PyTypeObject CredSshKeyType; PyObject * wrap_transfer_progress(const git_transfer_progress *stats) diff --git a/src/types.h b/src/types.h index 1a73fc5ea..a36fad6b5 100644 --- a/src/types.h +++ b/src/types.h @@ -207,25 +207,6 @@ typedef struct { PyObject *update_tips; } Remote; -typedef struct { - PyObject_HEAD - git_credtype_t credtype; -} Cred; - -typedef struct { - Cred parent; - char *username; - char *password; -} CredUsernamePassword; - -typedef struct { - Cred parent; - char *username; - char *pubkey; - char *privkey; - char *passphrase; -} CredSshKey; - /* git_refspec */ typedef struct { PyObject_HEAD diff --git a/src/utils.c b/src/utils.c index 59e7ad5f7..24b6bbd47 100644 --- a/src/utils.c +++ b/src/utils.c @@ -31,8 +31,6 @@ #include "utils.h" extern PyTypeObject ReferenceType; -extern PyTypeObject CredUsernamePasswordType; -extern PyTypeObject CredSshKeyType; /** * py_str_to_c_str() returns a newly allocated C string holding the string @@ -159,42 +157,62 @@ get_strarraygit_from_pylist(git_strarray *array, PyObject *pylist) static int py_cred_to_git_cred(git_cred **out, PyObject *py_cred, unsigned int allowed) { - Cred *base_cred; - int err; + PyObject *py_type, *py_tuple; + long type; + int err = -1; - if (!PyObject_TypeCheck(py_cred, &CredUsernamePasswordType) && - !PyObject_TypeCheck(py_cred, &CredSshKeyType)) { - PyErr_SetString(PyExc_TypeError, "unkown credential type"); - return -1; + py_type = PyObject_GetAttrString(py_cred, "credential_type"); + py_tuple = PyObject_GetAttrString(py_cred, "credential_tuple"); + + if (!py_type || !py_tuple) { + printf("py_type %p, py_tuple %p\n", py_type, py_tuple); + PyErr_SetString(PyExc_TypeError, "credential doesn't implement the interface"); + goto cleanup; } - base_cred = (Cred *) py_cred; + if (!PyLong_Check(py_type)) { + PyErr_SetString(PyExc_TypeError, "credential type is not a long"); + goto cleanup; + } + + type = PyLong_AsLong(py_type); /* Sanity check, make sure we're given credentials we can use */ - if (!(allowed & base_cred->credtype)) { + if (!(allowed & type)) { PyErr_SetString(PyExc_TypeError, "invalid credential type"); - return -1; + goto cleanup; } - switch (base_cred->credtype) { + switch (type) { case GIT_CREDTYPE_USERPASS_PLAINTEXT: { - CredUsernamePassword *cred = (CredUsernamePassword *) base_cred; - err = git_cred_userpass_plaintext_new(out, cred->username, cred->password); + const char *username, *password; + + if (!PyArg_ParseTuple(py_tuple, "ss", &username, &password)) + goto cleanup; + + err = git_cred_userpass_plaintext_new(out, username, password); break; } case GIT_CREDTYPE_SSH_KEY: { - CredSshKey *cred = (CredSshKey *) base_cred; - err = git_cred_ssh_key_new(out, cred->username, cred->pubkey, cred->privkey, cred->passphrase); + const char *username, *pubkey, *privkey, *passphrase; + + if (!PyArg_ParseTuple(py_tuple, "ssss", &username, &pubkey, &privkey, &passphrase)) + goto cleanup; + + err = git_cred_ssh_key_new(out, username, pubkey, privkey, passphrase); break; } default: PyErr_SetString(PyExc_TypeError, "unsupported credential type"); - err = -1; break; } +cleanup: + Py_XDECREF(py_type); + Py_XDECREF(py_tuple); + return err; } diff --git a/test/test_credentials.py b/test/test_credentials.py index c1b95e548..3bdeb6fc4 100644 --- a/test/test_credentials.py +++ b/test/test_credentials.py @@ -30,7 +30,6 @@ import unittest import pygit2 -from pygit2 import CredUsernamePassword, CredSshKey from pygit2 import GIT_CREDTYPE_USERPASS_PLAINTEXT from pygit2 import UserPass, Keypair from . import utils @@ -49,9 +48,8 @@ def test_userpass(self): username = "git" password = "sekkrit" - cred = CredUsernamePassword(username, password) - self.assertEqual(username, cred.username) - self.assertEqual(password, cred.password) + cred = UserPass(username, password) + self.assertEqual((username, password), cred.credential_tuple) def test_ssh_key(self): username = "git" @@ -59,11 +57,8 @@ def test_ssh_key(self): privkey = "id_rsa" passphrase = "bad wolf" - cred = CredSshKey(username, pubkey, privkey, passphrase) - self.assertEqual(username, cred.username) - self.assertEqual(pubkey, cred.pubkey) - self.assertEqual(privkey, cred.privkey) - self.assertEqual(passphrase, cred.passphrase) + cred = Keypair(username, pubkey, privkey, passphrase) + self.assertEqual((username, pubkey, privkey, passphrase), cred.credential_tuple) class CredentialCallback(utils.RepoTestCase): def test_callback(self): @@ -79,7 +74,7 @@ def credentials_cb(url, username, allowed): def test_bad_cred_type(self): def credentials_cb(url, username, allowed): self.assertTrue(allowed & GIT_CREDTYPE_USERPASS_PLAINTEXT) - return CredSshKey("git", "foo.pub", "foo", "sekkrit") + return Keypair("git", "foo.pub", "foo", "sekkrit") remote = self.repo.create_remote("github", "https://github.com/github/github") remote.credentials = credentials_cb From 940a6da92987acaba9954890be00e14cc71bbd4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Thu, 27 Mar 2014 09:45:10 +0100 Subject: [PATCH 0145/1630] Make dealloc use tp_free or don't allow inheritance When a class is a base type, it must use its type's tp_free function to trigger the real free, instead of PyObjec_Del(). We do not always follow this convention, so let's give it a once-over and make sure we do that or that it's not a base type. Many of the types have the flag set in the struct, but do not pass the allocator function at init time, which makes them not really be a base. Remove the flag for those types. --- TODO.txt | 4 ---- src/blame.c | 6 +++--- src/blob.c | 2 +- src/commit.c | 2 +- src/config.c | 2 +- src/diff.c | 4 ++-- src/index.c | 4 ++-- src/note.c | 4 ++-- src/object.c | 2 +- src/reference.c | 2 +- src/remote.c | 2 +- src/repository.c | 2 +- src/tag.c | 2 +- src/tree.c | 6 +++--- 14 files changed, 20 insertions(+), 24 deletions(-) diff --git a/TODO.txt b/TODO.txt index 4a48a284c..a648a06e8 100644 --- a/TODO.txt +++ b/TODO.txt @@ -14,7 +14,3 @@ Other - Make the Py_LOCAL_INLINE macro to work with Python 2.6, 2.7 and 3.1 - Use surrogateescape in Python 3, see PEP-383 - Expose the ODB (Repository.odb) -- According to Python documentation, tp_dealloc must call tp_free (instead of - PyObject_Del or similar) if the type is subclassable. So, go through the - code and switch to tp_free, or make the type not subclassable, on a case by - case basis. diff --git a/src/blame.c b/src/blame.c index f0ee18f2d..80b82089a 100644 --- a/src/blame.c +++ b/src/blame.c @@ -170,7 +170,7 @@ PyTypeObject BlameHunkType = { 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ + Py_TPFLAGS_DEFAULT, /* tp_flags */ BlameHunk__doc__, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ @@ -233,7 +233,7 @@ PyTypeObject BlameIterType = { 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ + Py_TPFLAGS_DEFAULT, /* tp_flags */ BlameIter__doc__, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ @@ -362,7 +362,7 @@ PyTypeObject BlameType = { 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ + Py_TPFLAGS_DEFAULT, /* tp_flags */ Blame__doc__, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ diff --git a/src/blob.c b/src/blob.c index 94352dfda..133d49423 100644 --- a/src/blob.c +++ b/src/blob.c @@ -181,7 +181,7 @@ PyTypeObject BlobType = { 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ + Py_TPFLAGS_DEFAULT, /* tp_flags */ Blob__doc__, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ diff --git a/src/commit.c b/src/commit.c index 8a61d29e7..85f59cdbf 100644 --- a/src/commit.c +++ b/src/commit.c @@ -259,7 +259,7 @@ PyTypeObject CommitType = { 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ + Py_TPFLAGS_DEFAULT, /* tp_flags */ Commit__doc__, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ diff --git a/src/config.c b/src/config.c index 8cbb05451..9b2625d6a 100644 --- a/src/config.c +++ b/src/config.c @@ -92,7 +92,7 @@ void Config_dealloc(Config *self) { git_config_free(self->config); - PyObject_Del(self); + Py_TYPE(self)->tp_free(self); } PyDoc_STRVAR(Config_get_global_config__doc__, diff --git a/src/diff.c b/src/diff.c index b3a32ddc2..adea3cdd8 100644 --- a/src/diff.c +++ b/src/diff.c @@ -207,7 +207,7 @@ PyTypeObject PatchType = { 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ + Py_TPFLAGS_DEFAULT, /* tp_flags */ Patch__doc__, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ @@ -269,7 +269,7 @@ PyTypeObject DiffIterType = { 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ + Py_TPFLAGS_DEFAULT, /* tp_flags */ DiffIter__doc__, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ diff --git a/src/index.c b/src/index.c index cc1b365df..9fb587817 100644 --- a/src/index.c +++ b/src/index.c @@ -71,7 +71,7 @@ Index_dealloc(Index* self) PyObject_GC_UnTrack(self); Py_XDECREF(self->repo); git_index_free(self->index); - PyObject_GC_Del(self); + Py_TYPE(self)->tp_free(self); } int @@ -561,7 +561,7 @@ void IndexIter_dealloc(IndexIter *self) { Py_CLEAR(self->owner); - PyObject_Del(self); + Py_TYPE(self)->tp_free(self); } PyObject * diff --git a/src/note.c b/src/note.c index 2e5c9b149..e14e30862 100644 --- a/src/note.c +++ b/src/note.c @@ -134,7 +134,7 @@ PyTypeObject NoteType = { 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ + Py_TPFLAGS_DEFAULT, /* tp_flags */ Note__doc__, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ @@ -200,7 +200,7 @@ PyTypeObject NoteIterType = { 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ + Py_TPFLAGS_DEFAULT, /* tp_flags */ NoteIter__doc__, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ diff --git a/src/object.c b/src/object.c index 74c380129..f737e8bd2 100644 --- a/src/object.c +++ b/src/object.c @@ -45,7 +45,7 @@ Object_dealloc(Object* self) { Py_CLEAR(self->repo); git_object_free(self->obj); - PyObject_Del(self); + Py_TYPE(self)->tp_free(self); } diff --git a/src/reference.c b/src/reference.c index 2efbca3c5..e597e353a 100644 --- a/src/reference.c +++ b/src/reference.c @@ -97,7 +97,7 @@ PyTypeObject RefLogIterType = { 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ + Py_TPFLAGS_DEFAULT, /* tp_flags */ RefLogIterType__doc__, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ diff --git a/src/remote.c b/src/remote.c index c73878423..ab085c8ba 100644 --- a/src/remote.c +++ b/src/remote.c @@ -658,7 +658,7 @@ PyTypeObject RemoteType = { 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ + Py_TPFLAGS_DEFAULT, /* tp_flags */ Remote__doc__, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ diff --git a/src/repository.c b/src/repository.c index 031e6e0ec..d42264a9c 100644 --- a/src/repository.c +++ b/src/repository.c @@ -105,7 +105,7 @@ Repository_dealloc(Repository *self) Py_CLEAR(self->index); Py_CLEAR(self->config); git_repository_free(self->repo); - PyObject_GC_Del(self); + Py_TYPE(self)->tp_free(self); } int diff --git a/src/tag.c b/src/tag.c index 6224d4c80..d6507ae2f 100644 --- a/src/tag.c +++ b/src/tag.c @@ -151,7 +151,7 @@ PyTypeObject TagType = { 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ + Py_TPFLAGS_DEFAULT, /* tp_flags */ Tag__doc__, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ diff --git a/src/tree.c b/src/tree.c index 8c83d3aa7..d1968faed 100644 --- a/src/tree.c +++ b/src/tree.c @@ -170,7 +170,7 @@ PyTypeObject TreeEntryType = { 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ + Py_TPFLAGS_DEFAULT, /* tp_flags */ TreeEntry__doc__, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ @@ -518,7 +518,7 @@ PyTypeObject TreeType = { 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ + Py_TPFLAGS_DEFAULT, /* tp_flags */ Tree__doc__, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ @@ -589,7 +589,7 @@ PyTypeObject TreeIterType = { 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ + Py_TPFLAGS_DEFAULT, /* tp_flags */ TreeIter__doc__, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ From b37d1c9c1f055e5f08ad20f71d9ade9f25ac03f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Thu, 27 Mar 2014 13:03:42 +0100 Subject: [PATCH 0146/1630] Plug memory leaks This patch is mostly about making sure that we free the copies of what we have, as well as making sure that we can free it. The IndexEntry forgot to free its path, but it also used a pointer to python-owned memory, which could be freed at any time. MergeResult completely lacked a deallocator. Signature needs to make sure we can free the enocoding, and not to set an owner when we own the memory (in this case for the default signature). The repository needs to get rid of its reference to the object list when returning. The transfer progress callback needs to decref the stats object. --- src/index.c | 6 ++++-- src/mergeresult.c | 10 +++++++++- src/remote.c | 1 + src/repository.c | 8 ++++++-- src/signature.c | 30 +++++++++++++++++++++--------- src/types.h | 2 +- 6 files changed, 42 insertions(+), 15 deletions(-) diff --git a/src/index.c b/src/index.c index cc1b365df..bc72c5994 100644 --- a/src/index.c +++ b/src/index.c @@ -623,8 +623,9 @@ IndexEntry_init(IndexEntry *self, PyObject *args, PyObject *kwds) return -1; memset(&self->entry, 0, sizeof(struct git_index_entry)); - if (c_path) - self->entry.path = c_path; + self->entry.path = strdup(c_path); + if (!self->entry.path) + return -1; if (id) git_oid_cpy(&self->entry.oid, &id->oid); @@ -638,6 +639,7 @@ IndexEntry_init(IndexEntry *self, PyObject *args, PyObject *kwds) void IndexEntry_dealloc(IndexEntry *self) { + free(self->entry.path); PyObject_Del(self); } diff --git a/src/mergeresult.c b/src/mergeresult.c index ada872fb4..345e56ee6 100644 --- a/src/mergeresult.c +++ b/src/mergeresult.c @@ -50,6 +50,14 @@ git_merge_result_to_python(git_merge_result *merge_result) return (PyObject*) py_merge_result; } +void +MergeResult_dealloc(MergeResult *self) +{ + git_merge_result_free(self->result); + PyObject_Del(self); +} + + PyDoc_STRVAR(MergeResult_is_uptodate__doc__, "Is up to date"); PyObject * @@ -99,7 +107,7 @@ PyTypeObject MergeResultType = { "_pygit2.MergeResult", /* tp_name */ sizeof(MergeResult), /* tp_basicsize */ 0, /* tp_itemsize */ - 0, /* tp_dealloc */ + (destructor)MergeResult_dealloc, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ diff --git a/src/remote.c b/src/remote.c index c73878423..379584ddd 100644 --- a/src/remote.c +++ b/src/remote.c @@ -175,6 +175,7 @@ transfer_progress_cb(const git_transfer_progress *stats, void *data) return -1; ret = PyObject_CallFunctionObjArgs(remote->transfer_progress, py_stats, NULL); + Py_DECREF(py_stats); if (!ret) return -1; diff --git a/src/repository.c b/src/repository.c index 031e6e0ec..b10dd5839 100644 --- a/src/repository.c +++ b/src/repository.c @@ -139,6 +139,7 @@ Repository_as_iter(Repository *self) git_odb *odb; int err; PyObject *accum = PyList_New(0); + PyObject *ret; err = git_repository_odb(&odb, self->repo); if (err < 0) @@ -151,7 +152,10 @@ Repository_as_iter(Repository *self) if (err < 0) return Error_set(err); - return PyObject_GetIter(accum); + ret = PyObject_GetIter(accum); + Py_DECREF(accum); + + return ret; } @@ -1345,7 +1349,7 @@ Repository_default_signature__get__(Repository *self) if ((err = git_signature_default(&sig, self->repo)) < 0) return Error_set(err); - return build_signature((Object*) self, sig, "utf-8"); + return build_signature(NULL, sig, "utf-8"); } PyDoc_STRVAR(Repository_checkout_head__doc__, diff --git a/src/signature.c b/src/signature.c index 809e80285..727178d9e 100644 --- a/src/signature.c +++ b/src/signature.c @@ -83,11 +83,12 @@ Signature_init(Signature *self, PyObject *args, PyObject *kwds) void Signature_dealloc(Signature *self) { - if (self->obj) + /* self->obj is the owner of the git_signature, so we musn't free it */ + if (self->obj) { Py_CLEAR(self->obj); - else { - git_signature_free((git_signature*)self->signature); - free((char*)self->encoding); + } else { + git_signature_free((git_signature *) self->signature); + free(self->encoding); } PyObject_Del(self); @@ -224,12 +225,23 @@ build_signature(Object *obj, const git_signature *signature, Signature *py_signature; py_signature = PyObject_New(Signature, &SignatureType); + if (!py_signature) + goto on_error; - if (py_signature) { - Py_INCREF(obj); - py_signature->obj = obj; - py_signature->signature = signature; - py_signature->encoding = encoding; + py_signature->encoding = NULL; + if (encoding) { + py_signature->encoding = strdup(encoding); + if (!py_signature->encoding) + goto on_error; } + + Py_XINCREF(obj); + py_signature->obj = obj; + py_signature->signature = signature; + return (PyObject*)py_signature; + +on_error: + git_signature_free((git_signature *) signature); + return NULL; } diff --git a/src/types.h b/src/types.h index e6a318994..3e704a7aa 100644 --- a/src/types.h +++ b/src/types.h @@ -191,7 +191,7 @@ typedef struct { PyObject_HEAD Object *obj; const git_signature *signature; - const char *encoding; + char *encoding; } Signature; From 687dc5388e7f75ee71ea683006ddd122d0fd2647 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Thu, 27 Mar 2014 16:53:16 +0100 Subject: [PATCH 0147/1630] config: make type conversion explicit The type of a config value depends on the tool that interprets it. Parsing eagerly can lead to a situation where we return a bool instead of a string or a number. Let the user specify the type themselves by passing in a (str, type) tuple into the mapping interface. --- docs/config.rst | 16 +++++++++++++++ src/config.c | 49 +++++++++++++++++++++++++++++++++++---------- src/utils.h | 2 ++ test/test_config.py | 12 +++++------ 4 files changed, 62 insertions(+), 17 deletions(-) diff --git a/docs/config.rst b/docs/config.rst index 348c34743..4bdaaa1cc 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -23,3 +23,19 @@ The Config type set multiple times in the configuration files. The :class:`Config` Mapping interface. + +Parsing the values +=================== + +Instead of a string, a tuple of `(str,type)` can be used to look up a +key and parse it through the Git rules. E.g. + + config['core.bare',bool] + +will return True if 'core.bare' is truthy. + +Truty values are: 'true', 1, 'on' or 'yes'. Falsy values are: 'false', +0, 'off' and 'no'. + +Available types are `bool` and `int`. Not specifying a type returns a +string. diff --git a/src/config.c b/src/config.c index 9b2625d6a..58fdeaaa8 100644 --- a/src/config.c +++ b/src/config.c @@ -170,29 +170,56 @@ Config_contains(Config *self, PyObject *py_key) { PyObject * -Config_getitem(Config *self, PyObject *py_key) +Config_getitem(Config *self, PyObject *py_input_key) { - int64_t value_int; - int err, value_bool; + int err; const char *value_str; const char *key; - PyObject* py_value, *tmp; + PyObject *py_key, *py_value, *tkey, *tmp_type = NULL; + PyTypeObject *py_type = NULL; + + if (PyTuple_Check(py_input_key) && PyTuple_Size(py_input_key) == 2) { + py_key = PyTuple_GetItem(py_input_key, 0); + tmp_type = PyTuple_GetItem(py_input_key, 1); + } else { + py_key = py_input_key; + } + + /* If passed a tuple, make sure the second item is a type */ + if (tmp_type) { + if (!PyType_Check(tmp_type)) + return NULL; + else + py_type = (PyTypeObject *) tmp_type; + } - key = py_str_borrow_c_str(&tmp, py_key, NULL); + key = py_str_borrow_c_str(&tkey, py_key, NULL); if (key == NULL) return NULL; err = git_config_get_string(&value_str, self->config, key); - Py_CLEAR(tmp); + Py_CLEAR(tkey); if (err < 0) goto cleanup; - if (git_config_parse_int64(&value_int, value_str) == 0) - py_value = PyLong_FromLongLong(value_int); - else if(git_config_parse_bool(&value_bool, value_str) == 0) - py_value = PyBool_FromLong(value_bool); - else + /* If the user specified a type, let's parse it */ + if (py_type) { + if (py_type == &PyBool_Type) { + int value; + if ((err = git_config_parse_bool(&value, value_str)) < 0) + goto cleanup; + + py_value = PyBool_FromLong(value); + } else if (py_type == &PyInteger_Type) { + int64_t value; + if ((err = git_config_parse_int64(&value, value_str)) < 0) + goto cleanup; + + py_value = PyLong_FromLongLong(value); + } + } else { py_value = to_unicode(value_str, NULL, NULL); + } cleanup: if (err < 0) { diff --git a/src/utils.h b/src/utils.h index 81744f686..e321ea12e 100644 --- a/src/utils.h +++ b/src/utils.h @@ -47,6 +47,7 @@ #undef PyLong_Check #define PyLong_Check PyInt_Check #define PyLong_FromLong PyInt_FromLong + #define PyInteger_Type PyInt_Type #define PyBytes_AS_STRING PyString_AS_STRING #define PyBytes_AsString PyString_AsString #define PyBytes_AsStringAndSize PyString_AsStringAndSize @@ -57,6 +58,7 @@ #define to_path(x) to_bytes(x) #define to_encoding(x) to_bytes(x) #else + #define PyInteger_Type PyLong_Type #define to_path(x) to_unicode(x, Py_FileSystemDefaultEncoding, "strict") #define to_encoding(x) PyUnicode_DecodeASCII(x, strlen(x), "strict") #endif diff --git a/test/test_config.py b/test/test_config.py index 9f4d460a7..f29c703e1 100644 --- a/test/test_config.py +++ b/test/test_config.py @@ -74,7 +74,7 @@ def test_new(self): config_read = Config(CONFIG_FILENAME) self.assertTrue('core.bare' in config_read) - self.assertFalse(config_read['core.bare']) + self.assertFalse(config_read['core.bare',bool]) self.assertTrue('core.editor' in config_read) self.assertEqual(config_read['core.editor'], 'ed') @@ -88,9 +88,9 @@ def test_add(self): config.add_file(CONFIG_FILENAME, 0) self.assertTrue('this.that' in config) - self.assertTrue(config['this.that']) + self.assertTrue(config['this.that',bool]) self.assertTrue('something.other.here' in config) - self.assertFalse(config['something.other.here']) + self.assertFalse(config['something.other.here',bool]) def test_read(self): config = self.repo.config @@ -103,11 +103,11 @@ def test_read(self): lambda: config['abc.def']) self.assertTrue('core.bare' in config) - self.assertFalse(config['core.bare']) + self.assertFalse(config['core.bare',bool]) self.assertTrue('core.editor' in config) self.assertEqual(config['core.editor'], 'ed') self.assertTrue('core.repositoryformatversion' in config) - self.assertEqual(config['core.repositoryformatversion'], 0) + self.assertEqual(config['core.repositoryformatversion',int], 0) new_file = open(CONFIG_FILENAME, "w") new_file.write("[this]\n\tthat = foobar\n\tthat = foobeer\n") @@ -129,7 +129,7 @@ def test_write(self): self.assertFalse('core.dummy1' in config) config['core.dummy1'] = 42 self.assertTrue('core.dummy1' in config) - self.assertEqual(config['core.dummy1'], 42) + self.assertEqual(config['core.dummy1',int], 42) self.assertFalse('core.dummy2' in config) config['core.dummy2'] = 'foobar' From f0874cc1ea3f270813d5a4699a96459bdc337db3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Thu, 27 Mar 2014 21:00:09 +0100 Subject: [PATCH 0148/1630] Always use new-style classes in Python 2 --- pygit2/credentials.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pygit2/credentials.py b/pygit2/credentials.py index 1789022d6..cad215a57 100644 --- a/pygit2/credentials.py +++ b/pygit2/credentials.py @@ -28,7 +28,7 @@ # Import from pygit2 from _pygit2 import GIT_CREDTYPE_USERPASS_PLAINTEXT, GIT_CREDTYPE_SSH_KEY -class UserPass: +class UserPass(object): """Username/Password credentials This is an object suitable for passing to a remote's credentials @@ -51,7 +51,7 @@ def credential_tuple(self): def __call__(self, _url, _username, _allowed): return self -class Keypair: +class Keypair(object): """SSH key pair credentials This is an object suitable for passing to a remote's credentials From 60b4cb537ae93e2a4d81b731d7f69a443939df81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Tue, 1 Apr 2014 18:44:39 +0200 Subject: [PATCH 0149/1630] Writing changelog for v0.20.3 --- README.rst | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/README.rst b/README.rst index a76fee96e..272b149dd 100644 --- a/README.rst +++ b/README.rst @@ -70,6 +70,38 @@ Authors Changelog ============== +0.20.3 (2014-04-XX) +------------------- + +- A number of memory issues fixed + `#328 `_ + `#348 `_ + `#353 `_ + `#355 `_ + `#356 `_ +- Compatibility fixes for + PyPy (`#338 `_), + Visual Studio 2008 (`#343 `_) + and Python 3.3 (`#351 `_) +- Make the sort mode parameter in ``Repository.walk(...)`` optional + `#337 `_ +- New ``Object.peel(...)`` + `#342 `_ +- New ``Index.add_all(...)`` + `#344 `_ +- Introduce support for libgit2 options + `#350 `_ +- More informative repr for ``Repository`` objects + `#352 `_ +- Introduce support for credentials + `#354 `_ +- Several documentation fixes + `#302 `_ + `#336 `_ +- Tests, remove temporary files + `#341 `_ + + 0.20.2 (2014-02-04) ------------------- From 7a606b9b1c87788e60f3ce3822533937cf204263 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sun, 19 Jan 2014 01:27:26 +0100 Subject: [PATCH 0150/1630] Adjust to libgit2 dup changes --- src/blame.c | 14 ++++++++++---- src/reference.c | 3 +-- src/tree.c | 20 ++++++++++---------- src/treebuilder.c | 10 +++++----- 4 files changed, 26 insertions(+), 21 deletions(-) diff --git a/src/blame.c b/src/blame.c index 80b82089a..b238f050a 100644 --- a/src/blame.c +++ b/src/blame.c @@ -69,14 +69,20 @@ wrap_blame_hunk(const git_blame_hunk *hunk, Blame *blame) py_hunk->lines_in_hunk = hunk->lines_in_hunk; py_hunk->final_commit_id = git_oid_allocfmt(&hunk->final_commit_id); py_hunk->final_start_line_number = hunk->final_start_line_number; - py_hunk->final_signature = hunk->final_signature != NULL ? - git_signature_dup(hunk->final_signature) : NULL; + + py_hunk->final_signature = NULL; + if (hunk->final_signature) + git_signature_dup(&py_hunk->final_signature, hunk->final_signature); + py_hunk->orig_commit_id = git_oid_allocfmt(&hunk->orig_commit_id); py_hunk->orig_path = hunk->orig_path != NULL ? strdup(hunk->orig_path) : NULL; py_hunk->orig_start_line_number = hunk->orig_start_line_number; - py_hunk->orig_signature = hunk->orig_signature != NULL ? - git_signature_dup(hunk->orig_signature) : NULL; + + py_hunk->orig_signature = NULL; + if (hunk->orig_signature) + git_signature_dup(&py_hunk->orig_signature, hunk->orig_signature); + py_hunk->boundary = hunk->boundary; } diff --git a/src/reference.c b/src/reference.c index e597e353a..1c78d4199 100644 --- a/src/reference.c +++ b/src/reference.c @@ -62,8 +62,7 @@ RefLogIter_iternext(RefLogIter *self) py_entry->oid_old = git_oid_allocfmt(git_reflog_entry_id_old(entry)); py_entry->oid_new = git_oid_allocfmt(git_reflog_entry_id_new(entry)); py_entry->message = strdup(git_reflog_entry_message(entry)); - py_entry->signature = git_signature_dup( - git_reflog_entry_committer(entry)); + git_signature_dup(&py_entry->signature, git_reflog_entry_committer(entry)); ++(self->i); diff --git a/src/tree.c b/src/tree.c index d1968faed..b72d57858 100644 --- a/src/tree.c +++ b/src/tree.c @@ -284,20 +284,20 @@ TreeEntry * Tree_getitem_by_index(Tree *self, PyObject *py_index) { int index; - const git_tree_entry *entry; + const git_tree_entry *entry_src; + git_tree_entry *entry; index = Tree_fix_index(self, py_index); if (PyErr_Occurred()) return NULL; - entry = git_tree_entry_byindex(self->tree, index); - if (!entry) { + entry_src = git_tree_entry_byindex(self->tree, index); + if (!entry_src) { PyErr_SetObject(PyExc_IndexError, py_index); return NULL; } - entry = git_tree_entry_dup(entry); - if (entry == NULL) { + if (git_tree_entry_dup(&entry, entry_src) < 0) { PyErr_SetNone(PyExc_MemoryError); return NULL; } @@ -550,16 +550,16 @@ TreeIter_dealloc(TreeIter *self) TreeEntry * TreeIter_iternext(TreeIter *self) { - const git_tree_entry *entry; + const git_tree_entry *entry_src; + git_tree_entry *entry; - entry = git_tree_entry_byindex(self->owner->tree, self->i); - if (!entry) + entry_src = git_tree_entry_byindex(self->owner->tree, self->i); + if (!entry_src) return NULL; self->i += 1; - entry = git_tree_entry_dup(entry); - if (entry == NULL) { + if (git_tree_entry_dup(&entry, entry_src) < 0) { PyErr_SetNone(PyExc_MemoryError); return NULL; } diff --git a/src/treebuilder.c b/src/treebuilder.c index 8354b1b5e..5957040ce 100644 --- a/src/treebuilder.c +++ b/src/treebuilder.c @@ -105,19 +105,19 @@ PyObject * TreeBuilder_get(TreeBuilder *self, PyObject *py_filename) { char *filename; - const git_tree_entry *entry; + const git_tree_entry *entry_src; + git_tree_entry *entry; filename = py_path_to_c_str(py_filename); if (filename == NULL) return NULL; - entry = git_treebuilder_get(self->bld, filename); + entry_src = git_treebuilder_get(self->bld, filename); free(filename); - if (entry == NULL) + if (entry_src == NULL) Py_RETURN_NONE; - entry = git_tree_entry_dup(entry); - if (entry == NULL) { + if (git_tree_entry_dup(&entry, entry_src) < 0) { PyErr_SetNone(PyExc_MemoryError); return NULL; } From 423b912fae0e17bd50b6989a354048b03a28a16b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sun, 19 Jan 2014 01:29:03 +0100 Subject: [PATCH 0151/1630] Adjust to reference creation signatures --- src/branch.c | 2 +- src/reference.c | 6 +++--- src/remote.c | 4 ++-- src/repository.c | 10 +++++----- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/branch.c b/src/branch.c index bcf763677..32879f346 100644 --- a/src/branch.c +++ b/src/branch.c @@ -101,7 +101,7 @@ Branch_rename(Branch *self, PyObject *args) if (!PyArg_ParseTuple(args, "s|i", &c_name, &force)) return NULL; - err = git_branch_move(&c_out, self->reference, c_name, force); + err = git_branch_move(&c_out, self->reference, c_name, force, NULL, NULL); if (err == GIT_OK) return wrap_branch(c_out, self->repo); else diff --git a/src/reference.c b/src/reference.c index 1c78d4199..2c5e0ff52 100644 --- a/src/reference.c +++ b/src/reference.c @@ -159,7 +159,7 @@ Reference_rename(Reference *self, PyObject *py_name) return NULL; /* Rename */ - err = git_reference_rename(&new_reference, self->reference, c_name, 0); + err = git_reference_rename(&new_reference, self->reference, c_name, 0, NULL, NULL); git_reference_free(self->reference); free(c_name); if (err < 0) @@ -239,7 +239,7 @@ Reference_target__set__(Reference *self, PyObject *py_target) if (err < 0) return err; - err = git_reference_set_target(&new_ref, self->reference, &oid); + err = git_reference_set_target(&new_ref, self->reference, &oid, NULL, NULL); if (err < 0) goto error; @@ -253,7 +253,7 @@ Reference_target__set__(Reference *self, PyObject *py_target) if (c_name == NULL) return -1; - err = git_reference_symbolic_set_target(&new_ref, self->reference, c_name); + err = git_reference_symbolic_set_target(&new_ref, self->reference, c_name, NULL, NULL); free(c_name); if (err < 0) goto error; diff --git a/src/remote.c b/src/remote.c index dee9b5a76..deb832d6c 100644 --- a/src/remote.c +++ b/src/remote.c @@ -467,7 +467,7 @@ Remote_fetch(Remote *self, PyObject *args) int err; PyErr_Clear(); - err = git_remote_fetch(self->remote); + err = git_remote_fetch(self->remote, NULL, NULL); /* * XXX: We should be checking for GIT_EUSER, but on v0.20, this does not * make it all the way to us for update_tips @@ -558,7 +558,7 @@ Remote_push(Remote *self, PyObject *args) return NULL; } - err = git_push_update_tips(push); + err = git_push_update_tips(push, NULL, NULL); if (err < 0) goto error; diff --git a/src/repository.c b/src/repository.c index bb2a11b96..738627fd3 100644 --- a/src/repository.c +++ b/src/repository.c @@ -192,7 +192,7 @@ Repository_head__set__(Repository *self, PyObject *py_refname) if (refname == NULL) return -1; - err = git_repository_set_head(self->repo, refname); + err = git_repository_set_head(self->repo, refname, NULL, NULL); Py_DECREF(trefname); if (err < 0) { Error_set_str(err, refname); @@ -929,7 +929,7 @@ Repository_create_branch(Repository *self, PyObject *args) if (!PyArg_ParseTuple(args, "sO!|i", &c_name, &CommitType, &py_commit, &force)) return NULL; - err = git_branch_create(&c_reference, self->repo, c_name, py_commit->commit, force); + err = git_branch_create(&c_reference, self->repo, c_name, py_commit->commit, force, NULL, NULL); if (err < 0) return Error_set(err); @@ -1096,7 +1096,7 @@ Repository_create_reference_direct(Repository *self, PyObject *args, if (err < 0) return NULL; - err = git_reference_create(&c_reference, self->repo, c_name, &oid, force); + err = git_reference_create(&c_reference, self->repo, c_name, &oid, force, NULL, NULL); if (err < 0) return Error_set(err); @@ -1130,7 +1130,7 @@ Repository_create_reference_symbolic(Repository *self, PyObject *args, return NULL; err = git_reference_symbolic_create(&c_reference, self->repo, c_name, - c_target, force); + c_target, force, NULL, NULL); if (err < 0) return Error_set(err); @@ -1607,7 +1607,7 @@ Repository_reset(Repository *self, PyObject* args) err = git_object_lookup_prefix(&target, self->repo, &oid, len, GIT_OBJ_ANY); - err = err < 0 ? err : git_reset(self->repo, target, reset_type); + err = err < 0 ? err : git_reset(self->repo, target, reset_type, NULL, NULL); git_object_free(target); if (err < 0) return Error_set_oid(err, &oid, len); From 9cec62d2e1faeb0bb7e3e72af08335e3b08f634f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sun, 19 Jan 2014 02:01:50 +0100 Subject: [PATCH 0152/1630] Blame: orig_ is now filled --- test/test_blame.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/test/test_blame.py b/test/test_blame.py index dbe9e6bf0..cbdba9c6a 100644 --- a/test/test_blame.py +++ b/test/test_blame.py @@ -65,11 +65,10 @@ def test_blame_index(self): self.assertEqual(HUNKS[i][0], hunk.final_commit_id) self.assertEqual(HUNKS[i][1], hunk.final_start_line_number) self.assertEqualSignature(HUNKS[i][2], hunk.final_committer) - self.assertEqual(hunk.orig_commit_id, - '0000000000000000000000000000000000000000') + self.assertEqual(HUNKS[i][0], hunk.orig_commit_id) self.assertEqual(hunk.orig_path, PATH) self.assertEqual(HUNKS[i][1], hunk.orig_start_line_number) - self.assertTrue(hunk.orig_committer is None) + self.assertEqualSignature(HUNKS[i][2], hunk.orig_committer) self.assertEqual(HUNKS[i][3], hunk.boundary) def test_blame_with_invalid_index(self): @@ -93,11 +92,10 @@ def test_blame_for_line(self): self.assertEqual(HUNKS[i][0], hunk.final_commit_id) self.assertEqual(HUNKS[i][1], hunk.final_start_line_number) self.assertEqualSignature(HUNKS[i][2], hunk.final_committer) - self.assertEqual(hunk.orig_commit_id, - '0000000000000000000000000000000000000000') + self.assertEqual(HUNKS[i][0], hunk.orig_commit_id) self.assertEqual(hunk.orig_path, PATH) self.assertEqual(HUNKS[i][1], hunk.orig_start_line_number) - self.assertTrue(hunk.orig_committer is None) + self.assertEqualSignature(HUNKS[i][2], hunk.orig_committer) self.assertEqual(HUNKS[i][3], hunk.boundary) def test_blame_with_invalid_line(self): @@ -131,11 +129,10 @@ def test_blame_newest(self): self.assertEqual(HUNKS[i][0], hunk.final_commit_id) self.assertEqual(HUNKS[i][1], hunk.final_start_line_number) self.assertEqualSignature(HUNKS[i][2], hunk.final_committer) - self.assertEqual(hunk.orig_commit_id, - '0000000000000000000000000000000000000000') + self.assertEqual(HUNKS[i][0], hunk.orig_commit_id) self.assertEqual(hunk.orig_path, PATH) self.assertEqual(HUNKS[i][1], hunk.orig_start_line_number) - self.assertTrue(hunk.orig_committer is None) + self.assertEqualSignature(HUNKS[i][2], hunk.orig_committer) self.assertEqual(HUNKS[i][3], hunk.boundary) if __name__ == '__main__': From dd6fc972dd3b47668e3352af202cb33b76544a70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sun, 2 Feb 2014 19:40:48 +0100 Subject: [PATCH 0153/1630] Adjust to the git_buf changes We no longer need the max-path define, so we can get rid of that and the pypy checking. --- src/branch.c | 47 +++++++++-------------------------------------- src/config.c | 20 ++++++++++++++------ src/diff.c | 20 ++++++-------------- src/pygit2.c | 22 ++++++++-------------- src/refspec.c | 46 ++++++++++++---------------------------------- 5 files changed, 49 insertions(+), 106 deletions(-) diff --git a/src/branch.c b/src/branch.c index 32879f346..39ddd675d 100644 --- a/src/branch.c +++ b/src/branch.c @@ -135,34 +135,19 @@ PyObject * Branch_remote_name__get__(Branch *self) { int err; + git_buf name = {NULL}; const char *branch_name; - char *c_name = NULL; PyObject *py_name; CHECK_REFERENCE(self); branch_name = git_reference_name(self->reference); - /* Get the length of the remote name */ - err = git_branch_remote_name(NULL, 0, self->repo->repo, branch_name); + err = git_branch_remote_name(&name, self->repo->repo, branch_name); if (err < GIT_OK) return Error_set(err); - /* Get the actual remote name */ - c_name = calloc(err, sizeof(char)); - if (c_name == NULL) - return PyErr_NoMemory(); - - err = git_branch_remote_name(c_name, - err * sizeof(char), - self->repo->repo, - branch_name); - if (err < GIT_OK) { - free(c_name); - return Error_set(err); - } - - py_name = to_unicode_n(c_name, err - 1, NULL, NULL); - free(c_name); + py_name = to_unicode(name.ptr, NULL, NULL); + git_buf_free(&name); return py_name; } @@ -227,34 +212,20 @@ PyObject * Branch_upstream_name__get__(Branch *self) { int err; + git_buf name = {NULL}; const char *branch_name; - char *c_name = NULL; PyObject *py_name; CHECK_REFERENCE(self); branch_name = git_reference_name(self->reference); - /* Get the length of the upstream name */ - err = git_branch_upstream_name(NULL, 0, self->repo->repo, branch_name); - if (err < GIT_OK) - return Error_set(err); - - /* Get the actual upstream name */ - c_name = calloc(err, sizeof(char)); - if (c_name == NULL) - return PyErr_NoMemory(); - err = git_branch_upstream_name(c_name, - err * sizeof(char), - self->repo->repo, - branch_name); - if (err < GIT_OK) { - free(c_name); + err = git_branch_upstream_name(&name, self->repo->repo, branch_name); + if (err < GIT_OK) return Error_set(err); - } - py_name = to_unicode_n(c_name, err - 1, NULL, NULL); - free(c_name); + py_name = to_unicode(name.ptr, NULL, NULL); + git_buf_free(&name); return py_name; } diff --git a/src/config.c b/src/config.c index 9b2625d6a..393fded93 100644 --- a/src/config.c +++ b/src/config.c @@ -103,10 +103,11 @@ PyDoc_STRVAR(Config_get_global_config__doc__, PyObject * Config_get_global_config(void) { - char path[GIT_PATH_MAX]; + git_buf path = {NULL}; + PyObject *py_config; int err; - err = git_config_find_global(path, GIT_PATH_MAX); + err = git_config_find_global(&path); if (err < 0) { if (err == GIT_ENOTFOUND) { PyErr_SetString(PyExc_IOError, "Global config file not found."); @@ -116,7 +117,10 @@ Config_get_global_config(void) return Error_set(err); } - return wrap_config(path); + py_config = wrap_config(path.ptr); + + git_buf_free(&path); + return py_config; } @@ -128,10 +132,11 @@ PyDoc_STRVAR(Config_get_system_config__doc__, PyObject * Config_get_system_config(void) { - char path[GIT_PATH_MAX]; + git_buf path = {NULL}; + PyObject *py_config; int err; - err = git_config_find_system(path, GIT_PATH_MAX); + err = git_config_find_system(&path); if (err < 0) { if (err == GIT_ENOTFOUND) { PyErr_SetString(PyExc_IOError, "System config file not found."); @@ -140,7 +145,10 @@ Config_get_system_config(void) return Error_set(err); } - return wrap_config(path); + py_config = wrap_config(path.ptr); + + git_buf_free(&path); + return py_config; } diff --git a/src/diff.c b/src/diff.c index adea3cdd8..da0887406 100644 --- a/src/diff.c +++ b/src/diff.c @@ -292,8 +292,7 @@ PyObject * Diff_patch__get__(Diff *self) { git_patch* patch; - char **strings = NULL; - char *buffer = NULL; + git_buf buf = {NULL}; int err = GIT_ERROR; size_t i, len, num; PyObject *py_patch = NULL; @@ -301,32 +300,25 @@ Diff_patch__get__(Diff *self) num = git_diff_num_deltas(self->list); if (num == 0) Py_RETURN_NONE; - MALLOC(strings, num * sizeof(char*), cleanup); for (i = 0, len = 1; i < num ; ++i) { err = git_patch_from_diff(&patch, self->list, i); if (err < 0) goto cleanup; - err = git_patch_to_str(&(strings[i]), patch); + /* This appends to the current buf, so we can simply keep passing it */ + err = git_patch_to_buf(&buf, patch); if (err < 0) goto cleanup; - len += strlen(strings[i]); git_patch_free(patch); } - CALLOC(buffer, (len + 1), sizeof(char), cleanup); - for (i = 0; i < num; ++i) { - strcat(buffer, strings[i]); - free(strings[i]); - } - free(strings); - - py_patch = to_unicode(buffer, NULL, NULL); - free(buffer); + py_patch = to_unicode(buf.ptr, NULL, NULL); + git_buf_free(&buf); cleanup: + git_buf_free(&buf); return (err < 0) ? Error_set(err) : py_patch; } diff --git a/src/pygit2.c b/src/pygit2.c index bda9545ec..2e80cc5b1 100644 --- a/src/pygit2.c +++ b/src/pygit2.c @@ -28,11 +28,6 @@ #define PY_SSIZE_T_CLEAN #include -/* Pypy does not provide this header */ -#ifndef PYPY_VERSION -# include -#endif - #include #include "error.h" #include "types.h" @@ -41,11 +36,6 @@ #include "oid.h" #include "options.h" -/* FIXME: This is for pypy */ -#ifndef MAXPATHLEN -# define MAXPATHLEN 1024 -#endif - extern PyObject *GitError; extern PyTypeObject RepositoryType; @@ -187,21 +177,25 @@ PyDoc_STRVAR(discover_repository__doc__, PyObject * discover_repository(PyObject *self, PyObject *args) { + git_buf repo_path = {NULL}; const char *path; + PyObject *py_repo_path; int across_fs = 0; const char *ceiling_dirs = NULL; - char repo_path[MAXPATHLEN]; int err; if (!PyArg_ParseTuple(args, "s|Is", &path, &across_fs, &ceiling_dirs)) return NULL; - err = git_repository_discover(repo_path, sizeof(repo_path), - path, across_fs, ceiling_dirs); + memset(&repo_path, 0, sizeof(git_buf)); + err = git_repository_discover(&repo_path, path, across_fs, ceiling_dirs); if (err < 0) return Error_set_str(err, path); - return to_path(repo_path); + py_repo_path = to_path(repo_path.ptr); + git_buf_free(&repo_path); + + return py_repo_path; }; PyDoc_STRVAR(hashfile__doc__, diff --git a/src/refspec.c b/src/refspec.c index 3053fe60b..008aa6648 100644 --- a/src/refspec.c +++ b/src/refspec.c @@ -155,35 +155,24 @@ PyDoc_STRVAR(Refspec_transform__doc__, PyObject * Refspec_transform(Refspec *self, PyObject *py_str) { + git_buf trans = {NULL}; const char *str; - char *trans; - int err, len, alen; + int err; PyObject *py_trans, *tstr; str = py_str_borrow_c_str(&tstr, py_str, NULL); - alen = len = strlen(str); - - do { - alen *= alen; - trans = malloc(alen); - if (!trans) { - Py_DECREF(tstr); - return PyErr_NoMemory(); - } - - err = git_refspec_transform(trans, alen, self->refspec, str); - } while(err == GIT_EBUFS); + + err = git_refspec_transform(&trans, self->refspec, str); Py_DECREF(tstr); if (err < 0) { - free(trans); Error_set(err); return NULL; } - py_trans = to_unicode(trans, NULL, NULL); + py_trans = to_unicode(trans.ptr, NULL, NULL); - free(trans); + git_buf_free(&trans); return py_trans; } @@ -196,35 +185,24 @@ PyDoc_STRVAR(Refspec_rtransform__doc__, PyObject * Refspec_rtransform(Refspec *self, PyObject *py_str) { + git_buf trans = {NULL}; const char *str; - char *trans; - int err, len, alen; + int err; PyObject *py_trans, *tstr; str = py_str_borrow_c_str(&tstr, py_str, NULL); - alen = len = strlen(str); - - do { - alen *= alen; - trans = malloc(alen); - if (!trans) { - Py_DECREF(tstr); - return PyErr_NoMemory(); - } - - err = git_refspec_rtransform(trans, alen, self->refspec, str); - } while(err == GIT_EBUFS); + + err = git_refspec_rtransform(&trans, self->refspec, str); Py_DECREF(tstr); if (err < 0) { - free(trans); Error_set(err); return NULL; } - py_trans = to_unicode(trans, NULL, NULL); + py_trans = to_unicode(trans.ptr, NULL, NULL); - free(trans); + git_buf_free(&trans); return py_trans; } From e5f6798f67cb7de2edbfbfc0b20571d6979c7650 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sun, 2 Feb 2014 20:28:23 +0100 Subject: [PATCH 0154/1630] Adjust to oid -> id renaming --- docs/diff.rst | 4 ++-- docs/merge.rst | 8 ++++---- docs/objects.rst | 24 ++++++++++++------------ docs/references.rst | 6 +++--- docs/working-copy.rst | 6 +++--- src/diff.c | 12 ++++++------ src/index.c | 16 ++++++++-------- src/mergeresult.c | 12 ++++++------ src/note.c | 8 ++++---- src/repository.c | 6 +++--- src/types.h | 4 ++-- test/test_diff.py | 6 +++--- test/test_index.py | 8 ++++---- test/test_note.py | 4 ++-- test/test_repository.py | 14 +++++++------- 15 files changed, 69 insertions(+), 69 deletions(-) diff --git a/docs/diff.rst b/docs/diff.rst index 09fc9da21..dc7bf4dc0 100644 --- a/docs/diff.rst +++ b/docs/diff.rst @@ -50,8 +50,8 @@ Attributes: .. autoattribute:: pygit2.Patch.old_file_path .. autoattribute:: pygit2.Patch.new_file_path -.. autoattribute:: pygit2.Patch.old_oid -.. autoattribute:: pygit2.Patch.new_oid +.. autoattribute:: pygit2.Patch.old_id +.. autoattribute:: pygit2.Patch.new_id .. autoattribute:: pygit2.Patch.status .. autoattribute:: pygit2.Patch.similarity .. autoattribute:: pygit2.Patch.hunks diff --git a/docs/merge.rst b/docs/merge.rst index 4d98590aa..47984a868 100644 --- a/docs/merge.rst +++ b/docs/merge.rst @@ -22,8 +22,8 @@ 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).id - >>> merge_result = self.repo.merge(branch_oid) + >>> branch_id = self.repo.get(branch_head_hex).id + >>> merge_result = self.repo.merge(branch_id) The MergeResult object ====================== @@ -33,5 +33,5 @@ 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. +- fastforward_id: Oid, in the case it was a fastforward, this is the + forwarded id. diff --git a/docs/objects.rst b/docs/objects.rst index 3583c17ec..b9d038bbc 100644 --- a/docs/objects.rst +++ b/docs/objects.rst @@ -14,14 +14,14 @@ type. Object lookup ================= -In the previous chapter we learnt about Object IDs. With an oid we can ask the +In the previous chapter we learnt about Object IDs. With an Oid we can ask the repository to get the associated object. To do that the ``Repository`` class implementes a subset of the mapping interface. .. automethod:: pygit2.Repository.get - Return the Git object for the given *oid*, returns the *default* value if - there's no object in the repository with that oid. The oid can be an Oid + Return the Git object for the given *id*, returns the *default* value if + there's no object in the repository with that id. The id can be an Oid object, or an hexadecimal string. Example:: @@ -32,16 +32,16 @@ implementes a subset of the mapping interface. >>> obj <_pygit2.Commit object at 0x7ff27a6b60f0> -.. method:: Repository.__getitem__(oid) +.. method:: Repository.__getitem__(id) - Return the Git object for the given oid, raise ``KeyError`` if there's no - object in the repository with that oid. The oid can be an Oid object, or + Return the Git object for the given id, raise ``KeyError`` if there's no + object in the repository with that id. The id can be an Oid object, or an hexadecimal string. -.. method:: Repository.__contains__(oid) +.. method:: Repository.__contains__(id) - Returns True if there is an object in the Repository with that oid, False - if there is not. The oid can be an Oid object, or an hexadecimal string. + Returns True if there is an object in the Repository with that id, False + if there is not. The id can be an Oid object, or an hexadecimal string. The Object base type @@ -125,15 +125,15 @@ them to the Git object database: Example: - >>> oid = repo.create_blob('foo bar') # Creates blob from bytes string - >>> blob = repo[oid] + >>> id = repo.create_blob('foo bar') # Creates blob from bytes string + >>> blob = repo[id] >>> blob.data 'foo bar' .. automethod:: pygit2.Repository.create_blob_fromworkdir .. automethod:: pygit2.Repository.create_blob_fromdisk -There are also some functions to calculate the oid for a byte string without +There are also some functions to calculate the id for a byte string without creating the blob object: .. autofunction:: pygit2.hash diff --git a/docs/references.rst b/docs/references.rst index 363e3eebe..049fa8122 100644 --- a/docs/references.rst +++ b/docs/references.rst @@ -92,8 +92,8 @@ Example:: >>> for entry in head.log(): ... print(entry.message) -.. autoattribute:: pygit2.RefLogEntry.oid_new -.. autoattribute:: pygit2.RefLogEntry.oid_old +.. autoattribute:: pygit2.RefLogEntry.id_new +.. autoattribute:: pygit2.RefLogEntry.id_old .. autoattribute:: pygit2.RefLogEntry.message .. autoattribute:: pygit2.RefLogEntry.committer @@ -109,6 +109,6 @@ The Note type -------------------- .. autoattribute:: pygit2.Note.annotated_id -.. autoattribute:: pygit2.Note.oid +.. autoattribute:: pygit2.Note.id .. autoattribute:: pygit2.Note.message .. automethod:: pygit2.Note.remove diff --git a/docs/working-copy.rst b/docs/working-copy.rst index 76bcada87..cc1853d1d 100644 --- a/docs/working-copy.rst +++ b/docs/working-copy.rst @@ -8,8 +8,8 @@ Index read:: >>> index = repo.index >>> index.read() - >>> oid = index['path/to/file'].id # from path to object id - >>> blob = repo[oid] # from object id to object + >>> id = index['path/to/file'].id # from path to object id + >>> blob = repo[id] # from object id to object Iterate over all entries of the index:: @@ -43,7 +43,7 @@ The Index type The IndexEntry type -------------------- -.. autoattribute:: pygit2.IndexEntry.oid +.. autoattribute:: pygit2.IndexEntry.id .. autoattribute:: pygit2.IndexEntry.hex .. autoattribute:: pygit2.IndexEntry.path .. autoattribute:: pygit2.IndexEntry.mode diff --git a/src/diff.c b/src/diff.c index da0887406..3aeddeb85 100644 --- a/src/diff.c +++ b/src/diff.c @@ -80,8 +80,8 @@ wrap_patch(git_patch *patch) py_patch->status = git_diff_status_char(delta->status); py_patch->similarity = delta->similarity; py_patch->flags = delta->flags; - py_patch->old_oid = git_oid_allocfmt(&delta->old_file.oid); - py_patch->new_oid = git_oid_allocfmt(&delta->new_file.oid); + py_patch->old_id = git_oid_allocfmt(&delta->old_file.id); + py_patch->new_id = git_oid_allocfmt(&delta->new_file.id); git_patch_line_stats(NULL, &additions, &deletions, patch); py_patch->additions = additions; @@ -149,8 +149,8 @@ static void Patch_dealloc(Patch *self) { Py_CLEAR(self->hunks); - free(self->old_oid); - free(self->new_oid); + free(self->old_id); + free(self->new_id); /* We do not have to free old_file_path and new_file_path, they will * be freed by git_diff_list_free in Diff_dealloc */ PyObject_Del(self); @@ -159,8 +159,8 @@ Patch_dealloc(Patch *self) PyMemberDef Patch_members[] = { MEMBER(Patch, old_file_path, T_STRING, "old file path"), MEMBER(Patch, new_file_path, T_STRING, "new file path"), - MEMBER(Patch, old_oid, T_STRING, "old oid"), - MEMBER(Patch, new_oid, T_STRING, "new oid"), + MEMBER(Patch, old_id, T_STRING, "old oid"), + MEMBER(Patch, new_id, T_STRING, "new oid"), MEMBER(Patch, status, T_CHAR, "status"), MEMBER(Patch, similarity, T_INT, "similarity"), MEMBER(Patch, hunks, T_OBJECT, "hunks"), diff --git a/src/index.c b/src/index.c index 726974986..1ae3a1bdd 100644 --- a/src/index.c +++ b/src/index.c @@ -628,7 +628,7 @@ IndexEntry_init(IndexEntry *self, PyObject *args, PyObject *kwds) return -1; if (id) - git_oid_cpy(&self->entry.oid, &id->oid); + git_oid_cpy(&self->entry.id, &id->oid); if (mode) self->entry.mode = mode; @@ -689,18 +689,18 @@ IndexEntry_path__set__(IndexEntry *self, PyObject *py_path) return 0; } -PyDoc_STRVAR(IndexEntry_oid__doc__, "Object id."); +PyDoc_STRVAR(IndexEntry_id__doc__, "Object id."); PyObject * -IndexEntry_oid__get__(IndexEntry *self) +IndexEntry_id__get__(IndexEntry *self) { - return git_oid_to_python(&self->entry.oid); + return git_oid_to_python(&self->entry.id); } int -IndexEntry_oid__set__(IndexEntry *self, PyObject *py_id) +IndexEntry_id__set__(IndexEntry *self, PyObject *py_id) { - if (!py_oid_to_git_oid(py_id, &self->entry.oid)) + if (!py_oid_to_git_oid(py_id, &self->entry.id)) return -1; return 0; @@ -711,13 +711,13 @@ PyDoc_STRVAR(IndexEntry_hex__doc__, "Hex id."); PyObject * IndexEntry_hex__get__(IndexEntry *self) { - return git_oid_to_py_str(&self->entry.oid); + return git_oid_to_py_str(&self->entry.id); } PyGetSetDef IndexEntry_getseters[] = { GETSET(IndexEntry, mode), GETSET(IndexEntry, path), - GETSET(IndexEntry, oid), + GETSET(IndexEntry, id), GETTER(IndexEntry, hex), {NULL}, }; diff --git a/src/mergeresult.c b/src/mergeresult.c index 345e56ee6..4579e4b02 100644 --- a/src/mergeresult.c +++ b/src/mergeresult.c @@ -80,15 +80,15 @@ MergeResult_is_fastforward__get__(MergeResult *self) Py_RETURN_FALSE; } -PyDoc_STRVAR(MergeResult_fastforward_oid__doc__, "Fastforward Oid"); +PyDoc_STRVAR(MergeResult_fastforward_id__doc__, "Fastforward Oid"); PyObject * -MergeResult_fastforward_oid__get__(MergeResult *self) +MergeResult_fastforward_id__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); + git_oid fastforward_id; + git_merge_result_fastforward_id(&fastforward_id, self->result); + return git_oid_to_python((const git_oid *)&fastforward_id); } else Py_RETURN_NONE; } @@ -96,7 +96,7 @@ MergeResult_fastforward_oid__get__(MergeResult *self) PyGetSetDef MergeResult_getseters[] = { GETTER(MergeResult, is_uptodate), GETTER(MergeResult, is_fastforward), - GETTER(MergeResult, fastforward_oid), + GETTER(MergeResult, fastforward_id), {NULL}, }; diff --git a/src/note.c b/src/note.c index e14e30862..9762ef61e 100644 --- a/src/note.c +++ b/src/note.c @@ -66,13 +66,13 @@ Note_remove(Note *self, PyObject* args) } -PyDoc_STRVAR(Note_oid__doc__, +PyDoc_STRVAR(Note_id__doc__, "Gets the id of the blob containing the note message\n"); PyObject * -Note_oid__get__(Note *self) +Note_id__get__(Note *self) { - return git_oid_to_python(git_note_oid(self->note)); + return git_oid_to_python(git_note_id(self->note)); } @@ -108,7 +108,7 @@ PyMemberDef Note_members[] = { PyGetSetDef Note_getseters[] = { GETTER(Note, message), - GETTER(Note, oid), + GETTER(Note, id), {NULL} }; diff --git a/src/repository.c b/src/repository.c index 738627fd3..53f9649db 100644 --- a/src/repository.c +++ b/src/repository.c @@ -588,9 +588,9 @@ Repository_merge_base(Repository *self, PyObject *args) } PyDoc_STRVAR(Repository_merge__doc__, - "merge(oid) -> MergeResult\n" + "merge(id) -> MergeResult\n" "\n" - "Merges the given oid and returns the MergeResult.\n" + "Merges the given id and returns the MergeResult.\n" "\n" "If the merge is fastforward the MergeResult will contain the new\n" "fastforward oid.\n" @@ -614,7 +614,7 @@ Repository_merge(Repository *self, PyObject *py_oid) if (len == 0) return NULL; - err = git_merge_head_from_oid(&oid_merge_head, self->repo, &oid); + err = git_merge_head_from_id(&oid_merge_head, self->repo, &oid); if (err < 0) return Error_set(err); diff --git a/src/types.h b/src/types.h index cfd00092c..f1273990f 100644 --- a/src/types.h +++ b/src/types.h @@ -114,8 +114,8 @@ typedef struct { PyObject* hunks; const char * old_file_path; const char * new_file_path; - char* old_oid; - char* new_oid; + char* old_id; + char* new_id; char status; unsigned similarity; unsigned additions; diff --git a/test/test_diff.py b/test/test_diff.py index f982d0194..2f4314d98 100644 --- a/test/test_diff.py +++ b/test/test_diff.py @@ -257,13 +257,13 @@ def test_diff_patch(self): self.assertEqual(diff.patch, PATCH) self.assertEqual(len(diff), len([patch for patch in diff])) - def test_diff_oids(self): + def test_diff_ids(self): commit_a = self.repo[COMMIT_SHA1_1] commit_b = self.repo[COMMIT_SHA1_2] patch = commit_a.tree.diff_to_tree(commit_b.tree)[0] - self.assertEqual(patch.old_oid, + self.assertEqual(patch.old_id, '7f129fd57e31e935c6d60a0c794efe4e6927664b') - self.assertEqual(patch.new_oid, + self.assertEqual(patch.new_id, 'af431f20fc541ed6d5afede3e2dc7160f6f01f16') def test_hunk_content(self): diff --git a/test/test_index.py b/test/test_index.py index 7bed86bb3..1426e7fa2 100644 --- a/test/test_index.py +++ b/test/test_index.py @@ -181,13 +181,13 @@ def test_change_attributes(self): index = self.repo.index entry = index['hello.txt'] ign_entry = index['.gitignore'] - self.assertNotEqual(ign_entry.oid, entry.oid) + self.assertNotEqual(ign_entry.id, entry.id) self.assertNotEqual(entry.mode, pygit2.GIT_FILEMODE_BLOB_EXECUTABLE) entry.path = 'foo.txt' - entry.oid = ign_entry.oid + entry.id = ign_entry.id entry.mode = pygit2.GIT_FILEMODE_BLOB_EXECUTABLE self.assertEqual('foo.txt', entry.path) - self.assertEqual(ign_entry.oid, entry.oid) + self.assertEqual(ign_entry.id, entry.id) self.assertEqual(pygit2.GIT_FILEMODE_BLOB_EXECUTABLE, entry.mode) def test_write_tree_to(self): @@ -201,7 +201,7 @@ class IndexEntryTest(utils.RepoTestCase): def test_create_entry(self): index = self.repo.index hello_entry = index['hello.txt'] - entry = pygit2.IndexEntry('README.md', hello_entry.oid, hello_entry.mode) + entry = pygit2.IndexEntry('README.md', hello_entry.id, hello_entry.mode) index.add(entry) tree_id = index.write_tree() self.assertEqual('60e769e57ae1d6a2ab75d8d253139e6260e1f912', str(tree_id)) diff --git a/test/test_note.py b/test/test_note.py index 74f08940d..4fcead56e 100644 --- a/test/test_note.py +++ b/test/test_note.py @@ -58,7 +58,7 @@ def test_create_note(self): def test_lookup_note(self): annotated_id = self.repo.head.target.hex note = self.repo.lookup_note(annotated_id) - self.assertEqual(NOTES[0][0], note.oid.hex) + self.assertEqual(NOTES[0][0], note.id.hex) self.assertEqual(NOTES[0][1], note.message) def test_remove_note(self): @@ -70,7 +70,7 @@ def test_remove_note(self): def test_iterate_notes(self): for i, note in enumerate(self.repo.notes()): - entry = (note.oid.hex, note.message, note.annotated_id) + entry = (note.id.hex, note.message, note.annotated_id) self.assertEqual(NOTES[i], entry) def test_iterate_non_existing_ref(self): diff --git a/test/test_repository.py b/test/test_repository.py index 71214ccef..8da731158 100644 --- a/test/test_repository.py +++ b/test/test_repository.py @@ -314,7 +314,7 @@ def test_merge_uptodate(self): merge_result = self.repo.merge(branch_oid) self.assertTrue(merge_result.is_uptodate) self.assertFalse(merge_result.is_fastforward) - self.assertEqual(None, merge_result.fastforward_oid) + self.assertEqual(None, merge_result.fastforward_id) self.assertEqual({}, self.repo.status()) def test_merge_fastforward(self): @@ -324,8 +324,8 @@ def test_merge_fastforward(self): self.assertFalse(merge_result.is_uptodate) self.assertTrue(merge_result.is_fastforward) # Asking twice to assure the reference counting is correct - self.assertEqual(branch_head_hex, merge_result.fastforward_oid.hex) - self.assertEqual(branch_head_hex, merge_result.fastforward_oid.hex) + self.assertEqual(branch_head_hex, merge_result.fastforward_id.hex) + self.assertEqual(branch_head_hex, merge_result.fastforward_id.hex) self.assertEqual({}, self.repo.status()) def test_merge_no_fastforward_no_conflicts(self): @@ -335,8 +335,8 @@ def test_merge_no_fastforward_no_conflicts(self): self.assertFalse(merge_result.is_uptodate) self.assertFalse(merge_result.is_fastforward) # Asking twice to assure the reference counting is correct - self.assertEqual(None, merge_result.fastforward_oid) - self.assertEqual(None, merge_result.fastforward_oid) + self.assertEqual(None, merge_result.fastforward_id) + self.assertEqual(None, merge_result.fastforward_id) self.assertEqual({'bye.txt': 1}, self.repo.status()) self.assertEqual({'bye.txt': 1}, self.repo.status()) # Checking the index works as expected @@ -351,8 +351,8 @@ def test_merge_no_fastforward_conflicts(self): self.assertFalse(merge_result.is_uptodate) self.assertFalse(merge_result.is_fastforward) # Asking twice to assure the reference counting is correct - self.assertEqual(None, merge_result.fastforward_oid) - self.assertEqual(None, merge_result.fastforward_oid) + self.assertEqual(None, merge_result.fastforward_id) + self.assertEqual(None, merge_result.fastforward_id) self.assertEqual({'.gitignore': 132}, self.repo.status()) self.assertEqual({'.gitignore': 132}, self.repo.status()) # Checking the index works as expected From a870a59f2a0216163f9b93378f2cf4704c2c9ba5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sun, 23 Mar 2014 14:51:51 +0100 Subject: [PATCH 0155/1630] Adjust to option struct naming change --- src/repository.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/repository.c b/src/repository.c index 53f9649db..4c7ba006f 100644 --- a/src/repository.c +++ b/src/repository.c @@ -1360,7 +1360,7 @@ PyDoc_STRVAR(Repository_checkout_head__doc__, PyObject * Repository_checkout_head(Repository *self, PyObject *args) { - git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; unsigned int strategy; int err; @@ -1384,7 +1384,7 @@ PyDoc_STRVAR(Repository_checkout_index__doc__, PyObject * Repository_checkout_index(Repository *self, PyObject *args) { - git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; unsigned int strategy; int err; @@ -1408,7 +1408,7 @@ PyDoc_STRVAR(Repository_checkout_tree__doc__, PyObject * Repository_checkout_tree(Repository *self, PyObject *args) { - git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; unsigned int strategy; Object *py_object; int err; From 55037c23a34421c28558b44a0726539baa9f371a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Tue, 25 Mar 2014 03:14:02 +0100 Subject: [PATCH 0156/1630] Adjust to the merge changes There is no more MergeResult type. Instead, the user can use Repository.merge_analysis() to get an overview of their options and call git_merge() when they mean to merge. The git_merge() function now also performs a checkout. --- src/mergeresult.c | 145 ---------------------------------------- src/mergeresult.h | 37 ---------- src/pygit2.c | 8 ++- src/repository.c | 65 +++++++++++++----- src/types.h | 6 -- test/test_repository.py | 65 +++++++++--------- 6 files changed, 84 insertions(+), 242 deletions(-) delete mode 100644 src/mergeresult.c delete mode 100644 src/mergeresult.h diff --git a/src/mergeresult.c b/src/mergeresult.c deleted file mode 100644 index 4579e4b02..000000000 --- a/src/mergeresult.c +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright 2010-2014 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) -{ - MergeResult *py_merge_result; - - py_merge_result = PyObject_New(MergeResult, &MergeResultType); - if (!py_merge_result) - return NULL; - - py_merge_result->result = merge_result; - - return (PyObject*) py_merge_result; -} - -void -MergeResult_dealloc(MergeResult *self) -{ - git_merge_result_free(self->result); - PyObject_Del(self); -} - - -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_id__doc__, "Fastforward Oid"); - -PyObject * -MergeResult_fastforward_id__get__(MergeResult *self) -{ - if (git_merge_result_is_fastforward(self->result)) { - git_oid fastforward_id; - git_merge_result_fastforward_id(&fastforward_id, self->result); - return git_oid_to_python((const git_oid *)&fastforward_id); - } - else Py_RETURN_NONE; -} - -PyGetSetDef MergeResult_getseters[] = { - GETTER(MergeResult, is_uptodate), - GETTER(MergeResult, is_fastforward), - GETTER(MergeResult, fastforward_id), - {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 */ - (destructor)MergeResult_dealloc, /* 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 deleted file mode 100644 index 74161ea9d..000000000 --- a/src/mergeresult.h +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2010-2014 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); - -#endif diff --git a/src/pygit2.c b/src/pygit2.c index 2e80cc5b1..6fedc5d44 100644 --- a/src/pygit2.c +++ b/src/pygit2.c @@ -71,7 +71,6 @@ extern PyTypeObject NoteIterType; extern PyTypeObject BlameType; extern PyTypeObject BlameIterType; extern PyTypeObject BlameHunkType; -extern PyTypeObject MergeResultType; @@ -477,8 +476,11 @@ moduleinit(PyObject* m) ADD_CONSTANT_INT(m, GIT_BLAME_TRACK_COPIES_ANY_COMMIT_COPIES) /* Merge */ - INIT_TYPE(MergeResultType, NULL, NULL) - ADD_TYPE(m, MergeResult) + ADD_CONSTANT_INT(m, GIT_MERGE_ANALYSIS_NONE) + ADD_CONSTANT_INT(m, GIT_MERGE_ANALYSIS_NORMAL) + ADD_CONSTANT_INT(m, GIT_MERGE_ANALYSIS_UP_TO_DATE) + ADD_CONSTANT_INT(m, GIT_MERGE_ANALYSIS_FASTFORWARD) + ADD_CONSTANT_INT(m, GIT_MERGE_ANALYSIS_UNBORN) /* Global initialization of libgit2 */ git_threads_init(); diff --git a/src/repository.c b/src/repository.c index 4c7ba006f..ef9c8e38a 100644 --- a/src/repository.c +++ b/src/repository.c @@ -38,7 +38,6 @@ #include "remote.h" #include "branch.h" #include "blame.h" -#include "mergeresult.h" #include "signature.h" #include @@ -587,28 +586,61 @@ Repository_merge_base(Repository *self, PyObject *args) return git_oid_to_python(&oid); } +PyDoc_STRVAR(Repository_merge_analysis__doc__, + "merge_analysis(id) -> Integer\n" + "\n" + "Analyzes the given branch and determines the opportunities for merging\n" + "them into the HEAD of the repository\n" + "\n" + "The returned value is a mixture of the GIT_MERGE_ANALYSIS_NONE, _NORMAL,\n" + " _UP_TO_DATE, _FASTFORWARD and _UNBORN flags"); + +PyObject * +Repository_merge_analysis(Repository *self, PyObject *py_id) +{ + int err; + size_t len; + git_oid id; + git_merge_head *merge_head; + git_merge_analysis_t analysis; + + len = py_oid_to_git_oid(py_id, &id); + if (len == 0) + return NULL; + + err = git_merge_head_from_id(&merge_head, self->repo, &id); + if (err < 0) + return Error_set(err); + + err = git_merge_analysis(&analysis, self->repo, (const git_merge_head **) &merge_head, 1); + git_merge_head_free(merge_head); + + if (err < 0) + return Error_set(err); + + return PyLong_FromLong(analysis); +} + PyDoc_STRVAR(Repository_merge__doc__, - "merge(id) -> MergeResult\n" + "merge(id)\n" "\n" - "Merges the given id and returns the MergeResult.\n" + "Merges the given id into HEAD.\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."); + "Merges the given commit(s) into HEAD, writing the results into the\n" + "working directory. Any changes are staged for commit and any conflicts\n" + "are written to the index. Callers should inspect the repository's\n" + "index after this completes, resolve any conflicts and prepare a\n" + "commit."); 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; + git_merge_options merge_opts = GIT_MERGE_OPTIONS_INIT; + git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT; len = py_oid_to_git_oid(py_oid, &oid); if (len == 0) @@ -618,15 +650,15 @@ Repository_merge(Repository *self, PyObject *py_oid) if (err < 0) return Error_set(err); - err = git_merge(&merge_result, self->repo, + err = git_merge(self->repo, (const git_merge_head **)&oid_merge_head, 1, - &default_opts); + &merge_opts, &checkout_opts); + git_merge_head_free(oid_merge_head); if (err < 0) return Error_set(err); - py_merge_result = git_merge_result_to_python(merge_result); - return py_merge_result; + Py_RETURN_NONE; } PyDoc_STRVAR(Repository_walk__doc__, @@ -1623,6 +1655,7 @@ PyMethodDef Repository_methods[] = { METHOD(Repository, TreeBuilder, METH_VARARGS), METHOD(Repository, walk, METH_VARARGS), METHOD(Repository, merge_base, METH_VARARGS), + METHOD(Repository, merge_analysis, METH_O), METHOD(Repository, merge, METH_O), METHOD(Repository, read, METH_O), METHOD(Repository, write, METH_VARARGS), diff --git a/src/types.h b/src/types.h index f1273990f..15ff74094 100644 --- a/src/types.h +++ b/src/types.h @@ -249,10 +249,4 @@ typedef struct { char boundary; } BlameHunk; -/* git_merge */ -typedef struct { - PyObject_HEAD - git_merge_result *result; -} MergeResult; - #endif diff --git a/test/test_repository.py b/test/test_repository.py index 8da731158..c35b0fd99 100644 --- a/test/test_repository.py +++ b/test/test_repository.py @@ -40,6 +40,8 @@ # Import from pygit2 from pygit2 import GIT_OBJ_ANY, GIT_OBJ_BLOB, GIT_OBJ_COMMIT +from pygit2 import GIT_MERGE_ANALYSIS_NONE, GIT_MERGE_ANALYSIS_NORMAL, GIT_MERGE_ANALYSIS_UP_TO_DATE +from pygit2 import GIT_MERGE_ANALYSIS_FASTFORWARD, GIT_MERGE_ANALYSIS_UNBORN from pygit2 import init_repository, clone_repository, discover_repository from pygit2 import Oid, Reference, hashfile import pygit2 @@ -308,57 +310,50 @@ class RepositoryTest_III(utils.RepoTestCaseForMerging): def test_merge_none(self): self.assertRaises(TypeError, self.repo.merge, None) - def test_merge_uptodate(self): + def test_merge_analysis_uptodate(self): branch_head_hex = '5ebeeebb320790caf276b9fc8b24546d63316533' - branch_oid = self.repo.get(branch_head_hex).id - merge_result = self.repo.merge(branch_oid) - self.assertTrue(merge_result.is_uptodate) - self.assertFalse(merge_result.is_fastforward) - self.assertEqual(None, merge_result.fastforward_id) + branch_id = self.repo.get(branch_head_hex).id + analysis = self.repo.merge_analysis(branch_id) + + self.assertTrue(analysis & GIT_MERGE_ANALYSIS_UP_TO_DATE) + self.assertFalse(analysis & GIT_MERGE_ANALYSIS_FASTFORWARD) self.assertEqual({}, self.repo.status()) - def test_merge_fastforward(self): + def test_merge_analysis_fastforward(self): branch_head_hex = 'e97b4cfd5db0fb4ebabf4f203979ca4e5d1c7c87' - branch_oid = self.repo.get(branch_head_hex).id - 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.assertEqual(branch_head_hex, merge_result.fastforward_id.hex) - self.assertEqual(branch_head_hex, merge_result.fastforward_id.hex) + branch_id = self.repo.get(branch_head_hex).id + analysis = self.repo.merge_analysis(branch_id) + self.assertFalse(analysis & GIT_MERGE_ANALYSIS_UP_TO_DATE) + self.assertTrue(analysis & GIT_MERGE_ANALYSIS_FASTFORWARD) self.assertEqual({}, self.repo.status()) def test_merge_no_fastforward_no_conflicts(self): branch_head_hex = '03490f16b15a09913edb3a067a3dc67fbb8d41f1' - branch_oid = self.repo.get(branch_head_hex).id - merge_result = self.repo.merge(branch_oid) - self.assertFalse(merge_result.is_uptodate) - self.assertFalse(merge_result.is_fastforward) + branch_id = self.repo.get(branch_head_hex).id + analysis= self.repo.merge_analysis(branch_id) + self.assertFalse(analysis & GIT_MERGE_ANALYSIS_UP_TO_DATE) + self.assertFalse(analysis & GIT_MERGE_ANALYSIS_FASTFORWARD) # Asking twice to assure the reference counting is correct - self.assertEqual(None, merge_result.fastforward_id) - self.assertEqual(None, merge_result.fastforward_id) - self.assertEqual({'bye.txt': 1}, self.repo.status()) - self.assertEqual({'bye.txt': 1}, self.repo.status()) - # Checking the index works as expected - self.repo.index.remove('bye.txt') - self.repo.index.write() - self.assertEqual({'bye.txt': 128}, self.repo.status()) + self.assertEqual({}, self.repo.status()) + self.assertEqual({}, self.repo.status()) def test_merge_no_fastforward_conflicts(self): branch_head_hex = '1b2bae55ac95a4be3f8983b86cd579226d0eb247' - branch_oid = self.repo.get(branch_head_hex).id - merge_result = self.repo.merge(branch_oid) - self.assertFalse(merge_result.is_uptodate) - self.assertFalse(merge_result.is_fastforward) + branch_id = self.repo.get(branch_head_hex).id + + analysis = self.repo.merge_analysis(branch_id) + self.assertFalse(analysis & GIT_MERGE_ANALYSIS_UP_TO_DATE) + self.assertFalse(analysis & GIT_MERGE_ANALYSIS_FASTFORWARD) + + self.repo.merge(branch_id) + status = pygit2.GIT_STATUS_WT_NEW | pygit2.GIT_STATUS_INDEX_DELETED # Asking twice to assure the reference counting is correct - self.assertEqual(None, merge_result.fastforward_id) - self.assertEqual(None, merge_result.fastforward_id) - self.assertEqual({'.gitignore': 132}, self.repo.status()) - self.assertEqual({'.gitignore': 132}, self.repo.status()) + self.assertEqual({'.gitignore': status}, self.repo.status()) + self.assertEqual({'.gitignore': status}, self.repo.status()) # Checking the index works as expected self.repo.index.add('.gitignore') self.repo.index.write() - self.assertEqual({'.gitignore': 2}, self.repo.status()) + self.assertEqual({'.gitignore': pygit2.GIT_STATUS_INDEX_MODIFIED}, self.repo.status()) def test_merge_invalid_hex(self): branch_head_hex = '12345678' From 114e300b08f911d4923374e23ba67465c87edc01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Tue, 1 Apr 2014 19:56:10 +0200 Subject: [PATCH 0157/1630] Adjust to options changes We can now use a git_buf to extract the search path. --- src/options.c | 33 +++++++-------------------------- 1 file changed, 7 insertions(+), 26 deletions(-) diff --git a/src/options.c b/src/options.c index abfa4656a..1679a93d2 100644 --- a/src/options.c +++ b/src/options.c @@ -37,35 +37,16 @@ extern PyObject *GitError; static PyObject * get_search_path(long level) { - char *buf = NULL; - size_t len = 64; + git_buf buf = {NULL}; PyObject *py_path; - int error; - - do { - len *= 2; - char *tmp = realloc(buf, len); - if (!tmp) { - free(buf); - PyErr_NoMemory(); - return NULL; - } - buf = tmp; - - error = git_libgit2_opts(GIT_OPT_GET_SEARCH_PATH, level, buf, len); - } while(error == GIT_EBUFS); + int err; - if (error < 0) { - free(buf); - Error_set(error); - return NULL; - } - - if (!buf) - return NULL; + err = git_libgit2_opts(GIT_OPT_GET_SEARCH_PATH, level, &buf); + if (err < 0) + return Error_set(err); - py_path = to_unicode(buf, NULL, NULL); - free(buf); + py_path = to_unicode(buf.ptr, NULL, NULL); + git_buf_free(&buf); if (!py_path) return NULL; From d882af8f52fadfdc35a98daaac8bf0a2fb8e155f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Wed, 2 Apr 2014 22:28:18 +0200 Subject: [PATCH 0158/1630] Get ready to release 0.20.3 --- README.rst | 46 ++++++++++++++++++++++++---------------------- docs/conf.py | 2 +- pygit2/version.py | 2 +- 3 files changed, 26 insertions(+), 24 deletions(-) diff --git a/README.rst b/README.rst index 272b149dd..04c436622 100644 --- a/README.rst +++ b/README.rst @@ -44,33 +44,35 @@ for the topic), send a pull request. Authors ============== -57 developers have contributed at least 1 commit to pygit2:: - - J. David Ibáñez Brodie Rao Adam Spiers - Nico von Geyso David Versmisse Alexander Bayandin - Carlos Martín Nieto Rémi Duraffort Andrew Chin - W. Trevor King Sebastian Thiel András Veres-Szentkirályi - Dave Borowitz Fraser Tweedale Benjamin Kircher - Daniel Rodríguez Troitiño Han-Wen Nienhuys Benjamin Pollack - Richo Healey Petr Viktorin Bryan O'Sullivan - Christian Boos Alex Chamberlain David Fischer - Julien Miotte Amit Bakshi David Sanders - Xu Tao Andrey Devyatkin Eric Davis - Jose Plana Ben Davis Erik van Zijst - Martin Lenders Eric Schrijver Ferengee - Petr Hosek Hervé Cauwelier Gustavo Di Pietro - Victor Garcia Huang Huang Hugh Cole-Baker - Xavier Delannoy Jared Flatow Josh Bleecher Snyder - Yonggang Luo Jiunn Haur Lim Jun Omae - Valentin Haenel Sarath Lakshman Óscar San José - Bernardo Heynemann Vicent Marti Ridge Kennedy - John Szakmeister Zoran Zaric Rui Abreu Ferreira +62 developers have contributed at least 1 commit to pygit2:: + + J. David Ibáñez Rémi Duraffort András Veres-Szentkirályi + Nico von Geyso Sebastian Thiel Benjamin Kircher + Carlos Martín Nieto Fraser Tweedale Benjamin Pollack + W. Trevor King Han-Wen Nienhuys Bryan O'Sullivan + Dave Borowitz Leonardo Rhodes David Fischer + Daniel Rodríguez Troitiño Petr Viktorin David Sanders + Richo Healey Alex Chamberlain Devaev Maxim + Christian Boos Amit Bakshi Eric Davis + Julien Miotte Andrey Devyatkin Erik Meusel + Xu Tao Ben Davis Erik van Zijst + Jose Plana Eric Schrijver Ferengee + Martin Lenders Hervé Cauwelier Gustavo Di Pietro + Petr Hosek Huang Huang Hugh Cole-Baker + Victor Garcia Jared Flatow Josh Bleecher Snyder + Xavier Delannoy Jiunn Haur Lim Jun Omae + Yonggang Luo Sarath Lakshman Óscar San José + Valentin Haenel Vicent Marti Ridge Kennedy + Bernardo Heynemann Zoran Zaric Rui Abreu Ferreira + John Szakmeister Adam Spiers Thomas Kluyver + Brodie Rao Alexander Bayandin earl + David Versmisse Andrew Chin Changelog ============== -0.20.3 (2014-04-XX) +0.20.3 (2014-04-02) ------------------- - A number of memory issues fixed diff --git a/docs/conf.py b/docs/conf.py index d7f2799c3..c432057f7 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -50,7 +50,7 @@ # The short X.Y version. version = '0.20' # The full version, including alpha/beta/rc tags. -release = '0.20.2' +release = '0.20.3' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/pygit2/version.py b/pygit2/version.py index af8676feb..e68ec8dc5 100644 --- a/pygit2/version.py +++ b/pygit2/version.py @@ -23,4 +23,4 @@ # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. -__version__ = '0.20.2' +__version__ = '0.20.3' From c76c3f0195aa8c3437db959e0780157931f006a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Fri, 11 Apr 2014 23:54:31 +0200 Subject: [PATCH 0159/1630] Start implementing remotes with CFFI This moves enough code into python with CFFI to pass the test_remotes unit tests. There is no credentials support yet. There is a small change in the return value of Remote.fetch() in that we now return a TransferProgress object instead of extracting a few values into a dictionary. --- .gitignore | 1 + pygit2/__init__.py | 1 + pygit2/decl.h | 127 ++++++++++++++++++ pygit2/errors.py | 55 ++++++++ pygit2/ffi.py | 106 +++++++++++++++ pygit2/refspec.py | 110 ++++++++++++++++ pygit2/remote.py | 207 ++++++++++++++++++++++++++++++ pygit2/repository.py | 48 +++++++ src/refspec.c | 297 ------------------------------------------- src/repository.c | 77 ++--------- test/test_remote.py | 12 +- 11 files changed, 675 insertions(+), 366 deletions(-) create mode 100644 pygit2/decl.h create mode 100644 pygit2/errors.py create mode 100644 pygit2/ffi.py create mode 100644 pygit2/refspec.py create mode 100644 pygit2/remote.py delete mode 100644 src/refspec.c diff --git a/.gitignore b/.gitignore index d8022420b..79889fed3 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ pygit2/__pycache__ *.egg-info *.swp docs/_build +__pycache__ diff --git a/pygit2/__init__.py b/pygit2/__init__.py index fc2e69366..396af196a 100644 --- a/pygit2/__init__.py +++ b/pygit2/__init__.py @@ -37,6 +37,7 @@ from .version import __version__ from .settings import Settings from .credentials import * +from .remote import Remote def init_repository(path, bare=False): """ diff --git a/pygit2/decl.h b/pygit2/decl.h new file mode 100644 index 000000000..e0ad82622 --- /dev/null +++ b/pygit2/decl.h @@ -0,0 +1,127 @@ +typedef ... git_repository; +typedef ... git_remote; +typedef ... git_refspec; +typedef ... git_push; +typedef ... git_cred; +typedef ... git_oid; + +typedef struct git_strarray { + char **strings; + size_t count; +} git_strarray; + +typedef enum { + GIT_OK = 0, + GIT_ERROR = -1, + GIT_ENOTFOUND = -3, + GIT_EEXISTS = -4, + GIT_EAMBIGUOUS = -5, + GIT_EBUFS = -6, + GIT_EUSER = -7, + GIT_EBAREREPO = -8, + GIT_EUNBORNBRANCH = -9, + GIT_EUNMERGED = -10, + GIT_ENONFASTFORWARD = -11, + GIT_EINVALIDSPEC = -12, + GIT_EMERGECONFLICT = -13, + GIT_ELOCKED = -14, + + GIT_PASSTHROUGH = -30, + GIT_ITEROVER = -31, +} git_error_code; + +typedef struct { + char *message; + int klass; +} git_error; + +const git_error * giterr_last(void); + +void git_strarray_free(git_strarray *array); + +typedef struct git_transfer_progress { + unsigned int total_objects; + unsigned int indexed_objects; + unsigned int received_objects; + unsigned int local_objects; + unsigned int total_deltas; + unsigned int indexed_deltas; + size_t received_bytes; +} git_transfer_progress; + +typedef enum git_remote_completion_type { + GIT_REMOTE_COMPLETION_DOWNLOAD, + GIT_REMOTE_COMPLETION_INDEXING, + GIT_REMOTE_COMPLETION_ERROR, +} git_remote_completion_type; + +typedef enum { + GIT_DIRECTION_FETCH = 0, + GIT_DIRECTION_PUSH = 1 +} git_direction; + +typedef struct git_remote_callbacks { + unsigned int version; + int (*progress)(const char *str, int len, void *data); + int (*completion)(git_remote_completion_type type, void *data); + int (*credentials)(git_cred **cred, const char *url, const char *username_from_url, unsigned int allowed_types, void *data); + int (*transfer_progress)(const git_transfer_progress *stats, void *data); + int (*update_tips)(const char *refname, const git_oid *a, const git_oid *b, void *data); + void *payload; +} git_remote_callbacks ; + +int git_remote_list(git_strarray *out, git_repository *repo); +int git_remote_load(git_remote **out, git_repository *repo, const char *name); +int git_remote_create(git_remote **out, + git_repository *repo, + const char *name, + const char *url); +const char * git_remote_name(const git_remote *remote); +typedef int (*git_remote_rename_problem_cb)(const char *problematic_refspec, void *payload); +int git_remote_rename(git_remote *remote, + const char *new_name, + git_remote_rename_problem_cb callback, + void *payload); +const char * git_remote_url(const git_remote *remote); +int git_remote_set_url(git_remote *remote, const char* url); +const char * git_remote_pushurl(const git_remote *remote); +int git_remote_set_pushurl(git_remote *remote, const char* url); +int git_remote_fetch(git_remote *remote); +const git_transfer_progress * git_remote_stats(git_remote *remote); +int git_remote_add_push(git_remote *remote, const char *refspec); +int git_remote_add_fetch(git_remote *remote, const char *refspec); +int git_remote_save(const git_remote *remote); +int git_remote_set_callbacks(git_remote *remote, const git_remote_callbacks *callbacks); +size_t git_remote_refspec_count(git_remote *remote); +const git_refspec * git_remote_get_refspec(git_remote *remote, size_t n); + +int git_remote_get_fetch_refspecs(git_strarray *array, git_remote *remote); +int git_remote_set_fetch_refspecs(git_remote *remote, git_strarray *array); +int git_remote_get_push_refspecs(git_strarray *array, git_remote *remote); +int git_remote_set_push_refspecs(git_remote *remote, git_strarray *array); + +void git_remote_free(git_remote *remote); + +int git_push_new(git_push **push, git_remote *remote); +int git_push_add_refspec(git_push *push, const char *refspec); +int git_push_finish(git_push *push); +int git_push_unpack_ok(git_push *push); + +int git_push_status_foreach(git_push *push, + int (*cb)(const char *ref, const char *msg, void *data), + void *data); + +int git_push_update_tips(git_push *push); +void git_push_free(git_push *push); + +const char * git_refspec_src(const git_refspec *refspec); +const char * git_refspec_dst(const git_refspec *refspec); +int git_refspec_force(const git_refspec *refspec); +const char * git_refspec_string(const git_refspec *refspec); +git_direction git_refspec_direction(const git_refspec *spec); + +int git_refspec_src_matches(const git_refspec *refspec, const char *refname); +int git_refspec_dst_matches(const git_refspec *refspec, const char *refname); + +int git_refspec_transform(char *out, size_t outlen, const git_refspec *spec, const char *name); +int git_refspec_rtransform(char *out, size_t outlen, const git_refspec *spec, const char *name); diff --git a/pygit2/errors.py b/pygit2/errors.py new file mode 100644 index 000000000..b9f6c9bfa --- /dev/null +++ b/pygit2/errors.py @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- +# +# Copyright 2010-2014 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. + +# Import from the Standard Library +from string import hexdigits + +# ffi +from .ffi import ffi, C + +from _pygit2 import GitError + +def check_error(err): + if err >= 0: + return + + message = "(no message provided)" + giterr = C.giterr_last() + if giterr != ffi.NULL: + message = ffi.string(giterr.message).decode() + + if err in [C.GIT_EEXISTS, C.GIT_EINVALIDSPEC, C.GIT_EEXISTS, C.GIT_EAMBIGUOUS]: + raise ValueError(message) + elif err == C.GIT_ENOTFOUND: + raise KeyError(message) + elif err == C.GIT_EINVALIDSPEC: + raise ValueError(message) + elif err == C.GIT_ITEROVER: + raise StopIteration() + + raise GitError(message) + diff --git a/pygit2/ffi.py b/pygit2/ffi.py new file mode 100644 index 000000000..ce25ff0ea --- /dev/null +++ b/pygit2/ffi.py @@ -0,0 +1,106 @@ +# -*- coding: utf-8 -*- +# +# Copyright 2010-2014 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. + +# Import from the future +from __future__ import absolute_import + +import inspect +from os import path +from cffi import FFI +import sys + +if sys.version_info.major < 3: + def to_str(s, encoding='utf-8', errors='strict'): + if s == ffi.NULL: + return ffi.NULL + encoding = encoding or 'utf-8' + if isinstance(s, unicode): + return s.encode(encoding, errors) + + return s +else: + def to_str(s, encoding='utf-8', errors='strict'): + if isinstance(s, bytes): + return s + else: + return bytes(s, encoding, errors) + +if sys.version_info.major < 3: + def is_string(s): + return isinstance(s, str) +else: + def is_string(s): + return isinstance(s, basestring) + +ffi = FFI() + +def strarray_to_strings(arr): + l = [None] * arr.count + for i in range(arr.count): + l[i] = ffi.string(arr.strings[i]).decode() + + return l + +def strings_to_strarray(l): + """Convert a list of strings to a git_strarray + + We return first the git_strarray* you can pass to libgit2 and a + list of references to the memory, which we must keep around for as + long as the git_strarray must live. + """ + + if not isinstance(l, list): + raise TypeError("Value must be a list") + + arr = ffi.new('git_strarray *') + strings = ffi.new('char *[]', len(l)) + + # We need refs in order to keep a reference to the value returned + # by the ffi.new(). Otherwise, they will be freed and the memory + # re-used, with less than great consequences. + refs = [None] * len(l) + + for i in range(len(l)): + if not is_string(l[i]): + raise TypeError("Value must be a string") + + s = ffi.new('char []', l[i]) + refs[i] = s + strings[i] = s + + arr.strings = strings + arr.count = len(l) + + return arr, refs + +dir_path = path.dirname(path.abspath(inspect.getfile(inspect.currentframe()))) + +decl_path = path.join(dir_path, 'decl.h') +with open(decl_path, 'rb') as f: + ffi.cdef(f.read()) + +C = ffi.verify("#include ", libraries=["git2"]) diff --git a/pygit2/refspec.py b/pygit2/refspec.py new file mode 100644 index 000000000..00974495d --- /dev/null +++ b/pygit2/refspec.py @@ -0,0 +1,110 @@ +# -*- coding: utf-8 -*- +# +# Copyright 2010-2014 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. + +# Import from the future +from __future__ import absolute_import + +from .ffi import ffi, C, to_str +from .errors import check_error + +class Refspec(object): + def __init__(self, owner, ptr): + self._owner = owner + self._refspec = ptr + + @property + def src(self): + """Source or lhs of the refspec""" + return ffi.string(C.git_refspec_src(self._refspec)).decode() + + @property + def dst(self): + """Destinaton or rhs of the refspec""" + return ffi.string(C.git_refspec_dst(self._refspec)).decode() + + @property + def force(self): + """Whether this refspeca llows non-fast-forward updates""" + return bool(C.git_refspec_force(self._refspec)) + + @property + def string(self): + """String which was used to create this refspec""" + return ffi.string(C.git_refspec_string(self._refspec)).decode() + + @property + def direction(self): + """Direction of this refspec (fetch or push)""" + return C.git_refspec_direction(self._refspec) + + def src_matches(self, ref): + """src_matches(str) -> Bool + + Returns whether the given string matches the source of this refspec""" + return bool(C.git_refspec_src_matches(self._refspec, to_str(ref))) + + def dst_matches(self, ref): + """dst_matches(str) -> Bool + + Returns whether the given string matches the destination of this refspec""" + return bool(C.git_refspec_dst_matches(self._refspec, to_str(ref))) + + def transform(self, ref): + """transform(str) -> str + + Transform a reference name according to this refspec from the lhs to the rhs.""" + alen = len(ref) + err = C.GIT_EBUFS + ptr = None + ref_str = to_str(ref) + + while err == C.GIT_EBUFS: + alen *= 2 + ptr = ffi.new('char []', alen) + + err = C.git_refspec_transform(ptr, alen, self._refspec, ref_str) + + check_error(err) + return ffi.string(ptr).decode() + + def rtransform(self, ref): + """transform(str) -> str + + Transform a reference name according to this refspec from the lhs to the rhs""" + alen = len(ref) + err = C.GIT_EBUFS + ptr = None + ref_str = to_str(ref) + + while err == C.GIT_EBUFS: + alen *= 2 + ptr = ffi.new('char []', alen) + + err = C.git_refspec_rtransform(ptr, alen, self._refspec, ref_str) + + check_error(err) + return ffi.string(ptr).decode() diff --git a/pygit2/remote.py b/pygit2/remote.py new file mode 100644 index 000000000..28a4860b3 --- /dev/null +++ b/pygit2/remote.py @@ -0,0 +1,207 @@ +# -*- coding: utf-8 -*- +# +# Copyright 2010-2014 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. + +# Import from the future +from __future__ import absolute_import + +from .ffi import ffi, C, to_str, strarray_to_strings, strings_to_strarray +from .errors import check_error, GitError +from .refspec import Refspec + +def maybe_string(ptr): + if not ptr: + return None + + return ffi.string(ptr).decode() + + +class TransferProgress(object): + """Progress downloading and indexing data during a fetch""" + + def __init__(self, tp): + self.total_objects = tp.total_objects + self.indexed_objects = tp.indexed_objects + self.received_objects = tp.received_objects + self.local_objects = tp.local_objects + self.total_deltas = tp.total_deltas + self.indexed_deltas = tp.indexed_deltas + self.received_bytes = tp.received_bytes + +class Remote(object): + def __init__(self, repo, ptr): + """The constructor is for internal use only""" + + self._repo = repo + self._remote = ptr + + # Build the callback structure + callbacks = ffi.new('git_remote_callbacks *') + callbacks.version = 1 + callbacks.transfer_progress = self._transfer_progress_cb + # We need to make sure that this handle stays alive + self._self_handle = ffi.new_handle(self) + callbacks.payload = self._self_handle + + err = C.git_remote_set_callbacks(self._remote, callbacks) + check_error(err) + + def __del__(self): + C.git_remote_free(self._remote) + + @property + def name(self): + return maybe_string(C.git_remote_name(self._remote)) + + @name.setter + def name(self, value): + err = C.git_remote_rename(self._remote, to_str(value), ffi.NULL, ffi.NULL) + check_error(err) + + @property + def url(self): + return maybe_string(C.git_remote_url(self._remote)) + + @url.setter + def url(self, value): + err = C.git_remote_set_url(self._remote, to_str(value)) + + @property + def push_url(self): + return maybe_string(C.git_remote_pushurl(self._remote)) + + @push_url.setter + def push_url(self, value): + err = C.git_remote_set_pushurl(self._remote, to_str(value)) + check_error(err) + + def save(self): + err = C.git_remote_save(self._remote) + check_error(err) + + def fetch(self): + err = C.git_remote_fetch(self._remote) + if err == C.GIT_EUSER: + raise self._stored_exception + + check_error(err) + + return TransferProgress(C.git_remote_stats(self._remote)) + + @property + def refspec_count(self): + return C.git_remote_refspec_count(self._remote) + + def get_refspec(self, n): + spec = C.git_remote_get_refspec(self._remote, n) + return Refspec(self, spec) + + @property + def fetch_refspecs(self): + specs = ffi.new('git_strarray *') + err = C.git_remote_get_fetch_refspecs(specs, self._remote) + check_error(err) + + return strarray_to_strings(specs) + + @fetch_refspecs.setter + def fetch_refspecs(self, l): + arr, refs = strings_to_strarray(l) + err = C.git_remote_set_fetch_refspecs(self._remote, arr) + check_error(err) + + @property + def push_refspecs(self): + specs = ffi.new('git_strarray *') + err = C.git_remote_get_push_refspecs(specs, self._remote) + check_error(err) + + return strarray_to_strings(specs) + + @push_refspecs.setter + def push_refspecs(self, l): + arr, refs = strings_to_strarray(l) + err = C.git_remote_set_push_refspecs(self._remote, arr) + check_error(err) + + def add_fetch(self, spec): + err = C.git_remote_add_fetch(self._remote, to_str(spec)) + + def add_push(self, spec): + err = C.git_remote_add_push(self._remote, to_str(spec)) + + @ffi.callback("int (*cb)(const char *ref, const char *msg, void *data)") + def _push_cb(ref, msg, data): + self = ffi.from_handle(data) + if msg: + self._bad_message = ffi.string(msg).decode() + return 0 + + def push(self, spec): + cpush = ffi.new('git_push **') + err = C.git_push_new(cpush, self._remote) + check_error(err) + + push = cpush[0] + + try: + err = C.git_push_add_refspec(push, to_str(spec)) + check_error(err) + + err = C.git_push_finish(push) + check_error(err) + + if not C.git_push_unpack_ok(push): + raise GitError("remote failed to unpack objects") + + err = C.git_push_status_foreach(push, self._push_cb, ffi.new_handle(self)) + check_error(err) + + if hasattr(self, '_bad_message'): + raise GitError(self._bad_message) + + err = C.git_push_update_tips(push) + check_error(err) + + finally: + C.git_push_free(push) + + # These functions exist to be called by the git_remote as + # callbacks. They proxy the call to whatever the user set + + @ffi.callback('int (*transfer_progress)(const git_transfer_progress *stats, void *data)') + def _transfer_progress_cb(stats_ptr, data): + self = ffi.from_handle(data) + if not hasattr(self, 'transfer_progress'): + return 0 + + try: + self.transfer_progress(TransferProgress(stats_ptr)) + except Exception, e: + self._stored_exception = e + return C.GIT_EUSER + + return 0 diff --git a/pygit2/repository.py b/pygit2/repository.py index e6c750f31..a22c8204c 100644 --- a/pygit2/repository.py +++ b/pygit2/repository.py @@ -35,6 +35,9 @@ from _pygit2 import GIT_CHECKOUT_SAFE_CREATE, GIT_DIFF_NORMAL from _pygit2 import Reference, Tree, Commit, Blob +from .ffi import ffi, C, to_str +from .errors import check_error +from .remote import Remote class Repository(_Repository): @@ -59,6 +62,51 @@ def __contains__(self, key): def __repr__(self): return "pygit2.Repository(%r)" % self.path + + # + # Remotes + # + def create_remote(self, name, url): + """create_remote(name, url) -> Remote + + Creates a new remote. + """ + + repo_cptr = ffi.new('git_repository **') + repo_cptr[0] = ffi.cast('git_repository *', self._pointer) + cremote = ffi.new('git_remote **') + + repo = repo_cptr[0] + err = C.git_remote_create(cremote, repo, to_str(name), to_str(url)) + check_error(err) + + return Remote(repo, cremote[0]) + + @property + def remotes(self): + """Returns all configured remotes""" + + repo_cptr = ffi.new('git_repository **') + repo_cptr[0] = ffi.cast('git_repository *', self._pointer) + names = ffi.new('git_strarray *') + + repo = repo_cptr[0] + try: + err = C.git_remote_list(names, repo) + check_error(err) + + l = [None] * names.count + cremote = ffi.new('git_remote **') + for i in range(names.count): + err = C.git_remote_load(cremote, repo, names.strings[i]) + check_error(err) + + l[i] = Remote(repo, cremote[0]) + return l + finally: + C.git_strarray_free(names) + + # # References # diff --git a/src/refspec.c b/src/refspec.c deleted file mode 100644 index 3053fe60b..000000000 --- a/src/refspec.c +++ /dev/null @@ -1,297 +0,0 @@ -/* - * Copyright 2010-2014 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 -#include "error.h" -#include "types.h" -#include "utils.h" -#include "refspec.h" - - -extern PyTypeObject RefspecType; - -Refspec * -wrap_refspec(const Remote *owner, const git_refspec *refspec) -{ - Refspec *spec; - - spec = PyObject_New(Refspec, &RefspecType); - if (!spec) - return NULL; - - Py_INCREF(owner); - spec->owner = owner; - spec->refspec = refspec; - - return spec; -} - -PyDoc_STRVAR(Refspec_direction__doc__, - "The direction of this refspec (fetch or push)"); - -PyObject * -Refspec_direction__get__(Refspec *self) -{ - return Py_BuildValue("i", git_refspec_direction(self->refspec)); -} - -PyDoc_STRVAR(Refspec_src__doc__, "Source or lhs of the refspec"); - -PyObject * -Refspec_src__get__(Refspec *self) -{ - return to_unicode(git_refspec_src(self->refspec), NULL, NULL); -} - -PyDoc_STRVAR(Refspec_dst__doc__, "Destination or rhs of the refspec"); - -PyObject * -Refspec_dst__get__(Refspec *self) -{ - return to_unicode(git_refspec_dst(self->refspec), NULL, NULL); -} - -PyDoc_STRVAR(Refspec_string__doc__, "String used to create this refspec"); - -PyObject * -Refspec_string__get__(Refspec *self) -{ - return to_unicode(git_refspec_string(self->refspec), NULL, NULL); -} - -PyDoc_STRVAR(Refspec_force__doc__, - "Whether this refspec allows non-fast-forward updates"); - -PyObject * -Refspec_force__get__(Refspec *self) -{ - if (git_refspec_force(self->refspec)) - Py_RETURN_TRUE; - - Py_RETURN_FALSE; -} - -PyDoc_STRVAR(Refspec_src_matches__doc__, - "src_matches(str) -> Bool\n" - "\n" - "Returns whether the string matches the source refspec\n"); - -PyObject * -Refspec_src_matches(Refspec *self, PyObject *py_str) -{ - const char *str; - PyObject *tstr; - int res; - - str = py_str_borrow_c_str(&tstr, py_str, NULL); - if (!str) - return NULL; - - res = git_refspec_src_matches(self->refspec, str); - Py_DECREF(tstr); - - if (res) - Py_RETURN_TRUE; - - Py_RETURN_FALSE; -} - -PyDoc_STRVAR(Refspec_dst_matches__doc__, - "dst_matches(str) -> Bool\n" - "\n" - "Returns whether the string matches the destination refspec\n"); - -PyObject * -Refspec_dst_matches(Refspec *self, PyObject *py_str) -{ - const char *str; - PyObject *tstr; - int res; - - str = py_str_borrow_c_str(&tstr, py_str, NULL); - if (!str) - return NULL; - - res = git_refspec_dst_matches(self->refspec, str); - Py_DECREF(tstr); - - if (res) - Py_RETURN_TRUE; - - Py_RETURN_FALSE; -} - -PyDoc_STRVAR(Refspec_transform__doc__, - "transform(str) -> str\n" - "\n" - "Transform a reference according to the refspec\n"); - -PyObject * -Refspec_transform(Refspec *self, PyObject *py_str) -{ - const char *str; - char *trans; - int err, len, alen; - PyObject *py_trans, *tstr; - - str = py_str_borrow_c_str(&tstr, py_str, NULL); - alen = len = strlen(str); - - do { - alen *= alen; - trans = malloc(alen); - if (!trans) { - Py_DECREF(tstr); - return PyErr_NoMemory(); - } - - err = git_refspec_transform(trans, alen, self->refspec, str); - } while(err == GIT_EBUFS); - Py_DECREF(tstr); - - if (err < 0) { - free(trans); - Error_set(err); - return NULL; - } - - py_trans = to_unicode(trans, NULL, NULL); - - free(trans); - - return py_trans; -} - -PyDoc_STRVAR(Refspec_rtransform__doc__, - "rtransform(str) -> str\n" - "\n" - "Transform a reference according to the refspec in reverse\n"); - -PyObject * -Refspec_rtransform(Refspec *self, PyObject *py_str) -{ - const char *str; - char *trans; - int err, len, alen; - PyObject *py_trans, *tstr; - - str = py_str_borrow_c_str(&tstr, py_str, NULL); - alen = len = strlen(str); - - do { - alen *= alen; - trans = malloc(alen); - if (!trans) { - Py_DECREF(tstr); - return PyErr_NoMemory(); - } - - err = git_refspec_rtransform(trans, alen, self->refspec, str); - } while(err == GIT_EBUFS); - Py_DECREF(tstr); - - if (err < 0) { - free(trans); - Error_set(err); - return NULL; - } - - py_trans = to_unicode(trans, NULL, NULL); - - free(trans); - - return py_trans; -} - -PyMethodDef Refspec_methods[] = { - METHOD(Refspec, src_matches, METH_O), - METHOD(Refspec, dst_matches, METH_O), - METHOD(Refspec, transform, METH_O), - METHOD(Refspec, rtransform, METH_O), - {NULL} -}; - -PyGetSetDef Refspec_getseters[] = { - GETTER(Refspec, direction), - GETTER(Refspec, src), - GETTER(Refspec, dst), - GETTER(Refspec, string), - GETTER(Refspec, force), - {NULL} -}; - -static void -Refspec_dealloc(Refspec *self) -{ - Py_CLEAR(self->owner); - PyObject_Del(self); -} - -PyDoc_STRVAR(Refspec__doc__, "Refspec object."); - -PyTypeObject RefspecType = { - PyVarObject_HEAD_INIT(NULL, 0) - "_pygit2.Refspec", /* tp_name */ - sizeof(Refspec), /* tp_basicsize */ - 0, /* tp_itemsize */ - (destructor)Refspec_dealloc, /* 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 | Py_TPFLAGS_BASETYPE, /* tp_flags */ - Refspec__doc__, /* tp_doc */ - 0, /* tp_traverse */ - 0, /* tp_clear */ - 0, /* tp_richcompare */ - 0, /* tp_weaklistoffset */ - 0, /* tp_iter */ - 0, /* tp_iternext */ - Refspec_methods, /* tp_methods */ - 0, /* tp_members */ - Refspec_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/repository.c b/src/repository.c index bb2a11b96..14e07a187 100644 --- a/src/repository.c +++ b/src/repository.c @@ -1277,67 +1277,6 @@ Repository_TreeBuilder(Repository *self, PyObject *args) return (PyObject*)builder; } - -PyDoc_STRVAR(Repository_create_remote__doc__, - "create_remote(name, url) -> Remote\n" - "\n" - "Creates a new remote."); - -PyObject * -Repository_create_remote(Repository *self, PyObject *args) -{ - git_remote *remote; - char *name = NULL, *url = NULL; - int err; - - if (!PyArg_ParseTuple(args, "ss", &name, &url)) - return NULL; - - err = git_remote_create(&remote, self->repo, name, url); - if (err < 0) - return Error_set(err); - - return (PyObject*) wrap_remote(remote, self); -} - - -PyDoc_STRVAR(Repository_remotes__doc__, "Returns all configured remotes."); - -PyObject * -Repository_remotes__get__(Repository *self) -{ - git_strarray remotes; - git_remote *remote = NULL; - PyObject *py_list = NULL; - PyObject *py_remote = NULL; - size_t i; - int err; - - git_remote_list(&remotes, self->repo); - - py_list = PyList_New(remotes.count); - for (i=0; i < remotes.count; ++i) { - err = git_remote_load(&remote, self->repo, remotes.strings[i]); - if (err < 0) - goto cleanup; - py_remote = wrap_remote(remote, self); - if (py_remote == NULL) - goto cleanup; - PyList_SetItem(py_list, i, py_remote); - } - - git_strarray_free(&remotes); - return (PyObject*) py_list; - -cleanup: - git_strarray_free(&remotes); - if (py_list) - Py_DECREF(py_list); - if (err < 0) - return Error_set(err); - return NULL; -} - PyDoc_STRVAR(Repository_default_signature__doc__, "Return the signature according to the repository's configuration"); PyObject * @@ -1352,6 +1291,19 @@ Repository_default_signature__get__(Repository *self) return build_signature(NULL, sig, "utf-8"); } +PyDoc_STRVAR(Repository__pointer__doc__, "Get the repo's pointer. For internal use only."); +PyObject * +Repository__pointer__get__(Repository *self) +{ + /* + * This is pretty bad. We shouldn't be casting a pointer into an + * integer, but we can't access the contents of a PyCapsule from + * python code, which we need to do in order to get a type that + * cffi likes. + */ + return PyLong_FromLongLong((long long) self->repo); +} + PyDoc_STRVAR(Repository_checkout_head__doc__, "checkout_head(strategy)\n" "\n" @@ -1633,7 +1585,6 @@ PyMethodDef Repository_methods[] = { METHOD(Repository, revparse_single, METH_O), METHOD(Repository, status, METH_NOARGS), METHOD(Repository, status_file, METH_O), - METHOD(Repository, create_remote, METH_VARARGS), METHOD(Repository, checkout_head, METH_VARARGS), METHOD(Repository, checkout_index, METH_VARARGS), METHOD(Repository, checkout_tree, METH_VARARGS), @@ -1659,8 +1610,8 @@ PyGetSetDef Repository_getseters[] = { GETTER(Repository, is_bare), GETTER(Repository, config), GETTER(Repository, workdir), - GETTER(Repository, remotes), GETTER(Repository, default_signature), + GETTER(Repository, _pointer), {NULL} }; diff --git a/test/test_remote.py b/test/test_remote.py index 797474c62..54ba27c9e 100644 --- a/test/test_remote.py +++ b/test/test_remote.py @@ -189,9 +189,9 @@ class EmptyRepositoryTest(utils.EmptyRepoTestCase): def test_fetch(self): remote = self.repo.remotes[0] stats = remote.fetch() - self.assertEqual(stats['received_bytes'], REMOTE_REPO_BYTES) - self.assertEqual(stats['indexed_objects'], REMOTE_REPO_OBJECTS) - self.assertEqual(stats['received_objects'], REMOTE_REPO_OBJECTS) + self.assertEqual(stats.received_bytes, REMOTE_REPO_BYTES) + self.assertEqual(stats.indexed_objects, REMOTE_REPO_OBJECTS) + self.assertEqual(stats.received_objects, REMOTE_REPO_OBJECTS) def test_transfer_progress(self): self.tp = None @@ -201,9 +201,9 @@ def tp_cb(stats): remote = self.repo.remotes[0] remote.transfer_progress = tp_cb stats = remote.fetch() - self.assertEqual(stats['received_bytes'], self.tp.received_bytes) - self.assertEqual(stats['indexed_objects'], self.tp.indexed_objects) - self.assertEqual(stats['received_objects'], self.tp.received_objects) + self.assertEqual(stats.received_bytes, self.tp.received_bytes) + self.assertEqual(stats.indexed_objects, self.tp.indexed_objects) + self.assertEqual(stats.received_objects, self.tp.received_objects) def test_update_tips(self): remote = self.repo.remotes[0] From 4ef3be18cce6b0645a4bb8d00c4976f00c6f3af3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sat, 12 Apr 2014 17:13:15 +0200 Subject: [PATCH 0160/1630] Remote: support credentials via CFFI --- pygit2/decl.h | 18 ++++++++++++++++++ pygit2/remote.py | 48 +++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 65 insertions(+), 1 deletion(-) diff --git a/pygit2/decl.h b/pygit2/decl.h index e0ad82622..9c4e05270 100644 --- a/pygit2/decl.h +++ b/pygit2/decl.h @@ -60,6 +60,13 @@ typedef enum { GIT_DIRECTION_PUSH = 1 } git_direction; +typedef enum { + GIT_CREDTYPE_USERPASS_PLAINTEXT = ..., + GIT_CREDTYPE_SSH_KEY = ..., + GIT_CREDTYPE_SSH_CUSTOM = ..., + GIT_CREDTYPE_DEFAULT = ..., +} git_credtype_t; + typedef struct git_remote_callbacks { unsigned int version; int (*progress)(const char *str, int len, void *data); @@ -125,3 +132,14 @@ int git_refspec_dst_matches(const git_refspec *refspec, const char *refname); int git_refspec_transform(char *out, size_t outlen, const git_refspec *spec, const char *name); int git_refspec_rtransform(char *out, size_t outlen, const git_refspec *spec, const char *name); + +int git_cred_userpass_plaintext_new( + git_cred **out, + const char *username, + const char *password); +int git_cred_ssh_key_new( + git_cred **out, + const char *username, + const char *publickey, + const char *privatekey, + const char *passphrase); diff --git a/pygit2/remote.py b/pygit2/remote.py index 28a4860b3..eca3dfc1c 100644 --- a/pygit2/remote.py +++ b/pygit2/remote.py @@ -57,11 +57,13 @@ def __init__(self, repo, ptr): self._repo = repo self._remote = ptr + self._stored_exception = None # Build the callback structure callbacks = ffi.new('git_remote_callbacks *') callbacks.version = 1 callbacks.transfer_progress = self._transfer_progress_cb + callbacks.credentials = self._credentials_cb # We need to make sure that this handle stays alive self._self_handle = ffi.new_handle(self) callbacks.payload = self._self_handle @@ -103,8 +105,9 @@ def save(self): check_error(err) def fetch(self): + self._stored_exception = None err = C.git_remote_fetch(self._remote) - if err == C.GIT_EUSER: + if self._stored_exception: raise self._stored_exception check_error(err) @@ -205,3 +208,46 @@ def _transfer_progress_cb(stats_ptr, data): return C.GIT_EUSER return 0 + + @ffi.callback('int (*credentials)(git_cred **cred, const char *url, const char *username_from_url, unsigned int allowed_types, void *data)') + def _credentials_cb(cred_out, url, username, allowed, data): + self = ffi.from_handle(data) + + if not hasattr(self, 'credentials'): + return 0 + + try: + url_str = maybe_string(url) + username_str = maybe_string(username) + + creds = self.credentials(url_str, username_str, allowed) + + if not hasattr(creds, 'credential_type') or not hasattr(creds, 'credential_tuple'): + raise TypeError("credential does not implement interface") + + cred_type = creds.credential_type + + if not (allowed & cred_type): + raise TypeError("invalid credential type") + + ccred = ffi.new('git_cred **') + if cred_type == C.GIT_CREDTYPE_USERPASS_PLAINTEXT: + name, passwd = creds.credential_tuple + err = C.git_cred_userpass_plaintext_new(ccred, to_str(name), to_str(passwd)) + + elif cred_type == C.GIT_CREDTYPE_SSH_KEY: + name, pubkey, privkey, passphrase = creds.credential_tuple + err = C.git_cred_ssh_key_new(ccred, to_str(name),to_str(pubkey), + to_str(privkey), to_str(passphrase)) + + else: + raise TypeError("unsupported credential type") + + check_error(err) + cred_out[0] = ccred[0] + + except Exception, e: + self._stored_exception = e + return C.GIT_EUSER + + return 0 From 2d1615dd29f496588fb894418d8a0ea3fd1cadb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sat, 12 Apr 2014 17:33:15 +0200 Subject: [PATCH 0161/1630] Remote: add support for transfer and update_tips though CFFI --- pygit2/decl.h | 7 ++++++- pygit2/remote.py | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/pygit2/decl.h b/pygit2/decl.h index 9c4e05270..40680ce6c 100644 --- a/pygit2/decl.h +++ b/pygit2/decl.h @@ -3,7 +3,12 @@ typedef ... git_remote; typedef ... git_refspec; typedef ... git_push; typedef ... git_cred; -typedef ... git_oid; + +#define GIT_OID_RAWSZ ... + +typedef struct git_oid { + unsigned char id[20]; +} git_oid; typedef struct git_strarray { char **strings; diff --git a/pygit2/remote.py b/pygit2/remote.py index eca3dfc1c..d68c884da 100644 --- a/pygit2/remote.py +++ b/pygit2/remote.py @@ -28,6 +28,8 @@ # Import from the future from __future__ import absolute_import +from _pygit2 import Oid + from .ffi import ffi, C, to_str, strarray_to_strings, strings_to_strarray from .errors import check_error, GitError from .refspec import Refspec @@ -62,7 +64,9 @@ def __init__(self, repo, ptr): # Build the callback structure callbacks = ffi.new('git_remote_callbacks *') callbacks.version = 1 + callbacks.progress = self._progress_cb callbacks.transfer_progress = self._transfer_progress_cb + callbacks.update_tips = self._update_tips_cb callbacks.credentials = self._credentials_cb # We need to make sure that this handle stays alive self._self_handle = ffi.new_handle(self) @@ -209,6 +213,41 @@ def _transfer_progress_cb(stats_ptr, data): return 0 + @ffi.callback('int (*progress)(const char *str, int len, void *data)') + def _progress_cb(string, length, data): + self = ffi.from_handle(data) + + if not hasattr(self, 'progress'): + return 0 + + try: + s = ffi.string(string, length).decode() + self.progress(s) + except Exception, e: + self._stored_exception = e + return C.GIT_EUSER + + return 0 + + @ffi.callback('int (*update_tips)(const char *refname, const git_oid *a, const git_oid *b, void *data)') + def _update_tips_cb(refname, a, b, data): + self = ffi.from_handle(data) + + if not hasattr(self, 'update_tips'): + return 0 + + try: + s = maybe_string(refname) + a = Oid(raw=bytes(ffi.buffer(a))) + b = Oid(raw=bytes(ffi.buffer(b))) + + self.update_tips(s, a, b) + except Exception, e: + self._stored_exception = e + return C.GIT_EUSER + + return 0 + @ffi.callback('int (*credentials)(git_cred **cred, const char *url, const char *username_from_url, unsigned int allowed_types, void *data)') def _credentials_cb(cred_out, url, username, allowed, data): self = ffi.from_handle(data) From cf2703998e42c36c65dc2078377b693a8b41cd01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sat, 12 Apr 2014 18:18:50 +0200 Subject: [PATCH 0162/1630] Remote: add documentation strings Now that it has the features of the old implementation, let's add documentation on how to use it. --- docs/remotes.rst | 6 +-- pygit2/remote.py | 104 +++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 103 insertions(+), 7 deletions(-) diff --git a/docs/remotes.rst b/docs/remotes.rst index 58ab8ef5d..445bef234 100644 --- a/docs/remotes.rst +++ b/docs/remotes.rst @@ -16,9 +16,9 @@ The Remote type .. autoattribute:: pygit2.Remote.refspec_count .. autoattribute:: pygit2.Remote.push_refspecs .. autoattribute:: pygit2.Remote.fetch_refspecs -.. autoattribute:: pygit2.Remote.progress -.. autoattribute:: pygit2.Remote.transfer_progress -.. autoattribute:: pygit2.Remote.update_tips +.. automethod:: pygit2.Remote.progress +.. automethod:: pygit2.Remote.transfer_progress +.. automethod:: pygit2.Remote.update_tips .. automethod:: pygit2.Remote.get_refspec .. automethod:: pygit2.Remote.fetch .. automethod:: pygit2.Remote.push diff --git a/pygit2/remote.py b/pygit2/remote.py index d68c884da..c7f122ca1 100644 --- a/pygit2/remote.py +++ b/pygit2/remote.py @@ -45,15 +45,73 @@ class TransferProgress(object): """Progress downloading and indexing data during a fetch""" def __init__(self, tp): + self.total_objects = tp.total_objects + """Total number objects to download""" + self.indexed_objects = tp.indexed_objects + """Objects which have been indexed""" + self.received_objects = tp.received_objects + """Objects which have been received up to now""" + self.local_objects = tp.local_objects + """Local objects which were used to fix the thin pack""" + self.total_deltas = tp.total_deltas + """Total number of deltas in the pack""" + self.indexed_deltas = tp.indexed_deltas + """Deltas which have been indexed""" + self.received_bytes = tp.received_bytes + """"Number of bytes received up to now""" class Remote(object): + + def progress(self, string): + """Progress output callback + + Override this function with your own progress reporting function + + :param str string: Progress otuput from the remote + """ + pass + + def credentials(self, url, username_from_url, allowed_types): + """Credentials callback + + If the remote server requires authentication, this function will + be called and its return value used for authentication. Override + it if you want to be able to perform authentication. + + :param str url: The url of the remote + :param username_from_url: Username extracted from the url, if any + :type username_from_url: str or None + :param int allowed_types: credential types supported by the remote + :rtype: credential + """ + pass + + def transfer_progress(self, stats): + """Transfer progress callback + + Override with your own function to report transfer progress. + + :param TransferProgress stats: The progress up to now + """ + pass + + def update_tips(self, refname, old, new): + """Update tips callabck + + Override with your own function to report reference updates + + :param str refname: the name of the reference that's being updated + :param Oid old: the reference's old value + :param Oid new: the reference's new value + """ + def __init__(self, repo, ptr): """The constructor is for internal use only""" @@ -80,6 +138,8 @@ def __del__(self): @property def name(self): + """Name of the remote""" + return maybe_string(C.git_remote_name(self._remote)) @name.setter @@ -89,6 +149,8 @@ def name(self, value): @property def url(self): + """Url of the remote""" + return maybe_string(C.git_remote_url(self._remote)) @url.setter @@ -97,6 +159,8 @@ def url(self, value): @property def push_url(self): + """Push url of the remote""" + return maybe_string(C.git_remote_pushurl(self._remote)) @push_url.setter @@ -105,10 +169,19 @@ def push_url(self, value): check_error(err) def save(self): + """save() + + Save a remote to its repository's configuration""" + err = C.git_remote_save(self._remote) check_error(err) def fetch(self): + """fetch() -> TransferProgress + + Perform a fetch against this remote. + """ + self._stored_exception = None err = C.git_remote_fetch(self._remote) if self._stored_exception: @@ -120,14 +193,22 @@ def fetch(self): @property def refspec_count(self): + """Total number of refspecs in this remote""" + return C.git_remote_refspec_count(self._remote) def get_refspec(self, n): + """get_refspec(n) -> Refspec + + Return the refspec at the given position + """ spec = C.git_remote_get_refspec(self._remote, n) return Refspec(self, spec) @property def fetch_refspecs(self): + """Refspecs that will be used for fetching""" + specs = ffi.new('git_strarray *') err = C.git_remote_get_fetch_refspecs(specs, self._remote) check_error(err) @@ -142,6 +223,8 @@ def fetch_refspecs(self, l): @property def push_refspecs(self): + """Refspecs that will be used for pushing""" + specs = ffi.new('git_strarray *') err = C.git_remote_get_push_refspecs(specs, self._remote) check_error(err) @@ -155,9 +238,17 @@ def push_refspecs(self, l): check_error(err) def add_fetch(self, spec): + """add_fetch(refspec) + + Add a fetch refspec to the remote""" + err = C.git_remote_add_fetch(self._remote, to_str(spec)) def add_push(self, spec): + """add_push(refspec) + + Add a push refspec to the remote""" + err = C.git_remote_add_push(self._remote, to_str(spec)) @ffi.callback("int (*cb)(const char *ref, const char *msg, void *data)") @@ -168,6 +259,10 @@ def _push_cb(ref, msg, data): return 0 def push(self, spec): + """push(refspec) + + Push the given refspec to the remote. Raises ``GitError`` on error""" + cpush = ffi.new('git_push **') err = C.git_push_new(cpush, self._remote) check_error(err) @@ -202,7 +297,8 @@ def push(self, spec): @ffi.callback('int (*transfer_progress)(const git_transfer_progress *stats, void *data)') def _transfer_progress_cb(stats_ptr, data): self = ffi.from_handle(data) - if not hasattr(self, 'transfer_progress'): + + if not hasattr(self, 'transfer_progress') or not self.transfer_progress: return 0 try: @@ -217,7 +313,7 @@ def _transfer_progress_cb(stats_ptr, data): def _progress_cb(string, length, data): self = ffi.from_handle(data) - if not hasattr(self, 'progress'): + if not hasattr(self, 'progress') or not self.progress: return 0 try: @@ -233,7 +329,7 @@ def _progress_cb(string, length, data): def _update_tips_cb(refname, a, b, data): self = ffi.from_handle(data) - if not hasattr(self, 'update_tips'): + if not hasattr(self, 'update_tips') or not self.update_tips: return 0 try: @@ -252,7 +348,7 @@ def _update_tips_cb(refname, a, b, data): def _credentials_cb(cred_out, url, username, allowed, data): self = ffi.from_handle(data) - if not hasattr(self, 'credentials'): + if not hasattr(self, 'credentials') or not self.credentials: return 0 try: From db218fae3f262fdb4367434bd7e02ecb30e77239 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sat, 12 Apr 2014 18:25:40 +0200 Subject: [PATCH 0163/1630] Remote: get rid of the C code This code has been obsoleted by the CFFI-using code. Some credentials code remains in C due to the clone functionality making use of it. --- pygit2/credentials.py | 6 +- src/pygit2.c | 15 - src/remote.c | 728 ------------------------------------------ src/remote.h | 39 --- src/repository.c | 2 - src/types.h | 32 -- 6 files changed, 4 insertions(+), 818 deletions(-) delete mode 100644 src/remote.c delete mode 100644 src/remote.h diff --git a/pygit2/credentials.py b/pygit2/credentials.py index cad215a57..9f060e2df 100644 --- a/pygit2/credentials.py +++ b/pygit2/credentials.py @@ -25,8 +25,10 @@ # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. -# Import from pygit2 -from _pygit2 import GIT_CREDTYPE_USERPASS_PLAINTEXT, GIT_CREDTYPE_SSH_KEY +from .ffi import ffi, C + +GIT_CREDTYPE_USERPASS_PLAINTEXT = C.GIT_CREDTYPE_USERPASS_PLAINTEXT +GIT_CREDTYPE_SSH_KEY = C.GIT_CREDTYPE_SSH_KEY class UserPass(object): """Username/Password credentials diff --git a/src/pygit2.c b/src/pygit2.c index bda9545ec..06e4f17e1 100644 --- a/src/pygit2.c +++ b/src/pygit2.c @@ -75,7 +75,6 @@ extern PyTypeObject BranchType; extern PyTypeObject SignatureType; extern PyTypeObject RemoteType; extern PyTypeObject RefspecType; -extern PyTypeObject TransferProgressType; extern PyTypeObject NoteType; extern PyTypeObject NoteIterType; extern PyTypeObject BlameType; @@ -456,20 +455,6 @@ moduleinit(PyObject* m) ADD_TYPE(m, Config) ADD_TYPE(m, ConfigIter) - /* Remotes */ - INIT_TYPE(RemoteType, NULL, NULL) - INIT_TYPE(RefspecType, NULL, NULL) - INIT_TYPE(TransferProgressType, NULL, NULL) - ADD_TYPE(m, Remote) - ADD_TYPE(m, Refspec) - ADD_TYPE(m, TransferProgress) - /* Direction for the refspec */ - ADD_CONSTANT_INT(m, GIT_DIRECTION_FETCH) - ADD_CONSTANT_INT(m, GIT_DIRECTION_PUSH) - /* Credential types */ - ADD_CONSTANT_INT(m, GIT_CREDTYPE_USERPASS_PLAINTEXT) - ADD_CONSTANT_INT(m, GIT_CREDTYPE_SSH_KEY) - /* Blame */ INIT_TYPE(BlameType, NULL, NULL) INIT_TYPE(BlameIterType, NULL, NULL) diff --git a/src/remote.c b/src/remote.c deleted file mode 100644 index dee9b5a76..000000000 --- a/src/remote.c +++ /dev/null @@ -1,728 +0,0 @@ -/* - * Copyright 2010-2014 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 -#include "error.h" -#include "types.h" -#include "utils.h" -#include "oid.h" -#include "refspec.h" -#include "remote.h" - - -extern PyObject *GitError; -extern PyTypeObject RepositoryType; -extern PyTypeObject TransferProgressType; - -PyObject * -wrap_transfer_progress(const git_transfer_progress *stats) -{ - TransferProgress *py_stats; - - py_stats = PyObject_New(TransferProgress, &TransferProgressType); - if (!py_stats) - return NULL; - - py_stats->total_objects = stats->total_objects; - py_stats->indexed_objects = stats->indexed_objects; - py_stats->received_objects = stats->received_objects; - py_stats->local_objects = stats->local_objects; - py_stats->total_deltas = stats->total_deltas; - py_stats->indexed_deltas = stats->indexed_deltas; - py_stats->received_bytes = stats->received_bytes; - - return (PyObject *) py_stats; -} - -void -TransferProgress_dealloc(TransferProgress *self) -{ - PyObject_Del(self); -} - -PyMemberDef TransferProgress_members[] = { - RMEMBER(TransferProgress, total_objects, T_UINT, - "Total number objects to download"), - RMEMBER(TransferProgress, indexed_objects, T_UINT, - "Objects which have been indexed"), - RMEMBER(TransferProgress, received_objects, T_UINT, - "Objects which have been received up to now"), - RMEMBER(TransferProgress, local_objects, T_UINT, - "Local objects which were used to fix the thin pack"), - RMEMBER(TransferProgress, total_deltas, T_UINT, - "Total number of deltas in the pack"), - RMEMBER(TransferProgress, indexed_deltas, T_UINT, - "Deltas which have been indexed"), - /* FIXME: technically this is unsigned, but there's no value for size_t - * here. */ - RMEMBER(TransferProgress, received_bytes, T_PYSSIZET, - "Number of bytes received up to now"), - {NULL}, -}; - -PyDoc_STRVAR(TransferProgress__doc__, - "Progress downloading and indexing data during a fetch"); - -PyTypeObject TransferProgressType = { - PyVarObject_HEAD_INIT(NULL, 0) - "_pygit2.TransferProgress", /* tp_name */ - sizeof(TransferProgress), /* tp_basicsize */ - 0, /* tp_itemsize */ - (destructor)TransferProgress_dealloc, /* 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 */ - TransferProgress__doc__, /* tp_doc */ - 0, /* tp_traverse */ - 0, /* tp_clear */ - 0, /* tp_richcompare */ - 0, /* tp_weaklistoffset */ - 0, /* tp_iter */ - 0, /* tp_iternext */ - 0, /* tp_methods */ - TransferProgress_members, /* tp_members */ - 0, /* 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 */ -}; - -static int -progress_cb(const char *str, int len, void *data) -{ - Remote *remote = (Remote *) data; - PyObject *arglist, *ret; - - if (remote->progress == NULL) - return 0; - - if (!PyCallable_Check(remote->progress)) { - PyErr_SetString(PyExc_TypeError, "progress callback is not callable"); - return -1; - } - - arglist = Py_BuildValue("(s#)", str, len); - ret = PyObject_CallObject(remote->progress, arglist); - Py_DECREF(arglist); - - if (!ret) - return -1; - - Py_DECREF(ret); - - return 0; -} - -static int -credentials_cb(git_cred **out, const char *url, const char *username_from_url, unsigned int allowed_types, void *data) -{ - Remote *remote = (Remote *) data; - - return callable_to_credentials(out, url, username_from_url, allowed_types, remote->credentials); -} - -static int -transfer_progress_cb(const git_transfer_progress *stats, void *data) -{ - Remote *remote = (Remote *) data; - PyObject *py_stats, *ret; - - if (remote->transfer_progress == NULL) - return 0; - - if (!PyCallable_Check(remote->transfer_progress)) { - PyErr_SetString(PyExc_TypeError, "transfer progress callback is not callable"); - return -1; - } - - py_stats = wrap_transfer_progress(stats); - if (!py_stats) - return -1; - - ret = PyObject_CallFunctionObjArgs(remote->transfer_progress, py_stats, NULL); - Py_DECREF(py_stats); - if (!ret) - return -1; - - Py_DECREF(ret); - - return 0; -} - -static int -update_tips_cb(const char *refname, const git_oid *a, const git_oid *b, void *data) -{ - Remote *remote = (Remote *) data; - PyObject *ret; - PyObject *old, *new; - - if (remote->update_tips == NULL) - return 0; - - if (!PyCallable_Check(remote->update_tips)) { - PyErr_SetString(PyExc_TypeError, "update tips callback is not callable"); - return -1; - } - - old = git_oid_to_python(a); - new = git_oid_to_python(b); - - ret = PyObject_CallFunction(remote->update_tips, "(s,O,O)", refname, old ,new); - - Py_DECREF(old); - Py_DECREF(new); - - if (!ret) - return -1; - - Py_DECREF(ret); - - return 0; -} - -static void -Remote_dealloc(Remote *self) -{ - Py_CLEAR(self->repo); - Py_CLEAR(self->progress); - git_remote_free(self->remote); - PyObject_Del(self); -} - -PyDoc_STRVAR(Remote_name__doc__, "Name of the remote refspec"); - -PyObject * -Remote_name__get__(Remote *self) -{ - return to_unicode(git_remote_name(self->remote), NULL, NULL); -} - -int -Remote_name__set__(Remote *self, PyObject* py_name) -{ - int err; - const char* name; - PyObject *tname; - - name = py_str_borrow_c_str(&tname, py_name, NULL); - if (name != NULL) { - err = git_remote_rename(self->remote, name, NULL, NULL); - Py_DECREF(tname); - - if (err == GIT_OK) - return 0; - - Error_set(err); - } - - return -1; -} - -PyDoc_STRVAR(Remote_fetch_refspecs__doc__, "Fetch refspecs"); - -PyObject * -Remote_fetch_refspecs__get__(Remote *self) -{ - int err; - git_strarray refspecs; - PyObject *new_list; - - err = git_remote_get_fetch_refspecs(&refspecs, self->remote); - if (err != GIT_OK) - return Error_set(err); - - new_list = get_pylist_from_git_strarray(&refspecs); - - git_strarray_free(&refspecs); - return new_list; -} - -int -Remote_fetch_refspecs__set__(Remote *self, PyObject *py_list) -{ - int err; - git_strarray fetch_refspecs; - - if (get_strarraygit_from_pylist(&fetch_refspecs, py_list) < 0) - return -1; - - err = git_remote_set_fetch_refspecs(self->remote, &fetch_refspecs); - git_strarray_free(&fetch_refspecs); - - if (err < 0) { - Error_set(err); - return -1; - } - - return 0; -} - -PyDoc_STRVAR(Remote_push_refspecs__doc__, "Push refspecs"); - -PyObject * -Remote_push_refspecs__get__(Remote *self) -{ - int err; - git_strarray refspecs; - PyObject *new_list; - - err = git_remote_get_push_refspecs(&refspecs, self->remote); - if (err != GIT_OK) - return Error_set(err); - - new_list = get_pylist_from_git_strarray(&refspecs); - - git_strarray_free(&refspecs); - return new_list; -} - -int -Remote_push_refspecs__set__(Remote *self, PyObject *py_list) -{ - int err; - git_strarray push_refspecs; - - if (get_strarraygit_from_pylist(&push_refspecs, py_list) != 0) - return -1; - - err = git_remote_set_push_refspecs(self->remote, &push_refspecs); - git_strarray_free(&push_refspecs); - - if (err < 0) { - Error_set(err); - return -1; - } - - return 0; -} - - -PyDoc_STRVAR(Remote_url__doc__, "Url of the remote"); - - -PyObject * -Remote_url__get__(Remote *self) -{ - const char *url; - - url = git_remote_url(self->remote); - if (!url) - Py_RETURN_NONE; - - return to_unicode(url, NULL, NULL); -} - - -int -Remote_url__set__(Remote *self, PyObject* py_url) -{ - int err; - const char* url = NULL; - PyObject *turl; - - url = py_str_borrow_c_str(&turl, py_url, NULL); - if (url != NULL) { - err = git_remote_set_url(self->remote, url); - Py_DECREF(turl); - - if (err == GIT_OK) - return 0; - - Error_set(err); - } - - return -1; -} - -PyDoc_STRVAR(Remote_push_url__doc__, "Push url of the remote"); - - -PyObject * -Remote_push_url__get__(Remote *self) -{ - const char *url; - - url = git_remote_pushurl(self->remote); - if (!url) - Py_RETURN_NONE; - - return to_unicode(url, NULL, NULL); -} - - -int -Remote_push_url__set__(Remote *self, PyObject* py_url) -{ - int err; - const char* url = NULL; - PyObject *turl; - - url = py_str_borrow_c_str(&turl, py_url, NULL); - if (url != NULL) { - err = git_remote_set_pushurl(self->remote, url); - Py_DECREF(turl); - - if (err == GIT_OK) - return 0; - - Error_set(err); - } - - return -1; -} - - -PyDoc_STRVAR(Remote_refspec_count__doc__, "Number of refspecs."); - -PyObject * -Remote_refspec_count__get__(Remote *self) -{ - size_t count; - - count = git_remote_refspec_count(self->remote); - return PyLong_FromSize_t(count); -} - - -PyDoc_STRVAR(Remote_get_refspec__doc__, - "get_refspec(n) -> (str, str)\n" - "\n" - "Return the refspec at the given position."); - -PyObject * -Remote_get_refspec(Remote *self, PyObject *value) -{ - size_t n; - const git_refspec *refspec; - - n = PyLong_AsSize_t(value); - if (PyErr_Occurred()) - return NULL; - - refspec = git_remote_get_refspec(self->remote, n); - if (refspec == NULL) { - PyErr_SetObject(PyExc_IndexError, value); - return NULL; - } - - return (PyObject*) wrap_refspec(self, refspec); -} - - -PyDoc_STRVAR(Remote_fetch__doc__, - "fetch() -> {'indexed_objects': int, 'received_objects' : int," - " 'received_bytesa' : int}\n" - "\n" - "Negotiate what objects should be downloaded and download the\n" - "packfile with those objects"); - -PyObject * -Remote_fetch(Remote *self, PyObject *args) -{ - PyObject* py_stats = NULL; - const git_transfer_progress *stats; - int err; - - PyErr_Clear(); - err = git_remote_fetch(self->remote); - /* - * XXX: We should be checking for GIT_EUSER, but on v0.20, this does not - * make it all the way to us for update_tips - */ - if (err < 0 && PyErr_Occurred()) - return NULL; - if (err < 0) - return Error_set(err); - - stats = git_remote_stats(self->remote); - py_stats = Py_BuildValue("{s:I,s:I,s:n}", - "indexed_objects", stats->indexed_objects, - "received_objects", stats->received_objects, - "received_bytes", stats->received_bytes); - - return (PyObject*) py_stats; -} - - -PyDoc_STRVAR(Remote_save__doc__, - "save()\n\n" - "Save a remote to its repository configuration."); - -PyObject * -Remote_save(Remote *self, PyObject *args) -{ - int err; - - err = git_remote_save(self->remote); - if (err == GIT_OK) { - Py_RETURN_NONE; - } - else { - return Error_set(err); - } -} - - -int -push_status_foreach_callback(const char *ref, const char *msg, void *data) -{ - const char **msg_dst = (const char **)data; - if (msg != NULL && *msg_dst == NULL) - *msg_dst = msg; - return 0; -} - -PyDoc_STRVAR(Remote_push__doc__, - "push(refspec)\n" - "\n" - "Push the given refspec to the remote. Raises ``GitError`` on error."); - -PyObject * -Remote_push(Remote *self, PyObject *args) -{ - git_push *push = NULL; - const char *refspec = NULL; - const char *msg = NULL; - int err; - - if (!PyArg_ParseTuple(args, "s", &refspec)) - return NULL; - - err = git_push_new(&push, self->remote); - if (err < 0) - return Error_set(err); - - err = git_push_add_refspec(push, refspec); - if (err < 0) - goto error; - - err = git_push_finish(push); - if (err < 0) - goto error; - - if (!git_push_unpack_ok(push)) { - git_push_free(push); - PyErr_SetString(GitError, "Remote failed to unpack objects"); - return NULL; - } - - err = git_push_status_foreach(push, push_status_foreach_callback, &msg); - if (err < 0) - goto error; - if (msg != NULL) { - git_push_free(push); - PyErr_SetString(GitError, msg); - return NULL; - } - - err = git_push_update_tips(push); - if (err < 0) - goto error; - - git_push_free(push); - Py_RETURN_NONE; - -error: - git_push_free(push); - return Error_set(err); -} - - -PyDoc_STRVAR(Remote_add_push__doc__, - "add_push(refspec)\n" - "\n" - "Add a push refspec to the remote."); - -PyObject * -Remote_add_push(Remote *self, PyObject *args) -{ - git_remote *remote; - char *refspec = NULL; - int err = 0; - - if (!PyArg_ParseTuple(args, "s", &refspec)) - return NULL; - - remote = self->remote; - err = git_remote_add_push(remote, refspec); - if (err < 0) - return Error_set(err); - - Py_RETURN_NONE; -} - - -PyDoc_STRVAR(Remote_add_fetch__doc__, - "add_fetch(refspec)\n" - "\n" - "Add a fetch refspec to the remote."); - -PyObject * -Remote_add_fetch(Remote *self, PyObject *args) -{ - git_remote *remote; - char *refspec = NULL; - int err = 0; - - if (!PyArg_ParseTuple(args, "s", &refspec)) - return NULL; - - remote = self->remote; - err = git_remote_add_fetch(remote, refspec); - if (err < 0) - return Error_set(err); - - Py_RETURN_NONE; -} - -PyMethodDef Remote_methods[] = { - METHOD(Remote, fetch, METH_NOARGS), - METHOD(Remote, save, METH_NOARGS), - METHOD(Remote, get_refspec, METH_O), - METHOD(Remote, push, METH_VARARGS), - METHOD(Remote, add_push, METH_VARARGS), - METHOD(Remote, add_fetch, METH_VARARGS), - {NULL} -}; - -PyGetSetDef Remote_getseters[] = { - GETSET(Remote, name), - GETSET(Remote, url), - GETSET(Remote, push_url), - GETTER(Remote, refspec_count), - GETSET(Remote, fetch_refspecs), - GETSET(Remote, push_refspecs), - {NULL} -}; - -PyMemberDef Remote_members[] = { - MEMBER(Remote, progress, T_OBJECT_EX, "Progress output callback"), - MEMBER(Remote, credentials, T_OBJECT_EX, - "credentials(url, username_from_url, allowed_types) -> credential\n" - "\n" - "Credentials callback\n" - "\n" - "If the remote server requires authentication, this function will\n" - "be called and its return value used for authentication.\n" - "\n" - ":param str url: The url of the remote\n" - ":param username_from_url: Username extracted from the url, if any\n" - ":type username_from_url: str or None\n" - ":param int allowed_types: credential types supported by the remote "), - MEMBER(Remote, transfer_progress, T_OBJECT_EX, "Transfer progress callback"), - MEMBER(Remote, update_tips, T_OBJECT_EX, "update tips callback"), - {NULL}, -}; - -PyDoc_STRVAR(Remote__doc__, "Remote object."); - -PyTypeObject RemoteType = { - PyVarObject_HEAD_INIT(NULL, 0) - "_pygit2.Remote", /* tp_name */ - sizeof(Remote), /* tp_basicsize */ - 0, /* tp_itemsize */ - (destructor)Remote_dealloc, /* 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 */ - Remote__doc__, /* tp_doc */ - 0, /* tp_traverse */ - 0, /* tp_clear */ - 0, /* tp_richcompare */ - 0, /* tp_weaklistoffset */ - 0, /* tp_iter */ - 0, /* tp_iternext */ - Remote_methods, /* tp_methods */ - Remote_members, /* tp_members */ - Remote_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 */ -}; - -PyObject * -wrap_remote(git_remote *c_remote, Repository *repo) -{ - Remote *py_remote = NULL; - git_remote_callbacks callbacks = GIT_REMOTE_CALLBACKS_INIT; - - py_remote = PyObject_New(Remote, &RemoteType); - if (py_remote) { - Py_INCREF(repo); - py_remote->repo = repo; - py_remote->remote = c_remote; - py_remote->progress = NULL; - py_remote->credentials = NULL; - py_remote->transfer_progress = NULL; - py_remote->update_tips = NULL; - - callbacks.progress = progress_cb; - callbacks.credentials = credentials_cb; - callbacks.transfer_progress = transfer_progress_cb; - callbacks.update_tips = update_tips_cb; - callbacks.payload = py_remote; - git_remote_set_callbacks(c_remote, &callbacks); - } - - return (PyObject *)py_remote; -} diff --git a/src/remote.h b/src/remote.h deleted file mode 100644 index ce6ee47d5..000000000 --- a/src/remote.h +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2010-2014 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_remote_h -#define INCLUDE_pygit2_remote_h - -#define PY_SSIZE_T_CLEAN -#include -#include -#include - -PyObject* Remote_fetch(Remote *self, PyObject *args); -PyObject* wrap_remote(git_remote *c_remote, Repository *repo); - -#endif diff --git a/src/repository.c b/src/repository.c index 14e07a187..e8e38f96b 100644 --- a/src/repository.c +++ b/src/repository.c @@ -35,7 +35,6 @@ #include "oid.h" #include "note.h" #include "repository.h" -#include "remote.h" #include "branch.h" #include "blame.h" #include "mergeresult.h" @@ -54,7 +53,6 @@ extern PyTypeObject TreeType; extern PyTypeObject TreeBuilderType; extern PyTypeObject ConfigType; extern PyTypeObject DiffType; -extern PyTypeObject RemoteType; extern PyTypeObject ReferenceType; extern PyTypeObject NoteType; extern PyTypeObject NoteIterType; diff --git a/src/types.h b/src/types.h index cfd00092c..e50f8341c 100644 --- a/src/types.h +++ b/src/types.h @@ -194,38 +194,6 @@ typedef struct { char *encoding; } Signature; - -/* git_remote */ -typedef struct { - PyObject_HEAD - Repository *repo; - git_remote *remote; - /* Callbacks for network events */ - PyObject *progress; - PyObject *credentials; - PyObject *transfer_progress; - PyObject *update_tips; -} Remote; - -/* git_refspec */ -typedef struct { - PyObject_HEAD - const Remote *owner; - const git_refspec *refspec; -} Refspec; - -/* git_transfer_progress */ -typedef struct { - PyObject_HEAD - unsigned int total_objects; - unsigned int indexed_objects; - unsigned int received_objects; - unsigned int local_objects; - unsigned int total_deltas; - unsigned int indexed_deltas; - size_t received_bytes; -} TransferProgress; - /* git_blame */ SIMPLE_TYPE(Blame, git_blame, blame) From 072b0382104f432721dbe9e31395d224f0b751b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sat, 12 Apr 2014 19:10:50 +0200 Subject: [PATCH 0164/1630] Implement clone via CFFI as well This lets us get rid of the last piece of C for anything related to remotes and credentials. --- pygit2/__init__.py | 55 ++++++++++++++++++++++++++-- pygit2/decl.h | 64 +++++++++++++++++++++++++++++++++ pygit2/remote.py | 55 +++++++++++++++------------- src/pygit2.c | 65 --------------------------------- src/utils.c | 89 ---------------------------------------------- src/utils.h | 2 -- 6 files changed, 147 insertions(+), 183 deletions(-) diff --git a/pygit2/__init__.py b/pygit2/__init__.py index 396af196a..df3a898c7 100644 --- a/pygit2/__init__.py +++ b/pygit2/__init__.py @@ -37,7 +37,9 @@ from .version import __version__ from .settings import Settings from .credentials import * -from .remote import Remote +from .remote import Remote, get_credentials +from .errors import check_error +from .ffi import ffi, C, to_str def init_repository(path, bare=False): """ @@ -50,6 +52,19 @@ def init_repository(path, bare=False): return Repository(path) +@ffi.callback('int (*credentials)(git_cred **cred, const char *url, const char *username_from_url, unsigned int allowed_types, void *data)') +def _credentials_cb(cred_out, url, username_from_url, allowed, data): + d = ffi.from_handle(data) + + try: + ccred = get_credentials(d['callback'], url, username_from_url, allowed) + cred_out[0] = ccred[0] + except Exception, e: + d['exception'] = e + return C.GIT_EUSER + + return 0 + def clone_repository( url, path, bare=False, ignore_cert_errors=False, remote_name="origin", checkout_branch=None, credentials=None): @@ -75,8 +90,42 @@ def clone_repository( """ - _pygit2.clone_repository( - url, path, bare, ignore_cert_errors, remote_name, checkout_branch, credentials) + opts = ffi.new('git_clone_options *') + crepo = ffi.new('git_repository **') + + branch = checkout_branch or None + + # Data, let's use a dict as we don't really want much more + d = {} + d['callback'] = credentials + d_handle = ffi.new_handle(d) + + # We need to keep the ref alive ourselves + checkout_branch_ref = None + if branch: + checkout_branch_ref = ffi.new('char []', branch) + opts.checkout_branch = checkout_branch_ref + + remote_name_ref = ffi.new('char []', to_str(remote_name)) + opts.remote_name = remote_name_ref + + opts.version = 1 + opts.ignore_cert_errors = ignore_cert_errors + opts.bare = bare + opts.remote_callbacks.version = 1 + opts.checkout_opts.version = 1 + if credentials: + opts.remote_callbacks.credentials = _credentials_cb + opts.remote_callbacks.payload = d_handle + + err = C.git_clone(crepo, to_str(url), to_str(path), opts) + C.git_repository_free(crepo[0]) + + if 'exception' in d: + raise d['exception'] + + check_error(err) + return Repository(path) settings = Settings() diff --git a/pygit2/decl.h b/pygit2/decl.h index 40680ce6c..a64c32fc4 100644 --- a/pygit2/decl.h +++ b/pygit2/decl.h @@ -3,6 +3,8 @@ typedef ... git_remote; typedef ... git_refspec; typedef ... git_push; typedef ... git_cred; +typedef ... git_diff_file; +typedef ... git_tree; #define GIT_OID_RAWSZ ... @@ -43,6 +45,7 @@ typedef struct { const git_error * giterr_last(void); void git_strarray_free(git_strarray *array); +void git_repository_free(git_repository *repo); typedef struct git_transfer_progress { unsigned int total_objects; @@ -148,3 +151,64 @@ int git_cred_ssh_key_new( const char *publickey, const char *privatekey, const char *passphrase); + +typedef enum { ... } git_checkout_notify_t; + +typedef int (*git_checkout_notify_cb)( + git_checkout_notify_t why, + const char *path, + const git_diff_file *baseline, + const git_diff_file *target, + const git_diff_file *workdir, + void *payload); + +typedef void (*git_checkout_progress_cb)( + const char *path, + size_t completed_steps, + size_t total_steps, + void *payload); + +typedef struct git_checkout_opts { + unsigned int version; + + unsigned int checkout_strategy; + + int disable_filters; + unsigned int dir_mode; + unsigned int file_mode; + int file_open_flags; + + unsigned int notify_flags; + git_checkout_notify_cb notify_cb; + void *notify_payload; + + git_checkout_progress_cb progress_cb; + void *progress_payload; + + git_strarray paths; + + git_tree *baseline; + + const char *target_directory; + + const char *our_label; + const char *their_label; +} git_checkout_opts; + + +typedef struct git_clone_options { + unsigned int version; + + git_checkout_opts checkout_opts; + git_remote_callbacks remote_callbacks; + + int bare; + int ignore_cert_errors; + const char *remote_name; + const char* checkout_branch; +} git_clone_options; + +int git_clone(git_repository **out, + const char *url, + const char *local_path, + const git_clone_options *options); diff --git a/pygit2/remote.py b/pygit2/remote.py index c7f122ca1..40e6ddaaa 100644 --- a/pygit2/remote.py +++ b/pygit2/remote.py @@ -352,37 +352,44 @@ def _credentials_cb(cred_out, url, username, allowed, data): return 0 try: - url_str = maybe_string(url) - username_str = maybe_string(username) + ccred = get_credentials(self.credentials, url, username, allowed) + cred_out[0] = ccred[0] - creds = self.credentials(url_str, username_str, allowed) + except Exception, e: + self._stored_exception = e + return C.GIT_EUSER - if not hasattr(creds, 'credential_type') or not hasattr(creds, 'credential_tuple'): - raise TypeError("credential does not implement interface") + return 0 - cred_type = creds.credential_type +def get_credentials(fn, url, username, allowed): + """Call fn and return the credentials object""" - if not (allowed & cred_type): - raise TypeError("invalid credential type") + url_str = maybe_string(url) + username_str = maybe_string(username) - ccred = ffi.new('git_cred **') - if cred_type == C.GIT_CREDTYPE_USERPASS_PLAINTEXT: - name, passwd = creds.credential_tuple - err = C.git_cred_userpass_plaintext_new(ccred, to_str(name), to_str(passwd)) + creds = fn(url_str, username_str, allowed) - elif cred_type == C.GIT_CREDTYPE_SSH_KEY: - name, pubkey, privkey, passphrase = creds.credential_tuple - err = C.git_cred_ssh_key_new(ccred, to_str(name),to_str(pubkey), - to_str(privkey), to_str(passphrase)) + if not hasattr(creds, 'credential_type') or not hasattr(creds, 'credential_tuple'): + raise TypeError("credential does not implement interface") - else: - raise TypeError("unsupported credential type") + cred_type = creds.credential_type - check_error(err) - cred_out[0] = ccred[0] + if not (allowed & cred_type): + raise TypeError("invalid credential type") - except Exception, e: - self._stored_exception = e - return C.GIT_EUSER + ccred = ffi.new('git_cred **') + if cred_type == C.GIT_CREDTYPE_USERPASS_PLAINTEXT: + name, passwd = creds.credential_tuple + err = C.git_cred_userpass_plaintext_new(ccred, to_str(name), to_str(passwd)) - return 0 + elif cred_type == C.GIT_CREDTYPE_SSH_KEY: + name, pubkey, privkey, passphrase = creds.credential_tuple + err = C.git_cred_ssh_key_new(ccred, to_str(name),to_str(pubkey), + to_str(privkey), to_str(passphrase)) + + else: + raise TypeError("unsupported credential type") + + check_error(err) + + return ccred diff --git a/src/pygit2.c b/src/pygit2.c index 06e4f17e1..d88c0e408 100644 --- a/src/pygit2.c +++ b/src/pygit2.c @@ -115,69 +115,6 @@ init_repository(PyObject *self, PyObject *args) { Py_RETURN_NONE; }; -static int -credentials_cb(git_cred **out, const char *url, const char *username_from_url, unsigned int allowed_types, void *data) -{ - PyObject *credentials = (PyObject *) data; - - return callable_to_credentials(out, url, username_from_url, allowed_types, credentials); -} - -PyDoc_STRVAR(clone_repository__doc__, - "clone_repository(url, path, bare, remote_name, checkout_branch)\n" - "\n" - "Clones a Git repository in the given url to the given path " - "with the specified options.\n" - "\n" - "Arguments:\n" - "\n" - "url\n" - " Git repository remote url.\n" - "path\n" - " Path where to create the repository.\n" - "bare\n" - " If 'bare' is not 0, then a bare git repository will be created.\n" - "remote_name\n" - " The name given to the 'origin' remote. The default is 'origin'.\n" - "checkout_branch\n" - " The name of the branch to checkout. None means use the remote's " - "HEAD.\n"); - - -PyObject * -clone_repository(PyObject *self, PyObject *args) { - git_repository *repo; - const char *url; - const char *path; - unsigned int bare, ignore_cert_errors; - const char *remote_name, *checkout_branch; - PyObject *credentials = NULL; - int err; - git_clone_options opts = GIT_CLONE_OPTIONS_INIT; - - if (!PyArg_ParseTuple(args, "zzIIzzO", - &url, &path, &bare, &ignore_cert_errors, &remote_name, &checkout_branch, &credentials)) - return NULL; - - opts.bare = bare; - opts.ignore_cert_errors = ignore_cert_errors; - opts.remote_name = remote_name; - opts.checkout_branch = checkout_branch; - - if (credentials != Py_None) { - opts.remote_callbacks.credentials = credentials_cb; - opts.remote_callbacks.payload = credentials; - } - - err = git_clone(&repo, url, path, &opts); - if (err < 0) - return Error_set(err); - - git_repository_free(repo); - Py_RETURN_NONE; -}; - - PyDoc_STRVAR(discover_repository__doc__, "discover_repository(path[, across_fs[, ceiling_dirs]]) -> str\n" "\n" @@ -252,8 +189,6 @@ hash(PyObject *self, PyObject *args) PyMethodDef module_methods[] = { {"init_repository", init_repository, METH_VARARGS, init_repository__doc__}, - {"clone_repository", clone_repository, METH_VARARGS, - clone_repository__doc__}, {"discover_repository", discover_repository, METH_VARARGS, discover_repository__doc__}, {"hashfile", hashfile, METH_VARARGS, hashfile__doc__}, diff --git a/src/utils.c b/src/utils.c index 24b6bbd47..44acf5c6a 100644 --- a/src/utils.c +++ b/src/utils.c @@ -153,92 +153,3 @@ get_strarraygit_from_pylist(git_strarray *array, PyObject *pylist) return -1; } - -static int -py_cred_to_git_cred(git_cred **out, PyObject *py_cred, unsigned int allowed) -{ - PyObject *py_type, *py_tuple; - long type; - int err = -1; - - py_type = PyObject_GetAttrString(py_cred, "credential_type"); - py_tuple = PyObject_GetAttrString(py_cred, "credential_tuple"); - - if (!py_type || !py_tuple) { - printf("py_type %p, py_tuple %p\n", py_type, py_tuple); - PyErr_SetString(PyExc_TypeError, "credential doesn't implement the interface"); - goto cleanup; - } - - if (!PyLong_Check(py_type)) { - PyErr_SetString(PyExc_TypeError, "credential type is not a long"); - goto cleanup; - } - - type = PyLong_AsLong(py_type); - - /* Sanity check, make sure we're given credentials we can use */ - if (!(allowed & type)) { - PyErr_SetString(PyExc_TypeError, "invalid credential type"); - goto cleanup; - } - - switch (type) { - case GIT_CREDTYPE_USERPASS_PLAINTEXT: - { - const char *username, *password; - - if (!PyArg_ParseTuple(py_tuple, "ss", &username, &password)) - goto cleanup; - - err = git_cred_userpass_plaintext_new(out, username, password); - break; - } - case GIT_CREDTYPE_SSH_KEY: - { - const char *username, *pubkey, *privkey, *passphrase; - - if (!PyArg_ParseTuple(py_tuple, "ssss", &username, &pubkey, &privkey, &passphrase)) - goto cleanup; - - err = git_cred_ssh_key_new(out, username, pubkey, privkey, passphrase); - break; - } - default: - PyErr_SetString(PyExc_TypeError, "unsupported credential type"); - break; - } - -cleanup: - Py_XDECREF(py_type); - Py_XDECREF(py_tuple); - - return err; -} - -int -callable_to_credentials(git_cred **out, const char *url, const char *username_from_url, unsigned int allowed_types, PyObject *credentials) -{ - int err; - PyObject *py_cred = NULL, *arglist = NULL; - - if (credentials == NULL || credentials == Py_None) - return 0; - - if (!PyCallable_Check(credentials)) { - PyErr_SetString(PyExc_TypeError, "credentials callback is not callable"); - return -1; - } - - arglist = Py_BuildValue("(szI)", url, username_from_url, allowed_types); - py_cred = PyObject_CallObject(credentials, arglist); - Py_DECREF(arglist); - - if (!py_cred) - return -1; - - err = py_cred_to_git_cred(out, py_cred, allowed_types); - Py_DECREF(py_cred); - - return err; -} diff --git a/src/utils.h b/src/utils.h index 7f95d73ee..81744f686 100644 --- a/src/utils.h +++ b/src/utils.h @@ -117,8 +117,6 @@ const char *py_str_borrow_c_str(PyObject **tvaue, PyObject *value, const char *e PyObject * get_pylist_from_git_strarray(git_strarray *strarray); int get_strarraygit_from_pylist(git_strarray *array, PyObject *pylist); -int callable_to_credentials(git_cred **out, const char *url, const char *username_from_url, unsigned int allowed_types, PyObject *credentials); - #define py_path_to_c_str(py_path) \ py_str_to_c_str(py_path, Py_FileSystemDefaultEncoding) From 73e9e58fa4941a6b5bcd14537848abbd1ab0000e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Mon, 14 Apr 2014 19:37:44 +0200 Subject: [PATCH 0165/1630] Config: make bool and int parsing explicit via functions Passing a tuple to the mapping interface isn't the best of interfaces, as the key is only the string. Instead, expose `Config.get_bool()` and `Config.get_int()` methods to parse the values as per the git-config rules before returning the appropriate type to the user. The mapping interface itself returns a string. --- docs/config.rst | 19 ++----- src/config.c | 128 ++++++++++++++++++++++++++------------------ test/test_config.py | 12 ++--- 3 files changed, 88 insertions(+), 71 deletions(-) diff --git a/docs/config.rst b/docs/config.rst index 4bdaaa1cc..881fc1d70 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -24,18 +24,9 @@ The Config type The :class:`Config` Mapping interface. -Parsing the values -=================== +When using the mapping interface, the value is returned as a +string. In order to apply the git-config parsing rules, you can use +:method:`Config.get_bool` or :method:`Config.get_int`. -Instead of a string, a tuple of `(str,type)` can be used to look up a -key and parse it through the Git rules. E.g. - - config['core.bare',bool] - -will return True if 'core.bare' is truthy. - -Truty values are: 'true', 1, 'on' or 'yes'. Falsy values are: 'false', -0, 'off' and 'no'. - -Available types are `bool` and `int`. Not specifying a type returns a -string. +.. automethod:: pygit2.Config.get_bool +.. automethod:: pygit2.Config.get_int diff --git a/src/config.c b/src/config.c index 58fdeaaa8..07dbb408a 100644 --- a/src/config.c +++ b/src/config.c @@ -168,70 +168,94 @@ Config_contains(Config *self, PyObject *py_key) { return 1; } - -PyObject * -Config_getitem(Config *self, PyObject *py_input_key) +/* Get the C string value given a python string as key */ +static int +get_string(const char **key_out, Config *self, PyObject *py_key) { - int err; - const char *value_str; + PyObject *tkey; const char *key; - PyObject *py_key, *py_value, *tkey, *tmp_type = NULL; - PyTypeObject *py_type = NULL; + int err; - if (PyTuple_Check(py_input_key) && PyTuple_Size(py_input_key) == 2) { - py_key = PyTuple_GetItem(py_input_key, 0); - tmp_type = PyTuple_GetItem(py_input_key, 1); - } else { - py_key = py_input_key; + key = py_str_borrow_c_str(&tkey, py_key, NULL); + if (key == NULL) + return -1; + + err = git_config_get_string(key_out, self->config, key); + Py_CLEAR(tkey); + + if (err == GIT_ENOTFOUND) { + PyErr_SetObject(PyExc_KeyError, py_key); + return -1; } - /* If passed a tuple, make sure the second item is a type */ - if (tmp_type) { - if (!PyType_Check(tmp_type)) - return NULL; - else - py_type = (PyTypeObject *) tmp_type; + if (err < 0) { + Error_set(err); + return -1; } - key = py_str_borrow_c_str(&tkey, py_key, NULL); - if (key == NULL) + return 0; +} + +PyObject * +Config_getitem(Config *self, PyObject *py_key) +{ + int err; + const char *value_str; + + err = get_string(&value_str, self, py_key); + if (err < 0) return NULL; - err = git_config_get_string(&value_str, self->config, key); - Py_CLEAR(tkey); + return to_unicode(value_str, NULL, NULL); +} + +PyDoc_STRVAR(Config_get_bool__doc__, + "get_bool(key) -> Bool\n" + "\n" + "Look up *key* and parse its value as a boolean as per the git-config rules\n" + "\n" + "Truthy values are: 'true', 1, 'on' or 'yes'. Falsy values are: 'false',\n" + "0, 'off' and 'no'"); + +PyObject * +Config_get_bool(Config *self, PyObject *key) +{ + int err, value; + const char *value_str; + + err = get_string(&value_str, self, key); if (err < 0) - goto cleanup; - - /* If the user specified a type, let's parse it */ - if (py_type) { - if (py_type == &PyBool_Type) { - int value; - if ((err = git_config_parse_bool(&value, value_str)) < 0) - goto cleanup; - - py_value = PyBool_FromLong(value); - } else if (py_type == &PyInteger_Type) { - int64_t value; - if ((err = git_config_parse_int64(&value, value_str)) < 0) - goto cleanup; - - py_value = PyLong_FromLongLong(value); - } - } else { - py_value = to_unicode(value_str, NULL, NULL); - } + return NULL; -cleanup: - if (err < 0) { - if (err == GIT_ENOTFOUND) { - PyErr_SetObject(PyExc_KeyError, py_key); - return NULL; - } + if ((err = git_config_parse_bool(&value, value_str)) < 0) + return NULL; - return Error_set(err); - } + return PyBool_FromLong(value); +} + +PyDoc_STRVAR(Config_get_int__doc__, + "get_int(key) -> int\n" + "\n" + "Look up *key* and parse its value as an integer as per the git-config rules\n" + "\n" + "A value can have a suffix 'k', 'm' or 'g' which stand for 'kilo', 'mega' and\n" + "'giga' respectively"); + +PyObject * +Config_get_int(Config *self, PyObject *key) +{ + int err; + int64_t value; + const char *value_str; + + err = get_string(&value_str, self, key); + if (err < 0) + return NULL; + + if ((err = git_config_parse_int64(&value, value_str)) < 0) + return NULL; - return py_value; + return PyLong_FromLongLong(value); } int @@ -396,6 +420,8 @@ PyMethodDef Config_methods[] = { METHOD(Config, add_file, METH_VARARGS | METH_KEYWORDS), METHOD(Config, get_multivar, METH_VARARGS), METHOD(Config, set_multivar, METH_VARARGS), + METHOD(Config, get_bool, METH_O), + METHOD(Config, get_int, METH_O), {NULL} }; diff --git a/test/test_config.py b/test/test_config.py index f29c703e1..937ed1bf9 100644 --- a/test/test_config.py +++ b/test/test_config.py @@ -74,7 +74,7 @@ def test_new(self): config_read = Config(CONFIG_FILENAME) self.assertTrue('core.bare' in config_read) - self.assertFalse(config_read['core.bare',bool]) + self.assertFalse(config_read.get_bool('core.bare')) self.assertTrue('core.editor' in config_read) self.assertEqual(config_read['core.editor'], 'ed') @@ -88,9 +88,9 @@ def test_add(self): config.add_file(CONFIG_FILENAME, 0) self.assertTrue('this.that' in config) - self.assertTrue(config['this.that',bool]) + self.assertTrue(config.get_bool('this.that')) self.assertTrue('something.other.here' in config) - self.assertFalse(config['something.other.here',bool]) + self.assertFalse(config.get_bool('something.other.here')) def test_read(self): config = self.repo.config @@ -103,11 +103,11 @@ def test_read(self): lambda: config['abc.def']) self.assertTrue('core.bare' in config) - self.assertFalse(config['core.bare',bool]) + self.assertFalse(config.get_bool('core.bare')) self.assertTrue('core.editor' in config) self.assertEqual(config['core.editor'], 'ed') self.assertTrue('core.repositoryformatversion' in config) - self.assertEqual(config['core.repositoryformatversion',int], 0) + self.assertEqual(config.get_int('core.repositoryformatversion'), 0) new_file = open(CONFIG_FILENAME, "w") new_file.write("[this]\n\tthat = foobar\n\tthat = foobeer\n") @@ -129,7 +129,7 @@ def test_write(self): self.assertFalse('core.dummy1' in config) config['core.dummy1'] = 42 self.assertTrue('core.dummy1' in config) - self.assertEqual(config['core.dummy1',int], 42) + self.assertEqual(config.get_int('core.dummy1'), 42) self.assertFalse('core.dummy2' in config) config['core.dummy2'] = 'foobar' From 674546bbb527106273e670b1dd6a4bf84194e4ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sat, 12 Apr 2014 23:30:00 +0200 Subject: [PATCH 0166/1630] Some python3 fixes --- pygit2/__init__.py | 2 +- pygit2/ffi.py | 13 +++++++------ pygit2/remote.py | 8 ++++---- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/pygit2/__init__.py b/pygit2/__init__.py index df3a898c7..2dbda72bf 100644 --- a/pygit2/__init__.py +++ b/pygit2/__init__.py @@ -59,7 +59,7 @@ def _credentials_cb(cred_out, url, username_from_url, allowed, data): try: ccred = get_credentials(d['callback'], url, username_from_url, allowed) cred_out[0] = ccred[0] - except Exception, e: + except Exception as e: d['exception'] = e return C.GIT_EUSER diff --git a/pygit2/ffi.py b/pygit2/ffi.py index ce25ff0ea..10762b633 100644 --- a/pygit2/ffi.py +++ b/pygit2/ffi.py @@ -29,7 +29,8 @@ from __future__ import absolute_import import inspect -from os import path +import codecs +from os import path, getenv from cffi import FFI import sys @@ -51,10 +52,10 @@ def to_str(s, encoding='utf-8', errors='strict'): if sys.version_info.major < 3: def is_string(s): - return isinstance(s, str) + return isinstance(s, basestring) else: def is_string(s): - return isinstance(s, basestring) + return isinstance(s, str) ffi = FFI() @@ -88,7 +89,7 @@ def strings_to_strarray(l): if not is_string(l[i]): raise TypeError("Value must be a string") - s = ffi.new('char []', l[i]) + s = ffi.new('char []', to_str(l[i])) refs[i] = s strings[i] = s @@ -100,7 +101,7 @@ def strings_to_strarray(l): dir_path = path.dirname(path.abspath(inspect.getfile(inspect.currentframe()))) decl_path = path.join(dir_path, 'decl.h') -with open(decl_path, 'rb') as f: - ffi.cdef(f.read()) +with codecs.open(decl_path, 'r', 'utf-8') as header: + ffi.cdef(header.read()) C = ffi.verify("#include ", libraries=["git2"]) diff --git a/pygit2/remote.py b/pygit2/remote.py index 40e6ddaaa..07614d3b8 100644 --- a/pygit2/remote.py +++ b/pygit2/remote.py @@ -303,7 +303,7 @@ def _transfer_progress_cb(stats_ptr, data): try: self.transfer_progress(TransferProgress(stats_ptr)) - except Exception, e: + except Exception as e: self._stored_exception = e return C.GIT_EUSER @@ -319,7 +319,7 @@ def _progress_cb(string, length, data): try: s = ffi.string(string, length).decode() self.progress(s) - except Exception, e: + except Exception as e: self._stored_exception = e return C.GIT_EUSER @@ -338,7 +338,7 @@ def _update_tips_cb(refname, a, b, data): b = Oid(raw=bytes(ffi.buffer(b))) self.update_tips(s, a, b) - except Exception, e: + except Exception as e: self._stored_exception = e return C.GIT_EUSER @@ -355,7 +355,7 @@ def _credentials_cb(cred_out, url, username, allowed, data): ccred = get_credentials(self.credentials, url, username, allowed) cred_out[0] = ccred[0] - except Exception, e: + except Exception as e: self._stored_exception = e return C.GIT_EUSER From d51174d34ffd898e2922b952da12451c1f74c4d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Sun, 13 Apr 2014 17:07:13 +0200 Subject: [PATCH 0167/1630] Install the "decl.h" file --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index eff54e184..a52fc448d 100644 --- a/setup.py +++ b/setup.py @@ -184,6 +184,7 @@ def get_file_list(self): maintainer_email='jdavid.ibp@gmail.com', long_description=long_description, packages=['pygit2'], + package_data={'pygit2': ['decl.h']}, ext_modules=[ Extension('_pygit2', pygit2_exts, include_dirs=[libgit2_include, 'include'], From e56ab370a7803067b2f230fceb53ebf4d46a580f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sun, 13 Apr 2014 18:57:03 +0200 Subject: [PATCH 0168/1630] Remote: make sure to take the contents of the id Pass the contents of the buffer containing the git_oid to bytes() so build a raw representation of its contents. Using bytes(ffi.buffer(...)) returns the representation of the buffer. --- pygit2/remote.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pygit2/remote.py b/pygit2/remote.py index 07614d3b8..6a2a7b99f 100644 --- a/pygit2/remote.py +++ b/pygit2/remote.py @@ -334,8 +334,8 @@ def _update_tips_cb(refname, a, b, data): try: s = maybe_string(refname) - a = Oid(raw=bytes(ffi.buffer(a))) - b = Oid(raw=bytes(ffi.buffer(b))) + a = Oid(raw=bytes(ffi.buffer(a)[:])) + b = Oid(raw=bytes(ffi.buffer(b)[:])) self.update_tips(s, a, b) except Exception as e: From b4bc2b6295e32fe8a4a46d5007ec1209e6af5bce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sun, 13 Apr 2014 19:20:45 +0200 Subject: [PATCH 0169/1630] Depend on the cffi package Let both pip and Travis know what we need. --- .travis.yml | 1 + setup.py | 1 + 2 files changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index f2ffa47a8..4fbf40373 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,6 +11,7 @@ env: LIBGIT2=~/libgit2/_install/ LD_LIBRARY_PATH=~/libgit2/_install/lib before_install: - sudo apt-get install cmake + - pip install cffi - "./.travis.sh" script: diff --git a/setup.py b/setup.py index a52fc448d..914cbed7c 100644 --- a/setup.py +++ b/setup.py @@ -185,6 +185,7 @@ def get_file_list(self): long_description=long_description, packages=['pygit2'], package_data={'pygit2': ['decl.h']}, + install_requires=['cffi'], ext_modules=[ Extension('_pygit2', pygit2_exts, include_dirs=[libgit2_include, 'include'], From f3bb8a45567b5244b231e164266972f89f8f7d72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sun, 13 Apr 2014 20:37:58 +0200 Subject: [PATCH 0170/1630] Fix installation-time cffi compilation The documentation recommends adding the ffi code as an extension so it gets built at the right time. Make use of the LIBGIT2 environment variable to build and link the ffi module the same way the C extension does so the user doesn't have to export CFLAGS. --- pygit2/ffi.py | 10 +++++++++- setup.py | 6 ++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/pygit2/ffi.py b/pygit2/ffi.py index 10762b633..0f225c899 100644 --- a/pygit2/ffi.py +++ b/pygit2/ffi.py @@ -104,4 +104,12 @@ def strings_to_strarray(l): with codecs.open(decl_path, 'r', 'utf-8') as header: ffi.cdef(header.read()) -C = ffi.verify("#include ", libraries=["git2"]) +# if LIBGIT2 exists, set build and link against that version +libgit2_path = getenv('LIBGIT2') +include_dirs = [] +library_dirs = [] +if libgit2_path: + include_dirs = [path.join(libgit2_path, 'include')] + library_dirs = [path.join(libgit2_path, 'lib')] + +C = ffi.verify("#include ", libraries=["git2"], include_dirs=include_dirs, library_dirs=library_dirs) diff --git a/setup.py b/setup.py index 914cbed7c..27733f40b 100644 --- a/setup.py +++ b/setup.py @@ -173,6 +173,11 @@ def get_file_list(self): with codecs.open('README.rst', 'r', 'utf-8') as readme: long_description = readme.read() +# This ffi is pygit2.ffi due to the path trick used in the beginning +# of the file +from ffi import ffi +ffi_ext = ffi.verifier.get_extension() + setup(name='pygit2', description='Python bindings for libgit2.', keywords='git', @@ -191,5 +196,6 @@ def get_file_list(self): include_dirs=[libgit2_include, 'include'], library_dirs=[libgit2_lib], libraries=['git2']), + ffi_ext, ], cmdclass=cmdclass) From 5a20510f8a73ec1603aac33652b64016b0502748 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Mon, 14 Apr 2014 02:13:36 +0200 Subject: [PATCH 0171/1630] Safer repository pointer extraction Casting a pointer to a non-pointer type is something which you should never do. Instead, do something a bit more convoluted, but which is guaranteed to give us the right pointer, as well as making sure that the memory we exchange between python/cffi and the C extension is of the right pointer size. While we're touching this code, fix which object we pass to the Remote constructor to keep alive. We need to pass the Repository object to stop it from becoming unreferenced (and thus freeing memory the remote needs) instead of the git_repository pointer. --- pygit2/repository.py | 25 ++++++++++++++----------- src/repository.c | 9 ++------- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/pygit2/repository.py b/pygit2/repository.py index a22c8204c..107db9128 100644 --- a/pygit2/repository.py +++ b/pygit2/repository.py @@ -41,6 +41,15 @@ class Repository(_Repository): + def __init__(self, *args, **kwargs): + super(Repository, self).__init__(*args, **kwargs) + + # Get the pointer as the contents of a buffer and store it for + # later access + repo_cptr = ffi.new('git_repository **') + ffi.buffer(repo_cptr)[:] = self._pointer[:] + self._repo = repo_cptr[0] + # # Mapping interface # @@ -72,36 +81,30 @@ def create_remote(self, name, url): Creates a new remote. """ - repo_cptr = ffi.new('git_repository **') - repo_cptr[0] = ffi.cast('git_repository *', self._pointer) cremote = ffi.new('git_remote **') - repo = repo_cptr[0] - err = C.git_remote_create(cremote, repo, to_str(name), to_str(url)) + err = C.git_remote_create(cremote, self._repo, to_str(name), to_str(url)) check_error(err) - return Remote(repo, cremote[0]) + return Remote(self, cremote[0]) @property def remotes(self): """Returns all configured remotes""" - repo_cptr = ffi.new('git_repository **') - repo_cptr[0] = ffi.cast('git_repository *', self._pointer) names = ffi.new('git_strarray *') - repo = repo_cptr[0] try: - err = C.git_remote_list(names, repo) + err = C.git_remote_list(names, self._repo) check_error(err) l = [None] * names.count cremote = ffi.new('git_remote **') for i in range(names.count): - err = C.git_remote_load(cremote, repo, names.strings[i]) + err = C.git_remote_load(cremote, self._repo, names.strings[i]) check_error(err) - l[i] = Remote(repo, cremote[0]) + l[i] = Remote(self, cremote[0]) return l finally: C.git_strarray_free(names) diff --git a/src/repository.c b/src/repository.c index e8e38f96b..1713136cf 100644 --- a/src/repository.c +++ b/src/repository.c @@ -1293,13 +1293,8 @@ PyDoc_STRVAR(Repository__pointer__doc__, "Get the repo's pointer. For internal u PyObject * Repository__pointer__get__(Repository *self) { - /* - * This is pretty bad. We shouldn't be casting a pointer into an - * integer, but we can't access the contents of a PyCapsule from - * python code, which we need to do in order to get a type that - * cffi likes. - */ - return PyLong_FromLongLong((long long) self->repo); + /* Bytes means a raw buffer */ + return PyBytes_FromStringAndSize((char *) &self->repo, sizeof(git_repository *)); } PyDoc_STRVAR(Repository_checkout_head__doc__, From d7d0eb37c35607d217819d5b2ae5783a875cb023 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Mon, 14 Apr 2014 16:41:30 +0200 Subject: [PATCH 0172/1630] ffi: style changes Make to_str() accept None as well as ffi.NULL to return as a negative value, and grab the version in a more compatible way. --- pygit2/ffi.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/pygit2/ffi.py b/pygit2/ffi.py index 0f225c899..468b9bc2f 100644 --- a/pygit2/ffi.py +++ b/pygit2/ffi.py @@ -34,23 +34,29 @@ from cffi import FFI import sys -if sys.version_info.major < 3: +(major_version, _, _, _, _) = sys.version_info + +if major_version < 3: def to_str(s, encoding='utf-8', errors='strict'): - if s == ffi.NULL: + if s == ffi.NULL or s == None: return ffi.NULL - encoding = encoding or 'utf-8' + if isinstance(s, unicode): + encoding = encoding or 'utf-8' return s.encode(encoding, errors) return s else: def to_str(s, encoding='utf-8', errors='strict'): + if s == ffi.NULL or s == None: + return ffi.NULL + if isinstance(s, bytes): return s - else: - return bytes(s, encoding, errors) -if sys.version_info.major < 3: + return s.encode(encoding, errors) + +if major_version < 3: def is_string(s): return isinstance(s, basestring) else: From 37c01d79c5afc20377328fcbebf45abfffbbf61e Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Thu, 17 Apr 2014 10:13:26 -0700 Subject: [PATCH 0173/1630] Update some docstrings which had got out of date. --- pygit2/repository.py | 2 +- src/repository.c | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/pygit2/repository.py b/pygit2/repository.py index e6c750f31..75650708e 100644 --- a/pygit2/repository.py +++ b/pygit2/repository.py @@ -78,7 +78,7 @@ def create_reference(self, name, target, force=False): Examples:: - repo.create_reference('refs/heads/foo', repo.head.hex) + repo.create_reference('refs/heads/foo', repo.head.get_object().hex) repo.create_reference('refs/tags/foo', 'refs/heads/master') repo.create_reference('refs/tags/foo', 'bbb78a9cec580') """ diff --git a/src/repository.c b/src/repository.c index bb2a11b96..2a606e13b 100644 --- a/src/repository.c +++ b/src/repository.c @@ -916,7 +916,7 @@ PyDoc_STRVAR(Repository_create_branch__doc__, "\n" "Examples::\n" "\n" - " repo.create_branch('foo', repo.head.hex, force=False)"); + " repo.create_branch('foo', repo.head.get_object(), force=False)"); PyObject * Repository_create_branch(Repository *self, PyObject *args) @@ -1065,7 +1065,7 @@ Repository_lookup_reference(Repository *self, PyObject *py_name) } PyDoc_STRVAR(Repository_create_reference_direct__doc__, - "git_reference_create(name, target, force) -> Reference\n" + "create_reference_direct(name, target, force) -> Reference\n" "\n" "Create a new reference \"name\" which points to an object.\n" "\n" @@ -1077,7 +1077,8 @@ PyDoc_STRVAR(Repository_create_reference_direct__doc__, "\n" "Examples::\n" "\n" - " repo.git_reference_create('refs/heads/foo', repo.head.hex, False)"); + " repo.create_reference_direct('refs/heads/foo',\n" + " repo.head.get_object().hex, False)"); PyObject * Repository_create_reference_direct(Repository *self, PyObject *args, @@ -1104,7 +1105,7 @@ Repository_create_reference_direct(Repository *self, PyObject *args, } PyDoc_STRVAR(Repository_create_reference_symbolic__doc__, - "git_reference_symbolic_create(name, source, force) -> Reference\n" + "create_reference_symbolic(name, source, force) -> Reference\n" "\n" "Create a new reference \"name\" which points to another reference.\n" "\n" @@ -1116,7 +1117,7 @@ PyDoc_STRVAR(Repository_create_reference_symbolic__doc__, "\n" "Examples::\n" "\n" - " repo.git_reference_symbolic_create('refs/tags/foo', 'refs/heads/master', False)"); + " repo.create_reference_symbolic('refs/tags/foo', 'refs/heads/master', False)"); PyObject * Repository_create_reference_symbolic(Repository *self, PyObject *args, From 7ed89b0aabcc2c551807f368faf21310ca30ccec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Mon, 14 Apr 2014 17:19:36 +0200 Subject: [PATCH 0174/1630] Remote: protect against invalid input on rename The C API expects a non-NULL new name and raises an assertion if we don't protect against such input, so let's guard against falsy values, which also takes care of the empty string, which is also not valid input. --- pygit2/remote.py | 3 +++ test/test_remote.py | 1 + 2 files changed, 4 insertions(+) diff --git a/pygit2/remote.py b/pygit2/remote.py index 6a2a7b99f..11bc86c60 100644 --- a/pygit2/remote.py +++ b/pygit2/remote.py @@ -144,6 +144,9 @@ def name(self): @name.setter def name(self, value): + if not value: + raise ValueError("New remote name must be a non-empty string") + err = C.git_remote_rename(self._remote, to_str(value), ffi.NULL, ffi.NULL) check_error(err) diff --git a/test/test_remote.py b/test/test_remote.py index 54ba27c9e..9c95f80cd 100644 --- a/test/test_remote.py +++ b/test/test_remote.py @@ -65,6 +65,7 @@ def test_remote_rename(self): self.assertEqual('new', remote.name) self.assertRaisesAssign(ValueError, remote, 'name', '') + self.assertRaisesAssign(ValueError, remote, 'name', None) def test_remote_set_url(self): From 4c4968a2fb63dd7a147bcbef4830ac9afaad4b3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Fri, 18 Apr 2014 12:17:54 +0200 Subject: [PATCH 0175/1630] Fix config documentation keyword The keyword for linking to a mehtod is 'meth', not 'method'. Setting the 'currentmodule' allows us to link without the 'pygit2' prefix in the link text. --- docs/config.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/config.rst b/docs/config.rst index 881fc1d70..5c091e752 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -22,11 +22,13 @@ The Config type aware that this may return multiple versions of each entry if they are set multiple times in the configuration files. +.. currentmodule:: pygit2 + The :class:`Config` Mapping interface. When using the mapping interface, the value is returned as a string. In order to apply the git-config parsing rules, you can use -:method:`Config.get_bool` or :method:`Config.get_int`. +:meth:`Config.get_bool` or :meth:`Config.get_int`. .. automethod:: pygit2.Config.get_bool .. automethod:: pygit2.Config.get_int From 569d396f0d5a99215b1fd5b058c7ab3646fac3a5 Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Fri, 18 Apr 2014 13:44:25 -0700 Subject: [PATCH 0176/1630] Use repo.head.target instead of repo.head.get_object().hex --- pygit2/repository.py | 2 +- src/repository.c | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/pygit2/repository.py b/pygit2/repository.py index 75650708e..e184ec2a6 100644 --- a/pygit2/repository.py +++ b/pygit2/repository.py @@ -78,7 +78,7 @@ def create_reference(self, name, target, force=False): Examples:: - repo.create_reference('refs/heads/foo', repo.head.get_object().hex) + repo.create_reference('refs/heads/foo', repo.head.target) repo.create_reference('refs/tags/foo', 'refs/heads/master') repo.create_reference('refs/tags/foo', 'bbb78a9cec580') """ diff --git a/src/repository.c b/src/repository.c index 2a606e13b..89120761c 100644 --- a/src/repository.c +++ b/src/repository.c @@ -1077,8 +1077,7 @@ PyDoc_STRVAR(Repository_create_reference_direct__doc__, "\n" "Examples::\n" "\n" - " repo.create_reference_direct('refs/heads/foo',\n" - " repo.head.get_object().hex, False)"); + " repo.create_reference_direct('refs/heads/foo', repo.head.target, False)"); PyObject * Repository_create_reference_direct(Repository *self, PyObject *args, From c41c1a1c97f5dae70160e96f77ad10d259fe8ec7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sun, 20 Apr 2014 18:18:25 +0200 Subject: [PATCH 0177/1630] Config: move to cffi This halves the amount of code we have to take into account for dealing with the config. There is a slight change in the API. Config.get_multivar() returns an iterator instead of a list, which lets us reuse code from the general iterator and is closer to libgit2's API. --- pygit2/__init__.py | 1 + pygit2/config.py | 259 +++++++++++++++++++++ pygit2/decl.h | 49 ++++ pygit2/errors.py | 7 +- pygit2/repository.py | 18 ++ src/config.c | 540 ------------------------------------------- src/config.h | 45 ---- src/pygit2.c | 7 - src/repository.c | 40 ---- src/types.h | 13 -- test/test_config.py | 13 +- 11 files changed, 339 insertions(+), 653 deletions(-) create mode 100644 pygit2/config.py delete mode 100644 src/config.c delete mode 100644 src/config.h diff --git a/pygit2/__init__.py b/pygit2/__init__.py index 2dbda72bf..62954f64b 100644 --- a/pygit2/__init__.py +++ b/pygit2/__init__.py @@ -38,6 +38,7 @@ from .settings import Settings from .credentials import * from .remote import Remote, get_credentials +from .config import Config from .errors import check_error from .ffi import ffi, C, to_str diff --git a/pygit2/config.py b/pygit2/config.py new file mode 100644 index 000000000..a6f238835 --- /dev/null +++ b/pygit2/config.py @@ -0,0 +1,259 @@ +# -*- coding: utf-8 -*- +# +# Copyright 2010-2014 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. + +# Import from the future +from __future__ import absolute_import, unicode_literals + +from _pygit2 import Oid + +from .ffi import ffi, C, to_str, is_string +from .errors import check_error, GitError +from .refspec import Refspec + +def assert_string(v, desc): + if not is_string(v): + raise TypeError("%s must be a string" % desc) + +class ConfigIterator(object): + + def __init__(self, config, ptr): + self._iter = ptr + self._config = config + + def __del__(self): + C.git_config_iterator_free(self._iter) + + def __iter__(self): + return self + + def _next_entry(self): + centry = ffi.new('git_config_entry **') + err = C.git_config_next(centry, self._iter) + check_error(err) + + return centry[0] + + def next(self): + return self.__next__() + + def __next__(self): + entry = self._next_entry() + name = ffi.string(entry.name).decode('utf-8') + value = ffi.string(entry.value).decode('utf-8') + + return name, value + +class ConfigMultivarIterator(ConfigIterator): + def __next__(self): + entry = self._next_entry() + + return ffi.string(entry.value).decode('utf-8') + +class Config(object): + """Git configuration management""" + + def __init__(self, path=None): + cconfig = ffi.new('git_config **') + + if not path: + err = C.git_config_new(cconfig) + else: + assert_string(path, "path") + err = C.git_config_open_ondisk(cconfig, to_str(path)) + + check_error(err, True) + self._config = cconfig[0] + + @classmethod + def from_c(cls, repo, ptr): + config = cls.__new__(cls) + config._repo = repo + config._config = ptr + + return config + + def __del__(self): + C.git_config_free(self._config) + + def _get(self, key): + assert_string(key, "key") + + cstr = ffi.new('char **') + err = C.git_config_get_string(cstr, self._config, to_str(key)) + + return err, cstr + + def _get_string(self, key): + err, cstr = self._get(key) + + if err == C.GIT_ENOTFOUND: + raise KeyError(key) + + check_error(err) + return cstr[0] + + def __contains__(self, key): + err, cstr = self._get(key) + + if err == C.GIT_ENOTFOUND: + return False + + check_error(err) + + return True + + def __getitem__(self, key): + val = self._get_string(key) + + return ffi.string(val).decode() + + def __setitem__(self, key, value): + assert_string(key, "key") + + err = 0 + if isinstance(value, bool): + err = C.git_config_set_bool(self._config, to_str(key), value) + elif isinstance(value, int): + err = C.git_config_set_int64(self._config, to_str(key), value) + else: + err = C.git_config_set_string(self._config, to_str(key), to_str(value)) + + check_error(err) + + def __delitem__(self, key): + assert_string(key, "key") + + err = C.git_config_delete_entry(self._config, to_str(key)) + check_error(err) + + def __iter__(self): + citer = ffi.new('git_config_iterator **') + err = C.git_config_iterator_new(citer, self._config) + check_error(err) + + return ConfigIterator(self, citer[0]) + + def get_multivar(self, name, regex=None): + """get_multivar(name[, regex]) -> [str, ...] + + Get each value of a multivar ''name'' as a list. The optional ''regex'' + parameter is expected to be a regular expression to filter the variables + we're interested in.""" + + assert_string(name, "name") + + citer = ffi.new('git_config_iterator **') + err = C.git_config_multivar_iterator_new(citer, self._config, to_str(name), to_str(regex)) + check_error(err) + + return ConfigMultivarIterator(self, citer[0]) + + def set_multivar(self, name, regex, value): + """set_multivar(name, regex, value) + + Set a multivar ''name'' to ''value''. ''regexp'' is a regular expression + to indicate which values to replace""" + + assert_string(name, "name") + assert_string(regex, "regex") + assert_string(value, "value") + + err = C.git_config_set_multivar(self._config, to_str(name), to_str(regex), to_str(value)) + check_error(err) + + def get_bool(self, key): + """get_bool(key) -> Bool + + Look up *key* and parse its value as a boolean as per the git-config rules + + Truthy values are: 'true', 1, 'on' or 'yes'. Falsy values are: 'false', + 0, 'off' and 'no'""" + + val = self._get_string(key) + res = ffi.new('int *') + err = C.git_config_parse_bool(res, val) + check_error(err) + + return res[0] != 0 + + def get_int(self, key): + """get_int(key) -> int + + Look up *key* and parse its value as an integer as per the git-config rules. + + A value can have a suffix 'k', 'm' or 'g' which stand for 'kilo', 'mega' and + 'giga' respectively""" + + val = self._get_string(key) + res = ffi.new('int64_t *') + err = C.git_config_parse_int64(res, val) + check_error(err) + + return res[0] + + def add_file(self, path, level=0, force=0): + """add_file(path, level=0, force=0) + + Add a config file instance to an existing config.""" + + err = C.git_config_add_file_ondisk(self._config, to_str(path), level, force) + check_error(err) + + # + # Static methods to get specialized version of the config + # + + @staticmethod + def _from_found_config(fn): + buf = ffi.new('char []', C.GIT_PATH_MAX) + err = fn(buf, C.GIT_PATH_MAX) + check_error(err, True) + return Config(ffi.string(buf).decode()) + + @staticmethod + def get_system_config(): + """get_system_config() -> Config + + Return an object representing the system configuration file.""" + + return Config._from_found_config(C.git_config_find_system) + + @staticmethod + def get_global_config(): + """get_global_config() -> Config + + Return an object representing the global configuration file.""" + + return Config._from_found_config(C.git_config_find_global) + + @staticmethod + def get_xdg_config(): + """get_xdg_config() -> Config + + Return an object representing the global configuration file.""" + + return Config._from_found_config(C.git_config_find_xdg) diff --git a/pygit2/decl.h b/pygit2/decl.h index a64c32fc4..29d4e1c74 100644 --- a/pygit2/decl.h +++ b/pygit2/decl.h @@ -5,8 +5,11 @@ typedef ... git_push; typedef ... git_cred; typedef ... git_diff_file; typedef ... git_tree; +typedef ... git_config; +typedef ... git_config_iterator; #define GIT_OID_RAWSZ ... +#define GIT_PATH_MAX ... typedef struct git_oid { unsigned char id[20]; @@ -212,3 +215,49 @@ int git_clone(git_repository **out, const char *url, const char *local_path, const git_clone_options *options); + + +typedef enum { + GIT_CONFIG_LEVEL_SYSTEM = 1, + GIT_CONFIG_LEVEL_XDG = 2, + GIT_CONFIG_LEVEL_GLOBAL = 3, + GIT_CONFIG_LEVEL_LOCAL = 4, + GIT_CONFIG_LEVEL_APP = 5, + GIT_CONFIG_HIGHEST_LEVEL = -1, +} git_config_level_t; + +typedef struct { + const char *name; + const char *value; + git_config_level_t level; +} git_config_entry; + +int git_repository_config(git_config **out, git_repository *repo); +void git_config_free(git_config *cfg); + +int git_config_get_string(const char **out, const git_config *cfg, const char *name); +int git_config_set_string(git_config *cfg, const char *name, const char *value); +int git_config_set_bool(git_config *cfg, const char *name, int value); +int git_config_set_int64(git_config *cfg, const char *name, int64_t value); + +int git_config_parse_bool(int *out, const char *value); +int git_config_parse_int64(int64_t *out, const char *value); + +int git_config_delete_entry(git_config *cfg, const char *name); +int git_config_add_file_ondisk(git_config *cfg, + const char *path, + git_config_level_t level, + int force); + +int git_config_iterator_new(git_config_iterator **out, const git_config *cfg); +int git_config_next(git_config_entry **entry, git_config_iterator *iter); +void git_config_iterator_free(git_config_iterator *iter); + +int git_config_multivar_iterator_new(git_config_iterator **out, const git_config *cfg, const char *name, const char *regexp); +int git_config_set_multivar(git_config *cfg, const char *name, const char *regexp, const char *value); + +int git_config_new(git_config **out); +int git_config_open_ondisk(git_config **out, const char *path); +int git_config_find_system(char *out, size_t length); +int git_config_find_global(char *out, size_t length); +int git_config_find_xdg(char *out, size_t length); diff --git a/pygit2/errors.py b/pygit2/errors.py index b9f6c9bfa..398d08c72 100644 --- a/pygit2/errors.py +++ b/pygit2/errors.py @@ -33,7 +33,7 @@ from _pygit2 import GitError -def check_error(err): +def check_error(err, io=False): if err >= 0: return @@ -45,7 +45,10 @@ def check_error(err): if err in [C.GIT_EEXISTS, C.GIT_EINVALIDSPEC, C.GIT_EEXISTS, C.GIT_EAMBIGUOUS]: raise ValueError(message) elif err == C.GIT_ENOTFOUND: - raise KeyError(message) + if io: + raise IOError(message) + else: + raise KeyError(message) elif err == C.GIT_EINVALIDSPEC: raise ValueError(message) elif err == C.GIT_ITEROVER: diff --git a/pygit2/repository.py b/pygit2/repository.py index 107db9128..2827c28bb 100644 --- a/pygit2/repository.py +++ b/pygit2/repository.py @@ -38,6 +38,7 @@ from .ffi import ffi, C, to_str from .errors import check_error from .remote import Remote +from .config import Config class Repository(_Repository): @@ -110,6 +111,23 @@ def remotes(self): C.git_strarray_free(names) + # + # Configuration + # + @property + def config(self): + """The configuration file for this repository + + If a the configuration hasn't been set yet, the default config for + repository will be returned, including global and system configurations + (if they are available).""" + + cconfig = ffi.new('git_config **') + err = C.git_repository_config(cconfig, self._repo) + check_error(err) + + return Config.from_c(self, cconfig[0]) + # # References # diff --git a/src/config.c b/src/config.c deleted file mode 100644 index 07dbb408a..000000000 --- a/src/config.c +++ /dev/null @@ -1,540 +0,0 @@ -/* - * Copyright 2010-2014 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 "error.h" -#include "types.h" -#include "utils.h" -#include "config.h" - -extern PyTypeObject ConfigType; -extern PyTypeObject ConfigIterType; - - -PyObject * -wrap_config(char *c_path) { - int err; - PyObject *py_path; - Config *py_config; - - py_path = Py_BuildValue("(s)", c_path); - py_config = PyObject_New(Config, &ConfigType); - - err = Config_init(py_config, py_path, NULL); - if (err < 0) - return NULL; - - return (PyObject*) py_config; -} - - -int -Config_init(Config *self, PyObject *args, PyObject *kwds) -{ - char *path = NULL; - int err; - - if (kwds && PyDict_Size(kwds) > 0) { - PyErr_SetString(PyExc_TypeError, - "Config takes no keyword arguments"); - return -1; - } - - if (!PyArg_ParseTuple(args, "|s", &path)) - return -1; - - if (path == NULL) - err = git_config_new(&self->config); - else - err = git_config_open_ondisk(&self->config, path); - - if (err < 0) { - git_config_free(self->config); - - if (err == GIT_ENOTFOUND) - Error_set_exc(PyExc_IOError); - else - Error_set(err); - - return -1; - } - - return 0; -} - - -void -Config_dealloc(Config *self) -{ - git_config_free(self->config); - Py_TYPE(self)->tp_free(self); -} - -PyDoc_STRVAR(Config_get_global_config__doc__, - "get_global_config() -> Config\n" - "\n" - "Return an object representing the global configuration file."); - -PyObject * -Config_get_global_config(void) -{ - char path[GIT_PATH_MAX]; - int err; - - err = git_config_find_global(path, GIT_PATH_MAX); - if (err < 0) { - if (err == GIT_ENOTFOUND) { - PyErr_SetString(PyExc_IOError, "Global config file not found."); - return NULL; - } - - return Error_set(err); - } - - return wrap_config(path); -} - - -PyDoc_STRVAR(Config_get_system_config__doc__, - "get_system_config() -> Config\n" - "\n" - "Return an object representing the system configuration file."); - -PyObject * -Config_get_system_config(void) -{ - char path[GIT_PATH_MAX]; - int err; - - err = git_config_find_system(path, GIT_PATH_MAX); - if (err < 0) { - if (err == GIT_ENOTFOUND) { - PyErr_SetString(PyExc_IOError, "System config file not found."); - return NULL; - } - return Error_set(err); - } - - return wrap_config(path); -} - - -int -Config_contains(Config *self, PyObject *py_key) { - int err; - const char *c_value, *c_key; - PyObject *tkey; - - c_key = py_str_borrow_c_str(&tkey, py_key, NULL); - if (c_key == NULL) - return -1; - - err = git_config_get_string(&c_value, self->config, c_key); - Py_DECREF(tkey); - - if (err < 0) { - if (err == GIT_ENOTFOUND) - return 0; - - Error_set(err); - return -1; - } - - return 1; -} - -/* Get the C string value given a python string as key */ -static int -get_string(const char **key_out, Config *self, PyObject *py_key) -{ - PyObject *tkey; - const char *key; - int err; - - key = py_str_borrow_c_str(&tkey, py_key, NULL); - if (key == NULL) - return -1; - - err = git_config_get_string(key_out, self->config, key); - Py_CLEAR(tkey); - - if (err == GIT_ENOTFOUND) { - PyErr_SetObject(PyExc_KeyError, py_key); - return -1; - } - - if (err < 0) { - Error_set(err); - return -1; - } - - return 0; -} - -PyObject * -Config_getitem(Config *self, PyObject *py_key) -{ - int err; - const char *value_str; - - err = get_string(&value_str, self, py_key); - if (err < 0) - return NULL; - - return to_unicode(value_str, NULL, NULL); -} - -PyDoc_STRVAR(Config_get_bool__doc__, - "get_bool(key) -> Bool\n" - "\n" - "Look up *key* and parse its value as a boolean as per the git-config rules\n" - "\n" - "Truthy values are: 'true', 1, 'on' or 'yes'. Falsy values are: 'false',\n" - "0, 'off' and 'no'"); - -PyObject * -Config_get_bool(Config *self, PyObject *key) -{ - int err, value; - const char *value_str; - - err = get_string(&value_str, self, key); - if (err < 0) - return NULL; - - if ((err = git_config_parse_bool(&value, value_str)) < 0) - return NULL; - - return PyBool_FromLong(value); -} - -PyDoc_STRVAR(Config_get_int__doc__, - "get_int(key) -> int\n" - "\n" - "Look up *key* and parse its value as an integer as per the git-config rules\n" - "\n" - "A value can have a suffix 'k', 'm' or 'g' which stand for 'kilo', 'mega' and\n" - "'giga' respectively"); - -PyObject * -Config_get_int(Config *self, PyObject *key) -{ - int err; - int64_t value; - const char *value_str; - - err = get_string(&value_str, self, key); - if (err < 0) - return NULL; - - if ((err = git_config_parse_int64(&value, value_str)) < 0) - return NULL; - - return PyLong_FromLongLong(value); -} - -int -Config_setitem(Config *self, PyObject *py_key, PyObject *py_value) -{ - int err; - const char *key, *value; - PyObject *tkey, *tvalue; - - key = py_str_borrow_c_str(&tkey, py_key, NULL); - if (key == NULL) - return -1; - - if (py_value == NULL) - err = git_config_delete_entry(self->config, key); - else if (PyBool_Check(py_value)) { - err = git_config_set_bool(self->config, key, - (int)PyObject_IsTrue(py_value)); - } else if (PyLong_Check(py_value)) { - err = git_config_set_int64(self->config, key, - (int64_t)PyLong_AsLong(py_value)); - } else { - value = py_str_borrow_c_str(&tvalue, py_value, NULL); - err = git_config_set_string(self->config, key, value); - Py_DECREF(tvalue); - } - - Py_DECREF(tkey); - if (err < 0) { - Error_set(err); - return -1; - } - return 0; -} - -PyDoc_STRVAR(Config_add_file__doc__, - "add_file(path, level=0, force=0)\n" - "\n" - "Add a config file instance to an existing config."); - -PyObject * -Config_add_file(Config *self, PyObject *args, PyObject *kwds) -{ - char *keywords[] = {"path", "level", "force", NULL}; - int err; - char *path; - unsigned int level = 0; - int force = 0; - - if (!PyArg_ParseTupleAndKeywords(args, kwds, "s|Ii", keywords, - &path, &level, &force)) - return NULL; - - err = git_config_add_file_ondisk(self->config, path, level, force); - if (err < 0) - return Error_set_str(err, path); - - Py_RETURN_NONE; -} - - -PyDoc_STRVAR(Config_get_multivar__doc__, - "get_multivar(name[, regex]) -> [str, ...]\n" - "\n" - "Get each value of a multivar ''name'' as a list. The optional ''regex''\n" - "parameter is expected to be a regular expression to filter the variables\n" - "we're interested in."); - -PyObject * -Config_get_multivar(Config *self, PyObject *args) -{ - int err; - PyObject *list; - const char *name = NULL; - const char *regex = NULL; - git_config_iterator *iter; - git_config_entry *entry; - - if (!PyArg_ParseTuple(args, "s|s", &name, ®ex)) - return NULL; - - list = PyList_New(0); - err = git_config_multivar_iterator_new(&iter, self->config, name, regex); - if (err < 0) - return Error_set(err); - - while ((err = git_config_next(&entry, iter)) == 0) { - PyObject *item; - - item = to_unicode(entry->value, NULL, NULL); - if (item == NULL) { - git_config_iterator_free(iter); - return NULL; - } - - PyList_Append(list, item); - Py_CLEAR(item); - } - - git_config_iterator_free(iter); - if (err == GIT_ITEROVER) - err = 0; - - if (err < 0) - return Error_set(err); - - return list; -} - - -PyDoc_STRVAR(Config_set_multivar__doc__, - "set_multivar(name, regex, value)\n" - "\n" - "Set a multivar ''name'' to ''value''. ''regexp'' is a regular expression\n" - "to indicate which values to replace"); - -PyObject * -Config_set_multivar(Config *self, PyObject *args) -{ - int err; - const char *name = NULL; - const char *regex = NULL; - const char *value = NULL; - - if (!PyArg_ParseTuple(args, "sss", &name, ®ex, &value)) - return NULL; - - err = git_config_set_multivar(self->config, name, regex, value); - if (err < 0) { - if (err == GIT_ENOTFOUND) - Error_set(err); - else - PyErr_SetNone(PyExc_TypeError); - return NULL; - } - - Py_RETURN_NONE; -} - -PyObject * -Config_iter(Config *self) -{ - ConfigIter *iter; - int err; - - iter = PyObject_New(ConfigIter, &ConfigIterType); - if (!iter) - return NULL; - - if ((err = git_config_iterator_new(&iter->iter, self->config)) < 0) - return Error_set(err); - - Py_INCREF(self); - iter->owner = self; - - return (PyObject*)iter; -} - -PyMethodDef Config_methods[] = { - METHOD(Config, get_system_config, METH_NOARGS | METH_STATIC), - METHOD(Config, get_global_config, METH_NOARGS | METH_STATIC), - METHOD(Config, add_file, METH_VARARGS | METH_KEYWORDS), - METHOD(Config, get_multivar, METH_VARARGS), - METHOD(Config, set_multivar, METH_VARARGS), - METHOD(Config, get_bool, METH_O), - METHOD(Config, get_int, METH_O), - {NULL} -}; - -PySequenceMethods Config_as_sequence = { - 0, /* sq_length */ - 0, /* sq_concat */ - 0, /* sq_repeat */ - 0, /* sq_item */ - 0, /* sq_slice */ - 0, /* sq_ass_item */ - 0, /* sq_ass_slice */ - (objobjproc)Config_contains, /* sq_contains */ -}; - -PyMappingMethods Config_as_mapping = { - 0, /* mp_length */ - (binaryfunc)Config_getitem, /* mp_subscript */ - (objobjargproc)Config_setitem, /* mp_ass_subscript */ -}; - - -PyDoc_STRVAR(Config__doc__, "Configuration management."); - -PyTypeObject ConfigType = { - PyVarObject_HEAD_INIT(NULL, 0) - "_pygit2.Config", /* tp_name */ - sizeof(Config), /* tp_basicsize */ - 0, /* tp_itemsize */ - (destructor)Config_dealloc, /* tp_dealloc */ - 0, /* tp_print */ - 0, /* tp_getattr */ - 0, /* tp_setattr */ - 0, /* tp_compare */ - 0, /* tp_repr */ - 0, /* tp_as_number */ - &Config_as_sequence, /* tp_as_sequence */ - &Config_as_mapping, /* 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 */ - Config__doc__, /* tp_doc */ - 0, /* tp_traverse */ - 0, /* tp_clear */ - 0, /* tp_richcompare */ - 0, /* tp_weaklistoffset */ - (getiterfunc)Config_iter, /* tp_iter */ - 0, /* tp_iternext */ - Config_methods, /* tp_methods */ - 0, /* tp_members */ - 0, /* tp_getset */ - 0, /* tp_base */ - 0, /* tp_dict */ - 0, /* tp_descr_get */ - 0, /* tp_descr_set */ - 0, /* tp_dictoffset */ - (initproc)Config_init, /* tp_init */ - 0, /* tp_alloc */ - 0, /* tp_new */ -}; - -void -ConfigIter_dealloc(ConfigIter *self) -{ - Py_CLEAR(self->owner); - git_config_iterator_free(self->iter); - PyObject_Del(self); -} - -PyObject * -ConfigIter_iternext(ConfigIter *self) -{ - int err; - git_config_entry *entry; - - if ((err = git_config_next(&entry, self->iter)) < 0) - return Error_set(err); - - return Py_BuildValue("ss", entry->name, entry->value); -} - -PyDoc_STRVAR(ConfigIter__doc__, "Configuration iterator."); - -PyTypeObject ConfigIterType = { - PyVarObject_HEAD_INIT(NULL, 0) - "_pygit2.ConfigIter", /* tp_name */ - sizeof(ConfigIter), /* tp_basicsize */ - 0, /* tp_itemsize */ - (destructor)ConfigIter_dealloc , /* 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 | Py_TPFLAGS_BASETYPE, /* tp_flags */ - ConfigIter__doc__, /* tp_doc */ - 0, /* tp_traverse */ - 0, /* tp_clear */ - 0, /* tp_richcompare */ - 0, /* tp_weaklistoffset */ - PyObject_SelfIter, /* tp_iter */ - (iternextfunc)ConfigIter_iternext, /* tp_iternext */ - -}; diff --git a/src/config.h b/src/config.h deleted file mode 100644 index ffa0733bb..000000000 --- a/src/config.h +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2010-2014 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_config_h -#define INCLUDE_pygit2_config_h - -#define PY_SSIZE_T_CLEAN -#include -#include - -PyObject* wrap_config(char *c_path); -PyObject* Config_get_global_config(void); -PyObject* Config_get_system_config(void); -PyObject* Config_add_file(Config *self, PyObject *args, PyObject *kwds); -PyObject* Config_getitem(Config *self, PyObject *key); -PyObject* Config_foreach(Config *self, PyObject *args); -PyObject* Config_get_multivar(Config *self, PyObject *args); -PyObject* Config_set_multivar(Config *self, PyObject *args); -int Config_init(Config *self, PyObject *args, PyObject *kwds); -int Config_setitem(Config *self, PyObject *key, PyObject *value); -#endif diff --git a/src/pygit2.c b/src/pygit2.c index d88c0e408..2ed080863 100644 --- a/src/pygit2.c +++ b/src/pygit2.c @@ -66,8 +66,6 @@ extern PyTypeObject IndexType; extern PyTypeObject IndexEntryType; extern PyTypeObject IndexIterType; extern PyTypeObject WalkerType; -extern PyTypeObject ConfigType; -extern PyTypeObject ConfigIterType; extern PyTypeObject ReferenceType; extern PyTypeObject RefLogIterType; extern PyTypeObject RefLogEntryType; @@ -385,11 +383,6 @@ moduleinit(PyObject* m) ADD_CONSTANT_INT(m, GIT_CONFIG_LEVEL_XDG); ADD_CONSTANT_INT(m, GIT_CONFIG_LEVEL_SYSTEM); - INIT_TYPE(ConfigType, NULL, PyType_GenericNew) - INIT_TYPE(ConfigIterType, NULL, NULL) - ADD_TYPE(m, Config) - ADD_TYPE(m, ConfigIter) - /* Blame */ INIT_TYPE(BlameType, NULL, NULL) INIT_TYPE(BlameIterType, NULL, NULL) diff --git a/src/repository.c b/src/repository.c index 1713136cf..acbe1d3fa 100644 --- a/src/repository.c +++ b/src/repository.c @@ -513,45 +513,6 @@ Repository_workdir__get__(Repository *self, void *closure) return to_path(c_path); } - -PyDoc_STRVAR(Repository_config__doc__, - "Get the configuration file for this repository.\n" - "\n" - "If a configuration file has not been set, the default config set for the\n" - "repository will be returned, including global and system configurations\n" - "(if they are available)."); - -PyObject * -Repository_config__get__(Repository *self) -{ - int err; - git_config *config; - Config *py_config; - - assert(self->repo); - - if (self->config == NULL) { - err = git_repository_config(&config, self->repo); - if (err < 0) - return Error_set(err); - - py_config = PyObject_New(Config, &ConfigType); - if (py_config == NULL) { - git_config_free(config); - return NULL; - } - - py_config->config = config; - self->config = (PyObject*)py_config; - /* We need 2 refs here. One is returned, one is kept internally. */ - Py_INCREF(self->config); - } else { - Py_INCREF(self->config); - } - - return self->config; -} - PyDoc_STRVAR(Repository_merge_base__doc__, "merge_base(oid, oid) -> Oid\n" "\n" @@ -1601,7 +1562,6 @@ PyGetSetDef Repository_getseters[] = { GETTER(Repository, head_is_unborn), GETTER(Repository, is_empty), GETTER(Repository, is_bare), - GETTER(Repository, config), GETTER(Repository, workdir), GETTER(Repository, default_signature), GETTER(Repository, _pointer), diff --git a/src/types.h b/src/types.h index e50f8341c..c39ade975 100644 --- a/src/types.h +++ b/src/types.h @@ -70,19 +70,6 @@ SIMPLE_TYPE(Tree, git_tree, tree) SIMPLE_TYPE(Blob, git_blob, blob) SIMPLE_TYPE(Tag, git_tag, tag) - -/* git_config */ -typedef struct { - PyObject_HEAD - git_config* config; -} Config; - -typedef struct { - PyObject_HEAD - Config *owner; - git_config_iterator *iter; -} ConfigIter; - /* git_note */ typedef struct { PyObject_HEAD diff --git a/test/test_config.py b/test/test_config.py index 937ed1bf9..5598c0580 100644 --- a/test/test_config.py +++ b/test/test_config.py @@ -115,9 +115,10 @@ def test_read(self): config.add_file(CONFIG_FILENAME, 0) self.assertTrue('this.that' in config) - self.assertEqual(len(config.get_multivar('this.that')), 2) - l = config.get_multivar('this.that', 'bar') - self.assertEqual(len(l), 1) + + self.assertEqual(2, len(list(config.get_multivar('this.that')))) + l = list(config.get_multivar('this.that', 'bar')) + self.assertEqual(1, len(l)) self.assertEqual(l[0], 'foobar') def test_write(self): @@ -155,16 +156,16 @@ def test_write(self): config.add_file(CONFIG_FILENAME, 5) self.assertTrue('this.that' in config) l = config.get_multivar('this.that', 'foo.*') - self.assertEqual(len(l), 2) + self.assertEqual(2, len(list(l))) config.set_multivar('this.that', '^.*beer', 'fool') - l = config.get_multivar('this.that', 'fool') + l = list(config.get_multivar('this.that', 'fool')) self.assertEqual(len(l), 1) self.assertEqual(l[0], 'fool') config.set_multivar('this.that', 'foo.*', 'foo-123456') l = config.get_multivar('this.that', 'foo.*') - self.assertEqual(len(l), 2) + self.assertEqual(2, len(list(l))) for i in l: self.assertEqual(i, 'foo-123456') From 1e13a1094915a48ff2a7a2552068986deaa88098 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sun, 20 Apr 2014 18:37:08 +0200 Subject: [PATCH 0178/1630] Config: expose config rules parsing Expose Config.parse_bool() and Config.parse_int() to parse text according to git-config rules. --- pygit2/config.py | 19 +++++++++++++++++++ test/test_config.py | 7 +++++++ 2 files changed, 26 insertions(+) diff --git a/pygit2/config.py b/pygit2/config.py index a6f238835..80f38dc43 100644 --- a/pygit2/config.py +++ b/pygit2/config.py @@ -223,6 +223,25 @@ def add_file(self, path, level=0, force=0): err = C.git_config_add_file_ondisk(self._config, to_str(path), level, force) check_error(err) + # + # Methods to parse a string according to the git-config rules + # + + @staticmethod + def parse_bool(text): + res = ffi.new('int *') + err = C.git_config_parse_bool(res, to_str(text)) + + return res[0] != 0 + + @staticmethod + def parse_int(text): + res = ffi.new('int64_t *') + err = C.git_config_parse_int64(res, to_str(text)) + check_error(err) + + return res[0] + # # Static methods to get specialized version of the config # diff --git a/test/test_config.py b/test/test_config.py index 5598c0580..9787c3e13 100644 --- a/test/test_config.py +++ b/test/test_config.py @@ -179,5 +179,12 @@ def test_iterator(self): self.assertTrue('core.bare' in lst) self.assertTrue(lst['core.bare']) + def test_parsing(self): + self.assertTrue(Config.parse_bool("on")) + self.assertTrue(Config.parse_bool("1")) + + self.assertEqual(5, Config.parse_int("5")) + self.assertEqual(1024, Config.parse_int("1k")) + if __name__ == '__main__': unittest.main() From 1c76d5667a9a9f77c84805f1cbf41f5396b1b07d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Wed, 23 Apr 2014 12:25:23 +0200 Subject: [PATCH 0179/1630] Blob: implement the memory buffer interface This allows us to expose access to the blob's data without the need to copy it into new buffer. --- src/blob.c | 60 +++++++++++++++++++++++++++++++++++++++++++++-- test/test_blob.py | 7 ++++++ 2 files changed, 65 insertions(+), 2 deletions(-) diff --git a/src/blob.c b/src/blob.c index 133d49423..de0141bf8 100644 --- a/src/blob.c +++ b/src/blob.c @@ -158,8 +158,58 @@ PyGetSetDef Blob_getseters[] = { {NULL} }; +static int +Blob_getbuffer(Blob *self, Py_buffer *view, int flags) +{ + return PyBuffer_FillInfo(view, (PyObject *) self, + (void *) git_blob_rawcontent(self->blob), + git_blob_rawsize(self->blob), 1, flags); +} + +#if PY_MAJOR_VERSION == 2 -PyDoc_STRVAR(Blob__doc__, "Blob objects."); +static Py_ssize_t +Blob_getreadbuffer(Blob *self, Py_ssize_t index, const void **ptr) +{ + if (index != 0) { + PyErr_SetString(PyExc_SystemError, + "accessing non-existent blob segment"); + return -1; + } + *ptr = (void *) git_blob_rawcontent(self->blob); + return git_blob_rawsize(self->blob); +} + +static Py_ssize_t +Blob_getsegcount(Blob *self, Py_ssize_t *lenp) +{ + if (lenp) + *lenp = git_blob_rawsize(self->blob); + + return 1; +} + +static PyBufferProcs Blob_as_buffer = { + (readbufferproc)Blob_getreadbuffer, + NULL, /* bf_getwritebuffer */ + (segcountproc)Blob_getsegcount, + NULL, /* charbufferproc */ + (getbufferproc)Blob_getbuffer, +}; + +#else + +static PyBufferProcs Blob_as_buffer = { + (getbufferproc)Blob_getbuffer, +}; + +#endif /* python 2 vs python 3 buffers */ + +PyDoc_STRVAR(Blob__doc__, "Blob object.\n" + "\n" + "Blobs implement the buffer interface, which means you can get access\n" + "to its data via `memoryview(blob)` without the need to create a copy." +); PyTypeObject BlobType = { PyVarObject_HEAD_INIT(NULL, 0) @@ -180,8 +230,14 @@ PyTypeObject BlobType = { 0, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ - 0, /* tp_as_buffer */ + &Blob_as_buffer, /* tp_as_buffer */ +#if PY_MAJOR_VERSION == 2 + Py_TPFLAGS_DEFAULT | /* tp_flags */ + Py_TPFLAGS_HAVE_GETCHARBUFFER | + Py_TPFLAGS_HAVE_NEWBUFFER, +#else Py_TPFLAGS_DEFAULT, /* tp_flags */ +#endif Blob__doc__, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ diff --git a/test/test_blob.py b/test/test_blob.py index da3db3e8f..9fd1d4f9e 100644 --- a/test/test_blob.py +++ b/test/test_blob.py @@ -74,6 +74,13 @@ def test_create_blob(self): self.assertEqual(BLOB_NEW_CONTENT, blob.data) self.assertEqual(len(BLOB_NEW_CONTENT), blob.size) self.assertEqual(BLOB_NEW_CONTENT, blob.read_raw()) + blob_buffer = memoryview(blob) + self.assertEqual(len(BLOB_NEW_CONTENT), len(blob_buffer)) + self.assertEqual(BLOB_NEW_CONTENT, blob_buffer) + def set_content(): + blob_buffer[:2] = b'hi' + + self.assertRaises(TypeError, set_content) def test_create_blob_fromworkdir(self): From 17ba85831bb9ab6ed28e0565d64db097b873968f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Wed, 23 Apr 2014 14:01:09 +0200 Subject: [PATCH 0180/1630] Drop official support for Python 2.6 --- .travis.yml | 1 - README.rst | 8 ++++++-- docs/index.rst | 7 +++++-- docs/install.rst | 2 +- 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4fbf40373..8440098d0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,6 @@ language: python python: - - "2.6" - "2.7" - "3.2" - "3.3" diff --git a/README.rst b/README.rst index 04c436622..48ffc0686 100644 --- a/README.rst +++ b/README.rst @@ -6,8 +6,12 @@ pygit2 - libgit2 bindings in Python :target: http://travis-ci.org/libgit2/pygit2 Pygit2 is a set of Python bindings to the libgit2 shared library, libgit2 -implements the core of Git. Pygit2 works with Python 2.6, 2.7, 3.1, 3.2 and -3.3 +implements the core of Git. Pygit2 works with Python 2.7, 3.2, 3.3, 3.4 and +pypy. + +It is likely to work with Python 2.6 and 3.1, but these versions are not +officially supported. + Pygit2 links: diff --git a/docs/index.rst b/docs/index.rst index 6eccbd564..62f112fc3 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -10,8 +10,11 @@ Welcome to pygit2's documentation! :target: http://travis-ci.org/libgit2/pygit2 Pygit2 is a set of Python bindings to the libgit2 shared library, libgit2 -implements the core of Git. Pygit2 works with Python 2.6, 2.7, 3.1, 3.2 and -3.3 +implements the core of Git. Pygit2 works with Python 2.7, 3.2, 3.3, 3.4 and +pypy. + +It is likely to work with Python 2.6 and 3.1, but these versions are not +officially supported. Pygit2 links: diff --git a/docs/install.rst b/docs/install.rst index f74e2d7ac..5f058f22d 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -13,7 +13,7 @@ website: http://libgit2.github.com -Also, make sure you have Python 2.6+ installed together with the Python +Also, make sure you have Python 2.7 or 3.2+ installed together with the Python development headers. When those are installed, you can install pygit2: From c68de8e2b8a0c95b90f033dd7883460e39d1f112 Mon Sep 17 00:00:00 2001 From: Jun Omae Date: Mon, 28 Apr 2014 16:24:49 +0900 Subject: [PATCH 0181/1630] make build options.c with VS2008 compiler --- src/options.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/options.c b/src/options.c index abfa4656a..072a5ae24 100644 --- a/src/options.c +++ b/src/options.c @@ -41,10 +41,11 @@ get_search_path(long level) size_t len = 64; PyObject *py_path; int error; + char *tmp; do { len *= 2; - char *tmp = realloc(buf, len); + tmp = realloc(buf, len); if (!tmp) { free(buf); PyErr_NoMemory(); From 1fbe52c0f753d36ad5f71b9dd1be75c21532b223 Mon Sep 17 00:00:00 2001 From: Daniel Bruce Date: Tue, 29 Apr 2014 12:38:30 +0200 Subject: [PATCH 0182/1630] Add version check to C code --- src/types.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/types.h b/src/types.h index c39ade975..15f7bfedb 100644 --- a/src/types.h +++ b/src/types.h @@ -32,6 +32,10 @@ #include #include +#if !(LIBGIT2_VER_MAJOR == 0 && LIBGIT2_VER_MINOR == 20) +#error You need a compatible libgit2 version (v0.20.x) +#endif + /* * Python objects * From 0c62c83135a799535cda5ce0e33a3f72afc634ca Mon Sep 17 00:00:00 2001 From: "Ian P. McCullough" Date: Thu, 8 May 2014 08:50:21 -0400 Subject: [PATCH 0183/1630] Fix format string for Blob.diff(); Format string items out of order relative to docstring and outargs. --- src/blob.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/blob.c b/src/blob.c index 133d49423..d1f5f3b08 100644 --- a/src/blob.c +++ b/src/blob.c @@ -63,7 +63,7 @@ Blob_diff(Blob *self, PyObject *args, PyObject *kwds) int err; char *keywords[] = {"blob", "flag", "old_as_path", "new_as_path", NULL}; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "|O!ssI", keywords, + if (!PyArg_ParseTupleAndKeywords(args, kwds, "|O!Iss", keywords, &BlobType, &py_blob, &opts.flags, &old_as_path, &new_as_path)) return NULL; From 6b3f9e92f7a67cdb14f86ef972bd41311361afec Mon Sep 17 00:00:00 2001 From: "Ian P. McCullough" Date: Thu, 8 May 2014 09:01:06 -0400 Subject: [PATCH 0184/1630] And on diff_to_buffer too. --- src/blob.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/blob.c b/src/blob.c index d1f5f3b08..076827bfd 100644 --- a/src/blob.c +++ b/src/blob.c @@ -106,7 +106,7 @@ Blob_diff_to_buffer(Blob *self, PyObject *args, PyObject *kwds) char *keywords[] = {"buffer", "flag", "old_as_path", "buffer_as_path", NULL}; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "|s#ssI", keywords, + if (!PyArg_ParseTupleAndKeywords(args, kwds, "|s#Iss", keywords, &buffer, &buffer_len, &opts.flags, &old_as_path, &buffer_as_path)) return NULL; From b1bacdd8d58923859eb5b3f015a8f142c9420436 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sun, 18 May 2014 11:03:30 +0200 Subject: [PATCH 0185/1630] Wrap config snapshot functions These allow complex reads to come from the same version of the config. --- pygit2/config.py | 13 +++++++++++++ pygit2/decl.h | 2 ++ pygit2/repository.py | 13 +++++++++++++ 3 files changed, 28 insertions(+) diff --git a/pygit2/config.py b/pygit2/config.py index 6bbe6d20c..a757b9713 100644 --- a/pygit2/config.py +++ b/pygit2/config.py @@ -223,6 +223,19 @@ def add_file(self, path, level=0, force=0): err = C.git_config_add_file_ondisk(self._config, to_str(path), level, force) check_error(err) + def snapshot(self): + """Create a snapshot from this Config object + + This means that looking up multiple values will use the same version + of the configuration files + """ + + ccfg = ffi.new('git_config **') + err = C.git_config_snapshot(cfg, self._config) + check_error(err) + + return Config.from_c(self._repo, ccfg[0]) + # # Methods to parse a string according to the git-config rules # diff --git a/pygit2/decl.h b/pygit2/decl.h index a628f3b57..b26147292 100644 --- a/pygit2/decl.h +++ b/pygit2/decl.h @@ -255,6 +255,7 @@ typedef struct { } git_config_entry; int git_repository_config(git_config **out, git_repository *repo); +int git_repository_config_snapshot(git_config **out, git_repository *repo); void git_config_free(git_config *cfg); int git_config_get_string(const char **out, const git_config *cfg, const char *name); @@ -279,6 +280,7 @@ int git_config_multivar_iterator_new(git_config_iterator **out, const git_config int git_config_set_multivar(git_config *cfg, const char *name, const char *regexp, const char *value); int git_config_new(git_config **out); +int git_config_snapshot(git_config **out, git_config *config); int git_config_open_ondisk(git_config **out, const char *path); int git_config_find_system(git_buf *out); int git_config_find_global(git_buf *out); diff --git a/pygit2/repository.py b/pygit2/repository.py index 3f18c2c40..db047f5ee 100644 --- a/pygit2/repository.py +++ b/pygit2/repository.py @@ -128,6 +128,19 @@ def config(self): return Config.from_c(self, cconfig[0]) + @property + def config_snapshot(self): + """A snapshot for this repositiory's configuration + + This allows reads over multiple values to use the same version + of the configuration files""" + + cconfig = ffi.new('git_config **') + err = C.git_repository_config_snapshot(cconfig, self._repo) + check_error(err) + + return Config.from_c(self, cconfig[0]) + # # References # From 19be4b6aa4a8e0b06f9f7bbe0cfe071ee40e35f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Fri, 16 May 2014 03:18:14 +0200 Subject: [PATCH 0186/1630] clone: wrap clone_into() This allows the user to prepare the repository and remote with whichever custom settings they want before performing the "clone" proper. --- pygit2/__init__.py | 23 +++++++++++++++++++++++ pygit2/decl.h | 1 + test/test_repository.py | 9 ++++++++- 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/pygit2/__init__.py b/pygit2/__init__.py index 62954f64b..9aa0c428e 100644 --- a/pygit2/__init__.py +++ b/pygit2/__init__.py @@ -129,4 +129,27 @@ def clone_repository( return Repository(path) +def clone_into(repo, remote, branch=None): + """Clone into an empty repository from the specified remote + + :param Repository repo: The empty repository into which to clone + + :param Remote remote: The remote from which to clone + + :param str branch: Branch to checkout after the clone. Pass None + to use the remotes's default branch. + + This allows you specify arbitrary repository and remote configurations + before performing the clone step itself. E.g. you can replicate git-clone's + '--mirror' option by setting a refspec of '+refs/*:refs/*', 'core.mirror' to true + and calling this function. + """ + + err = C.git_clone_into(repo._repo, remote._remote, ffi.NULL, to_str(branch)) + + if remote._stored_exception: + raise remote._stored_exception + + check_error(err) + settings = Settings() diff --git a/pygit2/decl.h b/pygit2/decl.h index 29d4e1c74..9f82b1cbd 100644 --- a/pygit2/decl.h +++ b/pygit2/decl.h @@ -216,6 +216,7 @@ int git_clone(git_repository **out, const char *local_path, const git_clone_options *options); +int git_clone_into(git_repository *repo, git_remote *remote, const git_checkout_opts *co_opts, const char *branch); typedef enum { GIT_CONFIG_LEVEL_SYSTEM = 1, diff --git a/test/test_repository.py b/test/test_repository.py index 71214ccef..e70fe9779 100644 --- a/test/test_repository.py +++ b/test/test_repository.py @@ -40,7 +40,7 @@ # Import from pygit2 from pygit2 import GIT_OBJ_ANY, GIT_OBJ_BLOB, GIT_OBJ_COMMIT -from pygit2 import init_repository, clone_repository, discover_repository +from pygit2 import init_repository, clone_repository, clone_into, discover_repository from pygit2 import Oid, Reference, hashfile import pygit2 from . import utils @@ -461,6 +461,13 @@ def test_clone_remote_name(self): self.assertFalse(repo.is_empty) self.assertEqual(repo.remotes[0].name, "custom_remote") + def test_clone_into(self): + repo_path = "./test/data/testrepo.git/" + repo = init_repository(os.path.join(self._temp_dir, "clone-into")) + remote = repo.create_remote("origin", 'file://' + os.path.realpath(repo_path)) + clone_into(repo, remote) + self.assertTrue('refs/remotes/origin/master' in repo.listall_references()) + def test_clone_with_credentials(self): credentials = pygit2.UserPass("libgit2", "libgit2") repo = clone_repository( From 97c0e476a31ccc7fc0d9a8c238892f99f1299582 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Tue, 27 May 2014 18:06:28 +0200 Subject: [PATCH 0187/1630] Index: add failing tests for a standalone Index The index can exist purely as a data structure. Let's test that so we make sure we support that. --- test/test_index.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/test/test_index.py b/test/test_index.py index 7bed86bb3..eb7fa05d7 100644 --- a/test/test_index.py +++ b/test/test_index.py @@ -34,7 +34,7 @@ import tempfile import pygit2 -from pygit2 import Repository +from pygit2 import Repository, Index from . import utils @@ -206,5 +206,19 @@ def test_create_entry(self): tree_id = index.write_tree() self.assertEqual('60e769e57ae1d6a2ab75d8d253139e6260e1f912', str(tree_id)) +class StandaloneIndexTest(utils.RepoTestCase): + + def test_create_empty(self): + index = Index() + + def test_create_empty_read_tree_as_string(self): + index = Index() + # no repo associated, so we don't know where to read from + self.assertRaises(TypeError, index, 'read_tree', 'fd937514cb799514d4b81bb24c5fcfeb6472b245') + + def test_create_empty_read_tree(self): + index = Index() + index.read_tree(self.repo['fd937514cb799514d4b81bb24c5fcfeb6472b245']) + if __name__ == '__main__': unittest.main() From f69a57a82a05c161511cb4f651aa231dd6e05ad7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Tue, 27 May 2014 18:10:06 +0200 Subject: [PATCH 0188/1630] Index: make the file optional There is no obligation for an index to exist as a file at all. --- src/index.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/index.c b/src/index.c index 726974986..0cb8207b7 100644 --- a/src/index.c +++ b/src/index.c @@ -45,7 +45,7 @@ extern PyTypeObject RepositoryType; int Index_init(Index *self, PyObject *args, PyObject *kwds) { - char *path; + char *path = NULL; int err; if (kwds && PyDict_Size(kwds) > 0) { @@ -53,9 +53,10 @@ Index_init(Index *self, PyObject *args, PyObject *kwds) return -1; } - if (!PyArg_ParseTuple(args, "s", &path)) + if (!PyArg_ParseTuple(args, "|s", &path)) return -1; + self->repo = NULL; err = git_index_open(&self->index, path); if (err < 0) { Error_set_str(err, path); From 9e91a390cc39625ebc72d609e1be6d04c71fab25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Tue, 27 May 2014 18:24:53 +0200 Subject: [PATCH 0189/1630] Index: accept a tree for read_tree() An index may not have an associated repository, so giving it an id in that case is useless. Raise an error in that case and accept a Tree object to make the function useful then. --- docs/working-copy.rst | 8 ++++++++ src/index.c | 38 +++++++++++++++++++++++++++++--------- 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/docs/working-copy.rst b/docs/working-copy.rst index 76bcada87..88762b706 100644 --- a/docs/working-copy.rst +++ b/docs/working-copy.rst @@ -26,6 +26,14 @@ Custom entries:: >>> entry = pygit2.IndexEntry('README.md', blob_id, blob_filemode) >>> repo.index.add(entry) +The index fulfills a dual role as the in-memory representation of the +index file and data structure which represents a flat list of a +tree. You can use it independently of the index file, e.g. + + >>> index = pygit2.Index() + >>> entry = pygit2.IndexEntry('README.md', blob_id, blob_filemode) + >>> index.add(entry) + The Index type ==================== diff --git a/src/index.c b/src/index.c index 0cb8207b7..6a855f41a 100644 --- a/src/index.c +++ b/src/index.c @@ -426,26 +426,46 @@ Index_remove(Index *self, PyObject *args) PyDoc_STRVAR(Index_read_tree__doc__, "read_tree(tree)\n" "\n" - "Update the index file from the tree identified by the given oid."); + "Update the index file from the specified tree. The tree can be a Tree object or an Oid.\n" + "Using an Oid is only possible if this index is associated with a repository"); PyObject * Index_read_tree(Index *self, PyObject *value) { git_oid oid; - git_tree *tree; - int err; + git_tree *tree = NULL; + int err, need_free = 0; size_t len; len = py_oid_to_git_oid(value, &oid); - if (len == 0) - return NULL; + if (len == 0) { + Tree *py_tree; + if (!PyObject_TypeCheck(value, &TreeType)) { + return NULL; + } - err = git_tree_lookup_prefix(&tree, self->repo->repo, &oid, len); - if (err < 0) - return Error_set(err); + PyErr_Clear(); + py_tree = (Tree *) value; + tree = py_tree->tree; + } + + /* + * if the user passed in an id but we're not associated with a + * repo, we can't do anything + */ + if (tree == NULL && self->repo == NULL) { + PyErr_SetString(PyExc_TypeError, "id given but no associated repository"); + return NULL; + } else if (tree == NULL) { + need_free = 1; + err = git_tree_lookup_prefix(&tree, self->repo->repo, &oid, len); + if (err < 0) + return Error_set(err); + } err = git_index_read_tree(self->index, tree); - git_tree_free(tree); + if (need_free) + git_tree_free(tree); if (err < 0) return Error_set(err); From 6cf06ba9fe89e0673c0d8c96d1fc8cbae738e852 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Mon, 2 Jun 2014 18:50:55 +0200 Subject: [PATCH 0190/1630] Rewrite init_repository using cffi --- pygit2/__init__.py | 6 ++++-- pygit2/decl.h | 9 ++++++++- src/pygit2.c | 32 -------------------------------- 3 files changed, 12 insertions(+), 35 deletions(-) diff --git a/pygit2/__init__.py b/pygit2/__init__.py index 9aa0c428e..a067a3bfe 100644 --- a/pygit2/__init__.py +++ b/pygit2/__init__.py @@ -29,7 +29,6 @@ from __future__ import absolute_import # Low level API -import _pygit2 from _pygit2 import * # High level API @@ -49,7 +48,10 @@ def init_repository(path, bare=False): If *bare* is True the repository will be bare, i.e. it will not have a working copy. """ - _pygit2.init_repository(path, bare) + crepository = ffi.new('git_repository **') + err = C.git_repository_init(crepository, to_str(path), bare) + check_error(err) + return Repository(path) diff --git a/pygit2/decl.h b/pygit2/decl.h index 9f82b1cbd..1fb28e7d2 100644 --- a/pygit2/decl.h +++ b/pygit2/decl.h @@ -254,7 +254,10 @@ int git_config_iterator_new(git_config_iterator **out, const git_config *cfg); int git_config_next(git_config_entry **entry, git_config_iterator *iter); void git_config_iterator_free(git_config_iterator *iter); -int git_config_multivar_iterator_new(git_config_iterator **out, const git_config *cfg, const char *name, const char *regexp); +int git_config_multivar_iterator_new(git_config_iterator **out, + const git_config *cfg, const char *name, + const char *regexp); + int git_config_set_multivar(git_config *cfg, const char *name, const char *regexp, const char *value); int git_config_new(git_config **out); @@ -262,3 +265,7 @@ int git_config_open_ondisk(git_config **out, const char *path); int git_config_find_system(char *out, size_t length); int git_config_find_global(char *out, size_t length); int git_config_find_xdg(char *out, size_t length); + + +int git_repository_init(git_repository **out, const char *path, + unsigned is_bare); diff --git a/src/pygit2.c b/src/pygit2.c index 2ed080863..4808f7437 100644 --- a/src/pygit2.c +++ b/src/pygit2.c @@ -82,37 +82,6 @@ extern PyTypeObject MergeResultType; -PyDoc_STRVAR(init_repository__doc__, - "init_repository(path, bare)\n" - "\n" - "Creates a new Git repository in the given path.\n" - "\n" - "Arguments:\n" - "\n" - "path\n" - " Path where to create the repository.\n" - "\n" - "bare\n" - " Whether the repository will be bare or not.\n"); - -PyObject * -init_repository(PyObject *self, PyObject *args) { - git_repository *repo; - const char *path; - unsigned int bare; - int err; - - if (!PyArg_ParseTuple(args, "sI", &path, &bare)) - return NULL; - - err = git_repository_init(&repo, path, bare); - if (err < 0) - return Error_set_str(err, path); - - git_repository_free(repo); - Py_RETURN_NONE; -}; - PyDoc_STRVAR(discover_repository__doc__, "discover_repository(path[, across_fs[, ceiling_dirs]]) -> str\n" "\n" @@ -186,7 +155,6 @@ hash(PyObject *self, PyObject *args) PyMethodDef module_methods[] = { - {"init_repository", init_repository, METH_VARARGS, init_repository__doc__}, {"discover_repository", discover_repository, METH_VARARGS, discover_repository__doc__}, {"hashfile", hashfile, METH_VARARGS, hashfile__doc__}, From 95e6593625f2a870c8729bc65537b19f0b5df8aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Tue, 3 Jun 2014 12:52:46 +0200 Subject: [PATCH 0191/1630] init_repository now wraps git_repository_init_ext Fixes #347 --- pygit2/__init__.py | 46 +++++++++++++++++- pygit2/decl.h | 113 ++++++++++++++++++++++++++++++++++++--------- pygit2/ffi.py | 9 ++-- 3 files changed, 139 insertions(+), 29 deletions(-) diff --git a/pygit2/__init__.py b/pygit2/__init__.py index a067a3bfe..eed50c81a 100644 --- a/pygit2/__init__.py +++ b/pygit2/__init__.py @@ -41,17 +41,59 @@ from .errors import check_error from .ffi import ffi, C, to_str -def init_repository(path, bare=False): +def init_repository(path, bare=False, + flags=C.GIT_REPOSITORY_INIT_MKPATH, + mode=0, + workdir_path=None, + description=None, + template_path=None, + initial_head=None, + origin_url=None): """ Creates a new Git repository in the given *path*. If *bare* is True the repository will be bare, i.e. it will not have a working copy. + + The *flags* may be a combination of: + + - GIT_REPOSITORY_INIT_BARE (overriden by the *bare* parameter) + - GIT_REPOSITORY_INIT_NO_REINIT + - GIT_REPOSITORY_INIT_NO_DOTGIT_DIR + - GIT_REPOSITORY_INIT_MKDIR + - GIT_REPOSITORY_INIT_MKPATH (set by default) + - GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE + + The *mode* parameter may be any of GIT_REPOSITORY_SHARED_UMASK (default), + GIT_REPOSITORY_SHARED_GROUP or GIT_REPOSITORY_INIT_SHARED_ALL, or a custom + value. + + The *workdir_path*, *description*, *template_path*, *initial_head* and + *origin_url* are all strings. + + See libgit2's documentation on git_repository_init_ext for further details. """ + # Pre-process input parameters + if bare: + flags |= C.GIT_REPOSITORY_INIT_BARE + + # Options + options = ffi.new('git_repository_init_options *') + options.version = 1 + options.flags = flags + options.mode = mode + options.workdir_path = to_str(workdir_path) + options.description = to_str(description) + options.template_path = to_str(template_path) + options.initial_head = to_str(initial_head) + options.origin_url = to_str(origin_url) + + # Call crepository = ffi.new('git_repository **') - err = C.git_repository_init(crepository, to_str(path), bare) + err = C.git_repository_init_ext(crepository, to_str(path), options) check_error(err) + # Ok return Repository(path) diff --git a/pygit2/decl.h b/pygit2/decl.h index 1fb28e7d2..fd1c22ee5 100644 --- a/pygit2/decl.h +++ b/pygit2/decl.h @@ -5,8 +5,6 @@ typedef ... git_push; typedef ... git_cred; typedef ... git_diff_file; typedef ... git_tree; -typedef ... git_config; -typedef ... git_config_iterator; #define GIT_OID_RAWSZ ... #define GIT_PATH_MAX ... @@ -20,6 +18,7 @@ typedef struct git_strarray { size_t count; } git_strarray; + typedef enum { GIT_OK = 0, GIT_ERROR = -1, @@ -71,11 +70,13 @@ typedef enum { GIT_DIRECTION_PUSH = 1 } git_direction; + typedef enum { - GIT_CREDTYPE_USERPASS_PLAINTEXT = ..., - GIT_CREDTYPE_SSH_KEY = ..., - GIT_CREDTYPE_SSH_CUSTOM = ..., - GIT_CREDTYPE_DEFAULT = ..., + GIT_CREDTYPE_USERPASS_PLAINTEXT, + GIT_CREDTYPE_SSH_KEY, + GIT_CREDTYPE_SSH_CUSTOM, + GIT_CREDTYPE_DEFAULT, + ... } git_credtype_t; typedef struct git_remote_callbacks { @@ -90,10 +91,12 @@ typedef struct git_remote_callbacks { int git_remote_list(git_strarray *out, git_repository *repo); int git_remote_load(git_remote **out, git_repository *repo, const char *name); -int git_remote_create(git_remote **out, - git_repository *repo, - const char *name, - const char *url); +int git_remote_create( + git_remote **out, + git_repository *repo, + const char *name, + const char *url); + const char * git_remote_name(const git_remote *remote); typedef int (*git_remote_rename_problem_cb)(const char *problematic_refspec, void *payload); int git_remote_rename(git_remote *remote, @@ -125,9 +128,10 @@ int git_push_add_refspec(git_push *push, const char *refspec); int git_push_finish(git_push *push); int git_push_unpack_ok(git_push *push); -int git_push_status_foreach(git_push *push, - int (*cb)(const char *ref, const char *msg, void *data), - void *data); +int git_push_status_foreach( + git_push *push, + int (*cb)(const char *ref, const char *msg, void *data), + void *data); int git_push_update_tips(git_push *push); void git_push_free(git_push *push); @@ -155,6 +159,10 @@ int git_cred_ssh_key_new( const char *privatekey, const char *passphrase); +/* + * git_checkout + */ + typedef enum { ... } git_checkout_notify_t; typedef int (*git_checkout_notify_cb)( @@ -199,6 +207,10 @@ typedef struct git_checkout_opts { } git_checkout_opts; +/* + * git_clone + */ + typedef struct git_clone_options { unsigned int version; @@ -212,11 +224,22 @@ typedef struct git_clone_options { } git_clone_options; int git_clone(git_repository **out, - const char *url, - const char *local_path, - const git_clone_options *options); + const char *url, + const char *local_path, + const git_clone_options *options); + +int git_clone_into( + git_repository *repo, + git_remote *remote, + const git_checkout_opts *co_opts, + const char *branch); -int git_clone_into(git_repository *repo, git_remote *remote, const git_checkout_opts *co_opts, const char *branch); +/* + * git_config + */ + +typedef ... git_config; +typedef ... git_config_iterator; typedef enum { GIT_CONFIG_LEVEL_SYSTEM = 1, @@ -254,11 +277,17 @@ int git_config_iterator_new(git_config_iterator **out, const git_config *cfg); int git_config_next(git_config_entry **entry, git_config_iterator *iter); void git_config_iterator_free(git_config_iterator *iter); -int git_config_multivar_iterator_new(git_config_iterator **out, - const git_config *cfg, const char *name, - const char *regexp); +int git_config_multivar_iterator_new( + git_config_iterator **out, + const git_config *cfg, + const char *name, + const char *regexp); -int git_config_set_multivar(git_config *cfg, const char *name, const char *regexp, const char *value); +int git_config_set_multivar( + git_config *cfg, + const char *name, + const char *regexp, + const char *value); int git_config_new(git_config **out); int git_config_open_ondisk(git_config **out, const char *path); @@ -267,5 +296,43 @@ int git_config_find_global(char *out, size_t length); int git_config_find_xdg(char *out, size_t length); -int git_repository_init(git_repository **out, const char *path, - unsigned is_bare); +/* + * git_repository_init + */ +typedef enum { + GIT_REPOSITORY_INIT_BARE, + GIT_REPOSITORY_INIT_NO_REINIT, + GIT_REPOSITORY_INIT_NO_DOTGIT_DIR, + GIT_REPOSITORY_INIT_MKDIR, + GIT_REPOSITORY_INIT_MKPATH, + GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE, + ... +} git_repository_init_flag_t; + +typedef enum { + GIT_REPOSITORY_INIT_SHARED_UMASK, + GIT_REPOSITORY_INIT_SHARED_GROUP, + GIT_REPOSITORY_INIT_SHARED_ALL, + ... +} git_repository_init_mode_t; + +typedef struct { + unsigned int version; + uint32_t flags; + uint32_t mode; + const char *workdir_path; + const char *description; + const char *template_path; + const char *initial_head; + const char *origin_url; +} git_repository_init_options; + +int git_repository_init( + git_repository **out, + const char *path, + unsigned is_bare); + +int git_repository_init_ext( + git_repository **out, + const char *repo_path, + git_repository_init_options *opts); diff --git a/pygit2/ffi.py b/pygit2/ffi.py index 468b9bc2f..0dba2f34b 100644 --- a/pygit2/ffi.py +++ b/pygit2/ffi.py @@ -38,7 +38,7 @@ if major_version < 3: def to_str(s, encoding='utf-8', errors='strict'): - if s == ffi.NULL or s == None: + if s == ffi.NULL or s is None: return ffi.NULL if isinstance(s, unicode): @@ -48,7 +48,7 @@ def to_str(s, encoding='utf-8', errors='strict'): return s else: def to_str(s, encoding='utf-8', errors='strict'): - if s == ffi.NULL or s == None: + if s == ffi.NULL or s is None: return ffi.NULL if isinstance(s, bytes): @@ -108,7 +108,7 @@ def strings_to_strarray(l): decl_path = path.join(dir_path, 'decl.h') with codecs.open(decl_path, 'r', 'utf-8') as header: - ffi.cdef(header.read()) + ffi.cdef(header.read()) # if LIBGIT2 exists, set build and link against that version libgit2_path = getenv('LIBGIT2') @@ -118,4 +118,5 @@ def strings_to_strarray(l): include_dirs = [path.join(libgit2_path, 'include')] library_dirs = [path.join(libgit2_path, 'lib')] -C = ffi.verify("#include ", libraries=["git2"], include_dirs=include_dirs, library_dirs=library_dirs) +C = ffi.verify("#include ", libraries=["git2"], + include_dirs=include_dirs, library_dirs=library_dirs) From 491e352e4162542107e450f0223f87938b98f26c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sat, 7 Jun 2014 21:31:47 +0200 Subject: [PATCH 0192/1630] Update to latest libgit2 --- pygit2/decl.h | 8 ++++++++ src/repository.c | 12 +++++++----- test/test_repository.py | 8 ++++---- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/pygit2/decl.h b/pygit2/decl.h index b26147292..1d65df2c5 100644 --- a/pygit2/decl.h +++ b/pygit2/decl.h @@ -220,6 +220,13 @@ typedef struct git_checkout_options { const char *their_label; } git_checkout_options; +typedef enum { + GIT_CLONE_LOCAL_AUTO, + GIT_CLONE_LOCAL, + GIT_CLONE_NO_LOCAL, + GIT_CLONE_LOCAL_NO_LINKS, +} git_clone_local_t; + typedef struct git_clone_options { unsigned int version; @@ -228,6 +235,7 @@ typedef struct git_clone_options { int bare; int ignore_cert_errors; + git_clone_local_t local; const char *remote_name; const char* checkout_branch; git_signature *signature; diff --git a/src/repository.c b/src/repository.c index b2409ec27..0b12053a4 100644 --- a/src/repository.c +++ b/src/repository.c @@ -546,13 +546,14 @@ Repository_merge_base(Repository *self, PyObject *args) } PyDoc_STRVAR(Repository_merge_analysis__doc__, - "merge_analysis(id) -> Integer\n" + "merge_analysis(id) -> (Integer, Integer)\n" "\n" "Analyzes the given branch and determines the opportunities for merging\n" "them into the HEAD of the repository\n" "\n" - "The returned value is a mixture of the GIT_MERGE_ANALYSIS_NONE, _NORMAL,\n" - " _UP_TO_DATE, _FASTFORWARD and _UNBORN flags"); + "The first returned value is a mixture of the GIT_MERGE_ANALYSIS_NONE, _NORMAL,\n" + " _UP_TO_DATE, _FASTFORWARD and _UNBORN flags.\n" + "The second value is the user's preference from 'merge.ff'"); PyObject * Repository_merge_analysis(Repository *self, PyObject *py_id) @@ -562,6 +563,7 @@ Repository_merge_analysis(Repository *self, PyObject *py_id) git_oid id; git_merge_head *merge_head; git_merge_analysis_t analysis; + git_merge_preference_t preference; len = py_oid_to_git_oid(py_id, &id); if (len == 0) @@ -571,13 +573,13 @@ Repository_merge_analysis(Repository *self, PyObject *py_id) if (err < 0) return Error_set(err); - err = git_merge_analysis(&analysis, self->repo, (const git_merge_head **) &merge_head, 1); + err = git_merge_analysis(&analysis, &preference, self->repo, (const git_merge_head **) &merge_head, 1); git_merge_head_free(merge_head); if (err < 0) return Error_set(err); - return PyLong_FromLong(analysis); + return Py_BuildValue("(ii)", analysis, preference); } PyDoc_STRVAR(Repository_merge__doc__, diff --git a/test/test_repository.py b/test/test_repository.py index c35b0fd99..f40409287 100644 --- a/test/test_repository.py +++ b/test/test_repository.py @@ -313,7 +313,7 @@ def test_merge_none(self): def test_merge_analysis_uptodate(self): branch_head_hex = '5ebeeebb320790caf276b9fc8b24546d63316533' branch_id = self.repo.get(branch_head_hex).id - analysis = self.repo.merge_analysis(branch_id) + analysis, preference = self.repo.merge_analysis(branch_id) self.assertTrue(analysis & GIT_MERGE_ANALYSIS_UP_TO_DATE) self.assertFalse(analysis & GIT_MERGE_ANALYSIS_FASTFORWARD) @@ -322,7 +322,7 @@ def test_merge_analysis_uptodate(self): def test_merge_analysis_fastforward(self): branch_head_hex = 'e97b4cfd5db0fb4ebabf4f203979ca4e5d1c7c87' branch_id = self.repo.get(branch_head_hex).id - analysis = self.repo.merge_analysis(branch_id) + analysis, preference = self.repo.merge_analysis(branch_id) self.assertFalse(analysis & GIT_MERGE_ANALYSIS_UP_TO_DATE) self.assertTrue(analysis & GIT_MERGE_ANALYSIS_FASTFORWARD) self.assertEqual({}, self.repo.status()) @@ -330,7 +330,7 @@ def test_merge_analysis_fastforward(self): def test_merge_no_fastforward_no_conflicts(self): branch_head_hex = '03490f16b15a09913edb3a067a3dc67fbb8d41f1' branch_id = self.repo.get(branch_head_hex).id - analysis= self.repo.merge_analysis(branch_id) + analysis, preference = self.repo.merge_analysis(branch_id) self.assertFalse(analysis & GIT_MERGE_ANALYSIS_UP_TO_DATE) self.assertFalse(analysis & GIT_MERGE_ANALYSIS_FASTFORWARD) # Asking twice to assure the reference counting is correct @@ -341,7 +341,7 @@ def test_merge_no_fastforward_conflicts(self): branch_head_hex = '1b2bae55ac95a4be3f8983b86cd579226d0eb247' branch_id = self.repo.get(branch_head_hex).id - analysis = self.repo.merge_analysis(branch_id) + analysis, preference = self.repo.merge_analysis(branch_id) self.assertFalse(analysis & GIT_MERGE_ANALYSIS_UP_TO_DATE) self.assertFalse(analysis & GIT_MERGE_ANALYSIS_FASTFORWARD) From d3af09e86deba467daa8f6e058697c0f082561cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sat, 7 Jun 2014 21:45:10 +0200 Subject: [PATCH 0193/1630] Adjust to clone_into signature change --- pygit2/__init__.py | 2 +- pygit2/decl.h | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/pygit2/__init__.py b/pygit2/__init__.py index eed50c81a..b4faa031b 100644 --- a/pygit2/__init__.py +++ b/pygit2/__init__.py @@ -189,7 +189,7 @@ def clone_into(repo, remote, branch=None): and calling this function. """ - err = C.git_clone_into(repo._repo, remote._remote, ffi.NULL, to_str(branch)) + err = C.git_clone_into(repo._repo, remote._remote, ffi.NULL, to_str(branch), ffi.NULL) if remote._stored_exception: raise remote._stored_exception diff --git a/pygit2/decl.h b/pygit2/decl.h index 603509b92..862f867bc 100644 --- a/pygit2/decl.h +++ b/pygit2/decl.h @@ -262,7 +262,8 @@ int git_clone_into( git_repository *repo, git_remote *remote, const git_checkout_options *co_opts, - const char *branch); + const char *branch, + const git_signature *signature); /* * git_config From 130fff6f2c062235dade5b7ff6b7b74e517f9a05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sun, 8 Jun 2014 20:35:15 +0200 Subject: [PATCH 0194/1630] Bump required libgit2 version to 0.21 --- src/types.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/types.h b/src/types.h index 806e58978..e69b489eb 100644 --- a/src/types.h +++ b/src/types.h @@ -32,8 +32,8 @@ #include #include -#if !(LIBGIT2_VER_MAJOR == 0 && LIBGIT2_VER_MINOR == 20) -#error You need a compatible libgit2 version (v0.20.x) +#if !(LIBGIT2_VER_MAJOR == 0 && LIBGIT2_VER_MINOR == 21) +#error You need a compatible libgit2 version (v0.21.x) #endif /* From bde58d972742707094eeb16ac7b1d9dfdf75f342 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sun, 8 Jun 2014 20:35:21 +0200 Subject: [PATCH 0195/1630] Remote: make renaming take a method call Renaming a remote in pygit2 has been done via Remote.name= up to now, but this is inherently unsafe, as it provides no way to pass up the refspecs that libgit2 was unable to remap. In fact, if there ever was such problem, we would have segfaulted. libgit2 now provides a much more direct way of getting back the results, so expose it as the return value of Remote.rename(). This also removes the hint that a rename might be something that happens only to the in-memory structure. --- pygit2/decl.h | 10 +++++----- pygit2/remote.py | 19 +++++++++++++++---- test/test_remote.py | 9 +++++---- 3 files changed, 25 insertions(+), 13 deletions(-) diff --git a/pygit2/decl.h b/pygit2/decl.h index 862f867bc..d3b5b022d 100644 --- a/pygit2/decl.h +++ b/pygit2/decl.h @@ -116,11 +116,11 @@ int git_remote_create( const char *url); const char * git_remote_name(const git_remote *remote); -typedef int (*git_remote_rename_problem_cb)(const char *problematic_refspec, void *payload); -int git_remote_rename(git_remote *remote, - const char *new_name, - git_remote_rename_problem_cb callback, - void *payload); + +int git_remote_rename( + git_strarray *problems, + git_remote *remote, + const char *new_name); const char * git_remote_url(const git_remote *remote); int git_remote_set_url(git_remote *remote, const char* url); const char * git_remote_pushurl(const git_remote *remote); diff --git a/pygit2/remote.py b/pygit2/remote.py index 890e074e6..2b7f32d79 100644 --- a/pygit2/remote.py +++ b/pygit2/remote.py @@ -142,14 +142,25 @@ def name(self): return maybe_string(C.git_remote_name(self._remote)) - @name.setter - def name(self, value): - if not value: + def rename(self, new_name): + """Rename this remote + + Returns a list of fetch refspecs which were not in the standard format + and thus could not be remapped + """ + + if not new_name: raise ValueError("New remote name must be a non-empty string") - err = C.git_remote_rename(self._remote, to_str(value), ffi.NULL, ffi.NULL) + problems = ffi.new('git_strarray *') + err = C.git_remote_rename(problems, self._remote, to_str(new_name)) check_error(err) + ret = strarray_to_strings(problems) + C.git_strarray_free(problems) + + return ret + @property def url(self): """Url of the remote""" diff --git a/test/test_remote.py b/test/test_remote.py index 9c95f80cd..57814a386 100644 --- a/test/test_remote.py +++ b/test/test_remote.py @@ -61,11 +61,12 @@ def test_remote_rename(self): remote = self.repo.remotes[0] self.assertEqual(REMOTE_NAME, remote.name) - remote.name = 'new' + problems = remote.rename('new') + self.assertEqual([], problems) self.assertEqual('new', remote.name) - self.assertRaisesAssign(ValueError, remote, 'name', '') - self.assertRaisesAssign(ValueError, remote, 'name', None) + self.assertRaises(ValueError, remote.rename, '') + self.assertRaises(ValueError, remote.rename, None) def test_remote_set_url(self): @@ -153,7 +154,7 @@ def test_remote_list(self): def test_remote_save(self): remote = self.repo.remotes[0] - remote.name = 'new-name' + remote.rename('new-name') remote.url = 'http://example.com/test.git' remote.save() From 981fc32af6818faf92a7e3b3a9e688db05dc7bea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Mon, 9 Jun 2014 09:19:09 +0200 Subject: [PATCH 0196/1630] travis: test 3.4 too --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 8440098d0..9f31e6fc3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,7 @@ python: - "2.7" - "3.2" - "3.3" + - "3.4" - "pypy" env: LIBGIT2=~/libgit2/_install/ LD_LIBRARY_PATH=~/libgit2/_install/lib From bd322fa1320d83d027e085a71b2b7ae57aefdfb5 Mon Sep 17 00:00:00 2001 From: Michael Jones Date: Sun, 8 Jun 2014 19:23:13 +0100 Subject: [PATCH 0197/1630] Correct LIBGIT2_VERSION name and add documentation LIBGIT2_VERSION was previously recorded as LIBGIT2_VER_VERSION which is incorrect. We also add basic explanations to all the constants so that the page is a little less bare. Perhaps this should be done as autodoc style comments in the code but I guess not. --- docs/general.rst | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/docs/general.rst b/docs/general.rst index bf0b3c164..b71e6e7bb 100644 --- a/docs/general.rst +++ b/docs/general.rst @@ -6,14 +6,45 @@ General :local: +Top level constants and exceptions from the library. + Constants ========= +The following constants provide information about the version of the libgit2 +library that has been built against. The version number has a +``MAJOR.MINOR.REVISION`` format. + .. py:data:: LIBGIT2_VER_MAJOR + + Integer value of the major version number. For example, for the version + ``0.20.0``:: + + >>> print LIBGIT2_VER_MAJOR + 0 + .. py:data:: LIBGIT2_VER_MINOR + + Integer value of the minor version number. For example, for the version + ``0.20.0``:: + + >>> print LIBGIT2_VER_MINOR + 20 + .. py:data:: LIBGIT2_VER_REVISION -.. py:data:: LIBGIT2_VER_VERSION + Integer value of the revision version number. For example, for the version + ``0.20.0``:: + + >>> print LIBGIT2_VER_REVISION + 0 + +.. py:data:: LIBGIT2_VERSION + + The libgit2 version number as a string:: + + >>> print LIBGIT2_VERSION + '0.20.0' Errors ====== @@ -22,3 +53,4 @@ Errors :members: :show-inheritance: :undoc-members: + From 28ae47f42bcfee8ce5b33660798f6483a3e0f45d Mon Sep 17 00:00:00 2001 From: Michael Jones Date: Mon, 9 Jun 2014 22:28:04 +0100 Subject: [PATCH 0198/1630] Provide a doc example for discover_repository To clarify the behaviour and usage. --- docs/repository.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/repository.rst b/docs/repository.rst index c2bd0c20c..1ce6191de 100644 --- a/docs/repository.rst +++ b/docs/repository.rst @@ -33,6 +33,11 @@ Functions .. autofunction:: pygit2.discover_repository + Example:: + + >>> current_working_directory = os.getcwd() + >>> repository_path = discover_repository(current_working_directory) + >>> repo = Repository(repository_path) The Repository class From 83ccdd9c1fda5337e7b61324f25760f8a4850479 Mon Sep 17 00:00:00 2001 From: Michael Jones Date: Sun, 15 Jun 2014 11:28:26 +0100 Subject: [PATCH 0199/1630] Explain that reference targets are writable It might seem like a really obvious point to make but without emphasizing it, it isn't completely clear. I would like to mention this in the Branch section as well for how to point a branch at another commit but I can't see how to smoothly slide it in. --- src/reference.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/reference.c b/src/reference.c index e597e353a..dbf68105a 100644 --- a/src/reference.c +++ b/src/reference.c @@ -202,7 +202,10 @@ Reference_resolve(Reference *self, PyObject *args) PyDoc_STRVAR(Reference_target__doc__, "The reference target: If direct the value will be an Oid object, if it\n" "is symbolic it will be an string with the full name of the target\n" - "reference."); + "reference.\n" + "\n" + "The target is writable. Setting the Reference's target to another Oid\n" + "object will direct the reference to that Oid instead."); PyObject * Reference_target__get__(Reference *self) From 7296b921cceef795f55674479db6a8b32bfce09c Mon Sep 17 00:00:00 2001 From: Michael Jones Date: Sun, 15 Jun 2014 11:30:33 +0100 Subject: [PATCH 0200/1630] Fix spelling typo --- docs/references.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/references.rst b/docs/references.rst index 363e3eebe..6ede2a712 100644 --- a/docs/references.rst +++ b/docs/references.rst @@ -45,7 +45,7 @@ Example. These two lines are equivalent:: Branches ==================== -Branches inherit from References, and additionally provide spetialized +Branches inherit from References, and additionally provide specialized accessors for some unique features. .. automethod:: pygit2.Repository.listall_branches From 1f111c08b631b38985f67bbc36d3fadfc4db152c Mon Sep 17 00:00:00 2001 From: Michael Jones Date: Sun, 15 Jun 2014 11:39:24 +0100 Subject: [PATCH 0201/1630] Provide example for Reference.log_append I would have found this useful when trying to do reflog additions. It might not be massively complex but examples always help. --- docs/references.rst | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/docs/references.rst b/docs/references.rst index 6ede2a712..cccd68aa1 100644 --- a/docs/references.rst +++ b/docs/references.rst @@ -27,6 +27,23 @@ The Reference type .. automethod:: pygit2.Reference.resolve .. automethod:: pygit2.Reference.log .. automethod:: pygit2.Reference.log_append + + Example:: + + >>> branch = repository.lookup_reference("refs/heads/master") + >>> branch.target = another_commit.id + >>> committer = Signature('Cecil Committer', 'cecil@committers.tld') + >>> branch.log_append(another_commit.id, committer, + "changed branch target using pygit2") + + This creates a reflog entry in ``git reflog master`` which looks like:: + + 7296b92 master@{10}: changed branch target using pygit2 + + In order to make an entry in ``git reflog``, ie. the reflog for ``HEAD``, you + have to get the Reference object for ``HEAD`` and call ``log_append`` on + that. + .. automethod:: pygit2.Reference.get_object From 791b39433c8c8d7356d47ac030386256c57620d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Sat, 21 Jun 2014 10:23:52 +0200 Subject: [PATCH 0202/1630] C coding style: remove tabs --- src/blame.c | 14 +++--- src/options.c | 131 ++++++++++++++++++++++++------------------------ src/reference.c | 2 +- 3 files changed, 74 insertions(+), 73 deletions(-) diff --git a/src/blame.c b/src/blame.c index b238f050a..4fe1770de 100644 --- a/src/blame.c +++ b/src/blame.c @@ -70,18 +70,18 @@ wrap_blame_hunk(const git_blame_hunk *hunk, Blame *blame) py_hunk->final_commit_id = git_oid_allocfmt(&hunk->final_commit_id); py_hunk->final_start_line_number = hunk->final_start_line_number; - py_hunk->final_signature = NULL; - if (hunk->final_signature) - git_signature_dup(&py_hunk->final_signature, hunk->final_signature); + py_hunk->final_signature = NULL; + if (hunk->final_signature) + git_signature_dup(&py_hunk->final_signature, hunk->final_signature); py_hunk->orig_commit_id = git_oid_allocfmt(&hunk->orig_commit_id); py_hunk->orig_path = hunk->orig_path != NULL ? - strdup(hunk->orig_path) : NULL; + strdup(hunk->orig_path) : NULL; py_hunk->orig_start_line_number = hunk->orig_start_line_number; - py_hunk->orig_signature = NULL; - if (hunk->orig_signature) - git_signature_dup(&py_hunk->orig_signature, hunk->orig_signature); + py_hunk->orig_signature = NULL; + if (hunk->orig_signature) + git_signature_dup(&py_hunk->orig_signature, hunk->orig_signature); py_hunk->boundary = hunk->boundary; } diff --git a/src/options.c b/src/options.c index 1679a93d2..f6eaa5ec4 100644 --- a/src/options.c +++ b/src/options.c @@ -70,94 +70,95 @@ option(PyObject *self, PyObject *args) option = PyLong_AsLong(py_option); - switch (option) { - case GIT_OPT_GET_SEARCH_PATH: - { - PyObject *py_level; + switch (option) { + case GIT_OPT_GET_SEARCH_PATH: + { + PyObject *py_level; - py_level = PyTuple_GetItem(args, 1); - if (!py_level) - return NULL; + py_level = PyTuple_GetItem(args, 1); + if (!py_level) + return NULL; - if (!PyLong_Check(py_level)) - goto on_non_integer; + if (!PyLong_Check(py_level)) + goto on_non_integer; - return get_search_path(PyLong_AsLong(py_level)); - break; - } + return get_search_path(PyLong_AsLong(py_level)); + break; + } + + case GIT_OPT_SET_SEARCH_PATH: + { + PyObject *py_level, *py_path, *tpath; + const char *path; + int err; - case GIT_OPT_SET_SEARCH_PATH: - { - PyObject *py_level, *py_path, *tpath; - const char *path; - int err; + py_level = PyTuple_GetItem(args, 1); + if (!py_level) + return NULL; - py_level = PyTuple_GetItem(args, 1); - if (!py_level) - return NULL; + py_path = PyTuple_GetItem(args, 2); + if (!py_path) + return NULL; - py_path = PyTuple_GetItem(args, 2); - if (!py_path) - return NULL; + if (!PyLong_Check(py_level)) + goto on_non_integer; - if (!PyLong_Check(py_level)) - goto on_non_integer; + path = py_str_borrow_c_str(&tpath, py_path, NULL); + if (!path) + return NULL; - path = py_str_borrow_c_str(&tpath, py_path, NULL); - if (!path) - return NULL; + err = git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, PyLong_AsLong(py_level), path); + Py_DECREF(tpath); - err = git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, PyLong_AsLong(py_level), path); - Py_DECREF(tpath); + if (err < 0) { + Error_set(err); + return NULL; + } - if (err < 0) { - Error_set(err); - return NULL; + Py_RETURN_NONE; + break; } - Py_RETURN_NONE; - break; - } + case GIT_OPT_GET_MWINDOW_SIZE: + { + size_t size; - case GIT_OPT_GET_MWINDOW_SIZE: - { - size_t size; + error = git_libgit2_opts(GIT_OPT_GET_MWINDOW_SIZE, &size); + if (error < 0) { + Error_set(error); + return NULL; + } - if ((error = git_libgit2_opts(GIT_OPT_GET_MWINDOW_SIZE, &size)) < 0) { - Error_set(error); - return NULL; - } + return PyLong_FromSize_t(size); - return PyLong_FromSize_t(size); - - break; - } + break; + } - case GIT_OPT_SET_MWINDOW_SIZE: - { - size_t size; - PyObject *py_size; + case GIT_OPT_SET_MWINDOW_SIZE: + { + size_t size; + PyObject *py_size; - py_size = PyTuple_GetItem(args, 1); - if (!py_size) - return NULL; + py_size = PyTuple_GetItem(args, 1); + if (!py_size) + return NULL; - if (!PyLong_Check(py_size)) - goto on_non_integer; + if (!PyLong_Check(py_size)) + goto on_non_integer; + size = PyLong_AsSize_t(py_size); + error = git_libgit2_opts(GIT_OPT_SET_MWINDOW_SIZE, size); + if (error < 0) { + Error_set(error); + return NULL; + } - size = PyLong_AsSize_t(py_size); - if ((error = git_libgit2_opts(GIT_OPT_SET_MWINDOW_SIZE, size)) < 0) { - Error_set(error); - return NULL; + Py_RETURN_NONE; + break; } - - Py_RETURN_NONE; - break; } - } - PyErr_SetString(PyExc_ValueError, "unknown/unsupported option value"); + PyErr_SetString(PyExc_ValueError, "unknown/unsupported option value"); return NULL; on_non_integer: diff --git a/src/reference.c b/src/reference.c index b2b5bd233..d86d8a8e9 100644 --- a/src/reference.c +++ b/src/reference.c @@ -62,7 +62,7 @@ RefLogIter_iternext(RefLogIter *self) py_entry->oid_old = git_oid_allocfmt(git_reflog_entry_id_old(entry)); py_entry->oid_new = git_oid_allocfmt(git_reflog_entry_id_new(entry)); py_entry->message = strdup(git_reflog_entry_message(entry)); - git_signature_dup(&py_entry->signature, git_reflog_entry_committer(entry)); + git_signature_dup(&py_entry->signature, git_reflog_entry_committer(entry)); ++(self->i); From a9fcbb33d17ba8b2a80c43854dbcb97583bf1e17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Sat, 21 Jun 2014 21:14:33 +0200 Subject: [PATCH 0203/1630] Check errors returned by git_signature_dup --- src/blame.c | 41 +++++++++++++++++++++++++---------------- src/reference.c | 6 +++++- 2 files changed, 30 insertions(+), 17 deletions(-) diff --git a/src/blame.c b/src/blame.c index 4fe1770de..f1c2f6292 100644 --- a/src/blame.c +++ b/src/blame.c @@ -60,32 +60,41 @@ PyObject* wrap_blame_hunk(const git_blame_hunk *hunk, Blame *blame) { BlameHunk *py_hunk = NULL; + int err; if (!hunk) Py_RETURN_NONE; py_hunk = PyObject_New(BlameHunk, &BlameHunkType); - if (py_hunk != NULL) { - py_hunk->lines_in_hunk = hunk->lines_in_hunk; - py_hunk->final_commit_id = git_oid_allocfmt(&hunk->final_commit_id); - py_hunk->final_start_line_number = hunk->final_start_line_number; - - py_hunk->final_signature = NULL; - if (hunk->final_signature) - git_signature_dup(&py_hunk->final_signature, hunk->final_signature); + if (py_hunk == NULL) + return NULL; - py_hunk->orig_commit_id = git_oid_allocfmt(&hunk->orig_commit_id); - py_hunk->orig_path = hunk->orig_path != NULL ? - strdup(hunk->orig_path) : NULL; - py_hunk->orig_start_line_number = hunk->orig_start_line_number; + py_hunk->lines_in_hunk = hunk->lines_in_hunk; + py_hunk->final_commit_id = git_oid_allocfmt(&hunk->final_commit_id); + py_hunk->final_start_line_number = hunk->final_start_line_number; - py_hunk->orig_signature = NULL; - if (hunk->orig_signature) - git_signature_dup(&py_hunk->orig_signature, hunk->orig_signature); + py_hunk->final_signature = NULL; + if (hunk->final_signature) { + err = git_signature_dup(&py_hunk->final_signature, + hunk->final_signature); + if (err < 0) + return Error_set(err); + } - py_hunk->boundary = hunk->boundary; + py_hunk->orig_commit_id = git_oid_allocfmt(&hunk->orig_commit_id); + py_hunk->orig_path = hunk->orig_path != NULL ? + strdup(hunk->orig_path) : NULL; + py_hunk->orig_start_line_number = hunk->orig_start_line_number; + + py_hunk->orig_signature = NULL; + if (hunk->orig_signature) { + err = git_signature_dup(&py_hunk->orig_signature, + hunk->orig_signature); + if (err < 0) + return Error_set(err); } + py_hunk->boundary = hunk->boundary; return (PyObject*) py_hunk; } diff --git a/src/reference.c b/src/reference.c index d86d8a8e9..cbd96d75d 100644 --- a/src/reference.c +++ b/src/reference.c @@ -54,6 +54,7 @@ RefLogIter_iternext(RefLogIter *self) { const git_reflog_entry *entry; RefLogEntry *py_entry; + int err; if (self->i < self->size) { entry = git_reflog_entry_byindex(self->reflog, self->i); @@ -62,7 +63,10 @@ RefLogIter_iternext(RefLogIter *self) py_entry->oid_old = git_oid_allocfmt(git_reflog_entry_id_old(entry)); py_entry->oid_new = git_oid_allocfmt(git_reflog_entry_id_new(entry)); py_entry->message = strdup(git_reflog_entry_message(entry)); - git_signature_dup(&py_entry->signature, git_reflog_entry_committer(entry)); + err = git_signature_dup(&py_entry->signature, + git_reflog_entry_committer(entry)); + if (err < 0) + return Error_set(err); ++(self->i); From a063867fe0e4506e29f22c45dd403d805e3fb1b7 Mon Sep 17 00:00:00 2001 From: Jack O'Connor Date: Fri, 30 May 2014 11:09:29 +0900 Subject: [PATCH 0204/1630] Index: add a setter for workdir --- src/repository.c | 23 ++++++++++++++++++++++- test/test_repository.py | 5 +++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/repository.c b/src/repository.c index 0b12053a4..ad0b3d92f 100644 --- a/src/repository.c +++ b/src/repository.c @@ -512,6 +512,27 @@ Repository_workdir__get__(Repository *self, void *closure) return to_path(c_path); } +int +Repository_workdir__set__(Repository *self, PyObject *py_workdir) +{ + int err; + const char *workdir; + PyObject *tworkdir; + + workdir = py_str_borrow_c_str(&tworkdir, py_workdir, NULL); + if (workdir == NULL) + return -1; + + err = git_repository_set_workdir(self->repo, workdir, 0 /* update_gitlink */); + Py_DECREF(tworkdir); + if (err < 0) { + Error_set_str(err, workdir); + return -1; + } + + return 0; +} + PyDoc_STRVAR(Repository_merge_base__doc__, "merge_base(oid, oid) -> Oid\n" "\n" @@ -1597,7 +1618,7 @@ PyGetSetDef Repository_getseters[] = { GETTER(Repository, head_is_unborn), GETTER(Repository, is_empty), GETTER(Repository, is_bare), - GETTER(Repository, workdir), + GETSET(Repository, workdir), GETTER(Repository, default_signature), GETTER(Repository, _pointer), {NULL} diff --git a/test/test_repository.py b/test/test_repository.py index 4581f4774..37f575c0a 100644 --- a/test/test_repository.py +++ b/test/test_repository.py @@ -190,6 +190,11 @@ def test_get_workdir(self): expected = realpath(self.repo_path) self.assertEqual(directory, expected) + def test_set_workdir(self): + directory = tempfile.mkdtemp() + self.repo.workdir = directory + self.assertEqual(realpath(self.repo.workdir), realpath(directory)) + def test_checkout_ref(self): ref_i18n = self.repo.lookup_reference('refs/heads/i18n') From b190169f5e83cbdb2346acd52cea30e14a205eb5 Mon Sep 17 00:00:00 2001 From: Jack O'Connor Date: Fri, 30 May 2014 23:50:41 +0900 Subject: [PATCH 0205/1630] support setting a detatched HEAD --- src/repository.c | 32 +++++++++++++++++++++----------- test/test_repository.py | 9 +++++++++ 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/src/repository.c b/src/repository.c index ad0b3d92f..bd048c73d 100644 --- a/src/repository.c +++ b/src/repository.c @@ -179,21 +179,31 @@ Repository_head__get__(Repository *self) } int -Repository_head__set__(Repository *self, PyObject *py_refname) +Repository_head__set__(Repository *self, PyObject *py_val) { int err; - const char *refname; - PyObject *trefname; + if (PyObject_TypeCheck(py_val, &OidType)) { + git_oid oid; + py_oid_to_git_oid(py_val, &oid); + err = git_repository_set_head_detached(self->repo, &oid, NULL, NULL); + if (err < 0) { + Error_set(err); + return -1; + } + } else { + const char *refname; + PyObject *trefname; - refname = py_str_borrow_c_str(&trefname, py_refname, NULL); - if (refname == NULL) - return -1; + refname = py_str_borrow_c_str(&trefname, py_val, NULL); + if (refname == NULL) + return -1; - err = git_repository_set_head(self->repo, refname, NULL, NULL); - Py_DECREF(trefname); - if (err < 0) { - Error_set_str(err, refname); - return -1; + err = git_repository_set_head(self->repo, refname, NULL, NULL); + Py_DECREF(trefname); + if (err < 0) { + Error_set_str(err, refname); + return -1; + } } return 0; diff --git a/test/test_repository.py b/test/test_repository.py index 37f575c0a..4d6472ec1 100644 --- a/test/test_repository.py +++ b/test/test_repository.py @@ -70,6 +70,15 @@ def test_head(self): self.assertFalse(self.repo.head_is_unborn) self.assertFalse(self.repo.head_is_detached) + def test_set_head(self): + # Test setting a detatched HEAD. + self.repo.head = Oid(hex=PARENT_SHA) + self.assertEqual(self.repo.head.target.hex, PARENT_SHA) + # And test setting a normal HEAD. + self.repo.head = "refs/heads/master" + self.assertEqual(self.repo.head.name, "refs/heads/master") + self.assertEqual(self.repo.head.target.hex, HEAD_SHA) + def test_read(self): self.assertRaises(TypeError, self.repo.read, 123) self.assertRaisesWithArg(KeyError, '1' * 40, self.repo.read, '1' * 40) From 9811123922764f86f128cca8c637283333ba31eb Mon Sep 17 00:00:00 2001 From: Michael Jones Date: Thu, 26 Jun 2014 23:27:23 +0100 Subject: [PATCH 0206/1630] Fix docstrings for Blob.diff & diff_to_buffer They were both missing a closing parenthesis which stop Sphinx from interpreting and formatting them properly. --- src/blob.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/blob.c b/src/blob.c index 11ec3cd15..6ec9fd5cc 100644 --- a/src/blob.c +++ b/src/blob.c @@ -38,7 +38,7 @@ extern PyObject *GitError; extern PyTypeObject BlobType; PyDoc_STRVAR(Blob_diff__doc__, - "diff([blob, flag, old_as_path, new_as_path] -> Patch\n" + "diff([blob, flag, old_as_path, new_as_path]) -> Patch\n" "\n" "Directly generate a :py:class:`pygit2.Patch` from the difference\n" " between two blobs.\n" @@ -79,7 +79,7 @@ Blob_diff(Blob *self, PyObject *args, PyObject *kwds) PyDoc_STRVAR(Blob_diff_to_buffer__doc__, - "diff_to_buffer([buffer, flag, old_as_path, buffer_as_path] -> Patch\n" + "diff_to_buffer([buffer, flag, old_as_path, buffer_as_path]) -> Patch\n" "\n" "Directly generate a :py:class:`~pygit2.Patch` from the difference\n" " between a blob and a buffer.\n" From b96b285cea0177bee1039f0957daf30d411ea833 Mon Sep 17 00:00:00 2001 From: Michael Jones Date: Thu, 26 Jun 2014 23:29:24 +0100 Subject: [PATCH 0207/1630] Improve diff & diff_to_buffer doc formatting We switch to a parameter list for both functions and add a return type. We also remove the indentation from the second line of the explanation which was causing Sphinx to return it as two lines instead of one continuous one. We also added return types. I am not sure of the type of the GIT_DIFF* flags so I have not included that. --- src/blob.c | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/blob.c b/src/blob.c index 6ec9fd5cc..294c884cf 100644 --- a/src/blob.c +++ b/src/blob.c @@ -41,17 +41,17 @@ PyDoc_STRVAR(Blob_diff__doc__, "diff([blob, flag, old_as_path, new_as_path]) -> Patch\n" "\n" "Directly generate a :py:class:`pygit2.Patch` from the difference\n" - " between two blobs.\n" + "between two blobs.\n" "\n" - "Arguments:\n" + ":param Blob blob: the :py:class:`~pygit2.Blob` to diff.\n" "\n" - "blob: the :py:class:`~pygit2.Blob` to diff.\n" + ":param flag: a GIT_DIFF_* constant.\n" "\n" - "flag: a GIT_DIFF_* constant.\n" + ":param str old_as_path: treat old blob as if it had this filename.\n" "\n" - "old_as_path: treat old blob as if it had this filename.\n" + ":param str new_as_path: treat new blob as if it had this filename.\n" "\n" - "new_as_path: treat new blob as if it had this filename.\n"); + ":rtype: Patch\n"); PyObject * Blob_diff(Blob *self, PyObject *args, PyObject *kwds) @@ -82,17 +82,17 @@ PyDoc_STRVAR(Blob_diff_to_buffer__doc__, "diff_to_buffer([buffer, flag, old_as_path, buffer_as_path]) -> Patch\n" "\n" "Directly generate a :py:class:`~pygit2.Patch` from the difference\n" - " between a blob and a buffer.\n" + "between a blob and a buffer.\n" "\n" - "Arguments:\n" + ":param Blob buffer: Raw data for new side of diff.\n" "\n" - "buffer: Raw data for new side of diff.\n" + ":param flag: a GIT_DIFF_* constant.\n" "\n" - "flag: a GIT_DIFF_* constant.\n" + ":param str old_as_path: treat old blob as if it had this filename.\n" "\n" - "old_as_path: treat old blob as if it had this filename.\n" + ":param str buffer_as_path: treat buffer as if it had this filename.\n" "\n" - "buffer_as_path: treat buffer as if it had this filename.\n"); + ":rtype: Patch\n"); PyObject * Blob_diff_to_buffer(Blob *self, PyObject *args, PyObject *kwds) From 7b3201d868578e8e6936cb868c7acc38a4b04b45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Fri, 27 Jun 2014 17:28:01 +0200 Subject: [PATCH 0208/1630] Get ready to release 0.21.0 --- README.rst | 112 ++++++++++++++++++++++++++++++++++++---------- docs/conf.py | 4 +- docs/general.rst | 8 ++-- docs/install.rst | 4 +- pygit2/version.py | 2 +- 5 files changed, 98 insertions(+), 32 deletions(-) diff --git a/README.rst b/README.rst index 48ffc0686..776451c77 100644 --- a/README.rst +++ b/README.rst @@ -48,34 +48,100 @@ for the topic), send a pull request. Authors ============== -62 developers have contributed at least 1 commit to pygit2:: - - J. David Ibáñez Rémi Duraffort András Veres-Szentkirályi - Nico von Geyso Sebastian Thiel Benjamin Kircher - Carlos Martín Nieto Fraser Tweedale Benjamin Pollack - W. Trevor King Han-Wen Nienhuys Bryan O'Sullivan - Dave Borowitz Leonardo Rhodes David Fischer - Daniel Rodríguez Troitiño Petr Viktorin David Sanders - Richo Healey Alex Chamberlain Devaev Maxim - Christian Boos Amit Bakshi Eric Davis - Julien Miotte Andrey Devyatkin Erik Meusel - Xu Tao Ben Davis Erik van Zijst - Jose Plana Eric Schrijver Ferengee - Martin Lenders Hervé Cauwelier Gustavo Di Pietro - Petr Hosek Huang Huang Hugh Cole-Baker - Victor Garcia Jared Flatow Josh Bleecher Snyder - Xavier Delannoy Jiunn Haur Lim Jun Omae - Yonggang Luo Sarath Lakshman Óscar San José - Valentin Haenel Vicent Marti Ridge Kennedy - Bernardo Heynemann Zoran Zaric Rui Abreu Ferreira - John Szakmeister Adam Spiers Thomas Kluyver - Brodie Rao Alexander Bayandin earl - David Versmisse Andrew Chin +66 developers have contributed at least 1 commit to pygit2:: + + J. David Ibáñez Rémi Duraffort Adam Spiers + Nico von Geyso Sebastian Thiel Alexander Bayandin + Carlos Martín Nieto Fraser Tweedale Andrew Chin + W. Trevor King Han-Wen Nienhuys András Veres-Szentkirályi + Dave Borowitz Leonardo Rhodes Benjamin Kircher + Daniel Rodríguez Troitiño Petr Viktorin Benjamin Pollack + Richo Healey Thomas Kluyver Bryan O'Sullivan + Christian Boos Alex Chamberlain Daniel Bruce + Julien Miotte Amit Bakshi David Fischer + Xu Tao Andrey Devyatkin David Sanders + Jose Plana Ben Davis Devaev Maxim + Martin Lenders Eric Schrijver Eric Davis + Petr Hosek Hervé Cauwelier Erik Meusel + Victor Garcia Huang Huang Erik van Zijst + Xavier Delannoy Ian P. McCullough Ferengee + Yonggang Luo Jack O'Connor Gustavo Di Pietro + Valentin Haenel Jared Flatow Hugh Cole-Baker + Michael Jones Jiunn Haur Lim Josh Bleecher Snyder + Bernardo Heynemann Jun Omae Óscar San José + John Szakmeister Sarath Lakshman Ridge Kennedy + Brodie Rao Vicent Marti Rui Abreu Ferreira + David Versmisse Zoran Zaric earl Changelog ============== +0.21.0 (2014-06-27) +------------------- + +Highlights: + +- Drop official support for Python 2.6, and add support for Python 3.4 + `#376 `_ + +- Upgrade to libgit2 v0.21.0 + `#374 `_ + +- Start using cffi + `#360 `_ + `#361 `_ + +Backward incompatible changes: + +- Replace ``oid`` by ``id`` through the API to follow libgit2 conventions. +- Merge API overhaul following changes in libgit2. +- New ``Remote.rename(...)`` replaces ``Remote.name = ...`` +- Now ``Remote.fetch()`` returns a ``TransferProgress`` object. +- Now ``Config.get_multivar(...)`` returns an iterator instead of a list. + +New features: + +- New ``Config.snapshot()`` and ``Repository.config_snapshot()`` + +- New ``Config`` methods: ``get_bool(...)``, ``get_int(...)``, + ``parse_bool(...)`` and ``parse_int(...)`` + `#357 `_ + +- Blob: implement the memory buffer interface + `#362 `_ + +- New ``clone_into(...)`` function + `#368 `_ + +- Now ``Index`` can be used alone, without a repository + `#372 `_ + +- Add more options to ``init_repository`` + `#347 `_ + +- Support ``Repository.workdir = ...`` and + support setting detached heads ``Repository.head = `` + `#377 `_ + +Other: + +- Fix again build with VS2008 + `#364 `_ + +- Fix ``Blob.diff(...)`` and ``Blob.diff_to_buffer(...)`` arguments passing + `#366 `_ + +- Fail gracefully when compiling against the wrong version of libgit2 + `#365 `_ + +- Several documentation improvements and updates + `#359 `_ + `#375 `_ + `#378 `_ + + + 0.20.3 (2014-04-02) ------------------- diff --git a/docs/conf.py b/docs/conf.py index c432057f7..c51e1f922 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -48,9 +48,9 @@ # built documents. # # The short X.Y version. -version = '0.20' +version = '0.21' # The full version, including alpha/beta/rc tags. -release = '0.20.3' +release = '0.21.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/docs/general.rst b/docs/general.rst index b71e6e7bb..4ed9d3b21 100644 --- a/docs/general.rst +++ b/docs/general.rst @@ -18,7 +18,7 @@ library that has been built against. The version number has a .. py:data:: LIBGIT2_VER_MAJOR Integer value of the major version number. For example, for the version - ``0.20.0``:: + ``0.21.0``:: >>> print LIBGIT2_VER_MAJOR 0 @@ -26,10 +26,10 @@ library that has been built against. The version number has a .. py:data:: LIBGIT2_VER_MINOR Integer value of the minor version number. For example, for the version - ``0.20.0``:: + ``0.21.0``:: >>> print LIBGIT2_VER_MINOR - 20 + 21 .. py:data:: LIBGIT2_VER_REVISION @@ -44,7 +44,7 @@ library that has been built against. The version number has a The libgit2 version number as a string:: >>> print LIBGIT2_VERSION - '0.20.0' + '0.21.0' Errors ====== diff --git a/docs/install.rst b/docs/install.rst index 5f058f22d..fcc6877db 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -26,8 +26,8 @@ When those are installed, you can install pygit2: $ python setup.py test .. note:: A minor version of pygit2 must be used with the corresponding minor - version of libgit2. For example, pygit2 v0.20.x must be used with libgit2 - v0.20.0 + version of libgit2. For example, pygit2 v0.21.x must be used with libgit2 + v0.21.0 Building on \*nix (including OS X) =================================== diff --git a/pygit2/version.py b/pygit2/version.py index e68ec8dc5..cf9fe6be8 100644 --- a/pygit2/version.py +++ b/pygit2/version.py @@ -23,4 +23,4 @@ # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. -__version__ = '0.20.3' +__version__ = '0.21.0' From 3cbc9b6c33f28927b6cfc0bcb54e191e943444f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Tue, 1 Jul 2014 16:36:05 +0200 Subject: [PATCH 0209/1630] README, tell to install cffi first --- README.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/README.rst b/README.rst index 776451c77..284ac77ee 100644 --- a/README.rst +++ b/README.rst @@ -32,6 +32,7 @@ Quick install guide 3. Install pygit2 with *pip*:: + $ pip install cffi $ pip install pygit2 For detailed instructions check the documentation, From 1d509c110933e4fd978d8f748388eece1aa77cff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Thu, 3 Jul 2014 08:57:47 +0200 Subject: [PATCH 0210/1630] travis: download a tag instead of master libgit2's development now happens on the master branch, which means we can't use it to refer to the latest release. Download v0.21.0 explicitly instead. --- .travis.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.sh b/.travis.sh index 26985cf4c..1e7c4353f 100755 --- a/.travis.sh +++ b/.travis.sh @@ -2,7 +2,7 @@ cd ~ -git clone --depth=1 -b master https://github.com/libgit2/libgit2.git +git clone --depth=1 -b v0.21.0 https://github.com/libgit2/libgit2.git cd libgit2/ mkdir build && cd build From b0bf223276ac3a0a522aca4d1c2e8c08636f9081 Mon Sep 17 00:00:00 2001 From: Jasper Lievisse Adriaanse Date: Thu, 3 Jul 2014 10:10:38 +0200 Subject: [PATCH 0211/1630] Tweak include/lib dir detection in ffi.py Joint work with @carlosmn --- pygit2/ffi.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pygit2/ffi.py b/pygit2/ffi.py index 0dba2f34b..98de11c2a 100644 --- a/pygit2/ffi.py +++ b/pygit2/ffi.py @@ -112,11 +112,11 @@ def strings_to_strarray(l): # if LIBGIT2 exists, set build and link against that version libgit2_path = getenv('LIBGIT2') -include_dirs = [] -library_dirs = [] -if libgit2_path: - include_dirs = [path.join(libgit2_path, 'include')] - library_dirs = [path.join(libgit2_path, 'lib')] +if not libgit2_path: + libgit2_path = '/usr/local' + +include_dirs = [path.join(libgit2_path, 'include')] +library_dirs = [path.join(libgit2_path, 'lib')] C = ffi.verify("#include ", libraries=["git2"], include_dirs=include_dirs, library_dirs=library_dirs) From 0bfead36b23b884f8f3f3bb53c18963b7f2b5896 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sun, 6 Jul 2014 16:11:27 +0200 Subject: [PATCH 0212/1630] branch: correct notion of remote-tracking branch and upstream The current documentation text seems to be very confused about what a remote-tracking branch is and what the relationship to an upstream is. Correct the text to talk about the upstream branch when dealing with the upstream configuration, instead of implying that it's related to the setup of remote-tracking branches. --- src/branch.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/branch.c b/src/branch.c index 39ddd675d..1793ceaf8 100644 --- a/src/branch.c +++ b/src/branch.c @@ -129,7 +129,7 @@ Branch_branch_name__get__(Branch *self) PyDoc_STRVAR(Branch_remote_name__doc__, - "The name of the remote that the remote tracking branch belongs to."); + "The name of the remote set to be the upstream of this branch."); PyObject * Branch_remote_name__get__(Branch *self) @@ -154,8 +154,8 @@ Branch_remote_name__get__(Branch *self) PyDoc_STRVAR(Branch_upstream__doc__, - "The branch supporting the remote tracking branch or None if this is not a " - "remote tracking branch. Set to None to unset."); + "The branch's upstream branch or None if this branch does not have an upstream set. " + "Set to None to unset the upstream configuration."); PyObject * Branch_upstream__get__(Branch *self) @@ -206,7 +206,7 @@ int Branch_upstream__set__(Branch *self, Reference *py_ref) PyDoc_STRVAR(Branch_upstream_name__doc__, - "The name of the reference supporting the remote tracking branch."); + "The name of the reference set to be the upstream of this one"); PyObject * Branch_upstream_name__get__(Branch *self) From 9a7348a9d0af13e2d4bcf2ab19e167e64f173cd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Tue, 8 Jul 2014 13:53:34 +0200 Subject: [PATCH 0213/1630] Update docs for merging Remove references to MergeResult and put merge_analysis in the docs. --- docs/merge.rst | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/docs/merge.rst b/docs/merge.rst index 47984a868..413f03480 100644 --- a/docs/merge.rst +++ b/docs/merge.rst @@ -6,12 +6,13 @@ Merge .. automethod:: pygit2.Repository.merge_base .. automethod:: pygit2.Repository.merge +.. automethod:: pygit2.Repository.merge_analysis 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. +It gets an Oid object as a parameter. As its name says, it only does the merge, does not commit nor update the branch reference in the case of a fastforward. @@ -21,17 +22,14 @@ merge with the default ones defined in GIT_MERGE_OPTS_INIT libgit2 constant. Example:: - >>> branch_head_hex = '5ebeeebb320790caf276b9fc8b24546d63316533' - >>> branch_id = self.repo.get(branch_head_hex).id - >>> merge_result = self.repo.merge(branch_id) + >>> other_branch_tip = '5ebeeebb320790caf276b9fc8b24546d63316533' + >>> repo.merge(other_branch_tip) -The MergeResult object -====================== +You can now inspect the index file for conflicts and get back to the +user to resolve if there are. Once there are no conflicts left, you +can create a commit with these two parents. -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_id: Oid, in the case it was a fastforward, this is the - forwarded id. + >>> user = repo.default_signature() + >>> tree = repo.index.write_tree() + >>> new_commit = repo.create_commit('HEAD', user, user, tree, + [repo.head.target, other_branch_tip]) From 02fd05baae541e38ef10cc2058095672ba616703 Mon Sep 17 00:00:00 2001 From: vtemian Date: Wed, 9 Jul 2014 16:39:34 +0300 Subject: [PATCH 0214/1630] Added clean_state_files --- pygit2/decl.h | 1 + pygit2/repository.py | 3 +++ 2 files changed, 4 insertions(+) diff --git a/pygit2/decl.h b/pygit2/decl.h index d3b5b022d..7068e94a6 100644 --- a/pygit2/decl.h +++ b/pygit2/decl.h @@ -114,6 +114,7 @@ int git_remote_create( git_repository *repo, const char *name, const char *url); +int git_repository_state_cleanup(git_repository *repo); const char * git_remote_name(const git_remote *remote); diff --git a/pygit2/repository.py b/pygit2/repository.py index db047f5ee..c4e46c85d 100644 --- a/pygit2/repository.py +++ b/pygit2/repository.py @@ -299,3 +299,6 @@ def treeish_to_tree(obj): raise NotImplementedError('git_diff_blob_to_blob()') raise ValueError("Only blobs and treeish can be diffed") + + def clean_state_files(self): + C.git_repository_state_cleanup(self._repo) From 19dd7629d3c1266b12b6fb75913ba3aed9953be5 Mon Sep 17 00:00:00 2001 From: vtemian Date: Wed, 9 Jul 2014 16:46:52 +0300 Subject: [PATCH 0215/1630] Proper naming --- pygit2/repository.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygit2/repository.py b/pygit2/repository.py index c4e46c85d..61f5143da 100644 --- a/pygit2/repository.py +++ b/pygit2/repository.py @@ -300,5 +300,5 @@ def treeish_to_tree(obj): raise ValueError("Only blobs and treeish can be diffed") - def clean_state_files(self): + def state_cleanup(self): C.git_repository_state_cleanup(self._repo) From d8864bdf02a828478e849cdc1600ea48ec66fdc1 Mon Sep 17 00:00:00 2001 From: vtemian Date: Wed, 9 Jul 2014 16:50:17 +0300 Subject: [PATCH 0216/1630] Added docs --- pygit2/repository.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pygit2/repository.py b/pygit2/repository.py index 61f5143da..1dff8785c 100644 --- a/pygit2/repository.py +++ b/pygit2/repository.py @@ -301,4 +301,9 @@ def treeish_to_tree(obj): raise ValueError("Only blobs and treeish can be diffed") def state_cleanup(self): + """ + Remove all the metadata associated with an ongoing command like + merge, revert, cherry-pick, etc. For example: MERGE_HEAD, MERGE_MSG, + etc. + """ C.git_repository_state_cleanup(self._repo) From 6f438ad1732d6547c35da7df9a0627dc3534b125 Mon Sep 17 00:00:00 2001 From: vtemian Date: Wed, 9 Jul 2014 18:08:26 +0300 Subject: [PATCH 0217/1630] Added sphinx rtd theme --- docs/_themes/sphinx_rtd_theme/__init__.py | 17 + .../_themes/sphinx_rtd_theme/breadcrumbs.html | 19 + docs/_themes/sphinx_rtd_theme/footer.html | 32 ++ docs/_themes/sphinx_rtd_theme/layout.html | 160 +++++++ docs/_themes/sphinx_rtd_theme/layout_old.html | 205 +++++++++ docs/_themes/sphinx_rtd_theme/search.html | 50 +++ docs/_themes/sphinx_rtd_theme/searchbox.html | 7 + .../static/css/badge_only.css | 1 + .../sphinx_rtd_theme/static/css/theme.css | 4 + .../static/fonts/FontAwesome.otf | Bin 0 -> 62856 bytes .../static/fonts/fontawesome-webfont.eot | Bin 0 -> 38205 bytes .../static/fonts/fontawesome-webfont.svg | 414 ++++++++++++++++++ .../static/fonts/fontawesome-webfont.ttf | Bin 0 -> 80652 bytes .../static/fonts/fontawesome-webfont.woff | Bin 0 -> 44432 bytes .../sphinx_rtd_theme/static/js/theme.js | 47 ++ docs/_themes/sphinx_rtd_theme/theme.conf | 8 + docs/_themes/sphinx_rtd_theme/versions.html | 37 ++ docs/conf.py | 4 +- 18 files changed, 1003 insertions(+), 2 deletions(-) create mode 100644 docs/_themes/sphinx_rtd_theme/__init__.py create mode 100644 docs/_themes/sphinx_rtd_theme/breadcrumbs.html create mode 100644 docs/_themes/sphinx_rtd_theme/footer.html create mode 100644 docs/_themes/sphinx_rtd_theme/layout.html create mode 100644 docs/_themes/sphinx_rtd_theme/layout_old.html create mode 100644 docs/_themes/sphinx_rtd_theme/search.html create mode 100644 docs/_themes/sphinx_rtd_theme/searchbox.html create mode 100644 docs/_themes/sphinx_rtd_theme/static/css/badge_only.css create mode 100644 docs/_themes/sphinx_rtd_theme/static/css/theme.css create mode 100644 docs/_themes/sphinx_rtd_theme/static/fonts/FontAwesome.otf create mode 100644 docs/_themes/sphinx_rtd_theme/static/fonts/fontawesome-webfont.eot create mode 100644 docs/_themes/sphinx_rtd_theme/static/fonts/fontawesome-webfont.svg create mode 100644 docs/_themes/sphinx_rtd_theme/static/fonts/fontawesome-webfont.ttf create mode 100644 docs/_themes/sphinx_rtd_theme/static/fonts/fontawesome-webfont.woff create mode 100644 docs/_themes/sphinx_rtd_theme/static/js/theme.js create mode 100644 docs/_themes/sphinx_rtd_theme/theme.conf create mode 100644 docs/_themes/sphinx_rtd_theme/versions.html diff --git a/docs/_themes/sphinx_rtd_theme/__init__.py b/docs/_themes/sphinx_rtd_theme/__init__.py new file mode 100644 index 000000000..1440863d6 --- /dev/null +++ b/docs/_themes/sphinx_rtd_theme/__init__.py @@ -0,0 +1,17 @@ +"""Sphinx ReadTheDocs theme. + +From https://github.com/ryan-roemer/sphinx-bootstrap-theme. + +""" +import os + +VERSION = (0, 1, 5) + +__version__ = ".".join(str(v) for v in VERSION) +__version_full__ = __version__ + + +def get_html_theme_path(): + """Return list of HTML theme paths.""" + cur_dir = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) + return cur_dir diff --git a/docs/_themes/sphinx_rtd_theme/breadcrumbs.html b/docs/_themes/sphinx_rtd_theme/breadcrumbs.html new file mode 100644 index 000000000..ff0938e5c --- /dev/null +++ b/docs/_themes/sphinx_rtd_theme/breadcrumbs.html @@ -0,0 +1,19 @@ +
+ +
+
diff --git a/docs/_themes/sphinx_rtd_theme/footer.html b/docs/_themes/sphinx_rtd_theme/footer.html new file mode 100644 index 000000000..3c7afed33 --- /dev/null +++ b/docs/_themes/sphinx_rtd_theme/footer.html @@ -0,0 +1,32 @@ +
+ {% if next or prev %} + + {% endif %} + +
+ +
+

+ {%- if show_copyright %} + {%- if hasdoc('copyright') %} + {% trans path=pathto('copyright'), copyright=copyright|e %}© Copyright {{ copyright }}.{% endtrans %} + {%- else %} + {% trans copyright=copyright|e %}© Copyright {{ copyright }}.{% endtrans %} + {%- endif %} + {%- endif %} + + {%- if last_updated %} + {% trans last_updated=last_updated|e %}Last updated on {{ last_updated }}.{% endtrans %} + {%- endif %} +

+
+ + {% trans %}Sphinx theme provided by Read the Docs{% endtrans %} +
diff --git a/docs/_themes/sphinx_rtd_theme/layout.html b/docs/_themes/sphinx_rtd_theme/layout.html new file mode 100644 index 000000000..8585a6b31 --- /dev/null +++ b/docs/_themes/sphinx_rtd_theme/layout.html @@ -0,0 +1,160 @@ +{# TEMPLATE VAR SETTINGS #} +{%- set url_root = pathto('', 1) %} +{%- if url_root == '#' %}{% set url_root = '' %}{% endif %} +{%- if not embedded and docstitle %} + {%- set titlesuffix = " — "|safe + docstitle|e %} +{%- else %} + {%- set titlesuffix = "" %} +{%- endif %} + + + + + + + + {% block htmltitle %} + {{ title|striptags|e }}{{ titlesuffix }} + {% endblock %} + + {# FAVICON #} + {% if favicon %} + + {% endif %} + + {# CSS #} + + + {# OPENSEARCH #} + {% if not embedded %} + {% if use_opensearch %} + + {% endif %} + + {% endif %} + + {# RTD hosts this file, so just load on non RTD builds #} + {% if not READTHEDOCS %} + + {% endif %} + + {% for cssfile in css_files %} + + {% endfor %} + + {%- block linktags %} + {%- if hasdoc('about') %} + + {%- endif %} + {%- if hasdoc('genindex') %} + + {%- endif %} + {%- if hasdoc('search') %} + + {%- endif %} + {%- if hasdoc('copyright') %} + + {%- endif %} + + {%- if parents %} + + {%- endif %} + {%- if next %} + + {%- endif %} + {%- if prev %} + + {%- endif %} + {%- endblock %} + {%- block extrahead %} {% endblock %} + + {# Keep modernizr in head - http://modernizr.com/docs/#installing #} + + + + + + +
+ + {# SIDE NAV, TOGGLES ON MOBILE #} + + +
+ + {# MOBILE NAV, TRIGGLES SIDE NAV ON TOGGLE #} + + + + {# PAGE CONTENT #} +
+
+ {% include "breadcrumbs.html" %} +
+ {% block body %}{% endblock %} +
+ {% include "footer.html" %} +
+
+ +
+ +
+ {% include "versions.html" %} + + {% if not embedded %} + + + {%- for scriptfile in script_files %} + + {%- endfor %} + + {% endif %} + + {# RTD hosts this file, so just load on non RTD builds #} + {% if not READTHEDOCS %} + + {% endif %} + + {# STICKY NAVIGATION #} + {% if theme_sticky_navigation %} + + {% endif %} + + {%- block footer %} {% endblock %} + + + diff --git a/docs/_themes/sphinx_rtd_theme/layout_old.html b/docs/_themes/sphinx_rtd_theme/layout_old.html new file mode 100644 index 000000000..deb8df2a1 --- /dev/null +++ b/docs/_themes/sphinx_rtd_theme/layout_old.html @@ -0,0 +1,205 @@ +{# + basic/layout.html + ~~~~~~~~~~~~~~~~~ + + Master layout template for Sphinx themes. + + :copyright: Copyright 2007-2013 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +#} +{%- block doctype -%} + +{%- endblock %} +{%- set reldelim1 = reldelim1 is not defined and ' »' or reldelim1 %} +{%- set reldelim2 = reldelim2 is not defined and ' |' or reldelim2 %} +{%- set render_sidebar = (not embedded) and (not theme_nosidebar|tobool) and + (sidebars != []) %} +{%- set url_root = pathto('', 1) %} +{# XXX necessary? #} +{%- if url_root == '#' %}{% set url_root = '' %}{% endif %} +{%- if not embedded and docstitle %} + {%- set titlesuffix = " — "|safe + docstitle|e %} +{%- else %} + {%- set titlesuffix = "" %} +{%- endif %} + +{%- macro relbar() %} + +{%- endmacro %} + +{%- macro sidebar() %} + {%- if render_sidebar %} +
+
+ {%- block sidebarlogo %} + {%- if logo %} + + {%- endif %} + {%- endblock %} + {%- if sidebars != None %} + {#- new style sidebar: explicitly include/exclude templates #} + {%- for sidebartemplate in sidebars %} + {%- include sidebartemplate %} + {%- endfor %} + {%- else %} + {#- old style sidebars: using blocks -- should be deprecated #} + {%- block sidebartoc %} + {%- include "localtoc.html" %} + {%- endblock %} + {%- block sidebarrel %} + {%- include "relations.html" %} + {%- endblock %} + {%- block sidebarsourcelink %} + {%- include "sourcelink.html" %} + {%- endblock %} + {%- if customsidebar %} + {%- include customsidebar %} + {%- endif %} + {%- block sidebarsearch %} + {%- include "searchbox.html" %} + {%- endblock %} + {%- endif %} +
+
+ {%- endif %} +{%- endmacro %} + +{%- macro script() %} + + {%- for scriptfile in script_files %} + + {%- endfor %} +{%- endmacro %} + +{%- macro css() %} + + + {%- for cssfile in css_files %} + + {%- endfor %} +{%- endmacro %} + + + + + {{ metatags }} + {%- block htmltitle %} + {{ title|striptags|e }}{{ titlesuffix }} + {%- endblock %} + {{ css() }} + {%- if not embedded %} + {{ script() }} + {%- if use_opensearch %} + + {%- endif %} + {%- if favicon %} + + {%- endif %} + {%- endif %} +{%- block linktags %} + {%- if hasdoc('about') %} + + {%- endif %} + {%- if hasdoc('genindex') %} + + {%- endif %} + {%- if hasdoc('search') %} + + {%- endif %} + {%- if hasdoc('copyright') %} + + {%- endif %} + + {%- if parents %} + + {%- endif %} + {%- if next %} + + {%- endif %} + {%- if prev %} + + {%- endif %} +{%- endblock %} +{%- block extrahead %} {% endblock %} + + +{%- block header %}{% endblock %} + +{%- block relbar1 %}{{ relbar() }}{% endblock %} + +{%- block content %} + {%- block sidebar1 %} {# possible location for sidebar #} {% endblock %} + +
+ {%- block document %} +
+ {%- if render_sidebar %} +
+ {%- endif %} +
+ {% block body %} {% endblock %} +
+ {%- if render_sidebar %} +
+ {%- endif %} +
+ {%- endblock %} + + {%- block sidebar2 %}{{ sidebar() }}{% endblock %} +
+
+{%- endblock %} + +{%- block relbar2 %}{{ relbar() }}{% endblock %} + +{%- block footer %} + +

asdf asdf asdf asdf 22

+{%- endblock %} + + + diff --git a/docs/_themes/sphinx_rtd_theme/search.html b/docs/_themes/sphinx_rtd_theme/search.html new file mode 100644 index 000000000..e3aa9b5c6 --- /dev/null +++ b/docs/_themes/sphinx_rtd_theme/search.html @@ -0,0 +1,50 @@ +{# + basic/search.html + ~~~~~~~~~~~~~~~~~ + + Template for the search page. + + :copyright: Copyright 2007-2013 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +#} +{%- extends "layout.html" %} +{% set title = _('Search') %} +{% set script_files = script_files + ['_static/searchtools.js'] %} +{% block footer %} + + {# this is used when loading the search index using $.ajax fails, + such as on Chrome for documents on localhost #} + + {{ super() }} +{% endblock %} +{% block body %} + + + {% if search_performed %} +

{{ _('Search Results') }}

+ {% if not search_results %} +

{{ _('Your search did not match any documents. Please make sure that all words are spelled correctly and that you\'ve selected enough categories.') }}

+ {% endif %} + {% endif %} +
+ {% if search_results %} +
    + {% for href, caption, context in search_results %} +
  • + {{ caption }} +

    {{ context|e }}

    +
  • + {% endfor %} +
+ {% endif %} +
+{% endblock %} diff --git a/docs/_themes/sphinx_rtd_theme/searchbox.html b/docs/_themes/sphinx_rtd_theme/searchbox.html new file mode 100644 index 000000000..24418d32b --- /dev/null +++ b/docs/_themes/sphinx_rtd_theme/searchbox.html @@ -0,0 +1,7 @@ +
+
+ + + +
+
diff --git a/docs/_themes/sphinx_rtd_theme/static/css/badge_only.css b/docs/_themes/sphinx_rtd_theme/static/css/badge_only.css new file mode 100644 index 000000000..4868a0027 --- /dev/null +++ b/docs/_themes/sphinx_rtd_theme/static/css/badge_only.css @@ -0,0 +1 @@ +.fa:before{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:FontAwesome;font-weight:normal;font-style:normal;src:url("../font/fontawesome_webfont.eot");src:url("../font/fontawesome_webfont.eot?#iefix") format("embedded-opentype"),url("../font/fontawesome_webfont.woff") format("woff"),url("../font/fontawesome_webfont.ttf") format("truetype"),url("../font/fontawesome_webfont.svg#FontAwesome") format("svg")}.fa:before{display:inline-block;font-family:FontAwesome;font-style:normal;font-weight:normal;line-height:1;text-decoration:inherit}a .fa{display:inline-block;text-decoration:inherit}li .fa{display:inline-block}li .fa-large:before,li .fa-large:before{width:1.875em}ul.fas{list-style-type:none;margin-left:2em;text-indent:-0.8em}ul.fas li .fa{width:0.8em}ul.fas li .fa-large:before,ul.fas li .fa-large:before{vertical-align:baseline}.fa-book:before{content:"\f02d"}.icon-book:before{content:"\f02d"}.fa-caret-down:before{content:"\f0d7"}.icon-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.icon-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.icon-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.icon-caret-right:before{content:"\f0da"}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;border-top:solid 10px #343131;font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60;*zoom:1}.rst-versions .rst-current-version:before,.rst-versions .rst-current-version:after{display:table;content:""}.rst-versions .rst-current-version:after{clear:both}.rst-versions .rst-current-version .fa{color:#fcfcfc}.rst-versions .rst-current-version .fa-book{float:left}.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:gray;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:solid 1px #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px}.rst-versions.rst-badge .icon-book{float:none}.rst-versions.rst-badge .fa-book{float:none}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book{float:left}.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge .rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width: 768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}img{width:100%;height:auto}} diff --git a/docs/_themes/sphinx_rtd_theme/static/css/theme.css b/docs/_themes/sphinx_rtd_theme/static/css/theme.css new file mode 100644 index 000000000..eb3f865fe --- /dev/null +++ b/docs/_themes/sphinx_rtd_theme/static/css/theme.css @@ -0,0 +1,4 @@ +*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}audio:not([controls]){display:none}[hidden]{display:none}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}a:hover,a:active{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}blockquote{margin:0}dfn{font-style:italic}ins{background:#ff9;color:#000;text-decoration:none}mark{background:#ff0;color:#000;font-style:italic;font-weight:bold}pre,code,.rst-content tt,kbd,samp{font-family:monospace,serif;_font-family:"courier new",monospace;font-size:1em}pre{white-space:pre}q{quotes:none}q:before,q:after{content:"";content:none}small{font-size:85%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}ul,ol,dl{margin:0;padding:0;list-style:none;list-style-image:none}li{list-style:none}dd{margin:0}img{border:0;-ms-interpolation-mode:bicubic;vertical-align:middle;max-width:100%}svg:not(:root){overflow:hidden}figure{margin:0}form{margin:0}fieldset{border:0;margin:0;padding:0}label{cursor:pointer}legend{border:0;*margin-left:-7px;padding:0;white-space:normal}button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}button,input{line-height:normal}button,input[type="button"],input[type="reset"],input[type="submit"]{cursor:pointer;-webkit-appearance:button;*overflow:visible}button[disabled],input[disabled]{cursor:default}input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0;*width:13px;*height:13px}input[type="search"]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type="search"]::-webkit-search-decoration,input[type="search"]::-webkit-search-cancel-button{-webkit-appearance:none}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}textarea{overflow:auto;vertical-align:top;resize:vertical}table{border-collapse:collapse;border-spacing:0}td{vertical-align:top}.chromeframe{margin:0.2em 0;background:#ccc;color:#000;padding:0.2em 0}.ir{display:block;border:0;text-indent:-999em;overflow:hidden;background-color:transparent;background-repeat:no-repeat;text-align:left;direction:ltr;*line-height:0}.ir br{display:none}.hidden{display:none !important;visibility:hidden}.visuallyhidden{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.visuallyhidden.focusable:active,.visuallyhidden.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.invisible{visibility:hidden}.relative{position:relative}big,small{font-size:100%}@media print{html,body,section{background:none !important}*{box-shadow:none !important;text-shadow:none !important;filter:none !important;-ms-filter:none !important}a,a:visited{text-decoration:underline}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}@page{margin:0.5cm}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}}.fa:before,.rst-content .admonition-title:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content dl dt .headerlink:before,.icon:before,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-alert,.rst-content .note,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .warning,.rst-content .seealso,.rst-content .admonition-todo,.btn,input[type="text"],input[type="password"],input[type="email"],input[type="url"],input[type="date"],input[type="month"],input[type="time"],input[type="datetime"],input[type="datetime-local"],input[type="week"],input[type="number"],input[type="search"],input[type="tel"],input[type="color"],select,textarea,.wy-menu-vertical li.on a,.wy-menu-vertical li.current>a,.wy-side-nav-search>a,.wy-side-nav-search .wy-dropdown>a,.wy-nav-top a{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;content:""}.clearfix:after{clear:both}/*! + * Font Awesome 4.1.0 by @davegandy - http://fontawesome.io - @fontawesome + * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) + */@font-face{font-family:'FontAwesome';src:url("../fonts/fontawesome-webfont.eot?v=4.1.0");src:url("../fonts/fontawesome-webfont.eot?#iefix&v=4.1.0") format("embedded-opentype"),url("../fonts/fontawesome-webfont.woff?v=4.1.0") format("woff"),url("../fonts/fontawesome-webfont.ttf?v=4.1.0") format("truetype"),url("../fonts/fontawesome-webfont.svg?v=4.1.0#fontawesomeregular") format("svg");font-weight:normal;font-style:normal}.fa,.rst-content .admonition-title,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content dl dt .headerlink,.icon{display:inline-block;font-family:FontAwesome;font-style:normal;font-weight:normal;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333em;line-height:0.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14286em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14286em;width:2.14286em;top:0.14286em;text-align:center}.fa-li.fa-lg{left:-1.85714em}.fa-border{padding:.2em .25em .15em;border:solid 0.08em #eee;border-radius:.1em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left,.rst-content .pull-left.admonition-title,.rst-content h1 .pull-left.headerlink,.rst-content h2 .pull-left.headerlink,.rst-content h3 .pull-left.headerlink,.rst-content h4 .pull-left.headerlink,.rst-content h5 .pull-left.headerlink,.rst-content h6 .pull-left.headerlink,.rst-content dl dt .pull-left.headerlink,.pull-left.icon{margin-right:.3em}.fa.pull-right,.rst-content .pull-right.admonition-title,.rst-content h1 .pull-right.headerlink,.rst-content h2 .pull-right.headerlink,.rst-content h3 .pull-right.headerlink,.rst-content h4 .pull-right.headerlink,.rst-content h5 .pull-right.headerlink,.rst-content h6 .pull-right.headerlink,.rst-content dl dt .pull-right.headerlink,.pull-right.icon{margin-left:.3em}.fa-spin{-webkit-animation:spin 2s infinite linear;-moz-animation:spin 2s infinite linear;-o-animation:spin 2s infinite linear;animation:spin 2s infinite linear}@-moz-keyframes spin{0%{-moz-transform:rotate(0deg)}100%{-moz-transform:rotate(359deg)}}@-webkit-keyframes spin{0%{-webkit-transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg)}}@-o-keyframes spin{0%{-o-transform:rotate(0deg)}100%{-o-transform:rotate(359deg)}}@keyframes spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1);-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3);-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=0);-webkit-transform:scale(-1, 1);-moz-transform:scale(-1, 1);-ms-transform:scale(-1, 1);-o-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:scale(1, -1);-moz-transform:scale(1, -1);-ms-transform:scale(1, -1);-o-transform:scale(1, -1);transform:scale(1, -1)}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before,.icon-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before,.icon-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before,.icon-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before{content:"\f057"}.fa-check-circle:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.rst-content .admonition-title:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before,.icon-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook:before{content:"\f09a"}.fa-github:before,.icon-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before,.icon-circle-arrow-left:before{content:"\f0a8"}.fa-arrow-circle-right:before,.icon-circle-arrow-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before,.icon-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before,.wy-dropdown .caret:before,.icon-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before,.icon-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper-square:before,.fa-pied-piper:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa,.rst-content .admonition-title,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content dl dt .headerlink,.icon,.wy-dropdown .caret,.wy-inline-validate.wy-inline-validate-success .wy-input-context,.wy-inline-validate.wy-inline-validate-danger .wy-input-context,.wy-inline-validate.wy-inline-validate-warning .wy-input-context,.wy-inline-validate.wy-inline-validate-info .wy-input-context{font-family:inherit}.fa:before,.rst-content .admonition-title:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content dl dt .headerlink:before,.icon:before,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before{font-family:"FontAwesome";display:inline-block;font-style:normal;font-weight:normal;line-height:1;text-decoration:inherit}a .fa,a .rst-content .admonition-title,.rst-content a .admonition-title,a .rst-content h1 .headerlink,.rst-content h1 a .headerlink,a .rst-content h2 .headerlink,.rst-content h2 a .headerlink,a .rst-content h3 .headerlink,.rst-content h3 a .headerlink,a .rst-content h4 .headerlink,.rst-content h4 a .headerlink,a .rst-content h5 .headerlink,.rst-content h5 a .headerlink,a .rst-content h6 .headerlink,.rst-content h6 a .headerlink,a .rst-content dl dt .headerlink,.rst-content dl dt a .headerlink,a .icon{display:inline-block;text-decoration:inherit}.btn .fa,.btn .rst-content .admonition-title,.rst-content .btn .admonition-title,.btn .rst-content h1 .headerlink,.rst-content h1 .btn .headerlink,.btn .rst-content h2 .headerlink,.rst-content h2 .btn .headerlink,.btn .rst-content h3 .headerlink,.rst-content h3 .btn .headerlink,.btn .rst-content h4 .headerlink,.rst-content h4 .btn .headerlink,.btn .rst-content h5 .headerlink,.rst-content h5 .btn .headerlink,.btn .rst-content h6 .headerlink,.rst-content h6 .btn .headerlink,.btn .rst-content dl dt .headerlink,.rst-content dl dt .btn .headerlink,.btn .icon,.nav .fa,.nav .rst-content .admonition-title,.rst-content .nav .admonition-title,.nav .rst-content h1 .headerlink,.rst-content h1 .nav .headerlink,.nav .rst-content h2 .headerlink,.rst-content h2 .nav .headerlink,.nav .rst-content h3 .headerlink,.rst-content h3 .nav .headerlink,.nav .rst-content h4 .headerlink,.rst-content h4 .nav .headerlink,.nav .rst-content h5 .headerlink,.rst-content h5 .nav .headerlink,.nav .rst-content h6 .headerlink,.rst-content h6 .nav .headerlink,.nav .rst-content dl dt .headerlink,.rst-content dl dt .nav .headerlink,.nav .icon{display:inline}.btn .fa.fa-large,.btn .rst-content .fa-large.admonition-title,.rst-content .btn .fa-large.admonition-title,.btn .rst-content h1 .fa-large.headerlink,.rst-content h1 .btn .fa-large.headerlink,.btn .rst-content h2 .fa-large.headerlink,.rst-content h2 .btn .fa-large.headerlink,.btn .rst-content h3 .fa-large.headerlink,.rst-content h3 .btn .fa-large.headerlink,.btn .rst-content h4 .fa-large.headerlink,.rst-content h4 .btn .fa-large.headerlink,.btn .rst-content h5 .fa-large.headerlink,.rst-content h5 .btn .fa-large.headerlink,.btn .rst-content h6 .fa-large.headerlink,.rst-content h6 .btn .fa-large.headerlink,.btn .rst-content dl dt .fa-large.headerlink,.rst-content dl dt .btn .fa-large.headerlink,.btn .fa-large.icon,.nav .fa.fa-large,.nav .rst-content .fa-large.admonition-title,.rst-content .nav .fa-large.admonition-title,.nav .rst-content h1 .fa-large.headerlink,.rst-content h1 .nav .fa-large.headerlink,.nav .rst-content h2 .fa-large.headerlink,.rst-content h2 .nav .fa-large.headerlink,.nav .rst-content h3 .fa-large.headerlink,.rst-content h3 .nav .fa-large.headerlink,.nav .rst-content h4 .fa-large.headerlink,.rst-content h4 .nav .fa-large.headerlink,.nav .rst-content h5 .fa-large.headerlink,.rst-content h5 .nav .fa-large.headerlink,.nav .rst-content h6 .fa-large.headerlink,.rst-content h6 .nav .fa-large.headerlink,.nav .rst-content dl dt .fa-large.headerlink,.rst-content dl dt .nav .fa-large.headerlink,.nav .fa-large.icon{line-height:0.9em}.btn .fa.fa-spin,.btn .rst-content .fa-spin.admonition-title,.rst-content .btn .fa-spin.admonition-title,.btn .rst-content h1 .fa-spin.headerlink,.rst-content h1 .btn .fa-spin.headerlink,.btn .rst-content h2 .fa-spin.headerlink,.rst-content h2 .btn .fa-spin.headerlink,.btn .rst-content h3 .fa-spin.headerlink,.rst-content h3 .btn .fa-spin.headerlink,.btn .rst-content h4 .fa-spin.headerlink,.rst-content h4 .btn .fa-spin.headerlink,.btn .rst-content h5 .fa-spin.headerlink,.rst-content h5 .btn .fa-spin.headerlink,.btn .rst-content h6 .fa-spin.headerlink,.rst-content h6 .btn .fa-spin.headerlink,.btn .rst-content dl dt .fa-spin.headerlink,.rst-content dl dt .btn .fa-spin.headerlink,.btn .fa-spin.icon,.nav .fa.fa-spin,.nav .rst-content .fa-spin.admonition-title,.rst-content .nav .fa-spin.admonition-title,.nav .rst-content h1 .fa-spin.headerlink,.rst-content h1 .nav .fa-spin.headerlink,.nav .rst-content h2 .fa-spin.headerlink,.rst-content h2 .nav .fa-spin.headerlink,.nav .rst-content h3 .fa-spin.headerlink,.rst-content h3 .nav .fa-spin.headerlink,.nav .rst-content h4 .fa-spin.headerlink,.rst-content h4 .nav .fa-spin.headerlink,.nav .rst-content h5 .fa-spin.headerlink,.rst-content h5 .nav .fa-spin.headerlink,.nav .rst-content h6 .fa-spin.headerlink,.rst-content h6 .nav .fa-spin.headerlink,.nav .rst-content dl dt .fa-spin.headerlink,.rst-content dl dt .nav .fa-spin.headerlink,.nav .fa-spin.icon{display:inline-block}.btn.fa:before,.rst-content .btn.admonition-title:before,.rst-content h1 .btn.headerlink:before,.rst-content h2 .btn.headerlink:before,.rst-content h3 .btn.headerlink:before,.rst-content h4 .btn.headerlink:before,.rst-content h5 .btn.headerlink:before,.rst-content h6 .btn.headerlink:before,.rst-content dl dt .btn.headerlink:before,.btn.icon:before{opacity:0.5;-webkit-transition:opacity 0.05s ease-in;-moz-transition:opacity 0.05s ease-in;transition:opacity 0.05s ease-in}.btn.fa:hover:before,.rst-content .btn.admonition-title:hover:before,.rst-content h1 .btn.headerlink:hover:before,.rst-content h2 .btn.headerlink:hover:before,.rst-content h3 .btn.headerlink:hover:before,.rst-content h4 .btn.headerlink:hover:before,.rst-content h5 .btn.headerlink:hover:before,.rst-content h6 .btn.headerlink:hover:before,.rst-content dl dt .btn.headerlink:hover:before,.btn.icon:hover:before{opacity:1}.btn-mini .fa:before,.btn-mini .rst-content .admonition-title:before,.rst-content .btn-mini .admonition-title:before,.btn-mini .rst-content h1 .headerlink:before,.rst-content h1 .btn-mini .headerlink:before,.btn-mini .rst-content h2 .headerlink:before,.rst-content h2 .btn-mini .headerlink:before,.btn-mini .rst-content h3 .headerlink:before,.rst-content h3 .btn-mini .headerlink:before,.btn-mini .rst-content h4 .headerlink:before,.rst-content h4 .btn-mini .headerlink:before,.btn-mini .rst-content h5 .headerlink:before,.rst-content h5 .btn-mini .headerlink:before,.btn-mini .rst-content h6 .headerlink:before,.rst-content h6 .btn-mini .headerlink:before,.btn-mini .rst-content dl dt .headerlink:before,.rst-content dl dt .btn-mini .headerlink:before,.btn-mini .icon:before{font-size:14px;vertical-align:-15%}.wy-alert,.rst-content .note,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .warning,.rst-content .seealso,.rst-content .admonition-todo{padding:12px;line-height:24px;margin-bottom:24px;background:#e7f2fa}.wy-alert-title,.rst-content .admonition-title{color:#fff;font-weight:bold;display:block;color:#fff;background:#6ab0de;margin:-12px;padding:6px 12px;margin-bottom:12px}.wy-alert.wy-alert-danger,.rst-content .wy-alert-danger.note,.rst-content .wy-alert-danger.attention,.rst-content .wy-alert-danger.caution,.rst-content .danger,.rst-content .error,.rst-content .wy-alert-danger.hint,.rst-content .wy-alert-danger.important,.rst-content .wy-alert-danger.tip,.rst-content .wy-alert-danger.warning,.rst-content .wy-alert-danger.seealso,.rst-content .wy-alert-danger.admonition-todo{background:#fdf3f2}.wy-alert.wy-alert-danger .wy-alert-title,.rst-content .wy-alert-danger.note .wy-alert-title,.rst-content .wy-alert-danger.attention .wy-alert-title,.rst-content .wy-alert-danger.caution .wy-alert-title,.rst-content .danger .wy-alert-title,.rst-content .error .wy-alert-title,.rst-content .wy-alert-danger.hint .wy-alert-title,.rst-content .wy-alert-danger.important .wy-alert-title,.rst-content .wy-alert-danger.tip .wy-alert-title,.rst-content .wy-alert-danger.warning .wy-alert-title,.rst-content .wy-alert-danger.seealso .wy-alert-title,.rst-content .wy-alert-danger.admonition-todo .wy-alert-title,.wy-alert.wy-alert-danger .rst-content .admonition-title,.rst-content .wy-alert.wy-alert-danger .admonition-title,.rst-content .wy-alert-danger.note .admonition-title,.rst-content .wy-alert-danger.attention .admonition-title,.rst-content .wy-alert-danger.caution .admonition-title,.rst-content .danger .admonition-title,.rst-content .error .admonition-title,.rst-content .wy-alert-danger.hint .admonition-title,.rst-content .wy-alert-danger.important .admonition-title,.rst-content .wy-alert-danger.tip .admonition-title,.rst-content .wy-alert-danger.warning .admonition-title,.rst-content .wy-alert-danger.seealso .admonition-title,.rst-content .wy-alert-danger.admonition-todo .admonition-title{background:#f29f97}.wy-alert.wy-alert-warning,.rst-content .wy-alert-warning.note,.rst-content .attention,.rst-content .caution,.rst-content .wy-alert-warning.danger,.rst-content .wy-alert-warning.error,.rst-content .wy-alert-warning.hint,.rst-content .wy-alert-warning.important,.rst-content .wy-alert-warning.tip,.rst-content .warning,.rst-content .wy-alert-warning.seealso,.rst-content .admonition-todo{background:#ffedcc}.wy-alert.wy-alert-warning .wy-alert-title,.rst-content .wy-alert-warning.note .wy-alert-title,.rst-content .attention .wy-alert-title,.rst-content .caution .wy-alert-title,.rst-content .wy-alert-warning.danger .wy-alert-title,.rst-content .wy-alert-warning.error .wy-alert-title,.rst-content .wy-alert-warning.hint .wy-alert-title,.rst-content .wy-alert-warning.important .wy-alert-title,.rst-content .wy-alert-warning.tip .wy-alert-title,.rst-content .warning .wy-alert-title,.rst-content .wy-alert-warning.seealso .wy-alert-title,.rst-content .admonition-todo .wy-alert-title,.wy-alert.wy-alert-warning .rst-content .admonition-title,.rst-content .wy-alert.wy-alert-warning .admonition-title,.rst-content .wy-alert-warning.note .admonition-title,.rst-content .attention .admonition-title,.rst-content .caution .admonition-title,.rst-content .wy-alert-warning.danger .admonition-title,.rst-content .wy-alert-warning.error .admonition-title,.rst-content .wy-alert-warning.hint .admonition-title,.rst-content .wy-alert-warning.important .admonition-title,.rst-content .wy-alert-warning.tip .admonition-title,.rst-content .warning .admonition-title,.rst-content .wy-alert-warning.seealso .admonition-title,.rst-content .admonition-todo .admonition-title{background:#f0b37e}.wy-alert.wy-alert-info,.rst-content .note,.rst-content .wy-alert-info.attention,.rst-content .wy-alert-info.caution,.rst-content .wy-alert-info.danger,.rst-content .wy-alert-info.error,.rst-content .wy-alert-info.hint,.rst-content .wy-alert-info.important,.rst-content .wy-alert-info.tip,.rst-content .wy-alert-info.warning,.rst-content .seealso,.rst-content .wy-alert-info.admonition-todo{background:#e7f2fa}.wy-alert.wy-alert-info .wy-alert-title,.rst-content .note .wy-alert-title,.rst-content .wy-alert-info.attention .wy-alert-title,.rst-content .wy-alert-info.caution .wy-alert-title,.rst-content .wy-alert-info.danger .wy-alert-title,.rst-content .wy-alert-info.error .wy-alert-title,.rst-content .wy-alert-info.hint .wy-alert-title,.rst-content .wy-alert-info.important .wy-alert-title,.rst-content .wy-alert-info.tip .wy-alert-title,.rst-content .wy-alert-info.warning .wy-alert-title,.rst-content .seealso .wy-alert-title,.rst-content .wy-alert-info.admonition-todo .wy-alert-title,.wy-alert.wy-alert-info .rst-content .admonition-title,.rst-content .wy-alert.wy-alert-info .admonition-title,.rst-content .note .admonition-title,.rst-content .wy-alert-info.attention .admonition-title,.rst-content .wy-alert-info.caution .admonition-title,.rst-content .wy-alert-info.danger .admonition-title,.rst-content .wy-alert-info.error .admonition-title,.rst-content .wy-alert-info.hint .admonition-title,.rst-content .wy-alert-info.important .admonition-title,.rst-content .wy-alert-info.tip .admonition-title,.rst-content .wy-alert-info.warning .admonition-title,.rst-content .seealso .admonition-title,.rst-content .wy-alert-info.admonition-todo .admonition-title{background:#6ab0de}.wy-alert.wy-alert-success,.rst-content .wy-alert-success.note,.rst-content .wy-alert-success.attention,.rst-content .wy-alert-success.caution,.rst-content .wy-alert-success.danger,.rst-content .wy-alert-success.error,.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .wy-alert-success.warning,.rst-content .wy-alert-success.seealso,.rst-content .wy-alert-success.admonition-todo{background:#dbfaf4}.wy-alert.wy-alert-success .wy-alert-title,.rst-content .wy-alert-success.note .wy-alert-title,.rst-content .wy-alert-success.attention .wy-alert-title,.rst-content .wy-alert-success.caution .wy-alert-title,.rst-content .wy-alert-success.danger .wy-alert-title,.rst-content .wy-alert-success.error .wy-alert-title,.rst-content .hint .wy-alert-title,.rst-content .important .wy-alert-title,.rst-content .tip .wy-alert-title,.rst-content .wy-alert-success.warning .wy-alert-title,.rst-content .wy-alert-success.seealso .wy-alert-title,.rst-content .wy-alert-success.admonition-todo .wy-alert-title,.wy-alert.wy-alert-success .rst-content .admonition-title,.rst-content .wy-alert.wy-alert-success .admonition-title,.rst-content .wy-alert-success.note .admonition-title,.rst-content .wy-alert-success.attention .admonition-title,.rst-content .wy-alert-success.caution .admonition-title,.rst-content .wy-alert-success.danger .admonition-title,.rst-content .wy-alert-success.error .admonition-title,.rst-content .hint .admonition-title,.rst-content .important .admonition-title,.rst-content .tip .admonition-title,.rst-content .wy-alert-success.warning .admonition-title,.rst-content .wy-alert-success.seealso .admonition-title,.rst-content .wy-alert-success.admonition-todo .admonition-title{background:#1abc9c}.wy-alert.wy-alert-neutral,.rst-content .wy-alert-neutral.note,.rst-content .wy-alert-neutral.attention,.rst-content .wy-alert-neutral.caution,.rst-content .wy-alert-neutral.danger,.rst-content .wy-alert-neutral.error,.rst-content .wy-alert-neutral.hint,.rst-content .wy-alert-neutral.important,.rst-content .wy-alert-neutral.tip,.rst-content .wy-alert-neutral.warning,.rst-content .wy-alert-neutral.seealso,.rst-content .wy-alert-neutral.admonition-todo{background:#f3f6f6}.wy-alert.wy-alert-neutral .wy-alert-title,.rst-content .wy-alert-neutral.note .wy-alert-title,.rst-content .wy-alert-neutral.attention .wy-alert-title,.rst-content .wy-alert-neutral.caution .wy-alert-title,.rst-content .wy-alert-neutral.danger .wy-alert-title,.rst-content .wy-alert-neutral.error .wy-alert-title,.rst-content .wy-alert-neutral.hint .wy-alert-title,.rst-content .wy-alert-neutral.important .wy-alert-title,.rst-content .wy-alert-neutral.tip .wy-alert-title,.rst-content .wy-alert-neutral.warning .wy-alert-title,.rst-content .wy-alert-neutral.seealso .wy-alert-title,.rst-content .wy-alert-neutral.admonition-todo .wy-alert-title,.wy-alert.wy-alert-neutral .rst-content .admonition-title,.rst-content .wy-alert.wy-alert-neutral .admonition-title,.rst-content .wy-alert-neutral.note .admonition-title,.rst-content .wy-alert-neutral.attention .admonition-title,.rst-content .wy-alert-neutral.caution .admonition-title,.rst-content .wy-alert-neutral.danger .admonition-title,.rst-content .wy-alert-neutral.error .admonition-title,.rst-content .wy-alert-neutral.hint .admonition-title,.rst-content .wy-alert-neutral.important .admonition-title,.rst-content .wy-alert-neutral.tip .admonition-title,.rst-content .wy-alert-neutral.warning .admonition-title,.rst-content .wy-alert-neutral.seealso .admonition-title,.rst-content .wy-alert-neutral.admonition-todo .admonition-title{color:#404040;background:#e1e4e5}.wy-alert.wy-alert-neutral a,.rst-content .wy-alert-neutral.note a,.rst-content .wy-alert-neutral.attention a,.rst-content .wy-alert-neutral.caution a,.rst-content .wy-alert-neutral.danger a,.rst-content .wy-alert-neutral.error a,.rst-content .wy-alert-neutral.hint a,.rst-content .wy-alert-neutral.important a,.rst-content .wy-alert-neutral.tip a,.rst-content .wy-alert-neutral.warning a,.rst-content .wy-alert-neutral.seealso a,.rst-content .wy-alert-neutral.admonition-todo a{color:#2980b9}.wy-alert p:last-child,.rst-content .note p:last-child,.rst-content .attention p:last-child,.rst-content .caution p:last-child,.rst-content .danger p:last-child,.rst-content .error p:last-child,.rst-content .hint p:last-child,.rst-content .important p:last-child,.rst-content .tip p:last-child,.rst-content .warning p:last-child,.rst-content .seealso p:last-child,.rst-content .admonition-todo p:last-child{margin-bottom:0}.wy-tray-container{position:fixed;bottom:0px;left:0;z-index:600}.wy-tray-container li{display:block;width:300px;background:transparent;color:#fff;text-align:center;box-shadow:0 5px 5px 0 rgba(0,0,0,0.1);padding:0 24px;min-width:20%;opacity:0;height:0;line-height:56px;overflow:hidden;-webkit-transition:all 0.3s ease-in;-moz-transition:all 0.3s ease-in;transition:all 0.3s ease-in}.wy-tray-container li.wy-tray-item-success{background:#27ae60}.wy-tray-container li.wy-tray-item-info{background:#2980b9}.wy-tray-container li.wy-tray-item-warning{background:#e67e22}.wy-tray-container li.wy-tray-item-danger{background:#e74c3c}.wy-tray-container li.on{opacity:1;height:56px}@media screen and (max-width: 768px){.wy-tray-container{bottom:auto;top:0;width:100%}.wy-tray-container li{width:100%}}button{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle;cursor:pointer;line-height:normal;-webkit-appearance:button;*overflow:visible}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}button[disabled]{cursor:default}.btn{display:inline-block;border-radius:2px;line-height:normal;white-space:nowrap;text-align:center;cursor:pointer;font-size:100%;padding:6px 12px 8px 12px;color:#fff;border:1px solid rgba(0,0,0,0.1);background-color:#27ae60;text-decoration:none;font-weight:normal;font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif;box-shadow:0px 1px 2px -1px rgba(255,255,255,0.5) inset,0px -2px 0px 0px rgba(0,0,0,0.1) inset;outline-none:false;vertical-align:middle;*display:inline;zoom:1;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-transition:all 0.1s linear;-moz-transition:all 0.1s linear;transition:all 0.1s linear}.btn-hover{background:#2e8ece;color:#fff}.btn:hover{background:#2cc36b;color:#fff}.btn:focus{background:#2cc36b;outline:0}.btn:active{box-shadow:0px -1px 0px 0px rgba(0,0,0,0.05) inset,0px 2px 0px 0px rgba(0,0,0,0.1) inset;padding:8px 12px 6px 12px}.btn:visited{color:#fff}.btn:disabled{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:0.4;cursor:not-allowed;box-shadow:none}.btn-disabled{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:0.4;cursor:not-allowed;box-shadow:none}.btn-disabled:hover,.btn-disabled:focus,.btn-disabled:active{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:0.4;cursor:not-allowed;box-shadow:none}.btn::-moz-focus-inner{padding:0;border:0}.btn-small{font-size:80%}.btn-info{background-color:#2980b9 !important}.btn-info:hover{background-color:#2e8ece !important}.btn-neutral{background-color:#f3f6f6 !important;color:#404040 !important}.btn-neutral:hover{background-color:#e5ebeb !important;color:#404040}.btn-neutral:visited{color:#404040 !important}.btn-success{background-color:#27ae60 !important}.btn-success:hover{background-color:#295 !important}.btn-danger{background-color:#e74c3c !important}.btn-danger:hover{background-color:#ea6153 !important}.btn-warning{background-color:#e67e22 !important}.btn-warning:hover{background-color:#e98b39 !important}.btn-invert{background-color:#222}.btn-invert:hover{background-color:#2f2f2f !important}.btn-link{background-color:transparent !important;color:#2980b9;box-shadow:none;border-color:transparent !important}.btn-link:hover{background-color:transparent !important;color:#409ad5 !important;box-shadow:none}.btn-link:active{background-color:transparent !important;color:#409ad5 !important;box-shadow:none}.btn-link:visited{color:#9b59b6}.wy-btn-group .btn,.wy-control .btn{vertical-align:middle}.wy-btn-group{margin-bottom:24px;*zoom:1}.wy-btn-group:before,.wy-btn-group:after{display:table;content:""}.wy-btn-group:after{clear:both}.wy-dropdown{position:relative;display:inline-block}.wy-dropdown-menu{position:absolute;left:0;display:none;float:left;top:100%;min-width:100%;background:#fcfcfc;z-index:100;border:solid 1px #cfd7dd;box-shadow:0 2px 2px 0 rgba(0,0,0,0.1);padding:12px}.wy-dropdown-menu>dd>a{display:block;clear:both;color:#404040;white-space:nowrap;font-size:90%;padding:0 12px;cursor:pointer}.wy-dropdown-menu>dd>a:hover{background:#2980b9;color:#fff}.wy-dropdown-menu>dd.divider{border-top:solid 1px #cfd7dd;margin:6px 0}.wy-dropdown-menu>dd.search{padding-bottom:12px}.wy-dropdown-menu>dd.search input[type="search"]{width:100%}.wy-dropdown-menu>dd.call-to-action{background:#e3e3e3;text-transform:uppercase;font-weight:500;font-size:80%}.wy-dropdown-menu>dd.call-to-action:hover{background:#e3e3e3}.wy-dropdown-menu>dd.call-to-action .btn{color:#fff}.wy-dropdown.wy-dropdown-up .wy-dropdown-menu{bottom:100%;top:auto;left:auto;right:0}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu{background:#fcfcfc;margin-top:2px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a{padding:6px 12px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a:hover{background:#2980b9;color:#fff}.wy-dropdown.wy-dropdown-left .wy-dropdown-menu{right:0;text-align:right}.wy-dropdown-arrow:before{content:" ";border-bottom:5px solid #f5f5f5;border-left:5px solid transparent;border-right:5px solid transparent;position:absolute;display:block;top:-4px;left:50%;margin-left:-3px}.wy-dropdown-arrow.wy-dropdown-arrow-left:before{left:11px}.wy-form-stacked select{display:block}.wy-form-aligned input,.wy-form-aligned textarea,.wy-form-aligned select,.wy-form-aligned .wy-help-inline,.wy-form-aligned label{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-form-aligned .wy-control-group>label{display:inline-block;vertical-align:middle;width:10em;margin:6px 12px 0 0;float:left}.wy-form-aligned .wy-control{float:left}.wy-form-aligned .wy-control label{display:block}.wy-form-aligned .wy-control select{margin-top:6px}fieldset{border:0;margin:0;padding:0}legend{display:block;width:100%;border:0;padding:0;white-space:normal;margin-bottom:24px;font-size:150%;*margin-left:-7px}label{display:block;margin:0 0 0.3125em 0;color:#999;font-size:90%}input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}.wy-control-group{margin-bottom:24px;*zoom:1;max-width:68em;margin-left:auto;margin-right:auto;*zoom:1}.wy-control-group:before,.wy-control-group:after{display:table;content:""}.wy-control-group:after{clear:both}.wy-control-group:before,.wy-control-group:after{display:table;content:""}.wy-control-group:after{clear:both}.wy-control-group.wy-control-group-required>label:after{content:" *";color:#e74c3c}.wy-control-group .wy-form-full,.wy-control-group .wy-form-halves,.wy-control-group .wy-form-thirds{padding-bottom:12px}.wy-control-group .wy-form-full select,.wy-control-group .wy-form-halves select,.wy-control-group .wy-form-thirds select{width:100%}.wy-control-group .wy-form-full input[type="text"],.wy-control-group .wy-form-full input[type="password"],.wy-control-group .wy-form-full input[type="email"],.wy-control-group .wy-form-full input[type="url"],.wy-control-group .wy-form-full input[type="date"],.wy-control-group .wy-form-full input[type="month"],.wy-control-group .wy-form-full input[type="time"],.wy-control-group .wy-form-full input[type="datetime"],.wy-control-group .wy-form-full input[type="datetime-local"],.wy-control-group .wy-form-full input[type="week"],.wy-control-group .wy-form-full input[type="number"],.wy-control-group .wy-form-full input[type="search"],.wy-control-group .wy-form-full input[type="tel"],.wy-control-group .wy-form-full input[type="color"],.wy-control-group .wy-form-halves input[type="text"],.wy-control-group .wy-form-halves input[type="password"],.wy-control-group .wy-form-halves input[type="email"],.wy-control-group .wy-form-halves input[type="url"],.wy-control-group .wy-form-halves input[type="date"],.wy-control-group .wy-form-halves input[type="month"],.wy-control-group .wy-form-halves input[type="time"],.wy-control-group .wy-form-halves input[type="datetime"],.wy-control-group .wy-form-halves input[type="datetime-local"],.wy-control-group .wy-form-halves input[type="week"],.wy-control-group .wy-form-halves input[type="number"],.wy-control-group .wy-form-halves input[type="search"],.wy-control-group .wy-form-halves input[type="tel"],.wy-control-group .wy-form-halves input[type="color"],.wy-control-group .wy-form-thirds input[type="text"],.wy-control-group .wy-form-thirds input[type="password"],.wy-control-group .wy-form-thirds input[type="email"],.wy-control-group .wy-form-thirds input[type="url"],.wy-control-group .wy-form-thirds input[type="date"],.wy-control-group .wy-form-thirds input[type="month"],.wy-control-group .wy-form-thirds input[type="time"],.wy-control-group .wy-form-thirds input[type="datetime"],.wy-control-group .wy-form-thirds input[type="datetime-local"],.wy-control-group .wy-form-thirds input[type="week"],.wy-control-group .wy-form-thirds input[type="number"],.wy-control-group .wy-form-thirds input[type="search"],.wy-control-group .wy-form-thirds input[type="tel"],.wy-control-group .wy-form-thirds input[type="color"]{width:100%}.wy-control-group .wy-form-full{display:block;float:left;margin-right:2.35765%;width:100%;margin-right:0}.wy-control-group .wy-form-full:last-child{margin-right:0}.wy-control-group .wy-form-halves{display:block;float:left;margin-right:2.35765%;width:48.82117%}.wy-control-group .wy-form-halves:last-child{margin-right:0}.wy-control-group .wy-form-halves:nth-of-type(2n){margin-right:0}.wy-control-group .wy-form-halves:nth-of-type(2n+1){clear:left}.wy-control-group .wy-form-thirds{display:block;float:left;margin-right:2.35765%;width:31.76157%}.wy-control-group .wy-form-thirds:last-child{margin-right:0}.wy-control-group .wy-form-thirds:nth-of-type(3n){margin-right:0}.wy-control-group .wy-form-thirds:nth-of-type(3n+1){clear:left}.wy-control-group.wy-control-group-no-input .wy-control{margin:6px 0 0 0;font-size:90%}.wy-control-no-input{display:inline-block;margin:6px 0 0 0;font-size:90%}.wy-control-group.fluid-input input[type="text"],.wy-control-group.fluid-input input[type="password"],.wy-control-group.fluid-input input[type="email"],.wy-control-group.fluid-input input[type="url"],.wy-control-group.fluid-input input[type="date"],.wy-control-group.fluid-input input[type="month"],.wy-control-group.fluid-input input[type="time"],.wy-control-group.fluid-input input[type="datetime"],.wy-control-group.fluid-input input[type="datetime-local"],.wy-control-group.fluid-input input[type="week"],.wy-control-group.fluid-input input[type="number"],.wy-control-group.fluid-input input[type="search"],.wy-control-group.fluid-input input[type="tel"],.wy-control-group.fluid-input input[type="color"]{width:100%}.wy-form-message-inline{display:inline-block;padding-left:0.3em;color:#666;vertical-align:middle;font-size:90%}.wy-form-message{display:block;color:#999;font-size:70%;margin-top:0.3125em;font-style:italic}input{line-height:normal}input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer;font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif;*overflow:visible}input[type="text"],input[type="password"],input[type="email"],input[type="url"],input[type="date"],input[type="month"],input[type="time"],input[type="datetime"],input[type="datetime-local"],input[type="week"],input[type="number"],input[type="search"],input[type="tel"],input[type="color"]{-webkit-appearance:none;padding:6px;display:inline-block;border:1px solid #ccc;font-size:80%;font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif;box-shadow:inset 0 1px 3px #ddd;border-radius:0;-webkit-transition:border 0.3s linear;-moz-transition:border 0.3s linear;transition:border 0.3s linear}input[type="datetime-local"]{padding:0.34375em 0.625em}input[disabled]{cursor:default}input[type="checkbox"],input[type="radio"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0;margin-right:0.3125em;*height:13px;*width:13px}input[type="search"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}input[type="text"]:focus,input[type="password"]:focus,input[type="email"]:focus,input[type="url"]:focus,input[type="date"]:focus,input[type="month"]:focus,input[type="time"]:focus,input[type="datetime"]:focus,input[type="datetime-local"]:focus,input[type="week"]:focus,input[type="number"]:focus,input[type="search"]:focus,input[type="tel"]:focus,input[type="color"]:focus{outline:0;outline:thin dotted \9;border-color:#333}input.no-focus:focus{border-color:#ccc !important}input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus{outline:thin dotted #333;outline:1px auto #129fea}input[type="text"][disabled],input[type="password"][disabled],input[type="email"][disabled],input[type="url"][disabled],input[type="date"][disabled],input[type="month"][disabled],input[type="time"][disabled],input[type="datetime"][disabled],input[type="datetime-local"][disabled],input[type="week"][disabled],input[type="number"][disabled],input[type="search"][disabled],input[type="tel"][disabled],input[type="color"][disabled]{cursor:not-allowed;background-color:#f3f6f6;color:#cad2d3}input:focus:invalid,textarea:focus:invalid,select:focus:invalid{color:#e74c3c;border:1px solid #e74c3c}input:focus:invalid:focus,textarea:focus:invalid:focus,select:focus:invalid:focus{border-color:#e74c3c}input[type="file"]:focus:invalid:focus,input[type="radio"]:focus:invalid:focus,input[type="checkbox"]:focus:invalid:focus{outline-color:#e74c3c}input.wy-input-large{padding:12px;font-size:100%}textarea{overflow:auto;vertical-align:top;width:100%;font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif}select,textarea{padding:0.5em 0.625em;display:inline-block;border:1px solid #ccc;font-size:80%;box-shadow:inset 0 1px 3px #ddd;-webkit-transition:border 0.3s linear;-moz-transition:border 0.3s linear;transition:border 0.3s linear}select{border:1px solid #ccc;background-color:#fff}select[multiple]{height:auto}select:focus,textarea:focus{outline:0}select[disabled],textarea[disabled],input[readonly],select[readonly],textarea[readonly]{cursor:not-allowed;background-color:#fff;color:#cad2d3;border-color:transparent}.wy-checkbox,.wy-radio{margin:6px 0;color:#404040;display:block}.wy-checkbox input,.wy-radio input{vertical-align:baseline}.wy-form-message-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-input-prefix,.wy-input-suffix{white-space:nowrap}.wy-input-prefix .wy-input-context,.wy-input-suffix .wy-input-context{padding:6px;display:inline-block;font-size:80%;background-color:#f3f6f6;border:solid 1px #ccc;color:#999}.wy-input-suffix .wy-input-context{border-left:0}.wy-input-prefix .wy-input-context{border-right:0}.wy-control-group.wy-control-group-error .wy-form-message,.wy-control-group.wy-control-group-error>label{color:#e74c3c}.wy-control-group.wy-control-group-error input[type="text"],.wy-control-group.wy-control-group-error input[type="password"],.wy-control-group.wy-control-group-error input[type="email"],.wy-control-group.wy-control-group-error input[type="url"],.wy-control-group.wy-control-group-error input[type="date"],.wy-control-group.wy-control-group-error input[type="month"],.wy-control-group.wy-control-group-error input[type="time"],.wy-control-group.wy-control-group-error input[type="datetime"],.wy-control-group.wy-control-group-error input[type="datetime-local"],.wy-control-group.wy-control-group-error input[type="week"],.wy-control-group.wy-control-group-error input[type="number"],.wy-control-group.wy-control-group-error input[type="search"],.wy-control-group.wy-control-group-error input[type="tel"],.wy-control-group.wy-control-group-error input[type="color"]{border:solid 1px #e74c3c}.wy-control-group.wy-control-group-error textarea{border:solid 1px #e74c3c}.wy-inline-validate{white-space:nowrap}.wy-inline-validate .wy-input-context{padding:0.5em 0.625em;display:inline-block;font-size:80%}.wy-inline-validate.wy-inline-validate-success .wy-input-context{color:#27ae60}.wy-inline-validate.wy-inline-validate-danger .wy-input-context{color:#e74c3c}.wy-inline-validate.wy-inline-validate-warning .wy-input-context{color:#e67e22}.wy-inline-validate.wy-inline-validate-info .wy-input-context{color:#2980b9}.rotate-90{-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.rotate-180{-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.rotate-270{-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.mirror{-webkit-transform:scaleX(-1);-moz-transform:scaleX(-1);-ms-transform:scaleX(-1);-o-transform:scaleX(-1);transform:scaleX(-1)}.mirror.rotate-90{-webkit-transform:scaleX(-1) rotate(90deg);-moz-transform:scaleX(-1) rotate(90deg);-ms-transform:scaleX(-1) rotate(90deg);-o-transform:scaleX(-1) rotate(90deg);transform:scaleX(-1) rotate(90deg)}.mirror.rotate-180{-webkit-transform:scaleX(-1) rotate(180deg);-moz-transform:scaleX(-1) rotate(180deg);-ms-transform:scaleX(-1) rotate(180deg);-o-transform:scaleX(-1) rotate(180deg);transform:scaleX(-1) rotate(180deg)}.mirror.rotate-270{-webkit-transform:scaleX(-1) rotate(270deg);-moz-transform:scaleX(-1) rotate(270deg);-ms-transform:scaleX(-1) rotate(270deg);-o-transform:scaleX(-1) rotate(270deg);transform:scaleX(-1) rotate(270deg)}@media only screen and (max-width: 480px){.wy-form button[type="submit"]{margin:0.7em 0 0}.wy-form input[type="text"],.wy-form input[type="password"],.wy-form input[type="email"],.wy-form input[type="url"],.wy-form input[type="date"],.wy-form input[type="month"],.wy-form input[type="time"],.wy-form input[type="datetime"],.wy-form input[type="datetime-local"],.wy-form input[type="week"],.wy-form input[type="number"],.wy-form input[type="search"],.wy-form input[type="tel"],.wy-form input[type="color"]{margin-bottom:0.3em;display:block}.wy-form label{margin-bottom:0.3em;display:block}.wy-form input[type="password"],.wy-form input[type="email"],.wy-form input[type="url"],.wy-form input[type="date"],.wy-form input[type="month"],.wy-form input[type="time"],.wy-form input[type="datetime"],.wy-form input[type="datetime-local"],.wy-form input[type="week"],.wy-form input[type="number"],.wy-form input[type="search"],.wy-form input[type="tel"],.wy-form input[type="color"]{margin-bottom:0}.wy-form-aligned .wy-control-group label{margin-bottom:0.3em;text-align:left;display:block;width:100%}.wy-form-aligned .wy-control{margin:1.5em 0 0 0}.wy-form .wy-help-inline,.wy-form-message-inline,.wy-form-message{display:block;font-size:80%;padding:6px 0}}@media screen and (max-width: 768px){.tablet-hide{display:none}}@media screen and (max-width: 480px){.mobile-hide{display:none}}.float-left{float:left}.float-right{float:right}.full-width{width:100%}.wy-table,.rst-content table.docutils,.rst-content table.field-list{border-collapse:collapse;border-spacing:0;empty-cells:show;margin-bottom:24px}.wy-table caption,.rst-content table.docutils caption,.rst-content table.field-list caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.wy-table td,.rst-content table.docutils td,.rst-content table.field-list td,.wy-table th,.rst-content table.docutils th,.rst-content table.field-list th{font-size:90%;margin:0;overflow:visible;padding:8px 16px}.wy-table td:first-child,.rst-content table.docutils td:first-child,.rst-content table.field-list td:first-child,.wy-table th:first-child,.rst-content table.docutils th:first-child,.rst-content table.field-list th:first-child{border-left-width:0}.wy-table thead,.rst-content table.docutils thead,.rst-content table.field-list thead{color:#000;text-align:left;vertical-align:bottom;white-space:nowrap}.wy-table thead th,.rst-content table.docutils thead th,.rst-content table.field-list thead th{font-weight:bold;border-bottom:solid 2px #e1e4e5}.wy-table td,.rst-content table.docutils td,.rst-content table.field-list td{background-color:transparent;vertical-align:middle}.wy-table td p,.rst-content table.docutils td p,.rst-content table.field-list td p{line-height:18px}.wy-table td p:last-child,.rst-content table.docutils td p:last-child,.rst-content table.field-list td p:last-child{margin-bottom:0}.wy-table .wy-table-cell-min,.rst-content table.docutils .wy-table-cell-min,.rst-content table.field-list .wy-table-cell-min{width:1%;padding-right:0}.wy-table .wy-table-cell-min input[type=checkbox],.rst-content table.docutils .wy-table-cell-min input[type=checkbox],.rst-content table.field-list .wy-table-cell-min input[type=checkbox],.wy-table .wy-table-cell-min input[type=checkbox],.rst-content table.docutils .wy-table-cell-min input[type=checkbox],.rst-content table.field-list .wy-table-cell-min input[type=checkbox]{margin:0}.wy-table-secondary{color:gray;font-size:90%}.wy-table-tertiary{color:gray;font-size:80%}.wy-table-odd td,.wy-table-striped tr:nth-child(2n-1) td,.rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td{background-color:#f3f6f6}.wy-table-backed{background-color:#f3f6f6}.wy-table-bordered-all,.rst-content table.docutils{border:1px solid #e1e4e5}.wy-table-bordered-all td,.rst-content table.docutils td{border-bottom:1px solid #e1e4e5;border-left:1px solid #e1e4e5}.wy-table-bordered-all tbody>tr:last-child td,.rst-content table.docutils tbody>tr:last-child td{border-bottom-width:0}.wy-table-bordered{border:1px solid #e1e4e5}.wy-table-bordered-rows td{border-bottom:1px solid #e1e4e5}.wy-table-bordered-rows tbody>tr:last-child td{border-bottom-width:0}.wy-table-horizontal tbody>tr:last-child td{border-bottom-width:0}.wy-table-horizontal td,.wy-table-horizontal th{border-width:0 0 1px 0;border-bottom:1px solid #e1e4e5}.wy-table-horizontal tbody>tr:last-child td{border-bottom-width:0}.wy-table-responsive{margin-bottom:24px;max-width:100%;overflow:auto}.wy-table-responsive table{margin-bottom:0 !important}.wy-table-responsive table td,.wy-table-responsive table th{white-space:nowrap}a{color:#2980b9;text-decoration:none}a:hover{color:#3091d1}a:visited{color:#9b59b6}html{height:100%;overflow-x:hidden}body{font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif;font-weight:normal;color:#404040;min-height:100%;overflow-x:hidden;background:#edf0f2}.wy-text-left{text-align:left}.wy-text-center{text-align:center}.wy-text-right{text-align:right}.wy-text-large{font-size:120%}.wy-text-normal{font-size:100%}.wy-text-small,small{font-size:80%}.wy-text-strike{text-decoration:line-through}.wy-text-warning{color:#e67e22 !important}a.wy-text-warning:hover{color:#eb9950 !important}.wy-text-info{color:#2980b9 !important}a.wy-text-info:hover{color:#409ad5 !important}.wy-text-success{color:#27ae60 !important}a.wy-text-success:hover{color:#36d278 !important}.wy-text-danger{color:#e74c3c !important}a.wy-text-danger:hover{color:#ed7669 !important}.wy-text-neutral{color:#404040 !important}a.wy-text-neutral:hover{color:#595959 !important}h1,h2,h3,h4,h5,h6,legend{margin-top:0;font-weight:700;font-family:"Roboto Slab","ff-tisa-web-pro","Georgia",Arial,sans-serif}p{line-height:24px;margin:0;font-size:16px;margin-bottom:24px}h1{font-size:175%}h2{font-size:150%}h3{font-size:125%}h4{font-size:115%}h5{font-size:110%}h6{font-size:100%}hr{display:block;height:1px;border:0;border-top:1px solid #e1e4e5;margin:24px 0;padding:0}code,.rst-content tt{white-space:nowrap;max-width:100%;background:#fff;border:solid 1px #e1e4e5;font-size:75%;padding:0 5px;font-family:Consolas,"Andale Mono WT","Andale Mono","Lucida Console","Lucida Sans Typewriter","DejaVu Sans Mono","Bitstream Vera Sans Mono","Liberation Mono","Nimbus Mono L",Monaco,"Courier New",Courier,monospace;color:#e74c3c;overflow-x:auto}code.code-large,.rst-content tt.code-large{font-size:90%}.wy-plain-list-disc,.rst-content .section ul,.rst-content .toctree-wrapper ul,article ul{list-style:disc;line-height:24px;margin-bottom:24px}.wy-plain-list-disc li,.rst-content .section ul li,.rst-content .toctree-wrapper ul li,article ul li{list-style:disc;margin-left:24px}.wy-plain-list-disc li p:last-child,.rst-content .section ul li p:last-child,.rst-content .toctree-wrapper ul li p:last-child,article ul li p:last-child{margin-bottom:0}.wy-plain-list-disc li ul,.rst-content .section ul li ul,.rst-content .toctree-wrapper ul li ul,article ul li ul{margin-bottom:0}.wy-plain-list-disc li li,.rst-content .section ul li li,.rst-content .toctree-wrapper ul li li,article ul li li{list-style:circle}.wy-plain-list-disc li li li,.rst-content .section ul li li li,.rst-content .toctree-wrapper ul li li li,article ul li li li{list-style:square}.wy-plain-list-disc li ol li,.rst-content .section ul li ol li,.rst-content .toctree-wrapper ul li ol li,article ul li ol li{list-style:decimal}.wy-plain-list-decimal,.rst-content .section ol,.rst-content ol.arabic,article ol{list-style:decimal;line-height:24px;margin-bottom:24px}.wy-plain-list-decimal li,.rst-content .section ol li,.rst-content ol.arabic li,article ol li{list-style:decimal;margin-left:24px}.wy-plain-list-decimal li p:last-child,.rst-content .section ol li p:last-child,.rst-content ol.arabic li p:last-child,article ol li p:last-child{margin-bottom:0}.wy-plain-list-decimal li ul,.rst-content .section ol li ul,.rst-content ol.arabic li ul,article ol li ul{margin-bottom:0}.wy-plain-list-decimal li ul li,.rst-content .section ol li ul li,.rst-content ol.arabic li ul li,article ol li ul li{list-style:disc}.codeblock-example{border:1px solid #e1e4e5;border-bottom:none;padding:24px;padding-top:48px;font-weight:500;background:#fff;position:relative}.codeblock-example:after{content:"Example";position:absolute;top:0px;left:0px;background:#9b59b6;color:#fff;padding:6px 12px}.codeblock-example.prettyprint-example-only{border:1px solid #e1e4e5;margin-bottom:24px}.codeblock,pre.literal-block,.rst-content .literal-block,.rst-content pre.literal-block,div[class^='highlight']{border:1px solid #e1e4e5;padding:0px;overflow-x:auto;background:#fff;margin:1px 0 24px 0}.codeblock div[class^='highlight'],pre.literal-block div[class^='highlight'],.rst-content .literal-block div[class^='highlight'],div[class^='highlight'] div[class^='highlight']{border:none;background:none;margin:0}div[class^='highlight'] td.code{width:100%}.linenodiv pre{border-right:solid 1px #e6e9ea;margin:0;padding:12px 12px;font-family:Consolas,"Andale Mono WT","Andale Mono","Lucida Console","Lucida Sans Typewriter","DejaVu Sans Mono","Bitstream Vera Sans Mono","Liberation Mono","Nimbus Mono L",Monaco,"Courier New",Courier,monospace;font-size:12px;line-height:1.5;color:#d9d9d9}div[class^='highlight'] pre{white-space:pre;margin:0;padding:12px 12px;font-family:Consolas,"Andale Mono WT","Andale Mono","Lucida Console","Lucida Sans Typewriter","DejaVu Sans Mono","Bitstream Vera Sans Mono","Liberation Mono","Nimbus Mono L",Monaco,"Courier New",Courier,monospace;font-size:12px;line-height:1.5;display:block;overflow:auto;color:#404040}@media print{.codeblock,pre.literal-block,.rst-content .literal-block,.rst-content pre.literal-block,div[class^='highlight'],div[class^='highlight'] pre{white-space:pre-wrap}}.hll{background-color:#ffc;margin:0 -12px;padding:0 12px;display:block}.c{color:#998;font-style:italic}.err{color:#a61717;background-color:#e3d2d2}.k{font-weight:bold}.o{font-weight:bold}.cm{color:#998;font-style:italic}.cp{color:#999;font-weight:bold}.c1{color:#998;font-style:italic}.cs{color:#999;font-weight:bold;font-style:italic}.gd{color:#000;background-color:#fdd}.gd .x{color:#000;background-color:#faa}.ge{font-style:italic}.gr{color:#a00}.gh{color:#999}.gi{color:#000;background-color:#dfd}.gi .x{color:#000;background-color:#afa}.go{color:#888}.gp{color:#555}.gs{font-weight:bold}.gu{color:purple;font-weight:bold}.gt{color:#a00}.kc{font-weight:bold}.kd{font-weight:bold}.kn{font-weight:bold}.kp{font-weight:bold}.kr{font-weight:bold}.kt{color:#458;font-weight:bold}.m{color:#099}.s{color:#d14}.n{color:#333}.na{color:teal}.nb{color:#0086b3}.nc{color:#458;font-weight:bold}.no{color:teal}.ni{color:purple}.ne{color:#900;font-weight:bold}.nf{color:#900;font-weight:bold}.nn{color:#555}.nt{color:navy}.nv{color:teal}.ow{font-weight:bold}.w{color:#bbb}.mf{color:#099}.mh{color:#099}.mi{color:#099}.mo{color:#099}.sb{color:#d14}.sc{color:#d14}.sd{color:#d14}.s2{color:#d14}.se{color:#d14}.sh{color:#d14}.si{color:#d14}.sx{color:#d14}.sr{color:#009926}.s1{color:#d14}.ss{color:#990073}.bp{color:#999}.vc{color:teal}.vg{color:teal}.vi{color:teal}.il{color:#099}.gc{color:#999;background-color:#eaf2f5}.wy-breadcrumbs li{display:inline-block}.wy-breadcrumbs li.wy-breadcrumbs-aside{float:right}.wy-breadcrumbs li a{display:inline-block;padding:5px}.wy-breadcrumbs li a:first-child{padding-left:0}.wy-breadcrumbs-extra{margin-bottom:0;color:#b3b3b3;font-size:80%;display:inline-block}@media screen and (max-width: 480px){.wy-breadcrumbs-extra{display:none}.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}@media print{.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}.wy-affix{position:fixed;top:1.618em}.wy-menu a:hover{text-decoration:none}.wy-menu-horiz{*zoom:1}.wy-menu-horiz:before,.wy-menu-horiz:after{display:table;content:""}.wy-menu-horiz:after{clear:both}.wy-menu-horiz ul,.wy-menu-horiz li{display:inline-block}.wy-menu-horiz li:hover{background:rgba(255,255,255,0.1)}.wy-menu-horiz li.divide-left{border-left:solid 1px #404040}.wy-menu-horiz li.divide-right{border-right:solid 1px #404040}.wy-menu-horiz a{height:32px;display:inline-block;line-height:32px;padding:0 16px}.wy-menu-vertical header{height:32px;display:inline-block;line-height:32px;padding:0 1.618em;display:block;font-weight:bold;text-transform:uppercase;font-size:80%;color:#2980b9;white-space:nowrap}.wy-menu-vertical ul{margin-bottom:0}.wy-menu-vertical li.divide-top{border-top:solid 1px #404040}.wy-menu-vertical li.divide-bottom{border-bottom:solid 1px #404040}.wy-menu-vertical li.current{background:#e3e3e3}.wy-menu-vertical li.current a{color:gray;border-right:solid 1px #c9c9c9;padding:0.4045em 2.427em}.wy-menu-vertical li.current a:hover{background:#d6d6d6}.wy-menu-vertical li.on a,.wy-menu-vertical li.current>a{color:#404040;padding:0.4045em 1.618em;font-weight:bold;position:relative;background:#fcfcfc;border:none;border-bottom:solid 1px #c9c9c9;border-top:solid 1px #c9c9c9;padding-left:1.618em -4px}.wy-menu-vertical li.on a:hover,.wy-menu-vertical li.current>a:hover{background:#fcfcfc}.wy-menu-vertical li.toctree-l2.current>a{background:#c9c9c9;padding:0.4045em 2.427em}.wy-menu-vertical li.current ul{display:block}.wy-menu-vertical li ul{margin-bottom:0;display:none}.wy-menu-vertical .local-toc li ul{display:block}.wy-menu-vertical li ul li a{margin-bottom:0;color:#b3b3b3;font-weight:normal}.wy-menu-vertical a{display:inline-block;line-height:18px;padding:0.4045em 1.618em;display:block;position:relative;font-size:90%;color:#b3b3b3}.wy-menu-vertical a:hover{background-color:#4e4a4a;cursor:pointer}.wy-menu-vertical a:active{background-color:#2980b9;cursor:pointer;color:#fff}.wy-side-nav-search{z-index:200;background-color:#2980b9;text-align:center;padding:0.809em;display:block;color:#fcfcfc;margin-bottom:0.809em}.wy-side-nav-search input[type=text]{width:100%;border-radius:50px;padding:6px 12px;border-color:#2472a4}.wy-side-nav-search img{display:block;margin:auto auto 0.809em auto;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-side-nav-search>a,.wy-side-nav-search .wy-dropdown>a{color:#fcfcfc;font-size:100%;font-weight:bold;display:inline-block;padding:4px 6px;margin-bottom:0.809em}.wy-side-nav-search>a:hover,.wy-side-nav-search .wy-dropdown>a:hover{background:rgba(255,255,255,0.1)}.wy-nav .wy-menu-vertical header{color:#2980b9}.wy-nav .wy-menu-vertical a{color:#b3b3b3}.wy-nav .wy-menu-vertical a:hover{background-color:#2980b9;color:#fff}[data-menu-wrap]{-webkit-transition:all 0.2s ease-in;-moz-transition:all 0.2s ease-in;transition:all 0.2s ease-in;position:absolute;opacity:1;width:100%;opacity:0}[data-menu-wrap].move-center{left:0;right:auto;opacity:1}[data-menu-wrap].move-left{right:auto;left:-100%;opacity:0}[data-menu-wrap].move-right{right:-100%;left:auto;opacity:0}.wy-body-for-nav{background:left repeat-y #fcfcfc;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoTWFjaW50b3NoKSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDoxOERBMTRGRDBFMUUxMUUzODUwMkJCOThDMEVFNURFMCIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDoxOERBMTRGRTBFMUUxMUUzODUwMkJCOThDMEVFNURFMCI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjE4REExNEZCMEUxRTExRTM4NTAyQkI5OEMwRUU1REUwIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjE4REExNEZDMEUxRTExRTM4NTAyQkI5OEMwRUU1REUwIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+EwrlwAAAAA5JREFUeNpiMDU0BAgwAAE2AJgB9BnaAAAAAElFTkSuQmCC);background-size:300px 1px}.wy-grid-for-nav{position:absolute;width:100%;height:100%}.wy-nav-side{position:absolute;top:0;left:0;width:300px;overflow:hidden;min-height:100%;background:#343131;z-index:200}.wy-nav-top{display:none;background:#2980b9;color:#fff;padding:0.4045em 0.809em;position:relative;line-height:50px;text-align:center;font-size:100%;*zoom:1}.wy-nav-top:before,.wy-nav-top:after{display:table;content:""}.wy-nav-top:after{clear:both}.wy-nav-top a{color:#fff;font-weight:bold}.wy-nav-top img{margin-right:12px;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-nav-top i{font-size:30px;float:left;cursor:pointer}.wy-nav-content-wrap{margin-left:300px;background:#fcfcfc;min-height:100%}.wy-nav-content{padding:1.618em 3.236em;height:100%;max-width:800px;margin:auto}.wy-body-mask{position:fixed;width:100%;height:100%;background:rgba(0,0,0,0.2);display:none;z-index:499}.wy-body-mask.on{display:block}footer{color:#999}footer p{margin-bottom:12px}.rst-footer-buttons{*zoom:1}.rst-footer-buttons:before,.rst-footer-buttons:after{display:table;content:""}.rst-footer-buttons:after{clear:both}#search-results .search li{margin-bottom:24px;border-bottom:solid 1px #e1e4e5;padding-bottom:24px}#search-results .search li:first-child{border-top:solid 1px #e1e4e5;padding-top:24px}#search-results .search li a{font-size:120%;margin-bottom:12px;display:inline-block}#search-results .context{color:gray;font-size:90%}@media screen and (max-width: 768px){.wy-body-for-nav{background:#fcfcfc}.wy-nav-top{display:block}.wy-nav-side{left:-300px}.wy-nav-side.shift{width:85%;left:0}.wy-nav-content-wrap{margin-left:0}.wy-nav-content-wrap .wy-nav-content{padding:1.618em}.wy-nav-content-wrap.shift{position:fixed;min-width:100%;left:85%;top:0;height:100%;overflow:hidden}}@media screen and (min-width: 1400px){.wy-nav-content-wrap{background:rgba(0,0,0,0.05)}.wy-nav-content{margin:0;background:#fcfcfc}}@media print{.rst-versions,footer,.wy-nav-side{display:none}.wy-nav-content-wrap{margin-left:0}}nav.stickynav{position:fixed;top:0}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;border-top:solid 10px #343131;font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60;*zoom:1}.rst-versions .rst-current-version:before,.rst-versions .rst-current-version:after{display:table;content:""}.rst-versions .rst-current-version:after{clear:both}.rst-versions .rst-current-version .fa,.rst-versions .rst-current-version .rst-content .admonition-title,.rst-content .rst-versions .rst-current-version .admonition-title,.rst-versions .rst-current-version .rst-content h1 .headerlink,.rst-content h1 .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content h2 .headerlink,.rst-content h2 .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content h3 .headerlink,.rst-content h3 .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content h4 .headerlink,.rst-content h4 .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content h5 .headerlink,.rst-content h5 .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content h6 .headerlink,.rst-content h6 .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .rst-content dl dt .headerlink,.rst-content dl dt .rst-versions .rst-current-version .headerlink,.rst-versions .rst-current-version .icon{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:gray;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:solid 1px #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px}.rst-versions.rst-badge .icon-book{float:none}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge .rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width: 768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}img{width:100%;height:auto}}.rst-content img{max-width:100%;height:auto !important}.rst-content div.figure{margin-bottom:24px}.rst-content div.figure.align-center{text-align:center}.rst-content .section>img{margin-bottom:24px}.rst-content blockquote{margin-left:24px;line-height:24px;margin-bottom:24px}.rst-content .note .last,.rst-content .attention .last,.rst-content .caution .last,.rst-content .danger .last,.rst-content .error .last,.rst-content .hint .last,.rst-content .important .last,.rst-content .tip .last,.rst-content .warning .last,.rst-content .seealso .last,.rst-content .admonition-todo .last{margin-bottom:0}.rst-content .admonition-title:before{margin-right:4px}.rst-content .admonition table{border-color:rgba(0,0,0,0.1)}.rst-content .admonition table td,.rst-content .admonition table th{background:transparent !important;border-color:rgba(0,0,0,0.1) !important}.rst-content .section ol.loweralpha,.rst-content .section ol.loweralpha li{list-style:lower-alpha}.rst-content .section ol.upperalpha,.rst-content .section ol.upperalpha li{list-style:upper-alpha}.rst-content .section ol p,.rst-content .section ul p{margin-bottom:12px}.rst-content .line-block{margin-left:24px}.rst-content .topic-title{font-weight:bold;margin-bottom:12px}.rst-content .toc-backref{color:#404040}.rst-content .align-right{float:right;margin:0px 0px 24px 24px}.rst-content .align-left{float:left;margin:0px 24px 24px 0px}.rst-content .align-center{margin:auto;display:block}.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content dl dt .headerlink{display:none;visibility:hidden;font-size:14px}.rst-content h1 .headerlink:after,.rst-content h2 .headerlink:after,.rst-content h3 .headerlink:after,.rst-content h4 .headerlink:after,.rst-content h5 .headerlink:after,.rst-content h6 .headerlink:after,.rst-content dl dt .headerlink:after{visibility:visible;content:"\f0c1";font-family:FontAwesome;display:inline-block}.rst-content h1:hover .headerlink,.rst-content h2:hover .headerlink,.rst-content h3:hover .headerlink,.rst-content h4:hover .headerlink,.rst-content h5:hover .headerlink,.rst-content h6:hover .headerlink,.rst-content dl dt:hover .headerlink{display:inline-block}.rst-content .sidebar{float:right;width:40%;display:block;margin:0 0 24px 24px;padding:24px;background:#f3f6f6;border:solid 1px #e1e4e5}.rst-content .sidebar p,.rst-content .sidebar ul,.rst-content .sidebar dl{font-size:90%}.rst-content .sidebar .last{margin-bottom:0}.rst-content .sidebar .sidebar-title{display:block;font-family:"Roboto Slab","ff-tisa-web-pro","Georgia",Arial,sans-serif;font-weight:bold;background:#e1e4e5;padding:6px 12px;margin:-24px;margin-bottom:24px;font-size:100%}.rst-content .highlighted{background:#f1c40f;display:inline-block;font-weight:bold;padding:0 6px}.rst-content .footnote-reference,.rst-content .citation-reference{vertical-align:super;font-size:90%}.rst-content table.docutils.citation,.rst-content table.docutils.footnote{background:none;border:none;color:#999}.rst-content table.docutils.citation td,.rst-content table.docutils.citation tr,.rst-content table.docutils.footnote td,.rst-content table.docutils.footnote tr{border:none;background-color:transparent !important;white-space:normal}.rst-content table.docutils.citation td.label,.rst-content table.docutils.footnote td.label{padding-left:0;padding-right:0;vertical-align:top}.rst-content table.field-list{border:none}.rst-content table.field-list td{border:none;padding-top:5px}.rst-content table.field-list td>strong{display:inline-block;margin-top:3px}.rst-content table.field-list .field-name{padding-right:10px;text-align:left;white-space:nowrap}.rst-content table.field-list .field-body{text-align:left;padding-left:0}.rst-content tt{color:#000}.rst-content tt big,.rst-content tt em{font-size:100% !important;line-height:normal}.rst-content tt .xref,a .rst-content tt{font-weight:bold}.rst-content a tt{color:#2980b9}.rst-content dl{margin-bottom:24px}.rst-content dl dt{font-weight:bold}.rst-content dl p,.rst-content dl table,.rst-content dl ul,.rst-content dl ol{margin-bottom:12px !important}.rst-content dl dd{margin:0 0 12px 24px}.rst-content dl:not(.docutils){margin-bottom:24px}.rst-content dl:not(.docutils) dt{display:inline-block;margin:6px 0;font-size:90%;line-height:normal;background:#e7f2fa;color:#2980b9;border-top:solid 3px #6ab0de;padding:6px;position:relative}.rst-content dl:not(.docutils) dt:before{color:#6ab0de}.rst-content dl:not(.docutils) dt .headerlink{color:#404040;font-size:100% !important}.rst-content dl:not(.docutils) dl dt{margin-bottom:6px;border:none;border-left:solid 3px #ccc;background:#f0f0f0;color:gray}.rst-content dl:not(.docutils) dl dt .headerlink{color:#404040;font-size:100% !important}.rst-content dl:not(.docutils) dt:first-child{margin-top:0}.rst-content dl:not(.docutils) tt{font-weight:bold}.rst-content dl:not(.docutils) tt.descname,.rst-content dl:not(.docutils) tt.descclassname{background-color:transparent;border:none;padding:0;font-size:100% !important}.rst-content dl:not(.docutils) tt.descname{font-weight:bold}.rst-content dl:not(.docutils) .optional{display:inline-block;padding:0 4px;color:#000;font-weight:bold}.rst-content dl:not(.docutils) .property{display:inline-block;padding-right:8px}.rst-content .viewcode-link,.rst-content .viewcode-back{display:inline-block;color:#27ae60;font-size:80%;padding-left:24px}.rst-content .viewcode-back{display:block;float:right}.rst-content p.rubric{margin-bottom:12px;font-weight:bold}@media screen and (max-width: 480px){.rst-content .sidebar{width:100%}}span[id*='MathJax-Span']{color:#404040}.math{text-align:center} diff --git a/docs/_themes/sphinx_rtd_theme/static/fonts/FontAwesome.otf b/docs/_themes/sphinx_rtd_theme/static/fonts/FontAwesome.otf new file mode 100644 index 0000000000000000000000000000000000000000..8b0f54e47e1d356dcf1496942a50e228e0f1ee14 GIT binary patch literal 62856 zcmcfp2Y3_5)&LBzEbU6(wGF`%u_do$I-wUs=poc3^xzP>t859|l91%ydy%{4ZewH9 zLNU#OK%5)jlp7M#adH#VlN(Y~MSVYG)7F`Dsts8mQIv>+ztD)dFw+9OVG%`1 zdML`ns?&x=Qnp|IfM+dm&(}ePcdqmf37+Ghm#p%f+FVKQ2*chjkzF#ZB~9w-bef!xGBr6D7h{6UGOP@t%*!8rhr zqTX&D_txFJckW8F88SgJDOYWQiq1}9HpST zU`<34PZ)C!_3}_&M2)6kC53tq%16Wv<;B!kk^fL$a$g&o8ZTNrRL|U3FQqy}Aw%^t z%FjbIl=r0M9>Z`rYKq77t>{++@-k0@oM~*1+}p2(7`Q4V*n=HYq=vsI?g5v}-nP z3|{}}ibb1(*R0;YdDD}@+q7nj-e?F6nlWp}oWMD=X3yOms||yGW^I(#9B4HL0`>*2 zG{Pq6qjlCmi#Eba+D94TAv}p9V_D5%k=nR0b4*~E)oRv<#|upiMk~z0GGmR=Yz-V5 ze^pq5HgIj2Au?HKwVD>qoJsnJx#u=RZ=|+Tk5lVmJ2z1#N=q3aw}vu8YK7c-N>4=y zwHEjdq-Iky;2wVdD3u7c7HAy@>636rQ}I+R6-Jq%%_eFi6$}s_rB+ajpcD*stEugP zo136*FtrWZo1wQ}7%h+r0@$R$MYWppE&yKBVk^ODoieQIXI-PMCWPv3^jr9p7*cDDu9q6%xx{?3;;b@n3omixrmwx*YNmZf9p3xm@i;8 zp?TpJjUB@J0D^@;Vq@WEgcj}}s2gf=U*-SLs=qz||El20$!O-RlsfnS_J9)6lK^rf z@F|+|fem;DctSVzuQ6lCs>g=*`}C{(m-TP#-`gM6ukSbXXY`l%AL#GuKiB_u|L6U` z^xwJVb4z_|(yht2X53nKYvZlGw+y#3Zk69U@CS95u-8E9*x%q${UiIw^e^w<+#lK> z-M_Ej)SuN~+27uOroXrU-Tp88`)^UVM&1epcn{s0b!+*p&9_2tnQmp>swD94ennAt zcir7`_tDR9d~W}I%Sf-0+(^%nvXRn}u#+RjBRxinMp7g0j<_@8_K4p{{5Im&i2f13 zj`+pr(-A+9_-Vw=5kHRjVZ`?%z8i6aJ1^|@`u}w?=l`!y{JYkcahKF7zYy(4XAHaLAh7>kswf;WDJ8 zodnW*&mk}LA4ATyzs;HS z&jMIk)X1SUY8WQ8mk8qz!5gX{ac?|#KNXah-`{R{t;jx;+arrw4mTM?C=b`)g9B|K zKbe$=Z!xqbc>xxr!#G3cIJ_43-sk>0XiMsaXE3e+56S@N-W&nebhy1GS=0t{!`!CB zeXl$`20SDCO)=z#yl@A)%foXM<_FJ&aY(!S?qN9ajLc&>wDpF%>BD`=97%ujZX|^{ zkUJb;(Bvllh3Ak$Tkm1o9O@S+z@h#=rtsbrEayd0}DguL&kx00m+ja=Bpt$)C)Jj(+GE#@N5{qN_YooPx`~Xe7HP3 z{%{$_+eqqQIN>I3Ngv^P)=&zdhx-v8M)G7X!|w&{r;s|*7v>g7Gy(!cXqP3lRov@8 zR1fWh=MwT9Zqok0{>Y@@?`{gwSN{7?L`gvE7m2*?lX6LUm1893w2Pdz9?n{^!(W2e zdWpaFl9b@u0BLprBcj#q)KgjW@7iqlGG5Yvz*k2E1b+8G7f(?i1&vA9XxDLyUk5nmBs6~80?xA;He-^DJ8RN^C1NybWMO6ExxOV&s>OP-SKlxQUu zNxCEtRJdwMgQQb(MDmQ}tmIiqujCEMHOY0!HkBMipnS7>{u``WKCv$?i#JtM9$^4u7g87d5nYqQ>kup*r>4Q>U zI$1hRI!8KRx>mYFs*@&5bEW0dI%&J~sPvTdy!1usRp|%PFQwl}f0q6xb;-PBD%k|t zY}tI-V%aj;YS{+aQ?dwIjLaxYk`>BoWsR~9*)iEk*+tn)va7OpWS_{smHjSrdP+V0 zJk_4#J?D9@_1xwe?HTK7@=Wl|@+|Uf_B`o%#`BWri=J_T=4`v|*&UBhl-L)Zv5p0%+J>@(~s_AL7X`wDx7eUJT&{SSMK z9pETV%t<)~r{X4Z^SBk<7A}m7;^H_fm&|2x`CJ88%QbUt++pq*cal5LUErSMUf^El zUgJLCKIVSme)FQdBwi!E`Us0Q z%p9T98WOazMw1pS4`!>y8fGSUh&Ik-O^&x{%~AT;IIAusHq0EYwdzPtZ?PI<%-T3( zf;Poyj0@2lgv1zcHAY2Q^wEZ}*a%}ZXpR=04ir-WpbZI&wOaLYTC*`MGSZl6h=r8Y z4d>%cq(*NDHzt{4!;(WH^yY|Ityyc*hFL*fHES(8GA!v5YmA7AiVce8e_;!6kC&7Z?Hyy8O0n%G}drq zY^2^A7ORi2YLl!XIxW$Sg>0fe(yD_8(T0#%Z4_w&Inczd&{N0@YP37MFWzF+MkX06M(8q>71~9GMQF*2ge2%AwMG*R7f)W-5CO{_W(pxQ1Gtd{5P-01VNw=dm{|+^ z6%j+0-eT37Lc+r$ViLp5kx^l=IKzeEl&qvF4E7NA%LH2ey@o@10m4vTyAQN~fSq7A zx?gWNFHF`H8*d3AI~%7r4CUPWFH{<1gk*m_30u(tfF`iWB#nqQTC}hv2E8F#m?SuDFTQn3UEkkc8@TWC!-F{GC^ww z>q*$~q;*EKK82V{VgW}(B4CfL)4q56 z4)D)xH0hF~^)O1fFcUYy3iJruY7hufKutIFVd8R^gr`Ecp*I_TDL24)U$r5ORbRg-pCjNXR?8@hRjlg!)^B z(D!dOu%iM74)q`)qGOHW+C($Zqs|&;iLn3^gGC89>$Oo4U_&EF=f-R>g=zQ41JxU% z^ai~(IaX`22o=$0BPn|0z*CK8 zK%DqkW2^;?Z85-a0Z6ni9$1JOKmq#-j|FR7G;j-Zd_)ZF6-)}K?p{V%Lg*B4TBUeba0p4h(`{lkhnUa;!S@mlEwb3uRAAna%X|R34lqnNUbFX_%$pF{0bXxjWdRmGt^CFZcG*MWq&*% zpD-JDPJjsSWiSA$4WFQ~!(L z(g@%$q;&`!M=`(;0H;FcJiPEeUTy)bGXu%#O;$^MxH}UvXTe-kd`b#g8@(3xP*30x znc%M+5eqCjy*4&-n6xnX2oC%!5s^Uj?t@SuO@S=#uW(bx z{WX6b2|^FDjXG;w?7RqzWiB8Wa4|QJBTGftngtFZz*C@qy(Q$Y1K?iO@DUL*ch+1% z9wK1j&>$1McLEb&Zk8+5#cF{jf&aTxfx3yPAYib-S%s<1oju2WfRYkWB~Tuak9)I+ z(-1(skh!xT*2bHo!{JN-dNJ<8yjM5m zG60rH7zk-~uZGNixK`kLe=CruA#>*j!96b-j;Z)?t?(j4`6Spia^GJE{4Ojx680Zt zNWe8%t069;H$XAk92OS^LR}2VREDV856=$Q!%mO|6<}C_6UCa{zd}W<5upDiblg`Y z4Cvl7f*bc0-6U;-JxByu&zNWdaxxqBk$}(fNs-__0UlzBNj3priZ@%}*dQl4?7A@u zxFO-}z(C>X2fTOs4u7+;J0*%HiJsMQxqoBiu59bC{I)* zIwpEv)GK;ZbY1kl=qJ%1q5%)ugY$R_l;6D`VIDej?~k_t(Uq#ab(*CcOB-jjSFxlRYtLG(g8nl{qO zbOHT5{ZCLqIVOM^&rD@zGV_^TOav3dn3%)Nr_5K(_smbsZ;XR+Nxh{3(y`L%(je&q z=^E)esaBdKO_%0LE2WLn1JX|EJJNqkKa+kfy&=6R{Z;m$EI>A1Hd!`RHd8iFwn+Af zOe@pN;$&u7o$Qe8lVqKiD_fkJ-=Jui1W386V`Pb1S)E zZZ{Xs={O@7&!utMTpf3Udy%`wead~q-Q@bYKfGjKDz6z{L0&7o9`}0EYlm03m(I)J zmEe`?mG4#O)#laVb=0fN>w?#dUN3vS=Jl4>2VS3feeLyw*Uw(Rc{#l9deh#V_egJz z_ayH*-iy4Kd2jIE?ESR2*4ylzxhxHlZ~0u+4bSNe2Avwqk&^$DHRv=KS#CD3;S~8SQm|;x zN%uXOg<%H!6sOWpT07MECb~&~iaal%Kr~kA@W=0ly z{t+$Uxdi~XHN7!e%}J9R(_7UXGlAu{@LgPTdU`T9mC4D=%h61g=2Yj|)i)V?b+ui? zE#uW(1@DS-MfI`{o?I@T&abi;)~M_?7x@=n*uipt?Z;r>c-GlBp66Pcnp(J_b~W~k zJU4;W8IE;z9Xr-_5FpZ3`8gH2s@$By{Co|!66RIRN3*C1^>ST?V>+@U!LTF2up`?- zL$|?lw4^nqr~{nKnUu7&6b%lRrZlCsr~{Z@h76@~^htykcl!R`V4$yrCB3Hbq$wn746_@NOa-3Klzp2l^gn2VQjbAuo0?#JQLL z$Mz}bSE*b<%<3&$R%={A(pBfD{9}jO88R43TRRf@j!umu(~;H5a&uR%M853YmDj$} zIQyjET)Xy-no~>!4446Ue9XYDW$(ym^9NXsBiI!j&bBmH*VjYd5uCtsQXS7>`8HO> zDbN}`0?ouLy46Rz8=vn%p8Uqm@ezB}D0m6pght^=)w6thX?kgz2G3qG5zoOZl-P#$ z;62Eu9_V9|U>i5{jy^LBsJUYYou6NrldH_F$f?R#6Z}L^@PMpQjwrgSs={8Q zoOChE&E(fDVqJZ+_^S(9K%?|z4Qv@&$Gd6owP0l%>_y%&IxVx)7#jOLcGPC4#d!g42=Yrv!#JYwQRKph}ax;`_tIz`20);H(1 zsJH++i<8d1wvyoE7px2R-tQK>V~5{WU|KHT4=~~?>;J-zTfD!37u?D8Q>s%Z8#$yy z%h5wD_x>xdywB+ughWP$WMyPzRwT*3=TpiXGn-0FZKbMbDvnhisqR1g!-dcPCCh&K zU-?&5z+T@$$>=nPF5$IkC4LdF#0#)`=@RwFOYj1u#w%4&w-#zI;XGu*dusADPKoOm z8YZ0Itm0}4+W;2`1!=edNfwuq23(9Y^AiBwidZ$*g5O$1LZ$6+E(!Uc|#A>nDKry|{>zcC#+K%kF13+aeB` z9VD9p6UpVd$^V7B9CH{zE9`mIIchS3J(9JvNG|5m;2dy7E#^4~49g)Y8pA2@Lg!dK zg2BOf!)Nnef3=~Zrna)izq+0-OJ%Z4GBT8|Rd_LG9C|4SxZ~=3jfW$p9$pYw$y_dg z$>JhlV>uJMiW^X%#R@E9a470Q>roqx9zaWQErSDbk~yp(uQ0DT&%cNvuP5iE^LQ+u z26PNWna=x2;dpDwYtF2PX<;eXb5R_ zZZpZ*jjdH0&h{xRQ82^3_v)+fai0dznTkb#fpNA>TZj!$wMBp(y(a5G+OcF=O-IX7 zI1yn7^P5|gEmh6+^=fi-zRxzcYPfTi=c-TFqDL>HS)ZW?kxW)_xu>W{<;ZnRKUuRK|0& z{yIfL1XJ`OLv>qeQ+d6Ac^h59pu}O!d{)1 zv*gVuu9H;FWrMuddxQ0v#UA3Pz#$I+SM%g3Mhc$GgAw6?7&+-zJQ9zbG>QEFIth(L zBY*uBja2)zlewX3ESktVZS|5(mkM&oHz$Xv$b>E&ZkH^c3ZkKeyP{@`J>81Zl|K725KKL~og7cTUw&+r2C zUk9>oB)d(Z#5JNP*mUmDq4TywX6_8%+DKj@yYsN}P;F;x zs~Sy06X}*#uDQ7i4t1y4@e^&gBNN(#@|4_eym;lN^{dj7Q_?EUGMmj-qU3N8NR(vr zL5@U0AW!DyaDfW~n7L>qoU7ycb%~=uC}_($bO;~RAg|+gl_}Tm%SPM9pFM`C+p(U`f$Ogj39`p#D49F9Oe2B)Y(1=eW zw)bneg>cL|gV(T-@p*5{tE=Jcu_#{Qxp*GXIvt3kkYHpQ3rMZzl>31_u>s6-4t1k$ z+%4rq9}T342VUdi$!t^dQ!_JRmu7%?geCz#$k7y78#|!3og3_v;<;Rny}YW5!%{qk zYr=}g#4>emYj$g9vy8LVs?h8`L_|TiBLNz~6T}mIn`7Q#x%%eXmYM^ywlbt>Y*KQW ztPgGNM5|#@Lho##(bo(L9oRr~qe#cANDc%f=kjIw`MHHTDlBJG(mA{ekB4g&=UR+@ z#y>k2b08anAWukZCeRZa(ch0ofCOX(Es0wN+K`%qt+#QuZ7_-y0m}#2?n`dsD*wD% zU9TxGD=jNm!ZzETgs?z(%&2dH6S29assTs?*$2o*DW}7G$(=zkCn=n0K=g91j%PTP zO^O&KdH%vD8V)3XPz7L>;2B8w07~qv;%G|;IoyGV`0yOvTG|Z!pBsQ#a448*<@V{7 zdf2gEhBIedl9SbV5}wF0Z(rH8R)gfF3J%|GPxzE<#INuQA;=Fuj>54gr^1)E;a_nA zo)4mW8(@oc8NVA2@UCNk;D%})%w{#z2H@ok=K_g?v+@cKVge`%egi3pAfR$7s)V8% zDeAC@I!=iS?|Kv_iSmi9WFEB;;){P5Rf%dKM4(>OC~6j+5}g+P=`qz~g~xw9Zi~l? z6U67mcO<+dT5?YEC%uhsrC(z|gAE zO*vJ0Soy8esY(oZgqQLER6n4etX{4*s1K;GsNYi~jhAMuW{;*_b1QI4;QGKH$2>CT zA7i<(=f?Sr+dQskyn1}e_?r{PPpF*GHsRt#zlr~zR50n=$@LGNnX+igA5%|F+cqs@ z+S}6~n7(}aZ!^p@%4hsObLz||W*(ijYF6oN$QX$5KDr7zAHmywn^DlpJ_O|_m=Lh-A{Et-MyoGSNERokiok) zBnhB3NFqWKByj{Ii5OXtL=iv-I)VcRzH|jku>?yL&Y*4VU{JsS#rOmaeBcup%p(vg z?BW3W4M&OsA3!q@+*i8Vuj{V(uR|WXD@)op>iqEmJe@|bq0uaUO$x21Z|quaWJ_xUXAmZ_~hhx4bGFsw0wse^@d)0B zL-DjAP%gua%Yc&7*ptG~HMb>n%yYV^Ir+quNu8Y~X zOsAO}fxX6IZ{=QTe4}1~-O+ORpvERWcIMrGol^hUixhq6Nu^Kwy$j!Uz@hXT4-9Ss z-^eat$rCh}7lHN*%g%HL&}$Su8|+c)fPpL~YD3OWLx-U)QRDO)^r8pth-2Z11unc6 zgng%-ae6tu=(e_wW5-~S1W_f(E39}MY+<0HH}t}`?3|LK9Q9xyw$l+A#;7pmon0@m z&K*)1ESq+ndV%!`g!5xSUcduLyEub)22bZfY4K@?Qx%R1r~Nu#$Db%*0|u7If<;f- zZs~|Wl!(S*4>TT2kOs?S>p%Q{+3%`Sh&B5C`;XrEP=ho`23o%ajYA%X+By!lcghCs z(t*>G`3tf5iS25v9E+7>u>TlY=(eddSF1{x5@z+(?=Ec9VE;d`68_zm&3^yMUl5~Q z0Git}{%n4T8P1e5L>?Gep2ptkLk#cJzMcm|(|{by6<_nIywA5V(E)G8Gcom+3bm`G z563%p(Fbx;4q8>~c*j#Xi_WWWENE06tM5GgA^R;KAldIYrnu%>=<-IpTt0YLpJO5Z z7ka_5=ykNkF$!&QjdCo4<9+{Y{}-4YM?Pfn-Sr?2iLE?(P=OM*pd0w2DX66fl@N?-1iD^%I(}!F>Y{#DE3uA#DGd2hEe5<#MzbG*8eJ9rAVS*a7>X z{S`8p!61R*K0CV=3?EN|rl+Y>-AblM$u#nWsCFL|0B zfQG|)pZ4~I6JVA_-Cz?4mQ3W`hJitlTLhF*gLObK6@qDS+lA0x(4E2J0agpr&cu^; zCO{MD_+OBcSu~yntMX9y*I=$xBgAa|S3PuJ@wbLP?TrDFLn7oI!1w?W6b|fFfXJWR zs>T5*;3zvdesBW5jGjNr;s6}*4v+5OI|y>`@(7+gbxs`u84}+uPY@vw00iu76xufo z;xcky3)%Z&;>+Yhm+!$8%J?!scS9CB;mhtZ2z){+m9XdqJo!a-xeFw$i9EJ~O~`HB z##U^V3ifpbIY!5;!OjkR*D9R>68VYgd@_*MUtkE$$-fkUxcc07c}E{~7;XvDpX)Cb|1|XFuvZq>JsB#)PveQe{;jxBiN^8{5K0jUrRqVzDg~18#Ciz@>FQUv zymy! z&*Od810Fl&u{>a&NYRqnoKmjF>yBohOh1`&!vECeGZ#-?l2ulhSKE~}#We+0>ac&U zetlbytST=DEOI$HMPT2?V*?FMarLpa{zkN(ZYfS}NLFDp%px@Hdbg?*+HWKXULd8 zkEK16c|6zUdZ=x9l%!V#N--vs)1Y?7`7@ zUn0ko6}wEv0^s#bf$8Y;nt{g#G6c;O9Rxkp~37xp$cQT7Cj!TNVhT`^& zI&4Hw_&KKS_Q{rzgsVT3nbUxjS!=s=ByFFeTQM)>Kqhz5aopk1G=ntHm(bZMG8dQ$BhNn1}_Fh1}7Nti)0c zsT@ogRyZ#PtP12$h;{@IwrJG15JZTZim@zu2-s#H3a(^DF9b*f!~-`SXB4TWX_;v% zT*RcM)i;-FDx{sz1Pp>3(E_#;_tAw?r_B|uIG=Ss?X=o8Z{QexDBE<7`o%{7?Ua9oUL)qyK{_Ai_VIOP#S7N&Z?ckpe>SiZNU9u zm_q=i4bJZ5(sVGj!PB!f7mo=XL{82L5inMgk&7V{T*SK~8Nwgw=%`(Z+g00lwVjUA zU=<3WUD{k?Dq6tekKu^y$hJ1`S7AGt=)v}92iHh2woB0rmiQX{&w_)RM|6e?WpRxG1qwgX1Z!msyPF7Ub7d7P6Vlc}3fyKQX z{8za}`FR?A4PT@4^9plwl!99goGkcu9*=ILU}-~rO?{;X|K@0ah;2_8fQ@>SAE*Hu zm0Ehb1*Q3A1^#G9oZ@s=Z~7@U&T;h6C(|Pi z>r_B2x`_Sz(lt28)kCN2v$jPmT?xPQJ9rqtDh3Y{nDII?+Y{^5u5Q$qRByH=X89*( zW+qsbz#re{>&mNY!JH4q<+i%|_71QcjvmY20Be`s_Y9ba=Ca)^9*q@#$RFGQTd(6C zD%WBR767mVjOD@V9ovsqp^2K>2HSzmI?N+AtVd2c@Vk*_I(IXT8ZbX?y>VB zUjx`hNA3vvLF4-_R%7+suyd>U8$5c5_dOFpf9J3&TGE@)C^juSC%r(E5|OF3M9T2A z8F=ALyha5M-v?g!X1a!$w-VTSu>AxDq`vRwfu|HHXh4~0-SQeQgF!}1ZYz~VPn9c zflBaRv=`n3Qn*Usc#Ek45eF0^LSR7lb6Mh?HnDpSg`cyk1F(JR%Ob?7Vgyf{qpy_(zgvuS>Vj=cLo{pa z>7>`QufDBBFQFGv3;F@B7jX-I>9Oo}NgLE_GwF{*7W7V4osfp`C!~n`D{ zw)N2Ge`)&ziIhHfGEX#uH_&MpKf(LB?vesIuAl_mzgzL^#-FF3QCH;Vl;)~*24l45 z5hQEJ5XpdL?T;vL1Qt`RP}9%>a6BA^|X!|NjdB_-jxI_CZ_l=Idxa zYiv&H$kZH3Ka|;-Ec<2Ut6=@}QDUDhSUP#7+LCO}G^NX|nW;%eh5%56KxP0ZU4iv*KA7w1xTwa7;q_g#*D8$PI$hF$~8E;@fbZi2er?M%mste&UVe zXw>l^U;pv=3AlcEd7Zho235`~JX|gRb zKMD8VG5SSkg(gI)?#yI@*VMn7sL4H8YOkr6)!UoP8&pmwgM1I4LNhLF(2)Uk4S`SY@Fxs`Oc(;0h69>rvKnWwBS-<;xgEr(x6DibxmxA2GpmIW%yoQloTB&TirQB-&)3iy;JKCM^{C2fZQ!-8vmGcos@_>` zs?06jUahZ9ZjxoybQv>rMOIl>wlW*yIdawc z1=gI%9Q>fsugF}o-=uuC4DGI?OOHNR`nu}nH;VJ$(-gdSwdhq6NdZ#d`u?6~~Z{9B`t z1-wD7iVv{1TrJ$)^S%f-D(W5jPFReasvb;xyJU+{ge@XLF!sW1Y>t#pxHf&n1 zT#>nH|1Pz8XL!_BlgzYrRr(xN=QBka^;w~<(os*A)DqVV3{f`x~wu*<2rlCTY(;`{I>jL zIg(cYQuReK+EM8DP0?Fb7i+$1ey6Rcv#0a&>5I>wJl%P&@mbk{muvs|59Qaf*EhbW z_U+#I{v1%Pj(mLjABWnTWxgjboH*Xqepc3gw(i1Z<%PWN^t0;pv+-Sq_cH?QCUG% zdPQ{U<|=F`!^+a9%Ut<>^NXIy4^bDT=A~pM$7FvlUt%w-s(;S!0?Is#=3GHno8CWo>lpI)FKe$jT79zST+OkX zwj*_?YR}i6x1XsyQCHPo(E_mQ%IeFS(o1y3!G*H?$*YP&RM{3=S)>NP*O)ZkUffX9 zT;l&u;qy61(`3n|nI*aE+#T^)mAc-5XO|S1md4@P{+a8x;&v0(YMUovWmkUrJ&Pu zXoQi+mlzyVO8Y8*2502splvA@57<9pE;b(RGHHC@z@yN7Q&))11UB+fcs{K&H5xCf zKDlFG%!H&Hbw@N1lr{f|?xO7oSi+$#0O~rDel$eo146*S?V*`hq6(0H%NP%`pACJIXr6*_&%wUIKAOx$>g;p&(WnhH6fYKMq71sza*elGHFyzT zNPIVF5n6Pb9n8$&3wSgMoXv3B$C6Mh1fewGk~#e>zp;A#;b65xG}uIkv|TbiuX_H{ zk&Epb2jy&{55H9X#uX)4CZOX@#Zq2#rw<$&plbvIOi;aXCP=0bJUn3c-RxUQ+%1X* z{>fL~SNpafs_Cq6Q#Z8rzSI7;tgaj)tW-6%1zF{q_Q!hHHYCdG6KgDHrSE2tnfv2@ z*#3!n`zLrG>Rg06WEV2S+hbHQ5ecCgnnkz+d`6wy7t4G@cPx&bJ`uY72A&*2kiR() z6bXoV6U+i~@qib)t=M{V>dOo`ML-S4(`fXOqhDdqDM`!8!N1|({Bm;AN^(==Jist4j@u&|VHkfH@Du$@Qy2AQ$ zyS=B!4Apu-Qm z??=AR!Q1>cw5nx=g{6hW@|2gSS+|amKUv#qsXH{+_oKfB=iXcIlJfGBa)=elxEVFOi~iUHd&I=pcASXucdT%& zI1%%L?ZgRx=S$9)Xz&P5Vg--jbHH8UD3D7bnD#I%oeT0z8Q3~q@{90U0|W>Iq7TOh z1NXBNgAP&M96-(t7<7ax5CV`lsF`;0Kr{)mF%V-31dg>2)dn!v5Y0Px-e3)^bLR_u zAk-tD0EPi=Wb4oq5)tMOdh~ZfmOf-|vv(;;YY^!I0+^8?SJRo`dC@ukP#kZu9gS@X z7R zCS-&8Ac`H_`5nyExf3wSe-KjId?+zTryShb!;;qltDAkOl@Z$Z084;cCoF^bIV@Ee zi3{;N-Umb2864mq;zq|m6=t(Nu}cM>#x8r?A+v@+MLw**Gn*WdKniw(tq8euTdsi8Zq0W~rrMOat z%m0Qa9T0xxB&|C-8&94BV}cy@fj6lSv`8TpH^P5~fbH1MJPwr1O5YI>fq5L>0N%zO zpw)L380LDgt&xsGhe10dgc}3xt5^u(a<_ofE8Q_ik&>4J5mvKj)0vr&g(IvQf*&EM z=Wz@dRD$rSN=YG=v%iJN&b$_g?5u8v$WA1*LC~f?kA!H=1=V$Z2@4m*i z!)jf11|vI|n8CTKI0gr=6lqxSh(fRxsD;zUZFwYAz1w8iX;p%+pFb`A>8H=%KcT*I z^vK~Cl@~X6uZ!LX%cM?9PfXsuNtT-rdYCFNudJd#gZ+NZs4Z-@H~OP-Um>6O(8DSS zoDRl3UI$DI2g5tT@K!iGt*{MN6a;gygZes?bp@Y!A_yRcap%RV1Aj6_&7Kx;2d?wJhEtaB~olpbt#z|334}xAjCm}zo^*y)xKLutVI8W?{JDyFB1Q@ zZ_8I|ht9Q2;aCbEKK)ESZ-CDnes(Q&ErZV-ejfVF;b+G(wNC)OE>Uz9__G-Nz3=RO zZ6z2L7<36;qB{jz2UcO}R4@MkgsPa&d5c9es2Nn#RuU84VO2XdgMo>XE1Z^x!2y&xJLkH-3zbN3m%kH8KljihAJNb-ug>0nsnuBd*6X?d6;)zd+r*T zW2CS(mmnq)+H`6@{E%?I6J&tp0rb`DATh%L%b^w|O)E&6u#ND-5T68qh?oB|I~X|p z2@cFJ@H7ifZHSfthPe--wSjaqP6Yd#K)hyrfmUFjYbnTCJU^_5+x3N53hR# z%hh$(x|pT}S$1`GUZbk5zWG3NVQWdVrl`BPyIbklk4}H?SP7qr0PoF%gUtaaGMsqM zLWgx1?>y+dy%z!%qyh8|Q3L#d1ncPA3r`1b?*eB7@SU5^Ai{UTK*kTiV-(5hX({SM zd~#Y-s|GzOZEb1-=Sncs(wLU4DMm9C=_P4d;9uOpB&F3gYEqmc8a&F?73#_=d%0bO zOpM)LR8XaQxY8$jL6_Ykc&_$lHY{ri9Qr?lgOz-=rM)PkfMXZbcU8L&C61U zPD*?Y2U(X+x>f4h?fglZc;v8 z4XQz@C<#qQf2!cj1MkmH#g|cl&Gf^j-P?oJ;GFSuJ$4<3t(D<3({U9}#P2J0<+>`p zx+3xLwwx_^=b~}Sgz9{Iih9qH1F>&>{Td2=L3RG-`qbw&u{VB6y{SUe(A4wqAe9D; z`f9Wr?Y)Yw${Ma#zj>8d_#v(fJp@s(pg{&fWG{s1xT8FPC^iG04cu0s8#oI-dO3!C z)ukmxrS$QQT{BkW8dtF1<*URuP!?W^j$vPQNohq19dkwZ{d=g!5q!$w3*la{n*$Ow zUgQWyI(rdKs&+03P}IdMxon^wJ+EegJG^7B0Xxyc%CLKZ^bQ;6Uhr6Dl5U z*PMIqT+i`;$Qlk-w;v`8L*z602~b(lJVNvDvqSXW2=x9Z55$h2lomT!MMg4@`|!bbNtJ)t8(lGj!JyO57)!Bt(Pt>F0vKDH>o6MXX+Gi=;uJYQV7SX zDF7jBiywIBDywp93TsRJOKtE~7}!oUH*Z3GK79S*zYT3e^>CeVRgw<&V*iqIh%Zr9 zSC>^(g0^$Bwx+V7sNNq3IoG3kXx`16S5eTqtNx(10=0Et1*sM6Fn;`rt0#cl1;ImD zSRpS5K1Zw^3dHeOM zu@muwpA$d5brnd044QhC_)A~aod2Qw`&c>N|F)9h5%!0F8W~ zOX7qE><;<;HLE}y1wH9Hs3Sy80@-H}q@3Y{UXUS<^Hw5*49O3md?gc|=`UFU{A{4D zfsjB9Qhx~vM5zLGEd^u)kVD*p1(97&Lo5)Q4r>Qeb258EQC(D1Sf$265MffCpAA7} zu0Bx7gPCP)Q$bU99Yk<~t)Ve9xh6@Kl$@ImT2Y@%PG@Hoq@^K<+=iYnHXFSjIS=0spgd563i}N>f zk6XpVsBFQsxjg;O?JtUpi3k7a-Q)VbjFxT zvu)6pLrfF{lxH+gg0LQH5P-V>h`o9|_GVmVuA$1Ut2S;}6C%w{$x2C4(R#2LTireA zGXTz?AH*3;N=>Ee2jA~L^BMn|dECX&Z;-VqG#0AMi!9bMen9!STMt!W*k*AJ@r}uQ zOwxJ#0$W;D`|_L0>bXB)X}$J3c{4?dR8nb)ib(I>Bhm|}!`AHMjyMjLHP^%~-Mo6` zw)brZ^7oZWu@o)zM-Yj0asEV>kgepk&VHgHWG&VNHI`!fX8XTrvGZR*G;ak; z_W2{SfrA;dl|CgNoxWurPdk&P60(Nu^~V4|r@17&e~&0W^3bDNU~(%E9)-op%uY-c z!!*o*9Hxl@^o{X&85^7#&^;#N47#r>34Hv6m?MO%%Dp&A&K~$gK==z0Z!KOreIzYJ zA#wr=C8jcPn25upDggj}Cvm6@vF=Xfc`&lY418P3?p#c^TJ*y6+{M}Iawy-Ig>1DK zY~u>H*|&zM-k0?pe*4j*+qWO>+>w@4$0gOJ?bxYe?;qVB-jj3QZPzMy(gsqpp^5YA zFX&!-O}Fjd=*mbQYb6XH(N}FJ(GedN384c>e;Q10bUcFbZU6}(KwzBws*Q6FYaiCZ zZ#>h|a>fHt=4mJiy?OObZ6j8`8bz?L28{2 zw?jE)-rUJk=AOM;r}^|8;JYqI*Z+LN$?fbzkl5X$ltsyf3BcYCtWMdHv^{aV?~eVu z_U_y-&9MQ@s@g$iq|>$<&YF(d2q6oj0kB)y(C~t={B60uI#4%?j0yP(YC21tkd&N| z!6z;?Xbnq3Q^JzN5~<{SpB&GQAwU;D7aGMQZ2-R`&61Xr&NZyxwPDBF#4vqW>NfgX zxDR65@rf!rQ<9LESY+hLz;MUbg3zK+-;i~|8$#AgK|X~5LkN-i*M)PyeIgfQ&ov|Y zKxE(5B-QHcQhlqzLP;5J54mbj=OuLx1%qt?^bw&`B{My_)@>-2gp*gR(Pz9{PZ%WcbGeJfMYUJa}R{xq( z!4Wm+0@+>hv3$}5nLGtwdB2d)!dJ|$Z2BieX4oF0#rORpS2BDwoUT1t*y&<5l|L z6PbO#Ve63PCayBPXnBxIzSa7(#u8(Wjs~D}bToL~v?1%ZN$GZW z!(kqL9+nsmT)E>$aPm%m1+I3V)#N2Ly7HrVueeoKd$91>F;#VDO?nmAaHRC?IaN1U zZ&vTC^W|P??H8 zt(!nK+>8$!$*cVzZrvGPA673t_b$aqj8zAT<+D#>a3p8$?kzvX?;}qU@g5?BC5kU9 zNte%;U|{64t-UaPaW-@T5p?cToA-<*J~B<&ohWw)w!cW5@;|KTS&P zdM@^C&=Jm7WvQuF;Sk3XkA)rN%thJ7MXHv_mUYKCt3-bAB$=I!*|QU!uBKhZbP#=E z{Sx{zpByqec&nOX;AWqEGK|~B`?q~EWY@agEBCD0xAy$>Ep+Iw{iNP-%OAfs{d|!=I z%ex;^FJ#^vx*H}$k2uZ0HJ)?}>4_CsabMZA&Jc#Ys@R)F(Rw9Lnly(JKiTo73>MNq zq;8P#^nSs+0)*yGh>sxm?VNs(q>+3~)5-AR<@jg7zvM1>+fC`5PU709ONw3o%D0y+ z7|mswByTJ^_0cCMPF%l!bkVeIUby+#Unxi=_cmXCea8A#Yhts;gSNn2s#9Pz3USvXoF>* z1qz5+X8?tr|2n`1gQ*WEI3#r%uqSZ+d-PuzdxCevO7{WvelUFa4`d{OX2>D4?1)DchD@fD zkx%dkAp|kmQ5vKI{Ml#3kIgO2u;~m?lEMpM-UP%pX}gRT#qSnQ+qz-D6$q_np!we% z#v?kG2bBWvH=AG#w*FfNQ__W`u+YjV21KEFU3k~oQ%RRJQ(xlui|RfS2y{pT?e^Yl zoa-{#q3lO}fkjxdhI{XB1CWzLfSViu(}yU&meJ<>;tZL)HC{G=GR2dFGCGgM(hcOp zc<#XBrr@#!>B(h9OJ=BM1i{H1Fk=7*NWK%0{1(am0WAXt1hurZ6dgNxgexm*+I8T# zlzdnWQp*O$sKYg~>3mgubySt5{$3Fhd@G5fmb|miIhNGRb505zc}JO(V|1k3puUlv zVK8KvQ|##wWHRMgrSb{-)fbf+_Ed`@!;qN;Vuv*?H#5f~&5~GivT_Y}>8uM%b55o; z-2&{m$(U)(uo!Ha)=Zn(Y?0OnDswC*yTN9#rXh)#k(r%lO}85C#+)1}!T?>BW?Q-) z$N&gO7?C!&r8$gJd2c<)gch?+dfA|~r&?1?TuPcDJ&%jV_J>m7EhjX#&CG}$0P zV@ffmr)Q^Sg970&18-w9*`%(;t~pG_3l3q!?yMtxnd!T?G&{m;R=oLg7VQ$ITGp7= z0HX<~kKqLViyF`ZX25vy#L&qLUWauretq((&qI0l`2SD>mMinB4LhRCn7V~eVN$Fu zP8}EPK`3b5+K*vxxV7R}@zhr)XmR%Is!M9}cy4h%WV1ykvRAQnh@pe{fv& z4*p=(dxuqWYvqlw>o-&+{ZrCN-X*Vc=MP?M_+-0u_wDcZ{HT^2{IRNumXT-n?|1B1 z=UB5$IlSCH!4a1o75#4VyDL-+@C;qngg&E|n?r_%!H$Fxa>!;Y#Q zJ9