From 11eea2d5745c503f464b2744f4f628fed01fd2e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sun, 9 Nov 2014 12:07:42 +0100 Subject: [PATCH 01/26] Get rid of allocfmt() It is not possible to know how we can free the results of this allocation, so we shouldn't be using this function. We have a convention of returning Oid objects in pygit2, so let's keep to that in these places. --- src/diff.c | 13 +++++++------ src/note.c | 15 ++++++--------- src/reference.c | 8 ++++---- src/types.h | 10 +++++----- test/test_diff.py | 4 ++-- test/test_note.py | 2 +- 6 files changed, 25 insertions(+), 27 deletions(-) diff --git a/src/diff.c b/src/diff.c index 0f7bfb3..403c196 100644 --- a/src/diff.c +++ b/src/diff.c @@ -31,6 +31,7 @@ #include "error.h" #include "types.h" #include "utils.h" +#include "oid.h" #include "diff.h" extern PyObject *GitError; @@ -81,8 +82,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_id = git_oid_allocfmt(&delta->old_file.id); - py_patch->new_id = git_oid_allocfmt(&delta->new_file.id); + py_patch->old_id = git_oid_to_python(&delta->old_file.id); + py_patch->new_id = git_oid_to_python(&delta->new_file.id); git_patch_line_stats(NULL, &additions, &deletions, patch); py_patch->additions = additions; @@ -150,8 +151,8 @@ static void Patch_dealloc(Patch *self) { Py_CLEAR(self->hunks); - free(self->old_id); - free(self->new_id); + Py_CLEAR(self->old_id); + Py_CLEAR(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); @@ -160,8 +161,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_id, T_STRING, "old oid"), - MEMBER(Patch, new_id, T_STRING, "new oid"), + MEMBER(Patch, old_id, T_OBJECT, "old oid"), + MEMBER(Patch, new_id, T_OBJECT, "new oid"), MEMBER(Patch, status, T_CHAR, "status"), MEMBER(Patch, similarity, T_INT, "similarity"), MEMBER(Patch, hunks, T_OBJECT, "hunks"), diff --git a/src/note.c b/src/note.c index 9762ef6..ec50659 100644 --- a/src/note.c +++ b/src/note.c @@ -44,8 +44,8 @@ Note_remove(Note *self, PyObject* args) { char *ref = "refs/notes/commits"; int err = GIT_ERROR; - git_oid annotated_id; Signature *py_author, *py_committer; + Oid *id; if (!PyArg_ParseTuple(args, "O!O!|s", &SignatureType, &py_author, @@ -53,12 +53,9 @@ Note_remove(Note *self, PyObject* args) &ref)) return NULL; - err = git_oid_fromstr(&annotated_id, self->annotated_id); - if (err < 0) - return Error_set(err); - + id = (Oid *) self->annotated_id; err = git_note_remove(self->repo->repo, ref, py_author->signature, - py_committer->signature, &annotated_id); + py_committer->signature, &id->oid); if (err < 0) return Error_set(err); @@ -90,7 +87,7 @@ static void Note_dealloc(Note *self) { Py_CLEAR(self->repo); - free(self->annotated_id); + Py_CLEAR(self->annotated_id); git_note_free(self->note); PyObject_Del(self); } @@ -102,7 +99,7 @@ PyMethodDef Note_methods[] = { }; PyMemberDef Note_members[] = { - MEMBER(Note, annotated_id, T_STRING, "id of the annotated object."), + MEMBER(Note, annotated_id, T_OBJECT, "id of the annotated object."), {NULL} }; @@ -229,7 +226,7 @@ wrap_note(Repository* repo, git_oid* annotated_id, const char* ref) py_note->repo = repo; Py_INCREF(repo); - py_note->annotated_id = git_oid_allocfmt(annotated_id); + py_note->annotated_id = git_oid_to_python(annotated_id); return (PyObject*) py_note; } diff --git a/src/reference.c b/src/reference.c index 28c1bbd..571bb57 100644 --- a/src/reference.c +++ b/src/reference.c @@ -60,8 +60,8 @@ RefLogIter_iternext(RefLogIter *self) entry = git_reflog_entry_byindex(self->reflog, self->i); py_entry = PyObject_New(RefLogEntry, &RefLogEntryType); - 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->oid_old = git_oid_to_python(git_reflog_entry_id_old(entry)); + py_entry->oid_new = git_oid_to_python(git_reflog_entry_id_new(entry)); py_entry->message = strdup(git_reflog_entry_message(entry)); err = git_signature_dup(&py_entry->signature, git_reflog_entry_committer(entry)); @@ -431,8 +431,8 @@ RefLogEntry_init(RefLogEntry *self, PyObject *args, PyObject *kwds) static void RefLogEntry_dealloc(RefLogEntry *self) { - free(self->oid_old); - free(self->oid_new); + Py_CLEAR(self->oid_old); + Py_CLEAR(self->oid_new); free(self->message); git_signature_free(self->signature); PyObject_Del(self); diff --git a/src/types.h b/src/types.h index 58e9e99..077439d 100644 --- a/src/types.h +++ b/src/types.h @@ -79,7 +79,7 @@ typedef struct { PyObject_HEAD Repository *repo; git_note *note; - char* annotated_id; + PyObject* annotated_id; } Note; typedef struct { @@ -105,8 +105,8 @@ typedef struct { PyObject* hunks; const char * old_file_path; const char * new_file_path; - char* old_id; - char* new_id; + PyObject* old_id; + PyObject* new_id; char status; unsigned similarity; unsigned additions; @@ -164,8 +164,8 @@ typedef Reference Branch; typedef struct { PyObject_HEAD git_signature *signature; - char *oid_old; - char *oid_new; + PyObject *oid_old; + PyObject *oid_new; char *message; } RefLogEntry; diff --git a/test/test_diff.py b/test/test_diff.py index 2f4314d..6fd4fbb 100644 --- a/test/test_diff.py +++ b/test/test_diff.py @@ -261,9 +261,9 @@ class DiffTest(utils.BareRepoTestCase): 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_id, + self.assertEqual(patch.old_id.hex, '7f129fd57e31e935c6d60a0c794efe4e6927664b') - self.assertEqual(patch.new_id, + self.assertEqual(patch.new_id.hex, 'af431f20fc541ed6d5afede3e2dc7160f6f01f16') def test_hunk_content(self): diff --git a/test/test_note.py b/test/test_note.py index 4fcead5..5dce89c 100644 --- a/test/test_note.py +++ b/test/test_note.py @@ -70,7 +70,7 @@ class NotesTest(utils.BareRepoTestCase): def test_iterate_notes(self): for i, note in enumerate(self.repo.notes()): - entry = (note.id.hex, note.message, note.annotated_id) + entry = (note.id.hex, note.message, note.annotated_id.hex) self.assertEqual(NOTES[i], entry) def test_iterate_non_existing_ref(self): From 6484ef1e37b0566b8d21ffe9d5d197f068564ede Mon Sep 17 00:00:00 2001 From: Philippe Ombredanne Date: Tue, 11 Nov 2014 12:05:08 +0100 Subject: [PATCH 02/26] Make the GPL exception explicit in setup.py I think that is best to have that explicitly set there (and it should possibly be updated in Pypi too). Thanks for this lib! Cordially Philippe --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index ee8973f..9dfdda9 100644 --- a/setup.py +++ b/setup.py @@ -184,7 +184,7 @@ setup(name='pygit2', version=__version__, url='http://github.com/libgit2/pygit2', classifiers=classifiers, - license='GPLv2', + license='GPLv2 with linking exception', maintainer=u('J. David Ibáñez'), maintainer_email='jdavid.ibp@gmail.com', long_description=long_description, From b6f0bb08006d78167d679e3427536609dc36cca3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Tue, 11 Nov 2014 13:03:25 +0100 Subject: [PATCH 03/26] setup: minor cleanup --- setup.py | 33 ++++++++++++++------------------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/setup.py b/setup.py index 9dfdda9..92b5280 100644 --- a/setup.py +++ b/setup.py @@ -37,6 +37,8 @@ from distutils.command.build import build from distutils.command.sdist import sdist from distutils import log import os +from os import getenv, listdir, pathsep +from os.path import abspath, isfile from setuptools import setup, Extension, Command import shlex from subprocess import Popen, PIPE @@ -58,7 +60,7 @@ else: libgit2_bin, libgit2_include, libgit2_lib = get_libgit2_paths() -pygit2_exts = [os.path.join('src', name) for name in os.listdir('src') +pygit2_exts = [os.path.join('src', name) for name in listdir('src') if name.endswith('.c')] @@ -71,7 +73,6 @@ class TestCommand(Command): def initialize_options(self): self.args = '' - pass def finalize_options(self): pass @@ -80,7 +81,7 @@ class TestCommand(Command): self.run_command('build') bld = self.distribution.get_command_obj('build') # Add build_lib in to sys.path so that unittest can found DLLs and libs - sys.path = [os.path.abspath(bld.build_lib)] + sys.path + sys.path = [abspath(bld.build_lib)] + sys.path test_argv0 = [sys.argv[0] + ' test --args='] # For transfering args to unittest, we have to split args by ourself, @@ -93,6 +94,7 @@ class TestCommand(Command): test_argv = test_argv0 + shlex.split(self.args) unittest.main(None, defaultTest='test.test_suite', argv=test_argv) + class CFFIBuild(build): """Hack to combat the chicken and egg problem that we need cffi to add cffi as an extension. @@ -116,12 +118,12 @@ class BuildWithDLLs(CFFIBuild): libgit2_dlls.append('git2.dll') elif compiler_type == 'mingw32': libgit2_dlls.append('libgit2.dll') - look_dirs = [libgit2_bin] + os.getenv("PATH", "").split(os.pathsep) - target = os.path.abspath(self.build_lib) + look_dirs = [libgit2_bin] + getenv("PATH", "").split(pathsep) + target = abspath(self.build_lib) for bin in libgit2_dlls: for look in look_dirs: f = os.path.join(look, bin) - if os.path.isfile(f): + if isfile(f): ret.append((f, target)) break else: @@ -131,10 +133,9 @@ class BuildWithDLLs(CFFIBuild): def run(self): build.run(self) - if os.name == 'nt': - # On Windows we package up the dlls with the plugin. - for s, d in self._get_dlls(): - self.copy_file(s, d) + # On Windows we package up the dlls with the plugin. + for s, d in self._get_dlls(): + self.copy_file(s, d) class sdist_files_from_git(sdist): @@ -167,16 +168,10 @@ with codecs.open('README.rst', 'r', 'utf-8') as readme: cmdclass = { + 'build': BuildWithDLLs if os.name == 'nt' else CFFIBuild, 'test': TestCommand, - 'sdist': sdist_files_from_git} - -if os.name == 'nt': - # BuildWithDLLs can copy external DLLs into source directory. - cmdclass['build'] = BuildWithDLLs -else: - # Build cffi - cmdclass['build'] = CFFIBuild - + 'sdist': sdist_files_from_git, + } setup(name='pygit2', description='Python bindings for libgit2.', From b80103b0172939a0503b11c48964f159bde0ca96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sat, 8 Nov 2014 13:13:34 +0100 Subject: [PATCH 04/26] Introduce RemoteCollection This lets us look up remotes by name, which is not possible by just returning the list of remotes. Move remote creation to Repostiory.remotes.create() and keep the old Repository.create_remote() for compatibility, delegating to this new way. Existing code should keep working, but this moves us towards what we'd need for a better interface in 0.22 which makes remote renaming and deleting work with a name rather than an instance and would make sense to exist as part of an Remote.remotes object. --- docs/remotes.rst | 6 ++++- pygit2/remote.py | 63 ++++++++++++++++++++++++++++++++++++++++++++ pygit2/repository.py | 33 ++++++----------------- test/test_remote.py | 13 +++++++++ 4 files changed, 89 insertions(+), 26 deletions(-) diff --git a/docs/remotes.rst b/docs/remotes.rst index dfa8804..933637f 100644 --- a/docs/remotes.rst +++ b/docs/remotes.rst @@ -2,10 +2,14 @@ Remotes ********************************************************************** - .. autoattribute:: pygit2.Repository.remotes .. automethod:: pygit2.Repository.create_remote +The remote collection +========================== + +.. autoclass:: pygit2.remote.RemoteCollection + :members: The Remote type ==================== diff --git a/pygit2/remote.py b/pygit2/remote.py index c69578c..88d875d 100644 --- a/pygit2/remote.py +++ b/pygit2/remote.py @@ -494,3 +494,66 @@ def get_credentials(fn, url, username, allowed): check_error(err) return ccred + +class RemoteCollection(object): + """Collection of configured remotes + + You can use this class to look up and manage the remotes configured + in a repository. You can access repositories using index + access. E.g. to look up the "origin" remote, you can use + + >>> repo.remotes["origin"] + """ + + def __init__(self, repo): + self._repo = repo; + + def __len__(self): + names = ffi.new('git_strarray *') + + try: + err = C.git_remote_list(names, self._repo._repo) + check_error(err) + + return names.count + finally: + C.git_strarray_free(names) + + def __iter__(self): + names = ffi.new('git_strarray *') + + try: + err = C.git_remote_list(names, self._repo._repo) + check_error(err) + + cremote = ffi.new('git_remote **') + for i in range(names.count): + err = C.git_remote_load(cremote, self._repo._repo, names.strings[i]) + check_error(err) + + yield Remote(self._repo, cremote[0]) + finally: + C.git_strarray_free(names) + + def __getitem__(self, name): + if isinstance(name, int): + return list(self)[name] + + cremote = ffi.new('git_remote **') + err = C.git_remote_load(cremote, self._repo._repo, to_bytes(name)) + check_error(err) + + return Remote(self._repo, cremote[0]) + + def create(self, name, url): + """create(name, url) -> Remote + + Create a new remote with the given name and url. + """ + + cremote = ffi.new('git_remote **') + + err = C.git_remote_create(cremote, self._repo._repo, to_bytes(name), to_bytes(url)) + check_error(err) + + return Remote(self._repo, cremote[0]) diff --git a/pygit2/repository.py b/pygit2/repository.py index d9c1eed..d31e03e 100644 --- a/pygit2/repository.py +++ b/pygit2/repository.py @@ -48,7 +48,7 @@ from .config import Config from .errors import check_error from .ffi import ffi, C from .index import Index -from .remote import Remote +from .remote import Remote, RemoteCollection from .blame import Blame from .utils import to_bytes, is_string @@ -58,6 +58,8 @@ class Repository(_Repository): def __init__(self, *args, **kwargs): super(Repository, self).__init__(*args, **kwargs) + self._remotes = RemoteCollection(self) + # Get the pointer as the contents of a buffer and store it for # later access repo_cptr = ffi.new('git_repository **') @@ -90,36 +92,17 @@ class Repository(_Repository): """create_remote(name, url) -> Remote Creates a new remote. + + This method is deprecated, please use Remote.remotes.create() """ - cremote = ffi.new('git_remote **') - - err = C.git_remote_create(cremote, self._repo, to_bytes(name), - to_bytes(url)) - check_error(err) - - return Remote(self, cremote[0]) + return self.remotes.create(name, url) @property def remotes(self): - """Returns all configured remotes""" + """The collection of configured remotes""" - names = ffi.new('git_strarray *') - - try: - 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, self._repo, names.strings[i]) - check_error(err) - - l[i] = Remote(self, cremote[0]) - return l - finally: - C.git_strarray_free(names) + return self._remotes # # Configuration diff --git a/test/test_remote.py b/test/test_remote.py index e0b9fae..ee28743 100644 --- a/test/test_remote.py +++ b/test/test_remote.py @@ -167,6 +167,19 @@ class RepositoryTest(utils.RepoTestCase): remote = self.repo.create_remote(name, url) self.assertTrue(remote.name in [x.name for x in self.repo.remotes]) + def test_remote_collection(self): + remote = self.repo.remotes['origin'] + self.assertEqual(REMOTE_NAME, remote.name) + self.assertEqual(REMOTE_URL, remote.url) + + with self.assertRaises(KeyError): + self.repo.remotes['upstream'] + + name = 'upstream' + url = 'git://github.com/libgit2/pygit2.git' + remote = self.repo.remotes.create(name, url) + self.assertTrue(remote.name in [x.name for x in self.repo.remotes]) + def test_remote_save(self): remote = self.repo.remotes[0] From beff87192372a8276eab5893a4da112bfc9dbb8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Wed, 12 Nov 2014 10:18:21 +0100 Subject: [PATCH 05/26] Minor styling --- docs/remotes.rst | 6 +++++- pygit2/repository.py | 10 ++-------- src/diff.c | 26 +++++++++++++------------- src/note.c | 4 ++-- src/pygit2.c | 2 +- src/types.h | 6 +++--- 6 files changed, 26 insertions(+), 28 deletions(-) diff --git a/docs/remotes.rst b/docs/remotes.rst index 933637f..1dcbe5d 100644 --- a/docs/remotes.rst +++ b/docs/remotes.rst @@ -2,7 +2,11 @@ Remotes ********************************************************************** -.. autoattribute:: pygit2.Repository.remotes +.. py:attribute:: Repository.remotes + + The collection of configured remotes, an instance of + :py:class:`pygit2.remote.RemoteCollection` + .. automethod:: pygit2.Repository.create_remote The remote collection diff --git a/pygit2/repository.py b/pygit2/repository.py index d31e03e..7a3f8a6 100644 --- a/pygit2/repository.py +++ b/pygit2/repository.py @@ -48,7 +48,7 @@ from .config import Config from .errors import check_error from .ffi import ffi, C from .index import Index -from .remote import Remote, RemoteCollection +from .remote import RemoteCollection from .blame import Blame from .utils import to_bytes, is_string @@ -58,7 +58,7 @@ class Repository(_Repository): def __init__(self, *args, **kwargs): super(Repository, self).__init__(*args, **kwargs) - self._remotes = RemoteCollection(self) + self.remotes = RemoteCollection(self) # Get the pointer as the contents of a buffer and store it for # later access @@ -98,12 +98,6 @@ class Repository(_Repository): return self.remotes.create(name, url) - @property - def remotes(self): - """The collection of configured remotes""" - - return self._remotes - # # Configuration # diff --git a/src/diff.c b/src/diff.c index 403c196..b8fcdab 100644 --- a/src/diff.c +++ b/src/diff.c @@ -44,7 +44,7 @@ extern PyTypeObject RepositoryType; PyTypeObject PatchType; -PyObject* +PyObject * wrap_diff(git_diff *diff, Repository *repo) { Diff *py_diff; @@ -53,7 +53,7 @@ wrap_diff(git_diff *diff, Repository *repo) if (py_diff) { Py_INCREF(repo); py_diff->repo = repo; - py_diff->list = diff; + py_diff->diff = diff; } return (PyObject*) py_diff; @@ -134,7 +134,7 @@ wrap_patch(git_patch *patch) return (PyObject*) py_patch; } -PyObject* +PyObject * diff_get_patch_byindex(git_diff *diff, size_t idx) { git_patch *patch = NULL; @@ -235,7 +235,7 @@ PyObject * DiffIter_iternext(DiffIter *self) { if (self->i < self->n) - return diff_get_patch_byindex(self->diff->list, self->i++); + return diff_get_patch_byindex(self->diff->diff, self->i++); PyErr_SetNone(PyExc_StopIteration); return NULL; @@ -284,8 +284,8 @@ PyTypeObject DiffIterType = { Py_ssize_t Diff_len(Diff *self) { - assert(self->list); - return (Py_ssize_t)git_diff_num_deltas(self->list); + assert(self->diff); + return (Py_ssize_t)git_diff_num_deltas(self->diff); } PyDoc_STRVAR(Diff_patch__doc__, "Patch diff string."); @@ -299,12 +299,12 @@ Diff_patch__get__(Diff *self) size_t i, len, num; PyObject *py_patch = NULL; - num = git_diff_num_deltas(self->list); + num = git_diff_num_deltas(self->diff); if (num == 0) Py_RETURN_NONE; for (i = 0, len = 1; i < num ; ++i) { - err = git_patch_from_diff(&patch, self->list, i); + err = git_patch_from_diff(&patch, self->diff, i); if (err < 0) goto cleanup; @@ -430,7 +430,7 @@ Diff_merge(Diff *self, PyObject *args) if (py_diff->repo->repo != self->repo->repo) return Error_set(GIT_ERROR); - err = git_diff_merge(self->list, py_diff->list); + err = git_diff_merge(self->diff, py_diff->diff); if (err < 0) return Error_set(err); @@ -455,7 +455,7 @@ Diff_find_similar(Diff *self, PyObject *args, PyObject *kwds) &opts.flags, &opts.rename_threshold, &opts.copy_threshold, &opts.rename_from_rewrite_threshold, &opts.break_rewrite_threshold, &opts.rename_limit)) return NULL; - err = git_diff_find_similar(self->list, &opts); + err = git_diff_find_similar(self->diff, &opts); if (err < 0) return Error_set(err); @@ -472,7 +472,7 @@ Diff_iter(Diff *self) Py_INCREF(self); iter->diff = self; iter->i = 0; - iter->n = git_diff_num_deltas(self->list); + iter->n = git_diff_num_deltas(self->diff); } return (PyObject*)iter; } @@ -487,14 +487,14 @@ Diff_getitem(Diff *self, PyObject *value) i = PyLong_AsUnsignedLong(value); - return diff_get_patch_byindex(self->list, i); + return diff_get_patch_byindex(self->diff, i); } static void Diff_dealloc(Diff *self) { - git_diff_free(self->list); + git_diff_free(self->diff); Py_CLEAR(self->repo); PyObject_Del(self); } diff --git a/src/note.c b/src/note.c index ec50659..6a7a437 100644 --- a/src/note.c +++ b/src/note.c @@ -39,7 +39,7 @@ extern PyTypeObject SignatureType; PyDoc_STRVAR(Note_remove__doc__, "Removes a note for an annotated object"); -PyObject* +PyObject * Note_remove(Note *self, PyObject* args) { char *ref = "refs/notes/commits"; @@ -208,7 +208,7 @@ PyTypeObject NoteIterType = { }; -PyObject* +PyObject * wrap_note(Repository* repo, git_oid* annotated_id, const char* ref) { Note* py_note = NULL; diff --git a/src/pygit2.c b/src/pygit2.c index 767ae2e..2fec0c1 100644 --- a/src/pygit2.c +++ b/src/pygit2.c @@ -149,7 +149,7 @@ PyMethodDef module_methods[] = { {NULL} }; -PyObject* +PyObject * moduleinit(PyObject* m) { if (m == NULL) diff --git a/src/types.h b/src/types.h index 077439d..78747cf 100644 --- a/src/types.h +++ b/src/types.h @@ -90,12 +90,12 @@ typedef struct { } NoteIter; -/* git _diff */ -SIMPLE_TYPE(Diff, git_diff, list) +/* git_diff */ +SIMPLE_TYPE(Diff, git_diff, diff) typedef struct { PyObject_HEAD - Diff* diff; + Diff *diff; size_t i; size_t n; } DiffIter; From aff3a64e2df1832f0dff6865e41553929a91f9e8 Mon Sep 17 00:00:00 2001 From: Kevin KIN-FOO Date: Tue, 30 Dec 2014 16:07:31 +0100 Subject: [PATCH 06/26] Mention libssh2 in installation#requirements Fixes #456 --- docs/install.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/install.rst b/docs/install.rst index c63b254..621b103 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -16,6 +16,7 @@ Requirements - Python 2.7, 3.2+ or pypy (including the development headers) - Libgit2 v0.21.1+ - cffi 0.8.1+ +- Libssh2, optional, used for SSH network operations. .. warning:: From e807ad43d725da06bfc34187b387270baea74887 Mon Sep 17 00:00:00 2001 From: Kevin KIN-FOO Date: Tue, 30 Dec 2014 16:34:15 +0100 Subject: [PATCH 07/26] Mentioning libssh2 in remote's pydoc --- pygit2/remote.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pygit2/remote.py b/pygit2/remote.py index 88d875d..9a58213 100644 --- a/pygit2/remote.py +++ b/pygit2/remote.py @@ -197,7 +197,7 @@ class Remote(object): def fetch(self, signature=None, message=None): """fetch(signature, message) -> TransferProgress - Perform a fetch against this remote. + Perform a fetch against this remote. May require libssh2. """ # Get the default callbacks first @@ -317,6 +317,7 @@ class Remote(object): """push(refspec, signature, message) Push the given refspec to the remote. Raises ``GitError`` on error. + May require libssh2. :param str spec: push refspec to use :param Signature signature: signature to use when updating the tips From 22d1aef50dd440c120b43bf8947ea7197fdf178e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Richard=20M=C3=B6hn?= Date: Sun, 4 Jan 2015 20:42:44 +0100 Subject: [PATCH 08/26] Remove obsolete git-branch recipe MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The git-branch recipe says: »Note that the next release will probably allow repo.listall_branches().« Concluding from the README, Repository.listall_branches() was included in some release prior to 0.20.0, so at least that statement is obsolete. However, since pygit2.org brings up fairly accurate results for a search on »list all branches«, I figured that the whole recipe isn't needed anymore. Therefore delete it. --- docs/recipes.rst | 1 - docs/recipes/git-branch.rst | 30 ------------------------------ 2 files changed, 31 deletions(-) delete mode 100644 docs/recipes/git-branch.rst diff --git a/docs/recipes.rst b/docs/recipes.rst index 019f863..14f31c0 100644 --- a/docs/recipes.rst +++ b/docs/recipes.rst @@ -17,7 +17,6 @@ Main porcelain commands .. toctree:: :maxdepth: 1 - git-branch (List, create, or delete branches.) git-init (Create an empty git repository or reinitialize an existing one.) git-log (Show commit logs.) git-show (Show various types of objects.) diff --git a/docs/recipes/git-branch.rst b/docs/recipes/git-branch.rst deleted file mode 100644 index 5db822b..0000000 --- a/docs/recipes/git-branch.rst +++ /dev/null @@ -1,30 +0,0 @@ -********************************************************************** -git-branch -********************************************************************** - ----------------------------------------------------------------------- -Listing branches ----------------------------------------------------------------------- - -====================================================================== -List all branches -====================================================================== - -.. code-block:: bash - - $> git branch - -.. code-block:: python - - >>> regex = re.compile('^refs/heads/') - >>> branches = filter(lambda r: regex.match(r), repo.listall_references()) - -`Note that the next release will probably allow` ``repo.listall_branches()``. - ----------------------------------------------------------------------- -References ----------------------------------------------------------------------- - -- git-branch_. - -.. _git-branch: https://www.kernel.org/pub/software/scm/git/docs/git-branch.html From 4f88840e93cc1922b79c8580dff03194fcc52b07 Mon Sep 17 00:00:00 2001 From: Greg Fitzgerald Date: Thu, 18 Dec 2014 01:45:35 +0000 Subject: [PATCH 09/26] Fix use-after-free when patch outlives diff --- src/diff.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/diff.c b/src/diff.c index b8fcdab..d40c3e6 100644 --- a/src/diff.c +++ b/src/diff.c @@ -77,8 +77,8 @@ wrap_patch(git_patch *patch) 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->old_file_path = strdup(delta->old_file.path); + py_patch->new_file_path = strdup(delta->new_file.path); py_patch->status = git_diff_status_char(delta->status); py_patch->similarity = delta->similarity; py_patch->flags = delta->flags; @@ -153,8 +153,8 @@ Patch_dealloc(Patch *self) Py_CLEAR(self->hunks); Py_CLEAR(self->old_id); Py_CLEAR(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 */ + free(self->old_file_path); + free(self->new_file_path); PyObject_Del(self); } From d341cff7d6ff33f62474383f5a49651e10124721 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Richard=20M=C3=B6hn?= Date: Fri, 9 Jan 2015 19:25:50 +0100 Subject: [PATCH 10/26] Fix documentation for Repository.listall_branches Add a description of the possible flags and turn the "tuple" into a "list", as it had already happened in code and part of the documentation in aa5877e0. --- src/repository.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/repository.c b/src/repository.c index 31075c2..1920438 100644 --- a/src/repository.c +++ b/src/repository.c @@ -936,10 +936,15 @@ out: PyDoc_STRVAR(Repository_listall_branches__doc__, - "listall_branches([flags]) -> [str, ...]\n" + "listall_branches([flag]) -> [str, ...]\n" "\n" - "Return a tuple with all the branches in the repository.\n" - "By default, it returns all local branches."); + "Return a list with all the branches in the repository.\n" + "\n" + "The *flag* may be:\n" + "\n" + "- GIT_BRANCH_LOCAL - return all local branches (set by default)\n" + "- GIT_BRANCH_REMOTE - return all remote-tracking branches\n" + "- GIT_BRANCH_ALL - return local branches and remote-tracking branches"); PyObject * Repository_listall_branches(Repository *self, PyObject *args) From 4cbfade9735deaddb947254d60986eb962220a94 Mon Sep 17 00:00:00 2001 From: Lukas Fleischer Date: Sat, 10 Jan 2015 20:38:39 +0100 Subject: [PATCH 11/26] Fix data type of options in init_repository() Initializers for the char * fields of the git_repository_init_options structure must be cdata pointers. Signed-off-by: Lukas Fleischer --- pygit2/__init__.py | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/pygit2/__init__.py b/pygit2/__init__.py index 2674063..995fe6c 100644 --- a/pygit2/__init__.py +++ b/pygit2/__init__.py @@ -86,11 +86,26 @@ def init_repository(path, bare=False, C.git_repository_init_init_options(options, C.GIT_REPOSITORY_INIT_OPTIONS_VERSION) options.flags = flags options.mode = mode - options.workdir_path = to_bytes(workdir_path) - options.description = to_bytes(description) - options.template_path = to_bytes(template_path) - options.initial_head = to_bytes(initial_head) - options.origin_url = to_bytes(origin_url) + + if workdir_path: + workdir_path_ref = ffi.new('char []', to_bytes(workdir_path)) + options.workdir_path = workdir_path_ref + + if description: + description_ref = ffi.new('char []', to_bytes(description)) + options.description = description_ref + + if template_path: + template_path_ref = ffi.new('char []', to_bytes(template_path)) + options.template_path = template_path_ref + + if initial_head: + initial_head_ref = ffi.new('char []', to_bytes(initial_head)) + options.initial_head = initial_head_ref + + if origin_url: + origin_url_ref = ffi.new('char []', to_bytes(origin_url)) + options.origin_url = origin_url_ref # Call crepository = ffi.new('git_repository **') From 1dbf94011abbc9a7ee00892d082622360e4ef5d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Thu, 11 Dec 2014 12:47:34 +0100 Subject: [PATCH 12/26] Migrate to 0.22 Apart from the usual API changes, we now need to introduce the concept of whether we still own the C object underneath our Repository and Remote objects. When using the custom callbacks for repository and remote creation during clone, we pass the pointer and thus ownership of the object back to the library. We will then get the repository back at the end. We return the object which was handed to us rather than opening the repository again with the local path as there is now a much higher chance that the cloned repository does not use the standard backends. --- pygit2/__init__.py | 92 +++++++++++++++++++++-------------- pygit2/decl.h | 105 ++++++++++++++++++++++++++++------------ pygit2/remote.py | 69 +++++++++++++------------- pygit2/repository.py | 11 +++++ src/diff.c | 4 +- src/pygit2.c | 3 +- src/repository.c | 74 +++++++++++++++++++++++----- src/treebuilder.c | 2 +- src/types.h | 1 + test/test_remote.py | 14 ++---- test/test_repository.py | 29 ++++++----- 11 files changed, 264 insertions(+), 140 deletions(-) diff --git a/pygit2/__init__.py b/pygit2/__init__.py index 995fe6c..800fb00 100644 --- a/pygit2/__init__.py +++ b/pygit2/__init__.py @@ -41,7 +41,7 @@ from .index import Index, IndexEntry from .remote import Remote, get_credentials from .repository import Repository from .settings import Settings -from .utils import to_bytes +from .utils import to_bytes, to_str from ._utils import __version__ @@ -113,7 +113,7 @@ def init_repository(path, bare=False, check_error(err) # Ok - return Repository(path) + return Repository(to_str(path)) @ffi.callback('int (*credentials)(git_cred **cred, const char *url,' @@ -123,7 +123,7 @@ 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) + ccred = get_credentials(d['credentials_cb'], url, username_from_url, allowed) cred_out[0] = ccred[0] except Exception as e: d['exception'] = e @@ -131,10 +131,39 @@ def _credentials_cb(cred_out, url, username_from_url, allowed, data): return 0 +@ffi.callback('int (*git_repository_create_cb)(git_repository **out,' + 'const char *path, int bare, void *payload)') +def _repository_create_cb(repo_out, path, bare, data): + d = ffi.from_handle(data) + try: + repository = d['repository_cb'](ffi.string(path), bare != 0) + # we no longer own the C object + repository._disown() + repo_out[0] = repository._repo + except Exception as e: + d['exception'] = e + return C.GIT_EUSER + + return 0 + +@ffi.callback('int (*git_remote_create_cb)(git_remote **out, git_repository *repo,' + 'const char *name, const char *url, void *payload)') +def _remote_create_cb(remote_out, repo, name, url, data): + d = ffi.from_handle(data) + try: + remote = d['remote_cb'](Repository._from_c(repo, False), ffi.string(name), ffi.string(url)) + remote_out[0] = remote._remote + # we no longer own the C object + remote._remote = ffi.NULL + except Exception as 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): + url, path, bare=False, repository=None, remote=None, 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. @@ -145,7 +174,9 @@ def clone_repository( :param bool bare: Whether the local repository should be bare - :param str remote_name: Name to give the remote at *url*. + :param callable remote: Callback for the remote to use. + + :param callable repository: Callback for the repository to use. :param str checkout_branch: Branch to checkout after the clone. The default is to use the remote's default branch. @@ -155,6 +186,13 @@ def clone_repository( :rtype: Repository + The repository callback has `(path, bare) -> Repository` as a + signature. The Repository it returns will be used instead of + creating a new one. + + The remote callback has `(Repository, name, url) -> Remote` as a + signature. The Remote it returns will be used instead of the default + one. """ opts = ffi.new('git_clone_options *') @@ -164,7 +202,9 @@ def clone_repository( # Data, let's use a dict as we don't really want much more d = {} - d['callback'] = credentials + d['credentials_cb'] = credentials + d['repository_cb'] = repository + d['remote_cb'] = remote d_handle = ffi.new_handle(d) # Perform the initialization with the version we compiled @@ -176,48 +216,26 @@ def clone_repository( checkout_branch_ref = ffi.new('char []', to_bytes(branch)) opts.checkout_branch = checkout_branch_ref - remote_name_ref = ffi.new('char []', to_bytes(remote_name)) - opts.remote_name = remote_name_ref + if repository: + opts.repository_cb = _repository_create_cb + opts.repository_cb_payload = d_handle + + if remote: + opts.remote_cb = _remote_create_cb + opts.remote_cb_payload = d_handle - opts.ignore_cert_errors = ignore_cert_errors opts.bare = bare if credentials: opts.remote_callbacks.credentials = _credentials_cb opts.remote_callbacks.payload = d_handle err = C.git_clone(crepo, to_bytes(url), to_bytes(path), opts) - C.git_repository_free(crepo[0]) if 'exception' in d: raise d['exception'] check_error(err) - 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_bytes(branch), ffi.NULL) - - if remote._stored_exception: - raise remote._stored_exception - - check_error(err) + return Repository._from_c(crepo[0], owned=True) settings = Settings() diff --git a/pygit2/decl.h b/pygit2/decl.h index 5c4d9a6..7af2bd6 100644 --- a/pygit2/decl.h +++ b/pygit2/decl.h @@ -106,9 +106,38 @@ typedef enum { GIT_CREDTYPE_SSH_KEY, GIT_CREDTYPE_SSH_CUSTOM, GIT_CREDTYPE_DEFAULT, + GIT_CREDTYPE_SSH_INTERACTIVE, + GIT_CREDTYPE_USERNAME, ... } git_credtype_t; +typedef enum git_cert_t { + GIT_CERT_X509, + GIT_CERT_HOSTKEY_LIBSSH2, +} git_cert_t; + +typedef enum { + GIT_CERT_SSH_MD5 = 1, + GIT_CERT_SSH_SHA1 = 2, +} git_cert_ssh_t; + +typedef struct { + git_cert_t cert_type; + git_cert_ssh_t type; + unsigned char hash_md5[16]; + unsigned char hash_sha1[20]; +} git_cert_hostkey; + +typedef struct { + git_cert_t cert_type; + void *data; + size_t len; +} git_cert_x509; + +typedef struct { + git_cert_t cert_type; +} git_cert; + typedef int (*git_transport_message_cb)(const char *str, int len, void *data); typedef int (*git_cred_acquire_cb)( git_cred **cred, @@ -117,40 +146,53 @@ typedef int (*git_cred_acquire_cb)( unsigned int allowed_types, void *payload); typedef int (*git_transfer_progress_cb)(const git_transfer_progress *stats, void *payload); +typedef int (*git_transport_certificate_check_cb)(git_cert *cert, int valid, const char *host, void *payload); + +typedef int (*git_packbuilder_progress)( + int stage, + unsigned int current, + unsigned int total, + void *payload); +typedef int (*git_push_transfer_progress)( + unsigned int current, + unsigned int total, + size_t bytes, + void* payload); struct git_remote_callbacks { unsigned int version; git_transport_message_cb sideband_progress; int (*completion)(git_remote_completion_type type, void *data); git_cred_acquire_cb credentials; + git_transport_certificate_check_cb certificate_check; git_transfer_progress_cb transfer_progress; int (*update_tips)(const char *refname, const git_oid *a, const git_oid *b, void *data); + git_packbuilder_progress pack_progress; + git_push_transfer_progress push_transfer_progress; + int (*push_update_reference)(const char *refname, const char *status, void *data); void *payload; }; typedef struct git_remote_callbacks 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_lookup(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_delete(git_remote *remote); +int git_remote_delete(git_repository *repo, const char *name); int git_repository_state_cleanup(git_repository *repo); const char * git_remote_name(const git_remote *remote); -int git_remote_rename( - git_strarray *problems, - git_remote *remote, - const char *new_name); +int git_remote_rename(git_strarray *problems, git_repository *repo, const char *name, 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); int git_remote_set_pushurl(git_remote *remote, const char* url); -int git_remote_fetch(git_remote *remote, const git_signature *signature, const char *reflog_message); +int git_remote_fetch(git_remote *remote, const git_strarray *refspecs, const git_signature *signature, const char *reflog_message); 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); @@ -170,7 +212,6 @@ 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, @@ -261,14 +302,12 @@ typedef int (*git_diff_notify_cb)( typedef struct { unsigned int version; uint32_t flags; - git_submodule_ignore_t ignore_submodules; git_strarray pathspec; git_diff_notify_cb notify_cb; void *notify_payload; - - uint16_t context_lines; - uint16_t interhunk_lines; + uint32_t context_lines; + uint32_t interhunk_lines; uint16_t id_abbrev; git_off_t max_size; const char *old_prefix; @@ -339,13 +378,6 @@ 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; - int git_checkout_init_options(git_checkout_options *opts, unsigned int version); int git_checkout_tree(git_repository *repo, const git_object *treeish, const git_checkout_options *opts); int git_checkout_head(git_repository *repo, const git_checkout_options *opts); @@ -355,18 +387,38 @@ int git_checkout_index(git_repository *repo, git_index *index, const git_checkou * git_clone */ +typedef int (*git_remote_create_cb)( + git_remote **out, + git_repository *repo, + const char *name, + const char *url, + void *payload); + +typedef int (*git_repository_create_cb)( + git_repository **out, + const char *path, + int bare, + void *payload); + +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; - git_checkout_options checkout_opts; git_remote_callbacks remote_callbacks; - int bare; - int ignore_cert_errors; git_clone_local_t local; - const char *remote_name; const char* checkout_branch; git_signature *signature; + git_repository_create_cb repository_cb; + void *repository_cb_payload; + git_remote_create_cb remote_cb; + void *remote_cb_payload; } git_clone_options; #define GIT_CLONE_OPTIONS_VERSION ... @@ -377,13 +429,6 @@ 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_options *co_opts, - const char *branch, - const git_signature *signature); - /* * git_config */ diff --git a/pygit2/remote.py b/pygit2/remote.py index 9a58213..1385e48 100644 --- a/pygit2/remote.py +++ b/pygit2/remote.py @@ -137,25 +137,6 @@ class Remote(object): return maybe_string(C.git_remote_name(self._remote)) - 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") - - problems = ffi.new('git_strarray *') - err = C.git_remote_rename(problems, self._remote, to_bytes(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""" @@ -178,14 +159,6 @@ class Remote(object): err = C.git_remote_set_pushurl(self._remote, to_bytes(value)) check_error(err) - def delete(self): - """Remove this remote - - All remote-tracking branches and configuration settings for the remote will be removed. - """ - err = C.git_remote_delete(self._remote) - check_error(err) - def save(self): """save() @@ -231,7 +204,7 @@ class Remote(object): self._stored_exception = None try: - err = C.git_remote_fetch(self._remote, ptr, to_bytes(message)) + err = C.git_remote_fetch(self._remote, ffi.NULL, ptr, to_bytes(message)) if self._stored_exception: raise self._stored_exception @@ -361,9 +334,6 @@ class Remote(object): 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) @@ -529,7 +499,7 @@ class RemoteCollection(object): cremote = ffi.new('git_remote **') for i in range(names.count): - err = C.git_remote_load(cremote, self._repo._repo, names.strings[i]) + err = C.git_remote_lookup(cremote, self._repo._repo, names.strings[i]) check_error(err) yield Remote(self._repo, cremote[0]) @@ -541,7 +511,7 @@ class RemoteCollection(object): return list(self)[name] cremote = ffi.new('git_remote **') - err = C.git_remote_load(cremote, self._repo._repo, to_bytes(name)) + err = C.git_remote_lookup(cremote, self._repo._repo, to_bytes(name)) check_error(err) return Remote(self._repo, cremote[0]) @@ -558,3 +528,36 @@ class RemoteCollection(object): check_error(err) return Remote(self._repo, cremote[0]) + + def rename(self, name, new_name): + """rename(name, new_name) -> [str] + + Rename a remote in the configuration. The refspecs in strandard + format will be renamed. + + 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("Current remote name must be a non-empty string") + + if not new_name: + raise ValueError("New remote name must be a non-empty string") + + problems = ffi.new('git_strarray *') + err = C.git_remote_rename(problems, self._repo._repo, to_bytes(name), to_bytes(new_name)) + check_error(err) + + ret = strarray_to_strings(problems) + C.git_strarray_free(problems) + + return ret + + def delete(self, name): + """Remove a remote from the configuration + + All remote-tracking branches and configuration settings for the remote will be removed. + """ + err = C.git_remote_delete(self._repo._repo, to_bytes(name)) + check_error(err) diff --git a/pygit2/repository.py b/pygit2/repository.py index 7a3f8a6..3dd14f1 100644 --- a/pygit2/repository.py +++ b/pygit2/repository.py @@ -57,7 +57,18 @@ class Repository(_Repository): def __init__(self, *args, **kwargs): super(Repository, self).__init__(*args, **kwargs) + self._common_init() + @classmethod + def _from_c(cls, ptr, owned): + cptr = ffi.new('git_repository **') + cptr[0] = ptr + repo = cls.__new__(cls) + super(cls, repo)._from_c(bytes(ffi.buffer(cptr)[:]), owned) + repo._common_init() + return repo + + def _common_init(self): self.remotes = RemoteCollection(self) # Get the pointer as the contents of a buffer and store it for diff --git a/src/diff.c b/src/diff.c index d40c3e6..1874c50 100644 --- a/src/diff.c +++ b/src/diff.c @@ -296,14 +296,14 @@ Diff_patch__get__(Diff *self) git_patch* patch; git_buf buf = {NULL}; int err = GIT_ERROR; - size_t i, len, num; + size_t i, num; PyObject *py_patch = NULL; num = git_diff_num_deltas(self->diff); if (num == 0) Py_RETURN_NONE; - for (i = 0, len = 1; i < num ; ++i) { + for (i = 0; i < num ; ++i) { err = git_patch_from_diff(&patch, self->diff, i); if (err < 0) goto cleanup; diff --git a/src/pygit2.c b/src/pygit2.c index 2fec0c1..cf2decd 100644 --- a/src/pygit2.c +++ b/src/pygit2.c @@ -210,7 +210,6 @@ moduleinit(PyObject* m) ADD_CONSTANT_INT(m, GIT_OBJ_BLOB) ADD_CONSTANT_INT(m, GIT_OBJ_TAG) /* Valid modes for index and tree entries. */ - ADD_CONSTANT_INT(m, GIT_FILEMODE_NEW) ADD_CONSTANT_INT(m, GIT_FILEMODE_TREE) ADD_CONSTANT_INT(m, GIT_FILEMODE_BLOB) ADD_CONSTANT_INT(m, GIT_FILEMODE_BLOB_EXECUTABLE) @@ -347,7 +346,7 @@ moduleinit(PyObject* m) ADD_CONSTANT_INT(m, GIT_MERGE_ANALYSIS_UNBORN) /* Global initialization of libgit2 */ - git_threads_init(); + git_libgit2_init(); return m; } diff --git a/src/repository.c b/src/repository.c index 1920438..9eabbb9 100644 --- a/src/repository.c +++ b/src/repository.c @@ -55,6 +55,9 @@ extern PyTypeObject ReferenceType; extern PyTypeObject NoteType; extern PyTypeObject NoteIterType; +/* forward-declaration for Repsository._from_c() */ +PyTypeObject RepositoryType; + git_otype int_to_loose_object_type(int type_id) { @@ -88,19 +91,62 @@ Repository_init(Repository *self, PyObject *args, PyObject *kwds) return -1; } + self->owned = 1; self->config = NULL; self->index = NULL; return 0; } +PyDoc_STRVAR(Repository__from_c__doc__, "Init a Repository from a pointer. For internal use only."); +PyObject * +Repository__from_c(Repository *py_repo, PyObject *args) +{ + PyObject *py_pointer, *py_free; + char *buffer; + Py_ssize_t len; + int err; + + py_repo->repo = NULL; + py_repo->config = NULL; + py_repo->index = NULL; + + if (!PyArg_ParseTuple(args, "OO!", &py_pointer, &PyBool_Type, &py_free)) + return NULL; + + err = PyBytes_AsStringAndSize(py_pointer, &buffer, &len); + if (err < 0) + return NULL; + + if (len != sizeof(git_repository *)) { + PyErr_SetString(PyExc_TypeError, "invalid pointer length"); + return NULL; + } + + py_repo->repo = *((git_repository **) buffer); + py_repo->owned = py_free == Py_True; + + Py_RETURN_NONE; +} + +PyDoc_STRVAR(Repository__disown__doc__, "Mark the object as not-owned by us. For internal use only."); +PyObject * +Repository__disown(Repository *py_repo) +{ + py_repo->owned = 0; + Py_RETURN_NONE; +} + void Repository_dealloc(Repository *self) { PyObject_GC_UnTrack(self); Py_CLEAR(self->index); Py_CLEAR(self->config); - git_repository_free(self->repo); + + if (self->owned) + git_repository_free(self->repo); + Py_TYPE(self)->tp_free(self); } @@ -526,7 +572,7 @@ Repository_merge_analysis(Repository *self, PyObject *py_id) int err; size_t len; git_oid id; - git_merge_head *merge_head; + git_annotated_commit *commit; git_merge_analysis_t analysis; git_merge_preference_t preference; @@ -534,12 +580,12 @@ Repository_merge_analysis(Repository *self, PyObject *py_id) if (len == 0) return NULL; - err = git_merge_head_from_id(&merge_head, self->repo, &id); + err = git_annotated_commit_lookup(&commit, self->repo, &id); if (err < 0) return Error_set(err); - err = git_merge_analysis(&analysis, &preference, self->repo, (const git_merge_head **) &merge_head, 1); - git_merge_head_free(merge_head); + err = git_merge_analysis(&analysis, &preference, self->repo, (const git_annotated_commit **) &commit, 1); + git_annotated_commit_free(commit); if (err < 0) return Error_set(err); @@ -561,7 +607,7 @@ PyDoc_STRVAR(Repository_merge__doc__, PyObject * Repository_merge(Repository *self, PyObject *py_oid) { - git_merge_head *oid_merge_head; + git_annotated_commit *commit; git_oid oid; int err; size_t len; @@ -572,16 +618,16 @@ Repository_merge(Repository *self, PyObject *py_oid) if (len == 0) return NULL; - err = git_merge_head_from_id(&oid_merge_head, self->repo, &oid); + err = git_annotated_commit_lookup(&commit, self->repo, &oid); if (err < 0) return Error_set(err); checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE; err = git_merge(self->repo, - (const git_merge_head **)&oid_merge_head, 1, + (const git_annotated_commit **)&commit, 1, &merge_opts, &checkout_opts); - git_merge_head_free(oid_merge_head); + git_annotated_commit_free(commit); if (err < 0) return Error_set(err); @@ -1225,7 +1271,7 @@ Repository_TreeBuilder(Repository *self, PyObject *args) } } - err = git_treebuilder_create(&bld, tree); + err = git_treebuilder_new(&bld, self->repo, tree); if (must_free != NULL) git_tree_free(must_free); @@ -1318,8 +1364,8 @@ Repository_create_note(Repository *self, PyObject* args) if (err < 0) return Error_set(err); - err = git_note_create(¬e_id, self->repo, py_author->signature, - py_committer->signature, ref, + err = git_note_create(¬e_id, self->repo, ref, py_author->signature, + py_committer->signature, &annotated_id, message, force); if (err < 0) return Error_set(err); @@ -1380,7 +1426,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, NULL, NULL); + err = err < 0 ? err : git_reset(self->repo, target, reset_type, NULL, NULL, NULL); git_object_free(target); if (err < 0) return Error_set_oid(err, &oid, len); @@ -1415,6 +1461,8 @@ PyMethodDef Repository_methods[] = { METHOD(Repository, listall_branches, METH_VARARGS), METHOD(Repository, create_branch, METH_VARARGS), METHOD(Repository, reset, METH_VARARGS), + METHOD(Repository, _from_c, METH_VARARGS), + METHOD(Repository, _disown, METH_NOARGS), {NULL} }; diff --git a/src/treebuilder.c b/src/treebuilder.c index 5957040..dbec825 100644 --- a/src/treebuilder.c +++ b/src/treebuilder.c @@ -88,7 +88,7 @@ TreeBuilder_write(TreeBuilder *self) int err; git_oid oid; - err = git_treebuilder_write(&oid, self->repo->repo, self->bld); + err = git_treebuilder_write(&oid, self->bld); if (err < 0) return Error_set(err); diff --git a/src/types.h b/src/types.h index 78747cf..3b797b6 100644 --- a/src/types.h +++ b/src/types.h @@ -47,6 +47,7 @@ typedef struct { git_repository *repo; PyObject *index; /* It will be None for a bare repository */ PyObject *config; /* It will be None for a bare repository */ + int owned; /* _from_c() sometimes means we don't own the C pointer */ } Repository; diff --git a/test/test_remote.py b/test/test_remote.py index ee28743..be50b77 100644 --- a/test/test_remote.py +++ b/test/test_remote.py @@ -71,19 +71,19 @@ class RepositoryTest(utils.RepoTestCase): remote = self.repo.remotes[1] self.assertEqual(name, remote.name) - remote.delete() + self.repo.remotes.delete(remote.name) self.assertEqual(1, len(self.repo.remotes)) def test_remote_rename(self): remote = self.repo.remotes[0] self.assertEqual(REMOTE_NAME, remote.name) - problems = remote.rename('new') + problems = self.repo.remotes.rename(remote.name, "new") self.assertEqual([], problems) - self.assertEqual('new', remote.name) + self.assertNotEqual('new', remote.name) - self.assertRaises(ValueError, remote.rename, '') - self.assertRaises(ValueError, remote.rename, None) + self.assertRaises(ValueError, self.repo.remotes.rename, '', '') + self.assertRaises(ValueError, self.repo.remotes.rename, None, None) def test_remote_set_url(self): @@ -183,13 +183,9 @@ class RepositoryTest(utils.RepoTestCase): def test_remote_save(self): remote = self.repo.remotes[0] - - remote.rename('new-name') remote.url = 'http://example.com/test.git' - remote.save() - self.assertEqual('new-name', self.repo.remotes[0].name) self.assertEqual('http://example.com/test.git', self.repo.remotes[0].url) diff --git a/test/test_repository.py b/test/test_repository.py index 72a5e64..7640f39 100644 --- a/test/test_repository.py +++ b/test/test_repository.py @@ -41,7 +41,7 @@ import sys # Import from pygit2 from pygit2 import GIT_OBJ_ANY, GIT_OBJ_BLOB, GIT_OBJ_COMMIT -from pygit2 import init_repository, clone_repository, clone_into, discover_repository +from pygit2 import init_repository, clone_repository, discover_repository from pygit2 import Oid, Reference, hashfile import pygit2 from . import utils @@ -440,19 +440,22 @@ class CloneRepositoryTest(utils.NoRepoTestCase): self.assertFalse(repo.is_empty) self.assertTrue(repo.is_bare) - def test_clone_remote_name(self): - repo_path = "./test/data/testrepo.git/" - repo = clone_repository( - repo_path, self._temp_dir, remote_name="custom_remote") - self.assertFalse(repo.is_empty) - self.assertEqual(repo.remotes[0].name, "custom_remote") + def test_clone_repository_and_remote_callbacks(self): + src_repo_relpath = "./test/data/testrepo.git/" + repo_path = os.path.join(self._temp_dir, "clone-into") + url = 'file://' + os.path.realpath(src_repo_relpath) - 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 create_repository(path, bare): + return init_repository(path, bare) + + # here we override the name + def create_remote(repo, name, url): + return repo.remotes.create("custom_remote", url) + + repo = clone_repository(url, repo_path, repository=create_repository, remote=create_remote) + self.assertFalse(repo.is_empty) + self.assertTrue('refs/remotes/custom_remote/master' in repo.listall_references()) + self.assertIsNotNone(repo.remotes["custom_remote"]) def test_clone_with_credentials(self): credentials = pygit2.UserPass("libgit2", "libgit2") From f68b266e60b06a918d05c2c3da28f3d54df56037 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Thu, 11 Dec 2014 15:36:14 +0100 Subject: [PATCH 13/26] Remote: generalize push() Move to use git_remote_push() instead of doing the steps ourselves. We also change to accept a list of refspecs instead of just the one refspec for the push method. As part of this, we no longer error out if the server rejected any updates, as this is a different concern from whether the push itself failed or not. We do still error out if we attempt to push non-ff updates. --- pygit2/decl.h | 22 ++++-------- pygit2/remote.py | 85 +++++++++++++++++++++++---------------------- test/test_remote.py | 7 ++-- 3 files changed, 54 insertions(+), 60 deletions(-) diff --git a/pygit2/decl.h b/pygit2/decl.h index 7af2bd6..b1fd28f 100644 --- a/pygit2/decl.h +++ b/pygit2/decl.h @@ -1,7 +1,6 @@ typedef ... git_repository; typedef ... git_remote; typedef ... git_refspec; -typedef ... git_push; typedef ... git_cred; typedef ... git_object; typedef ... git_tree; @@ -175,6 +174,11 @@ struct git_remote_callbacks { typedef struct git_remote_callbacks git_remote_callbacks; +typedef struct { + unsigned int version; + unsigned int pb_parallelism; +} git_push_options; + int git_remote_list(git_strarray *out, git_repository *repo); int git_remote_lookup(git_remote **out, git_repository *repo, const char *name); int git_remote_create( @@ -193,6 +197,7 @@ 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_strarray *refspecs, const git_signature *signature, const char *reflog_message); +int git_remote_push(git_remote *remote, git_strarray *refspecs, const git_push_options *opts, const git_signature *signature, const char *reflog_message); 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); @@ -209,21 +214,6 @@ 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_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, - const git_signature *signature, - const char *reflog_message); -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); diff --git a/pygit2/remote.py b/pygit2/remote.py index 1385e48..af5a8b4 100644 --- a/pygit2/remote.py +++ b/pygit2/remote.py @@ -121,6 +121,16 @@ class Remote(object): :param Oid new: the reference's new value """ + def push_update_reference(self, refname, message): + """Push update reference callback + + Override with your own function to report the remote's + acceptace or rejection of reference updates. + + :param str refname: the name of the reference (on the remote) + :param str messsage: rejection message from the remote. If None, the update was accepted. + """ + def __init__(self, repo, ptr): """The constructor is for internal use only""" @@ -279,28 +289,31 @@ class Remote(object): err = C.git_remote_add_push(self._remote, to_bytes(spec)) check_error(err) - @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, specs, signature=None, message=None): + """push(specs, signature, message) - def push(self, spec, signature=None, message=None): - """push(refspec, signature, message) - - Push the given refspec to the remote. Raises ``GitError`` on error. + Push the given refspec to the remote. Raises ``GitError`` on + protocol error or unpack failure. May require libssh2. - :param str spec: push refspec to use + :param [str] specs: push refspecs to use :param Signature signature: signature to use when updating the tips :param str message: message to use when updating the tips + """ # Get the default callbacks first defaultcallbacks = ffi.new('git_remote_callbacks *') err = C.git_remote_init_callbacks(defaultcallbacks, 1) check_error(err) + refspecs, refspecs_refs = strings_to_strarray(specs) + if signature: + sig_cptr = ffi.new('git_signature **') + ffi.buffer(sig_cptr)[:] = signature._pointer[:] + sig_ptr = sig_cptr[0] + else: + sig_ptr = ffi.NULL + # Build custom callback structure callbacks = ffi.new('git_remote_callbacks *') callbacks.version = 1 @@ -308,50 +321,23 @@ class Remote(object): callbacks.transfer_progress = self._transfer_progress_cb callbacks.update_tips = self._update_tips_cb callbacks.credentials = self._credentials_cb + callbacks.push_update_reference = self._push_update_reference_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) - try: + err = C.git_remote_set_callbacks(self._remote, callbacks) check_error(err) except: self._self_handle = None raise - - 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_bytes(spec)) + err = C.git_remote_push(self._remote, refspecs, ffi.NULL, sig_ptr, to_bytes(message)) check_error(err) - - err = C.git_push_finish(push) - check_error(err) - - 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) - - if signature: - ptr = signature._pointer[:] - else: - ptr = ffi.NULL - - err = C.git_push_update_tips(push, ptr, to_bytes(message)) - check_error(err) - finally: self._self_handle = None - 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 @@ -408,6 +394,23 @@ class Remote(object): return 0 + @ffi.callback("int (*push_update_reference)(const char *ref, const char *msg, void *data)") + def _push_update_reference_cb(ref, msg, data): + self = ffi.from_handle(data) + + if not hasattr(self, 'push_update_reference') or not self.push_update_reference: + return 0 + + try: + refname = ffi.string(ref) + message = maybe_string(msg) + self.push_update_reference(refname, message) + except Exception as 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)') diff --git a/test/test_remote.py b/test/test_remote.py index be50b77..f825cd6 100644 --- a/test/test_remote.py +++ b/test/test_remote.py @@ -274,11 +274,11 @@ class PushTestCase(unittest.TestCase): 'refs/heads/master', tip.author, tip.author, 'empty commit', tip.tree.id, [tip.id] ) - self.remote.push('refs/heads/master') + self.remote.push(['refs/heads/master']) 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') + self.remote.push(['refs/heads/master']) origin_tip = self.origin[self.origin.head.target].id clone_tip = self.clone[self.clone.head.target].id self.assertEqual(origin_tip, clone_tip) @@ -294,7 +294,8 @@ class PushTestCase(unittest.TestCase): 'refs/heads/master', tip.author, tip.author, 'other commit', tip.tree.id, [tip.id] ) - self.assertRaises(pygit2.GitError, self.remote.push, 'refs/heads/master') + + self.assertRaises(pygit2.GitError, self.remote.push, ['refs/heads/master']) if __name__ == '__main__': unittest.main() From 66d55aee7e486e1655c218d7db7a9079b8ce4eb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Mon, 15 Dec 2014 10:18:23 +0100 Subject: [PATCH 14/26] Add certificate callback for clone We do not pass anything as the certificate, as there doesn't seem to be anything sensible for checking it. --- pygit2/__init__.py | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/pygit2/__init__.py b/pygit2/__init__.py index 800fb00..5e35fe4 100644 --- a/pygit2/__init__.py +++ b/pygit2/__init__.py @@ -161,9 +161,24 @@ def _remote_create_cb(remote_out, repo, name, url, data): return 0 +@ffi.callback('int (*git_transport_certificate_check_cb)' + '(git_cert *cert, int valid, const char *host, void *payload)') +def _certificate_cb(cert_i, valid, host, data): + d = ffi.from_handle(data) + try: + # python's parting is deep in the libraries and assumes an OpenSSL-owned cert + val = d['certificate_cb'](None, bool(valid), ffi.string(host)) + if not val: + return C.GIT_ECERTIFICATE + except Exception as e: + d['exception'] = e + return C.GIT_EUSER + + return 0 def clone_repository( - url, path, bare=False, repository=None, remote=None, checkout_branch=None, credentials=None): + url, path, bare=False, repository=None, remote=None, + checkout_branch=None, credentials=None, certificate=None): """Clones a new Git repository from *url* in the given *path*. Returns a Repository class pointing to the newly cloned repository. @@ -184,6 +199,9 @@ def clone_repository( :param callable credentials: authentication to use if the remote requires it + :param callable certificate: callback to verify the host's + certificate or fingerprint. + :rtype: Repository The repository callback has `(path, bare) -> Repository` as a @@ -193,6 +211,10 @@ def clone_repository( The remote callback has `(Repository, name, url) -> Remote` as a signature. The Remote it returns will be used instead of the default one. + + The certificate callback has `(cert, valid, hostname) -> bool` as + a signature. Return True to accept the connection, False to abort. + """ opts = ffi.new('git_clone_options *') @@ -205,6 +227,7 @@ def clone_repository( d['credentials_cb'] = credentials d['repository_cb'] = repository d['remote_cb'] = remote + d['certificate_cb'] = certificate d_handle = ffi.new_handle(d) # Perform the initialization with the version we compiled @@ -224,11 +247,16 @@ def clone_repository( opts.remote_cb = _remote_create_cb opts.remote_cb_payload = d_handle + opts.bare = bare if credentials: opts.remote_callbacks.credentials = _credentials_cb opts.remote_callbacks.payload = d_handle + if certificate: + opts.remote_callbacks.certificate_check = _certificate_cb + opts.remote_callbacks.payload = d_handle + err = C.git_clone(crepo, to_bytes(url), to_bytes(path), opts) if 'exception' in d: From 78695aa93a0207fbb1a9bc848824a436a66a1619 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Wed, 7 Jan 2015 12:54:36 +0000 Subject: [PATCH 15/26] Change required version to 0.22 --- .travis.sh | 2 +- docs/install.rst | 18 +++++++++--------- src/types.h | 4 ++-- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.travis.sh b/.travis.sh index 317d318..54bfd01 100755 --- a/.travis.sh +++ b/.travis.sh @@ -2,7 +2,7 @@ cd ~ -git clone --depth=1 -b v0.21.2 https://github.com/libgit2/libgit2.git +git clone --depth=1 -b maint/v0.22 https://github.com/libgit2/libgit2.git cd libgit2/ mkdir build && cd build diff --git a/docs/install.rst b/docs/install.rst index 621b103..bd13172 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -14,7 +14,7 @@ Requirements ============ - Python 2.7, 3.2+ or pypy (including the development headers) -- Libgit2 v0.21.1+ +- Libgit2 v0.22.x - cffi 0.8.1+ - Libssh2, optional, used for SSH network operations. @@ -34,11 +34,11 @@ while the last number |lq| *.micro* |rq| auto-increments independently. As illustration see this table of compatible releases: -+-----------+---------------------------------------+------------------------------+--------------+ -|**libgit2**|0.21.1, 0.21.2 |0.20.0 |0.19.0 | -+-----------+---------------------------------------+------------------------------+--------------+ -|**pygit2** |0.21.0, 0.21.1, 0.21.2, 0.21.3, 0.21.4 |0.20.0, 0.20.1, 0.20.2, 0.20.3|0.19.0, 0.19.1| -+-----------+---------------------------------------+------------------------------+--------------+ ++-----------+--------+----------------------------------------+-------------------------------+ +|**libgit2**| 0.22.0 | 0.21.1, 0.21.2 |0.20.0 | ++-----------+--------+----------------------------------------+-------------------------------+ +|**pygit2** | 0.22.0 | 0.21.0, 0.21.1, 0.21.2, 0.21.3, 0.21.4 | 0.20.0, 0.20.1, 0.20.2, 0.20.3| ++-----------+--------+----------------------------------------+-------------------------------+ .. warning:: @@ -55,9 +55,9 @@ directory, do: .. code-block:: sh - $ wget https://github.com/libgit2/libgit2/archive/v0.21.2.tar.gz - $ tar xzf v0.21.2.tar.gz - $ cd libgit2-0.21.2/ + $ wget https://github.com/libgit2/libgit2/archive/v0.22.0.tar.gz + $ tar xzf v0.22.0.tar.gz + $ cd libgit2-0.22.0/ $ cmake . $ make $ sudo make install diff --git a/src/types.h b/src/types.h index 3b797b6..e4f9e31 100644 --- a/src/types.h +++ b/src/types.h @@ -32,8 +32,8 @@ #include #include -#if !(LIBGIT2_VER_MAJOR == 0 && LIBGIT2_VER_MINOR == 21) -#error You need a compatible libgit2 version (v0.21.x) +#if !(LIBGIT2_VER_MAJOR == 0 && LIBGIT2_VER_MINOR == 22) +#error You need a compatible libgit2 version (v0.22.x) #endif /* From 5a06cd2688be5efdaad9aab32e417497b4d7eb86 Mon Sep 17 00:00:00 2001 From: Matthew Duggan Date: Fri, 9 Jan 2015 00:18:30 -0800 Subject: [PATCH 16/26] Make it explicit what to do when no passphrase is needed --- pygit2/credentials.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pygit2/credentials.py b/pygit2/credentials.py index 39d86ee..adefc6c 100644 --- a/pygit2/credentials.py +++ b/pygit2/credentials.py @@ -65,7 +65,8 @@ class Keypair(object): remote server :param str pubkey: the path to the user's public key file :param str privkey: the path to the user's private key file - :param str passphrase: the password used to decrypt the private key file + :param str passphrase: the password used to decrypt the private key file, + or empty string if no passphrase is required. """ From 52ac41a36279828e7d57be108b1c6d62e7705b10 Mon Sep 17 00:00:00 2001 From: Matthew Duggan Date: Fri, 9 Jan 2015 00:19:17 -0800 Subject: [PATCH 17/26] Make it explicit that Refspec objects are not for construction --- docs/remotes.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/remotes.rst b/docs/remotes.rst index 1dcbe5d..387e9ac 100644 --- a/docs/remotes.rst +++ b/docs/remotes.rst @@ -32,6 +32,10 @@ This class contains the data which is available to us during a fetch. The Refspec type =================== +Refspecs objects are not constructed directly, but returned by +:meth:`pygit2.Remote.get_refspec`. To create a new a refspec on a Remote, use +:meth:`pygit2.Remote.add_fetch` or :meth:`pygit2.Remote.add_push`. + .. autoclass:: pygit2.refspec.Refspec :members: From 34fb1c00eb857b117275a20a6d515fb43119cf17 Mon Sep 17 00:00:00 2001 From: Matthew Duggan Date: Mon, 12 Jan 2015 18:22:25 -0800 Subject: [PATCH 18/26] Make it explicit that respecs are added as strings. --- pygit2/remote.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pygit2/remote.py b/pygit2/remote.py index 9a58213..22b3652 100644 --- a/pygit2/remote.py +++ b/pygit2/remote.py @@ -293,7 +293,7 @@ class Remote(object): def add_fetch(self, spec): """add_fetch(refspec) - Add a fetch refspec to the remote""" + Add a fetch refspec (str) to the remote""" err = C.git_remote_add_fetch(self._remote, to_bytes(spec)) check_error(err) @@ -301,7 +301,7 @@ class Remote(object): def add_push(self, spec): """add_push(refspec) - Add a push refspec to the remote""" + Add a push refspec (str) to the remote""" err = C.git_remote_add_push(self._remote, to_bytes(spec)) check_error(err) From b2abfdec9e097f7929f1e1df50336b5c2fc521a8 Mon Sep 17 00:00:00 2001 From: Matthew Duggan Date: Mon, 12 Jan 2015 18:23:03 -0800 Subject: [PATCH 19/26] Note that Refspec constructor is internal. Fix typo. --- pygit2/refspec.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pygit2/refspec.py b/pygit2/refspec.py index 423b72b..e551710 100644 --- a/pygit2/refspec.py +++ b/pygit2/refspec.py @@ -35,6 +35,7 @@ from .utils import to_bytes class Refspec(object): + """The constructor is for internal use only""" def __init__(self, owner, ptr): self._owner = owner self._refspec = ptr @@ -95,7 +96,7 @@ class Refspec(object): return self._transform(ref, C.git_refspec_transform) def rtransform(self, ref): - """transform(str) -> str + """rtransform(str) -> str Transform a reference name according to this refspec from the lhs to the rhs""" From e15c0d828bfffe8cf5bb3a0afaede6ea1a95fa88 Mon Sep 17 00:00:00 2001 From: Peter Dave Hello Date: Wed, 14 Jan 2015 02:15:20 +0800 Subject: [PATCH 20/26] Use svg instead of png to get better image quality --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 9febfa5..cef1bfe 100644 --- a/README.rst +++ b/README.rst @@ -2,7 +2,7 @@ pygit2 - libgit2 bindings in Python ###################################################################### -.. image:: https://secure.travis-ci.org/libgit2/pygit2.png +.. image:: https://secure.travis-ci.org/libgit2/pygit2.svg :target: http://travis-ci.org/libgit2/pygit2 Pygit2 is a set of Python bindings to the libgit2 shared library, libgit2 From 81bde5d0e7314d9cba29587baca8ed15bb3c31ff Mon Sep 17 00:00:00 2001 From: Greg Fitzgerald Date: Wed, 14 Jan 2015 14:25:39 +0000 Subject: [PATCH 21/26] Fix compiler warnings --- src/diff.c | 4 ++-- src/types.h | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/diff.c b/src/diff.c index d40c3e6..1874c50 100644 --- a/src/diff.c +++ b/src/diff.c @@ -296,14 +296,14 @@ Diff_patch__get__(Diff *self) git_patch* patch; git_buf buf = {NULL}; int err = GIT_ERROR; - size_t i, len, num; + size_t i, num; PyObject *py_patch = NULL; num = git_diff_num_deltas(self->diff); if (num == 0) Py_RETURN_NONE; - for (i = 0, len = 1; i < num ; ++i) { + for (i = 0; i < num ; ++i) { err = git_patch_from_diff(&patch, self->diff, i); if (err < 0) goto cleanup; diff --git a/src/types.h b/src/types.h index 78747cf..937abdc 100644 --- a/src/types.h +++ b/src/types.h @@ -103,8 +103,8 @@ typedef struct { typedef struct { PyObject_HEAD PyObject* hunks; - const char * old_file_path; - const char * new_file_path; + char * old_file_path; + char * new_file_path; PyObject* old_id; PyObject* new_id; char status; From d0b00e3124a35d7b6adc73c0a36dfa3e50c9768f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Wed, 14 Jan 2015 20:47:47 +0100 Subject: [PATCH 22/26] Add support for libgit2 feature detection This lets us ask the library whether it supports threading, https and ssh. --- docs/features.rst | 8 ++++++++ docs/index.rst | 1 + pygit2/__init__.py | 5 +++++ pygit2/decl.h | 6 ++++++ 4 files changed, 20 insertions(+) create mode 100644 docs/features.rst diff --git a/docs/features.rst b/docs/features.rst new file mode 100644 index 0000000..2b26fde --- /dev/null +++ b/docs/features.rst @@ -0,0 +1,8 @@ +********************************************************************** +Feature detection +********************************************************************** + +.. py:data:: pygit2.features + + This variable contains a combination of `GIT_FEATURE_*` flags, + indicating which features a particular build of libgit2 supports. diff --git a/docs/index.rst b/docs/index.rst index fbdfd40..156f0ac 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -47,6 +47,7 @@ Usage guide: remotes blame settings + features Indices and tables diff --git a/pygit2/__init__.py b/pygit2/__init__.py index 995fe6c..3ddfdac 100644 --- a/pygit2/__init__.py +++ b/pygit2/__init__.py @@ -221,3 +221,8 @@ def clone_into(repo, remote, branch=None): check_error(err) settings = Settings() + +features = C.git_libgit2_features() +GIT_FEATURE_THREADS = C.GIT_FEATURE_THREADS +GIT_FEATURE_HTTPS = C.GIT_FEATURE_HTTPS +GIT_FEATURE_SSH = C.GIT_FEATURE_SSH diff --git a/pygit2/decl.h b/pygit2/decl.h index 5c4d9a6..55cf0cc 100644 --- a/pygit2/decl.h +++ b/pygit2/decl.h @@ -74,6 +74,12 @@ typedef struct git_signature { git_time when; } git_signature; +#define GIT_FEATURE_THREADS ... +#define GIT_FEATURE_HTTPS ... +#define GIT_FEATURE_SSH ... + +int git_libgit2_features(void); + const git_error * giterr_last(void); void git_strarray_free(git_strarray *array); From 9c9b925da8045b77d291d742db95b3b757b4e3b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Fri, 16 Jan 2015 10:24:06 +0100 Subject: [PATCH 23/26] Revert "Mentioning libssh2 in remote's pydoc" This reverts commit e807ad43d725da06bfc34187b387270baea74887. --- pygit2/remote.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pygit2/remote.py b/pygit2/remote.py index 22b3652..2d9b846 100644 --- a/pygit2/remote.py +++ b/pygit2/remote.py @@ -197,7 +197,7 @@ class Remote(object): def fetch(self, signature=None, message=None): """fetch(signature, message) -> TransferProgress - Perform a fetch against this remote. May require libssh2. + Perform a fetch against this remote. """ # Get the default callbacks first @@ -317,7 +317,6 @@ class Remote(object): """push(refspec, signature, message) Push the given refspec to the remote. Raises ``GitError`` on error. - May require libssh2. :param str spec: push refspec to use :param Signature signature: signature to use when updating the tips From beaaca7f63374969ce4986a5d0a3f88aeb7fb95f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Fri, 16 Jan 2015 12:39:16 +0100 Subject: [PATCH 24/26] Fix type of RefLogEntry.oid_old and RefLogEntry.oid_new This was left from PR #449 --- src/reference.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/reference.c b/src/reference.c index 571bb57..4d8e56e 100644 --- a/src/reference.c +++ b/src/reference.c @@ -439,8 +439,8 @@ RefLogEntry_dealloc(RefLogEntry *self) } PyMemberDef RefLogEntry_members[] = { - MEMBER(RefLogEntry, oid_new, T_STRING, "New oid."), - MEMBER(RefLogEntry, oid_old, T_STRING, "Old oid."), + MEMBER(RefLogEntry, oid_new, T_OBJECT, "New oid."), + MEMBER(RefLogEntry, oid_old, T_OBJECT, "Old oid."), MEMBER(RefLogEntry, message, T_STRING, "Message."), {NULL} }; From 9da91e554dd5be43506f53440ae6f2a5c56f4668 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Fri, 16 Jan 2015 13:52:15 +0100 Subject: [PATCH 25/26] Changelog for upcomming v0.22.0 release --- README.rst | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/README.rst b/README.rst index cef1bfe..b6905f7 100644 --- a/README.rst +++ b/README.rst @@ -25,6 +25,62 @@ How to install Changelog ============== +0.22.0 (2015-0X-XX) +------------------- + +New: + +- Update to libgit2 v0.22 + `#459 `_ + +- Add support for libgit2 feature detection + (new ``pygit2.features`` and ``pygit2.GIT_FEATURE_*``) + `#475 `_ + +- New ``Repository.remotes`` (``RemoteCollection``) + `#447 `_ + +API Changes: + +- Prototype of ``clone_repository`` changed, check documentation + +- Removed ``clone_into``, use ``clone_repository`` with callbacks instead + +- Use ``Repository.remotes.rename(name, new_name)`` instead fo + ``Remote.rename(new_name)`` + +- Use ``Repository.remotes.delete(name)`` instead fo ``Remote.delete()`` + +- Now ``Remote.push(...)`` takes a list of refspecs instead of just one. + +- Change ``Patch.old_id``, ``Patch.new_id``, ``Note.annotated_id``, + ``RefLogEntry.oid_old`` and ``RefLogEntry.oid_new`` to be ``Oid`` objects + instead of strings + `#449 `_ + +Other: + +- Fix ``init_repository`` when passing optional parameters ``workdir_path``, + ``description``, ``template_path``, ``initial_head`` or ``origin_url`` + `#466 `_ + `#471 `_ + +- Fix use-after-free when patch outlives diff + `#457 `_ + `#461 `_ + `#474 `_ + +- Documentation improvements + `#456 `_ + `#462 `_ + `#465 `_ + `#472 `_ + `#473 `_ + +- Make the GPL exception explicit in setup.py + `#450 `_ + + 0.21.4 (2014-11-04) ------------------- From 126308403b2678ed5753b9ad6fcfdfab20946bbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=2E=20David=20Ib=C3=A1=C3=B1ez?= Date: Fri, 16 Jan 2015 16:35:19 +0100 Subject: [PATCH 26/26] Get ready to release v0.22.0 --- README.rst | 10 +++++----- docs/conf.py | 4 ++-- docs/general.rst | 12 ++++++------ docs/install.rst | 12 ++++++------ pygit2/_utils.py | 2 +- 5 files changed, 20 insertions(+), 20 deletions(-) diff --git a/README.rst b/README.rst index b6905f7..6110310 100644 --- a/README.rst +++ b/README.rst @@ -25,7 +25,7 @@ How to install Changelog ============== -0.22.0 (2015-0X-XX) +0.22.0 (2015-01-16) ------------------- New: @@ -46,12 +46,12 @@ API Changes: - Removed ``clone_into``, use ``clone_repository`` with callbacks instead -- Use ``Repository.remotes.rename(name, new_name)`` instead fo +- Use ``Repository.remotes.rename(name, new_name)`` instead of ``Remote.rename(new_name)`` -- Use ``Repository.remotes.delete(name)`` instead fo ``Remote.delete()`` +- Use ``Repository.remotes.delete(name)`` instead of ``Remote.delete()`` -- Now ``Remote.push(...)`` takes a list of refspecs instead of just one. +- Now ``Remote.push(...)`` takes a list of refspecs instead of just one - Change ``Patch.old_id``, ``Patch.new_id``, ``Note.annotated_id``, ``RefLogEntry.oid_old`` and ``RefLogEntry.oid_new`` to be ``Oid`` objects @@ -502,7 +502,7 @@ Other: `#331 `_ Authors ============== -77 developers have contributed at least 1 commit to pygit2:: +83 developers have contributed at least 1 commit to pygit2:: J. David Ibáñez Sebastian Thiel András Veres-Szentkirályi Carlos Martín Nieto Fraser Tweedale Ash Berlin diff --git a/docs/conf.py b/docs/conf.py index f9f7fdc..0b3b646 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -50,9 +50,9 @@ copyright = u'2010-2014 The pygit2 contributors' # built documents. # # The short X.Y version. -version = '0.21' +version = '0.22' # The full version, including alpha/beta/rc tags. -release = '0.21.4' +release = '0.22.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 37bc32e..1518972 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.21.2``:: + ``0.22.0``:: >>> print LIBGIT2_VER_MAJOR 0 @@ -26,25 +26,25 @@ 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.21.2``:: + ``0.22.0``:: >>> print LIBGIT2_VER_MINOR - 21 + 22 .. py:data:: LIBGIT2_VER_REVISION Integer value of the revision version number. For example, for the version - ``0.21.2``:: + ``0.22.0``:: >>> print LIBGIT2_VER_REVISION - 1 + 0 .. py:data:: LIBGIT2_VERSION The libgit2 version number as a string:: >>> print LIBGIT2_VERSION - '0.21.2' + '0.22.0' Errors ====== diff --git a/docs/install.rst b/docs/install.rst index bd13172..88af0b4 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -139,9 +139,9 @@ Install libgit2 (see we define the installation prefix): .. code-block:: sh - $ wget https://github.com/libgit2/libgit2/archive/v0.21.2.tar.gz - $ tar xzf v0.21.2.tar.gz - $ cd libgit2-0.21.2/ + $ wget https://github.com/libgit2/libgit2/archive/v0.22.0.tar.gz + $ tar xzf v0.22.0.tar.gz + $ cd libgit2-0.22.0/ $ cmake . -DCMAKE_INSTALL_PREFIX=$LIBGIT2 $ make $ make install @@ -194,9 +194,9 @@ from a bash shell: .. code-block:: sh $ export LIBGIT2=C:/Dev/libgit2 - $ wget https://github.com/libgit2/libgit2/archive/v0.21.2.tar.gz - $ tar xzf v0.21.2.tar.gz - $ cd libgit2-0.21.2/ + $ wget https://github.com/libgit2/libgit2/archive/v0.22.0.tar.gz + $ tar xzf v0.22.0.tar.gz + $ cd libgit2-0.22.0/ $ cmake . -DSTDCALL=OFF -DCMAKE_INSTALL_PREFIX=$LIBGIT2 -G "Visual Studio 9 2008" $ cmake --build . --config release --target install $ ctest -v diff --git a/pygit2/_utils.py b/pygit2/_utils.py index 9ecb35b..9c13d9b 100644 --- a/pygit2/_utils.py +++ b/pygit2/_utils.py @@ -43,7 +43,7 @@ import sys # # The version number of pygit2 # -__version__ = '0.21.4' +__version__ = '0.22.0' #