From 072b0382104f432721dbe9e31395d224f0b751b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sat, 12 Apr 2014 19:10:50 +0200 Subject: [PATCH] Implement clone via CFFI as well This lets us get rid of the last piece of C for anything related to remotes and credentials. --- pygit2/__init__.py | 55 ++++++++++++++++++++++++++-- pygit2/decl.h | 64 +++++++++++++++++++++++++++++++++ pygit2/remote.py | 61 +++++++++++++++++-------------- src/pygit2.c | 65 --------------------------------- src/utils.c | 89 ---------------------------------------------- src/utils.h | 2 -- 6 files changed, 150 insertions(+), 186 deletions(-) diff --git a/pygit2/__init__.py b/pygit2/__init__.py index 396af19..df3a898 100644 --- a/pygit2/__init__.py +++ b/pygit2/__init__.py @@ -37,7 +37,9 @@ from .repository import Repository from .version import __version__ from .settings import Settings from .credentials import * -from .remote import Remote +from .remote import Remote, get_credentials +from .errors import check_error +from .ffi import ffi, C, to_str def init_repository(path, bare=False): """ @@ -50,6 +52,19 @@ def init_repository(path, bare=False): return Repository(path) +@ffi.callback('int (*credentials)(git_cred **cred, const char *url, const char *username_from_url, unsigned int allowed_types, void *data)') +def _credentials_cb(cred_out, url, username_from_url, allowed, data): + d = ffi.from_handle(data) + + try: + ccred = get_credentials(d['callback'], url, username_from_url, allowed) + cred_out[0] = ccred[0] + except Exception, e: + d['exception'] = e + return C.GIT_EUSER + + return 0 + def clone_repository( url, path, bare=False, ignore_cert_errors=False, remote_name="origin", checkout_branch=None, credentials=None): @@ -75,8 +90,42 @@ def clone_repository( """ - _pygit2.clone_repository( - url, path, bare, ignore_cert_errors, remote_name, checkout_branch, credentials) + opts = ffi.new('git_clone_options *') + crepo = ffi.new('git_repository **') + + branch = checkout_branch or None + + # Data, let's use a dict as we don't really want much more + d = {} + d['callback'] = credentials + d_handle = ffi.new_handle(d) + + # We need to keep the ref alive ourselves + checkout_branch_ref = None + if branch: + checkout_branch_ref = ffi.new('char []', branch) + opts.checkout_branch = checkout_branch_ref + + remote_name_ref = ffi.new('char []', to_str(remote_name)) + opts.remote_name = remote_name_ref + + opts.version = 1 + opts.ignore_cert_errors = ignore_cert_errors + opts.bare = bare + opts.remote_callbacks.version = 1 + opts.checkout_opts.version = 1 + if credentials: + opts.remote_callbacks.credentials = _credentials_cb + opts.remote_callbacks.payload = d_handle + + err = C.git_clone(crepo, to_str(url), to_str(path), opts) + C.git_repository_free(crepo[0]) + + if 'exception' in d: + raise d['exception'] + + check_error(err) + return Repository(path) settings = Settings() diff --git a/pygit2/decl.h b/pygit2/decl.h index 40680ce..a64c32f 100644 --- a/pygit2/decl.h +++ b/pygit2/decl.h @@ -3,6 +3,8 @@ typedef ... git_remote; typedef ... git_refspec; typedef ... git_push; typedef ... git_cred; +typedef ... git_diff_file; +typedef ... git_tree; #define GIT_OID_RAWSZ ... @@ -43,6 +45,7 @@ typedef struct { const git_error * giterr_last(void); void git_strarray_free(git_strarray *array); +void git_repository_free(git_repository *repo); typedef struct git_transfer_progress { unsigned int total_objects; @@ -148,3 +151,64 @@ int git_cred_ssh_key_new( const char *publickey, const char *privatekey, const char *passphrase); + +typedef enum { ... } git_checkout_notify_t; + +typedef int (*git_checkout_notify_cb)( + git_checkout_notify_t why, + const char *path, + const git_diff_file *baseline, + const git_diff_file *target, + const git_diff_file *workdir, + void *payload); + +typedef void (*git_checkout_progress_cb)( + const char *path, + size_t completed_steps, + size_t total_steps, + void *payload); + +typedef struct git_checkout_opts { + unsigned int version; + + unsigned int checkout_strategy; + + int disable_filters; + unsigned int dir_mode; + unsigned int file_mode; + int file_open_flags; + + unsigned int notify_flags; + git_checkout_notify_cb notify_cb; + void *notify_payload; + + git_checkout_progress_cb progress_cb; + void *progress_payload; + + git_strarray paths; + + git_tree *baseline; + + const char *target_directory; + + const char *our_label; + const char *their_label; +} git_checkout_opts; + + +typedef struct git_clone_options { + unsigned int version; + + git_checkout_opts checkout_opts; + git_remote_callbacks remote_callbacks; + + int bare; + int ignore_cert_errors; + const char *remote_name; + const char* checkout_branch; +} git_clone_options; + +int git_clone(git_repository **out, + const char *url, + const char *local_path, + const git_clone_options *options); diff --git a/pygit2/remote.py b/pygit2/remote.py index c7f122c..40e6dda 100644 --- a/pygit2/remote.py +++ b/pygit2/remote.py @@ -352,33 +352,7 @@ class Remote(object): return 0 try: - url_str = maybe_string(url) - username_str = maybe_string(username) - - creds = self.credentials(url_str, username_str, allowed) - - if not hasattr(creds, 'credential_type') or not hasattr(creds, 'credential_tuple'): - raise TypeError("credential does not implement interface") - - cred_type = creds.credential_type - - if not (allowed & cred_type): - raise TypeError("invalid credential type") - - ccred = ffi.new('git_cred **') - if cred_type == C.GIT_CREDTYPE_USERPASS_PLAINTEXT: - name, passwd = creds.credential_tuple - err = C.git_cred_userpass_plaintext_new(ccred, to_str(name), to_str(passwd)) - - elif cred_type == C.GIT_CREDTYPE_SSH_KEY: - name, pubkey, privkey, passphrase = creds.credential_tuple - err = C.git_cred_ssh_key_new(ccred, to_str(name),to_str(pubkey), - to_str(privkey), to_str(passphrase)) - - else: - raise TypeError("unsupported credential type") - - check_error(err) + ccred = get_credentials(self.credentials, url, username, allowed) cred_out[0] = ccred[0] except Exception, e: @@ -386,3 +360,36 @@ class Remote(object): return C.GIT_EUSER return 0 + +def get_credentials(fn, url, username, allowed): + """Call fn and return the credentials object""" + + url_str = maybe_string(url) + username_str = maybe_string(username) + + creds = fn(url_str, username_str, allowed) + + if not hasattr(creds, 'credential_type') or not hasattr(creds, 'credential_tuple'): + raise TypeError("credential does not implement interface") + + cred_type = creds.credential_type + + if not (allowed & cred_type): + raise TypeError("invalid credential type") + + ccred = ffi.new('git_cred **') + if cred_type == C.GIT_CREDTYPE_USERPASS_PLAINTEXT: + name, passwd = creds.credential_tuple + err = C.git_cred_userpass_plaintext_new(ccred, to_str(name), to_str(passwd)) + + elif cred_type == C.GIT_CREDTYPE_SSH_KEY: + name, pubkey, privkey, passphrase = creds.credential_tuple + err = C.git_cred_ssh_key_new(ccred, to_str(name),to_str(pubkey), + to_str(privkey), to_str(passphrase)) + + else: + raise TypeError("unsupported credential type") + + check_error(err) + + return ccred diff --git a/src/pygit2.c b/src/pygit2.c index 06e4f17..d88c0e4 100644 --- a/src/pygit2.c +++ b/src/pygit2.c @@ -115,69 +115,6 @@ init_repository(PyObject *self, PyObject *args) { Py_RETURN_NONE; }; -static int -credentials_cb(git_cred **out, const char *url, const char *username_from_url, unsigned int allowed_types, void *data) -{ - PyObject *credentials = (PyObject *) data; - - return callable_to_credentials(out, url, username_from_url, allowed_types, credentials); -} - -PyDoc_STRVAR(clone_repository__doc__, - "clone_repository(url, path, bare, remote_name, checkout_branch)\n" - "\n" - "Clones a Git repository in the given url to the given path " - "with the specified options.\n" - "\n" - "Arguments:\n" - "\n" - "url\n" - " Git repository remote url.\n" - "path\n" - " Path where to create the repository.\n" - "bare\n" - " If 'bare' is not 0, then a bare git repository will be created.\n" - "remote_name\n" - " The name given to the 'origin' remote. The default is 'origin'.\n" - "checkout_branch\n" - " The name of the branch to checkout. None means use the remote's " - "HEAD.\n"); - - -PyObject * -clone_repository(PyObject *self, PyObject *args) { - git_repository *repo; - const char *url; - const char *path; - unsigned int bare, ignore_cert_errors; - const char *remote_name, *checkout_branch; - PyObject *credentials = NULL; - int err; - git_clone_options opts = GIT_CLONE_OPTIONS_INIT; - - if (!PyArg_ParseTuple(args, "zzIIzzO", - &url, &path, &bare, &ignore_cert_errors, &remote_name, &checkout_branch, &credentials)) - return NULL; - - opts.bare = bare; - opts.ignore_cert_errors = ignore_cert_errors; - opts.remote_name = remote_name; - opts.checkout_branch = checkout_branch; - - if (credentials != Py_None) { - opts.remote_callbacks.credentials = credentials_cb; - opts.remote_callbacks.payload = credentials; - } - - err = git_clone(&repo, url, path, &opts); - if (err < 0) - return Error_set(err); - - git_repository_free(repo); - Py_RETURN_NONE; -}; - - PyDoc_STRVAR(discover_repository__doc__, "discover_repository(path[, across_fs[, ceiling_dirs]]) -> str\n" "\n" @@ -252,8 +189,6 @@ hash(PyObject *self, PyObject *args) PyMethodDef module_methods[] = { {"init_repository", init_repository, METH_VARARGS, init_repository__doc__}, - {"clone_repository", clone_repository, METH_VARARGS, - clone_repository__doc__}, {"discover_repository", discover_repository, METH_VARARGS, discover_repository__doc__}, {"hashfile", hashfile, METH_VARARGS, hashfile__doc__}, diff --git a/src/utils.c b/src/utils.c index 24b6bbd..44acf5c 100644 --- a/src/utils.c +++ b/src/utils.c @@ -153,92 +153,3 @@ on_error: return -1; } - -static int -py_cred_to_git_cred(git_cred **out, PyObject *py_cred, unsigned int allowed) -{ - PyObject *py_type, *py_tuple; - long type; - int err = -1; - - py_type = PyObject_GetAttrString(py_cred, "credential_type"); - py_tuple = PyObject_GetAttrString(py_cred, "credential_tuple"); - - if (!py_type || !py_tuple) { - printf("py_type %p, py_tuple %p\n", py_type, py_tuple); - PyErr_SetString(PyExc_TypeError, "credential doesn't implement the interface"); - goto cleanup; - } - - if (!PyLong_Check(py_type)) { - PyErr_SetString(PyExc_TypeError, "credential type is not a long"); - goto cleanup; - } - - type = PyLong_AsLong(py_type); - - /* Sanity check, make sure we're given credentials we can use */ - if (!(allowed & type)) { - PyErr_SetString(PyExc_TypeError, "invalid credential type"); - goto cleanup; - } - - switch (type) { - case GIT_CREDTYPE_USERPASS_PLAINTEXT: - { - const char *username, *password; - - if (!PyArg_ParseTuple(py_tuple, "ss", &username, &password)) - goto cleanup; - - err = git_cred_userpass_plaintext_new(out, username, password); - break; - } - case GIT_CREDTYPE_SSH_KEY: - { - const char *username, *pubkey, *privkey, *passphrase; - - if (!PyArg_ParseTuple(py_tuple, "ssss", &username, &pubkey, &privkey, &passphrase)) - goto cleanup; - - err = git_cred_ssh_key_new(out, username, pubkey, privkey, passphrase); - break; - } - default: - PyErr_SetString(PyExc_TypeError, "unsupported credential type"); - break; - } - -cleanup: - Py_XDECREF(py_type); - Py_XDECREF(py_tuple); - - return err; -} - -int -callable_to_credentials(git_cred **out, const char *url, const char *username_from_url, unsigned int allowed_types, PyObject *credentials) -{ - int err; - PyObject *py_cred = NULL, *arglist = NULL; - - if (credentials == NULL || credentials == Py_None) - return 0; - - if (!PyCallable_Check(credentials)) { - PyErr_SetString(PyExc_TypeError, "credentials callback is not callable"); - return -1; - } - - arglist = Py_BuildValue("(szI)", url, username_from_url, allowed_types); - py_cred = PyObject_CallObject(credentials, arglist); - Py_DECREF(arglist); - - if (!py_cred) - return -1; - - err = py_cred_to_git_cred(out, py_cred, allowed_types); - Py_DECREF(py_cred); - - return err; -} diff --git a/src/utils.h b/src/utils.h index 7f95d73..81744f6 100644 --- a/src/utils.h +++ b/src/utils.h @@ -117,8 +117,6 @@ const char *py_str_borrow_c_str(PyObject **tvaue, PyObject *value, const char *e PyObject * get_pylist_from_git_strarray(git_strarray *strarray); int get_strarraygit_from_pylist(git_strarray *array, PyObject *pylist); -int callable_to_credentials(git_cred **out, const char *url, const char *username_from_url, unsigned int allowed_types, PyObject *credentials); - #define py_path_to_c_str(py_path) \ py_str_to_c_str(py_path, Py_FileSystemDefaultEncoding)