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:
@@ -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()
|
||||
|
@@ -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);
|
||||
|
@@ -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
|
||||
|
65
src/pygit2.c
65
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__},
|
||||
|
89
src/utils.c
89
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;
|
||||
}
|
||||
|
@@ -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)
|
||||
|
||||
|
Reference in New Issue
Block a user