Implement clone via CFFI as well

This lets us get rid of the last piece of C for anything related to
remotes and credentials.
This commit is contained in:
Carlos Martín Nieto
2014-04-12 19:10:50 +02:00
parent db218fae3f
commit 072b038210
6 changed files with 150 additions and 186 deletions

View File

@@ -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()

View File

@@ -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);

View File

@@ -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

View File

@@ -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__},

View File

@@ -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;
}

View File

@@ -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)