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/pygit2/__init__.py b/pygit2/__init__.py index 3ddfdac..601a7b9 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,54 @@ 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 + +@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, 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, certificate=None): """Clones a new Git repository from *url* in the given *path*. Returns a Repository class pointing to the newly cloned repository. @@ -145,7 +189,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. @@ -153,8 +199,22 @@ 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 + 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. + + 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 *') @@ -164,7 +224,10 @@ 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['certificate_cb'] = certificate d_handle = ffi.new_handle(d) # Perform the initialization with the version we compiled @@ -176,49 +239,32 @@ 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 + 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) - 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 55cf0cc..e89f60f 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; @@ -112,9 +111,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, @@ -123,40 +151,59 @@ 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; +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_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); +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); @@ -173,22 +220,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_unpack_ok(git_push *push); - -int git_push_status_foreach( - git_push *push, - int (*cb)(const char *ref, const char *msg, void *data), - void *data); - -int git_push_update_tips( - git_push *push, - 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); @@ -267,14 +298,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; @@ -345,13 +374,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); @@ -361,18 +383,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 ... @@ -383,13 +425,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 2d9b846..c4a195f 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""" @@ -137,25 +147,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 +169,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 +214,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 @@ -306,27 +289,30 @@ 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 + protocol error or unpack failure. - Push the given refspec to the remote. Raises ``GitError`` on error. - - :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 @@ -334,53 +320,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) - - if not C.git_push_unpack_ok(push): - raise GitError("remote failed to unpack objects") - - err = C.git_push_status_foreach(push, self._push_cb, - ffi.new_handle(self)) - check_error(err) - - if hasattr(self, '_bad_message'): - raise GitError(self._bad_message) - - 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 @@ -437,6 +393,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)') @@ -528,7 +501,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]) @@ -540,7 +513,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]) @@ -557,3 +530,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/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 937abdc..4b9c2a1 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 /* @@ -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..f825cd6 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) @@ -278,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) @@ -298,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() 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")