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 1/4] 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 | 13 ++++- 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, 265 insertions(+), 141 deletions(-) diff --git a/pygit2/__init__.py b/pygit2/__init__.py index 995fe6cd0..800fb00a3 100644 --- a/pygit2/__init__.py +++ b/pygit2/__init__.py @@ -41,7 +41,7 @@ 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 5c4d9a68d..7af2bd67e 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 9a582132d..1385e481a 100644 --- a/pygit2/remote.py +++ b/pygit2/remote.py @@ -137,25 +137,6 @@ def name(self): 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 @@ def push_url(self, value): 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 @@ def fetch(self, signature=None, message=None): 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 @@ def push(self, spec, signature=None, message=None): 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 @@ def __iter__(self): 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 @@ def __getitem__(self, name): 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 @@ def create(self, name, url): 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 7a3f8a605..3dd14f1e4 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 d40c3e636..1874c5032 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 2fec0c145..cf2decd2c 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 19204389c..9eabbb98a 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 5957040ce..dbec82516 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 78747cf27..3b797b66f 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 ee28743e1..be50b7722 100644 --- a/test/test_remote.py +++ b/test/test_remote.py @@ -71,19 +71,19 @@ def test_remote_delete(self): 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 @@ def test_remote_collection(self): 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 72a5e64a9..7640f39a9 100644 --- a/test/test_repository.py +++ b/test/test_repository.py @@ -41,7 +41,7 @@ # 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 @@ def test_clone_bare_repository(self): 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 2/4] 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 7af2bd67e..b1fd28f82 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 1385e481a..af5a8b415 100644 --- a/pygit2/remote.py +++ b/pygit2/remote.py @@ -121,6 +121,16 @@ def update_tips(self, refname, old, new): :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 @@ def add_push(self, spec): 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, spec, signature=None, message=None): - """push(refspec, signature, message) + def push(self, specs, signature=None, message=None): + """push(specs, 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 @@ def push(self, spec, signature=None, message=None): 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)) - check_error(err) - - err = C.git_push_finish(push) + err = C.git_remote_push(self._remote, refspecs, ffi.NULL, sig_ptr, to_bytes(message)) 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 @@ def _update_tips_cb(refname, a, b, data): 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 be50b7722..f825cd617 100644 --- a/test/test_remote.py +++ b/test/test_remote.py @@ -274,11 +274,11 @@ def test_push_fast_forward_commits_to_remote_succeeds(self): '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 @@ def test_push_non_fast_forward_commits_to_remote_fails(self): '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 3/4] 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 800fb00a3..5e35fe4ee 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 4/4] 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 317d31853..54bfd014c 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 621b10351..bd13172bf 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 3b797b66f..e4f9e31d5 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 /*