Start implementing remotes with CFFI

This moves enough code into python with CFFI to pass the test_remotes
unit tests. There is no credentials support yet.

There is a small change in the return value of Remote.fetch() in that we
now return a TransferProgress object instead of extracting a few values
into a dictionary.
This commit is contained in:
Carlos Martín Nieto
2014-04-11 23:54:31 +02:00
parent d882af8f52
commit c76c3f0195
11 changed files with 675 additions and 366 deletions

1
.gitignore vendored
View File

@@ -10,3 +10,4 @@ pygit2/__pycache__
*.egg-info
*.swp
docs/_build
__pycache__

View File

@@ -37,6 +37,7 @@ from .repository import Repository
from .version import __version__
from .settings import Settings
from .credentials import *
from .remote import Remote
def init_repository(path, bare=False):
"""

127
pygit2/decl.h Normal file
View File

@@ -0,0 +1,127 @@
typedef ... git_repository;
typedef ... git_remote;
typedef ... git_refspec;
typedef ... git_push;
typedef ... git_cred;
typedef ... git_oid;
typedef struct git_strarray {
char **strings;
size_t count;
} git_strarray;
typedef enum {
GIT_OK = 0,
GIT_ERROR = -1,
GIT_ENOTFOUND = -3,
GIT_EEXISTS = -4,
GIT_EAMBIGUOUS = -5,
GIT_EBUFS = -6,
GIT_EUSER = -7,
GIT_EBAREREPO = -8,
GIT_EUNBORNBRANCH = -9,
GIT_EUNMERGED = -10,
GIT_ENONFASTFORWARD = -11,
GIT_EINVALIDSPEC = -12,
GIT_EMERGECONFLICT = -13,
GIT_ELOCKED = -14,
GIT_PASSTHROUGH = -30,
GIT_ITEROVER = -31,
} git_error_code;
typedef struct {
char *message;
int klass;
} git_error;
const git_error * giterr_last(void);
void git_strarray_free(git_strarray *array);
typedef struct git_transfer_progress {
unsigned int total_objects;
unsigned int indexed_objects;
unsigned int received_objects;
unsigned int local_objects;
unsigned int total_deltas;
unsigned int indexed_deltas;
size_t received_bytes;
} git_transfer_progress;
typedef enum git_remote_completion_type {
GIT_REMOTE_COMPLETION_DOWNLOAD,
GIT_REMOTE_COMPLETION_INDEXING,
GIT_REMOTE_COMPLETION_ERROR,
} git_remote_completion_type;
typedef enum {
GIT_DIRECTION_FETCH = 0,
GIT_DIRECTION_PUSH = 1
} git_direction;
typedef struct git_remote_callbacks {
unsigned int version;
int (*progress)(const char *str, int len, void *data);
int (*completion)(git_remote_completion_type type, void *data);
int (*credentials)(git_cred **cred, const char *url, const char *username_from_url, unsigned int allowed_types, void *data);
int (*transfer_progress)(const git_transfer_progress *stats, void *data);
int (*update_tips)(const char *refname, const git_oid *a, const git_oid *b, void *data);
void *payload;
} 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_create(git_remote **out,
git_repository *repo,
const char *name,
const char *url);
const char * git_remote_name(const git_remote *remote);
typedef int (*git_remote_rename_problem_cb)(const char *problematic_refspec, void *payload);
int git_remote_rename(git_remote *remote,
const char *new_name,
git_remote_rename_problem_cb callback,
void *payload);
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_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);
int git_remote_save(const git_remote *remote);
int git_remote_set_callbacks(git_remote *remote, const git_remote_callbacks *callbacks);
size_t git_remote_refspec_count(git_remote *remote);
const git_refspec * git_remote_get_refspec(git_remote *remote, size_t n);
int git_remote_get_fetch_refspecs(git_strarray *array, git_remote *remote);
int git_remote_set_fetch_refspecs(git_remote *remote, git_strarray *array);
int git_remote_get_push_refspecs(git_strarray *array, git_remote *remote);
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);
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);
const char * git_refspec_string(const git_refspec *refspec);
git_direction git_refspec_direction(const git_refspec *spec);
int git_refspec_src_matches(const git_refspec *refspec, const char *refname);
int git_refspec_dst_matches(const git_refspec *refspec, const char *refname);
int git_refspec_transform(char *out, size_t outlen, const git_refspec *spec, const char *name);
int git_refspec_rtransform(char *out, size_t outlen, const git_refspec *spec, const char *name);

55
pygit2/errors.py Normal file
View File

@@ -0,0 +1,55 @@
# -*- coding: utf-8 -*-
#
# Copyright 2010-2014 The pygit2 contributors
#
# This file is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2,
# as published by the Free Software Foundation.
#
# In addition to the permissions in the GNU General Public License,
# the authors give you unlimited permission to link the compiled
# version of this file into combinations with other programs,
# and to distribute those combinations without any restriction
# coming from the use of this file. (The General Public License
# restrictions do apply in other respects; for example, they cover
# modification of the file, and distribution when not linked into
# a combined executable.)
#
# This file is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; see the file COPYING. If not, write to
# the Free Software Foundation, 51 Franklin Street, Fifth Floor,
# Boston, MA 02110-1301, USA.
# Import from the Standard Library
from string import hexdigits
# ffi
from .ffi import ffi, C
from _pygit2 import GitError
def check_error(err):
if err >= 0:
return
message = "(no message provided)"
giterr = C.giterr_last()
if giterr != ffi.NULL:
message = ffi.string(giterr.message).decode()
if err in [C.GIT_EEXISTS, C.GIT_EINVALIDSPEC, C.GIT_EEXISTS, C.GIT_EAMBIGUOUS]:
raise ValueError(message)
elif err == C.GIT_ENOTFOUND:
raise KeyError(message)
elif err == C.GIT_EINVALIDSPEC:
raise ValueError(message)
elif err == C.GIT_ITEROVER:
raise StopIteration()
raise GitError(message)

106
pygit2/ffi.py Normal file
View File

@@ -0,0 +1,106 @@
# -*- coding: utf-8 -*-
#
# Copyright 2010-2014 The pygit2 contributors
#
# This file is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2,
# as published by the Free Software Foundation.
#
# In addition to the permissions in the GNU General Public License,
# the authors give you unlimited permission to link the compiled
# version of this file into combinations with other programs,
# and to distribute those combinations without any restriction
# coming from the use of this file. (The General Public License
# restrictions do apply in other respects; for example, they cover
# modification of the file, and distribution when not linked into
# a combined executable.)
#
# This file is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; see the file COPYING. If not, write to
# the Free Software Foundation, 51 Franklin Street, Fifth Floor,
# Boston, MA 02110-1301, USA.
# Import from the future
from __future__ import absolute_import
import inspect
from os import path
from cffi import FFI
import sys
if sys.version_info.major < 3:
def to_str(s, encoding='utf-8', errors='strict'):
if s == ffi.NULL:
return ffi.NULL
encoding = encoding or 'utf-8'
if isinstance(s, unicode):
return s.encode(encoding, errors)
return s
else:
def to_str(s, encoding='utf-8', errors='strict'):
if isinstance(s, bytes):
return s
else:
return bytes(s, encoding, errors)
if sys.version_info.major < 3:
def is_string(s):
return isinstance(s, str)
else:
def is_string(s):
return isinstance(s, basestring)
ffi = FFI()
def strarray_to_strings(arr):
l = [None] * arr.count
for i in range(arr.count):
l[i] = ffi.string(arr.strings[i]).decode()
return l
def strings_to_strarray(l):
"""Convert a list of strings to a git_strarray
We return first the git_strarray* you can pass to libgit2 and a
list of references to the memory, which we must keep around for as
long as the git_strarray must live.
"""
if not isinstance(l, list):
raise TypeError("Value must be a list")
arr = ffi.new('git_strarray *')
strings = ffi.new('char *[]', len(l))
# We need refs in order to keep a reference to the value returned
# by the ffi.new(). Otherwise, they will be freed and the memory
# re-used, with less than great consequences.
refs = [None] * len(l)
for i in range(len(l)):
if not is_string(l[i]):
raise TypeError("Value must be a string")
s = ffi.new('char []', l[i])
refs[i] = s
strings[i] = s
arr.strings = strings
arr.count = len(l)
return arr, refs
dir_path = path.dirname(path.abspath(inspect.getfile(inspect.currentframe())))
decl_path = path.join(dir_path, 'decl.h')
with open(decl_path, 'rb') as f:
ffi.cdef(f.read())
C = ffi.verify("#include <git2.h>", libraries=["git2"])

110
pygit2/refspec.py Normal file
View File

@@ -0,0 +1,110 @@
# -*- coding: utf-8 -*-
#
# Copyright 2010-2014 The pygit2 contributors
#
# This file is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2,
# as published by the Free Software Foundation.
#
# In addition to the permissions in the GNU General Public License,
# the authors give you unlimited permission to link the compiled
# version of this file into combinations with other programs,
# and to distribute those combinations without any restriction
# coming from the use of this file. (The General Public License
# restrictions do apply in other respects; for example, they cover
# modification of the file, and distribution when not linked into
# a combined executable.)
#
# This file is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; see the file COPYING. If not, write to
# the Free Software Foundation, 51 Franklin Street, Fifth Floor,
# Boston, MA 02110-1301, USA.
# Import from the future
from __future__ import absolute_import
from .ffi import ffi, C, to_str
from .errors import check_error
class Refspec(object):
def __init__(self, owner, ptr):
self._owner = owner
self._refspec = ptr
@property
def src(self):
"""Source or lhs of the refspec"""
return ffi.string(C.git_refspec_src(self._refspec)).decode()
@property
def dst(self):
"""Destinaton or rhs of the refspec"""
return ffi.string(C.git_refspec_dst(self._refspec)).decode()
@property
def force(self):
"""Whether this refspeca llows non-fast-forward updates"""
return bool(C.git_refspec_force(self._refspec))
@property
def string(self):
"""String which was used to create this refspec"""
return ffi.string(C.git_refspec_string(self._refspec)).decode()
@property
def direction(self):
"""Direction of this refspec (fetch or push)"""
return C.git_refspec_direction(self._refspec)
def src_matches(self, ref):
"""src_matches(str) -> Bool
Returns whether the given string matches the source of this refspec"""
return bool(C.git_refspec_src_matches(self._refspec, to_str(ref)))
def dst_matches(self, ref):
"""dst_matches(str) -> Bool
Returns whether the given string matches the destination of this refspec"""
return bool(C.git_refspec_dst_matches(self._refspec, to_str(ref)))
def transform(self, ref):
"""transform(str) -> str
Transform a reference name according to this refspec from the lhs to the rhs."""
alen = len(ref)
err = C.GIT_EBUFS
ptr = None
ref_str = to_str(ref)
while err == C.GIT_EBUFS:
alen *= 2
ptr = ffi.new('char []', alen)
err = C.git_refspec_transform(ptr, alen, self._refspec, ref_str)
check_error(err)
return ffi.string(ptr).decode()
def rtransform(self, ref):
"""transform(str) -> str
Transform a reference name according to this refspec from the lhs to the rhs"""
alen = len(ref)
err = C.GIT_EBUFS
ptr = None
ref_str = to_str(ref)
while err == C.GIT_EBUFS:
alen *= 2
ptr = ffi.new('char []', alen)
err = C.git_refspec_rtransform(ptr, alen, self._refspec, ref_str)
check_error(err)
return ffi.string(ptr).decode()

207
pygit2/remote.py Normal file
View File

@@ -0,0 +1,207 @@
# -*- coding: utf-8 -*-
#
# Copyright 2010-2014 The pygit2 contributors
#
# This file is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2,
# as published by the Free Software Foundation.
#
# In addition to the permissions in the GNU General Public License,
# the authors give you unlimited permission to link the compiled
# version of this file into combinations with other programs,
# and to distribute those combinations without any restriction
# coming from the use of this file. (The General Public License
# restrictions do apply in other respects; for example, they cover
# modification of the file, and distribution when not linked into
# a combined executable.)
#
# This file is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; see the file COPYING. If not, write to
# the Free Software Foundation, 51 Franklin Street, Fifth Floor,
# Boston, MA 02110-1301, USA.
# Import from the future
from __future__ import absolute_import
from .ffi import ffi, C, to_str, strarray_to_strings, strings_to_strarray
from .errors import check_error, GitError
from .refspec import Refspec
def maybe_string(ptr):
if not ptr:
return None
return ffi.string(ptr).decode()
class TransferProgress(object):
"""Progress downloading and indexing data during a fetch"""
def __init__(self, tp):
self.total_objects = tp.total_objects
self.indexed_objects = tp.indexed_objects
self.received_objects = tp.received_objects
self.local_objects = tp.local_objects
self.total_deltas = tp.total_deltas
self.indexed_deltas = tp.indexed_deltas
self.received_bytes = tp.received_bytes
class Remote(object):
def __init__(self, repo, ptr):
"""The constructor is for internal use only"""
self._repo = repo
self._remote = ptr
# Build the callback structure
callbacks = ffi.new('git_remote_callbacks *')
callbacks.version = 1
callbacks.transfer_progress = self._transfer_progress_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)
check_error(err)
def __del__(self):
C.git_remote_free(self._remote)
@property
def name(self):
return maybe_string(C.git_remote_name(self._remote))
@name.setter
def name(self, value):
err = C.git_remote_rename(self._remote, to_str(value), ffi.NULL, ffi.NULL)
check_error(err)
@property
def url(self):
return maybe_string(C.git_remote_url(self._remote))
@url.setter
def url(self, value):
err = C.git_remote_set_url(self._remote, to_str(value))
@property
def push_url(self):
return maybe_string(C.git_remote_pushurl(self._remote))
@push_url.setter
def push_url(self, value):
err = C.git_remote_set_pushurl(self._remote, to_str(value))
check_error(err)
def save(self):
err = C.git_remote_save(self._remote)
check_error(err)
def fetch(self):
err = C.git_remote_fetch(self._remote)
if err == C.GIT_EUSER:
raise self._stored_exception
check_error(err)
return TransferProgress(C.git_remote_stats(self._remote))
@property
def refspec_count(self):
return C.git_remote_refspec_count(self._remote)
def get_refspec(self, n):
spec = C.git_remote_get_refspec(self._remote, n)
return Refspec(self, spec)
@property
def fetch_refspecs(self):
specs = ffi.new('git_strarray *')
err = C.git_remote_get_fetch_refspecs(specs, self._remote)
check_error(err)
return strarray_to_strings(specs)
@fetch_refspecs.setter
def fetch_refspecs(self, l):
arr, refs = strings_to_strarray(l)
err = C.git_remote_set_fetch_refspecs(self._remote, arr)
check_error(err)
@property
def push_refspecs(self):
specs = ffi.new('git_strarray *')
err = C.git_remote_get_push_refspecs(specs, self._remote)
check_error(err)
return strarray_to_strings(specs)
@push_refspecs.setter
def push_refspecs(self, l):
arr, refs = strings_to_strarray(l)
err = C.git_remote_set_push_refspecs(self._remote, arr)
check_error(err)
def add_fetch(self, spec):
err = C.git_remote_add_fetch(self._remote, to_str(spec))
def add_push(self, spec):
err = C.git_remote_add_push(self._remote, to_str(spec))
@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):
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_str(spec))
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)
err = C.git_push_update_tips(push)
check_error(err)
finally:
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
@ffi.callback('int (*transfer_progress)(const git_transfer_progress *stats, void *data)')
def _transfer_progress_cb(stats_ptr, data):
self = ffi.from_handle(data)
if not hasattr(self, 'transfer_progress'):
return 0
try:
self.transfer_progress(TransferProgress(stats_ptr))
except Exception, e:
self._stored_exception = e
return C.GIT_EUSER
return 0

View File

@@ -35,6 +35,9 @@ from _pygit2 import Oid, GIT_OID_HEXSZ, GIT_OID_MINPREFIXLEN
from _pygit2 import GIT_CHECKOUT_SAFE_CREATE, GIT_DIFF_NORMAL
from _pygit2 import Reference, Tree, Commit, Blob
from .ffi import ffi, C, to_str
from .errors import check_error
from .remote import Remote
class Repository(_Repository):
@@ -59,6 +62,51 @@ class Repository(_Repository):
def __repr__(self):
return "pygit2.Repository(%r)" % self.path
#
# Remotes
#
def create_remote(self, name, url):
"""create_remote(name, url) -> Remote
Creates a new remote.
"""
repo_cptr = ffi.new('git_repository **')
repo_cptr[0] = ffi.cast('git_repository *', self._pointer)
cremote = ffi.new('git_remote **')
repo = repo_cptr[0]
err = C.git_remote_create(cremote, repo, to_str(name), to_str(url))
check_error(err)
return Remote(repo, cremote[0])
@property
def remotes(self):
"""Returns all configured remotes"""
repo_cptr = ffi.new('git_repository **')
repo_cptr[0] = ffi.cast('git_repository *', self._pointer)
names = ffi.new('git_strarray *')
repo = repo_cptr[0]
try:
err = C.git_remote_list(names, repo)
check_error(err)
l = [None] * names.count
cremote = ffi.new('git_remote **')
for i in range(names.count):
err = C.git_remote_load(cremote, repo, names.strings[i])
check_error(err)
l[i] = Remote(repo, cremote[0])
return l
finally:
C.git_strarray_free(names)
#
# References
#

View File

@@ -1,297 +0,0 @@
/*
* Copyright 2010-2014 The pygit2 contributors
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License, version 2,
* as published by the Free Software Foundation.
*
* In addition to the permissions in the GNU General Public License,
* the authors give you unlimited permission to link the compiled
* version of this file into combinations with other programs,
* and to distribute those combinations without any restriction
* coming from the use of this file. (The General Public License
* restrictions do apply in other respects; for example, they cover
* modification of the file, and distribution when not linked into
* a combined executable.)
*
* This file is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include <structmember.h>
#include "error.h"
#include "types.h"
#include "utils.h"
#include "refspec.h"
extern PyTypeObject RefspecType;
Refspec *
wrap_refspec(const Remote *owner, const git_refspec *refspec)
{
Refspec *spec;
spec = PyObject_New(Refspec, &RefspecType);
if (!spec)
return NULL;
Py_INCREF(owner);
spec->owner = owner;
spec->refspec = refspec;
return spec;
}
PyDoc_STRVAR(Refspec_direction__doc__,
"The direction of this refspec (fetch or push)");
PyObject *
Refspec_direction__get__(Refspec *self)
{
return Py_BuildValue("i", git_refspec_direction(self->refspec));
}
PyDoc_STRVAR(Refspec_src__doc__, "Source or lhs of the refspec");
PyObject *
Refspec_src__get__(Refspec *self)
{
return to_unicode(git_refspec_src(self->refspec), NULL, NULL);
}
PyDoc_STRVAR(Refspec_dst__doc__, "Destination or rhs of the refspec");
PyObject *
Refspec_dst__get__(Refspec *self)
{
return to_unicode(git_refspec_dst(self->refspec), NULL, NULL);
}
PyDoc_STRVAR(Refspec_string__doc__, "String used to create this refspec");
PyObject *
Refspec_string__get__(Refspec *self)
{
return to_unicode(git_refspec_string(self->refspec), NULL, NULL);
}
PyDoc_STRVAR(Refspec_force__doc__,
"Whether this refspec allows non-fast-forward updates");
PyObject *
Refspec_force__get__(Refspec *self)
{
if (git_refspec_force(self->refspec))
Py_RETURN_TRUE;
Py_RETURN_FALSE;
}
PyDoc_STRVAR(Refspec_src_matches__doc__,
"src_matches(str) -> Bool\n"
"\n"
"Returns whether the string matches the source refspec\n");
PyObject *
Refspec_src_matches(Refspec *self, PyObject *py_str)
{
const char *str;
PyObject *tstr;
int res;
str = py_str_borrow_c_str(&tstr, py_str, NULL);
if (!str)
return NULL;
res = git_refspec_src_matches(self->refspec, str);
Py_DECREF(tstr);
if (res)
Py_RETURN_TRUE;
Py_RETURN_FALSE;
}
PyDoc_STRVAR(Refspec_dst_matches__doc__,
"dst_matches(str) -> Bool\n"
"\n"
"Returns whether the string matches the destination refspec\n");
PyObject *
Refspec_dst_matches(Refspec *self, PyObject *py_str)
{
const char *str;
PyObject *tstr;
int res;
str = py_str_borrow_c_str(&tstr, py_str, NULL);
if (!str)
return NULL;
res = git_refspec_dst_matches(self->refspec, str);
Py_DECREF(tstr);
if (res)
Py_RETURN_TRUE;
Py_RETURN_FALSE;
}
PyDoc_STRVAR(Refspec_transform__doc__,
"transform(str) -> str\n"
"\n"
"Transform a reference according to the refspec\n");
PyObject *
Refspec_transform(Refspec *self, PyObject *py_str)
{
const char *str;
char *trans;
int err, len, alen;
PyObject *py_trans, *tstr;
str = py_str_borrow_c_str(&tstr, py_str, NULL);
alen = len = strlen(str);
do {
alen *= alen;
trans = malloc(alen);
if (!trans) {
Py_DECREF(tstr);
return PyErr_NoMemory();
}
err = git_refspec_transform(trans, alen, self->refspec, str);
} while(err == GIT_EBUFS);
Py_DECREF(tstr);
if (err < 0) {
free(trans);
Error_set(err);
return NULL;
}
py_trans = to_unicode(trans, NULL, NULL);
free(trans);
return py_trans;
}
PyDoc_STRVAR(Refspec_rtransform__doc__,
"rtransform(str) -> str\n"
"\n"
"Transform a reference according to the refspec in reverse\n");
PyObject *
Refspec_rtransform(Refspec *self, PyObject *py_str)
{
const char *str;
char *trans;
int err, len, alen;
PyObject *py_trans, *tstr;
str = py_str_borrow_c_str(&tstr, py_str, NULL);
alen = len = strlen(str);
do {
alen *= alen;
trans = malloc(alen);
if (!trans) {
Py_DECREF(tstr);
return PyErr_NoMemory();
}
err = git_refspec_rtransform(trans, alen, self->refspec, str);
} while(err == GIT_EBUFS);
Py_DECREF(tstr);
if (err < 0) {
free(trans);
Error_set(err);
return NULL;
}
py_trans = to_unicode(trans, NULL, NULL);
free(trans);
return py_trans;
}
PyMethodDef Refspec_methods[] = {
METHOD(Refspec, src_matches, METH_O),
METHOD(Refspec, dst_matches, METH_O),
METHOD(Refspec, transform, METH_O),
METHOD(Refspec, rtransform, METH_O),
{NULL}
};
PyGetSetDef Refspec_getseters[] = {
GETTER(Refspec, direction),
GETTER(Refspec, src),
GETTER(Refspec, dst),
GETTER(Refspec, string),
GETTER(Refspec, force),
{NULL}
};
static void
Refspec_dealloc(Refspec *self)
{
Py_CLEAR(self->owner);
PyObject_Del(self);
}
PyDoc_STRVAR(Refspec__doc__, "Refspec object.");
PyTypeObject RefspecType = {
PyVarObject_HEAD_INIT(NULL, 0)
"_pygit2.Refspec", /* tp_name */
sizeof(Refspec), /* tp_basicsize */
0, /* tp_itemsize */
(destructor)Refspec_dealloc, /* tp_dealloc */
0, /* tp_print */
0, /* tp_getattr */
0, /* tp_setattr */
0, /* tp_compare */
0, /* tp_repr */
0, /* tp_as_number */
0, /* tp_as_sequence */
0, /* tp_as_mapping */
0, /* tp_hash */
0, /* tp_call */
0, /* tp_str */
0, /* tp_getattro */
0, /* tp_setattro */
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
Refspec__doc__, /* tp_doc */
0, /* tp_traverse */
0, /* tp_clear */
0, /* tp_richcompare */
0, /* tp_weaklistoffset */
0, /* tp_iter */
0, /* tp_iternext */
Refspec_methods, /* tp_methods */
0, /* tp_members */
Refspec_getseters, /* tp_getset */
0, /* tp_base */
0, /* tp_dict */
0, /* tp_descr_get */
0, /* tp_descr_set */
0, /* tp_dictoffset */
0, /* tp_init */
0, /* tp_alloc */
0, /* tp_new */
};

View File

@@ -1277,67 +1277,6 @@ Repository_TreeBuilder(Repository *self, PyObject *args)
return (PyObject*)builder;
}
PyDoc_STRVAR(Repository_create_remote__doc__,
"create_remote(name, url) -> Remote\n"
"\n"
"Creates a new remote.");
PyObject *
Repository_create_remote(Repository *self, PyObject *args)
{
git_remote *remote;
char *name = NULL, *url = NULL;
int err;
if (!PyArg_ParseTuple(args, "ss", &name, &url))
return NULL;
err = git_remote_create(&remote, self->repo, name, url);
if (err < 0)
return Error_set(err);
return (PyObject*) wrap_remote(remote, self);
}
PyDoc_STRVAR(Repository_remotes__doc__, "Returns all configured remotes.");
PyObject *
Repository_remotes__get__(Repository *self)
{
git_strarray remotes;
git_remote *remote = NULL;
PyObject *py_list = NULL;
PyObject *py_remote = NULL;
size_t i;
int err;
git_remote_list(&remotes, self->repo);
py_list = PyList_New(remotes.count);
for (i=0; i < remotes.count; ++i) {
err = git_remote_load(&remote, self->repo, remotes.strings[i]);
if (err < 0)
goto cleanup;
py_remote = wrap_remote(remote, self);
if (py_remote == NULL)
goto cleanup;
PyList_SetItem(py_list, i, py_remote);
}
git_strarray_free(&remotes);
return (PyObject*) py_list;
cleanup:
git_strarray_free(&remotes);
if (py_list)
Py_DECREF(py_list);
if (err < 0)
return Error_set(err);
return NULL;
}
PyDoc_STRVAR(Repository_default_signature__doc__, "Return the signature according to the repository's configuration");
PyObject *
@@ -1352,6 +1291,19 @@ Repository_default_signature__get__(Repository *self)
return build_signature(NULL, sig, "utf-8");
}
PyDoc_STRVAR(Repository__pointer__doc__, "Get the repo's pointer. For internal use only.");
PyObject *
Repository__pointer__get__(Repository *self)
{
/*
* This is pretty bad. We shouldn't be casting a pointer into an
* integer, but we can't access the contents of a PyCapsule from
* python code, which we need to do in order to get a type that
* cffi likes.
*/
return PyLong_FromLongLong((long long) self->repo);
}
PyDoc_STRVAR(Repository_checkout_head__doc__,
"checkout_head(strategy)\n"
"\n"
@@ -1633,7 +1585,6 @@ PyMethodDef Repository_methods[] = {
METHOD(Repository, revparse_single, METH_O),
METHOD(Repository, status, METH_NOARGS),
METHOD(Repository, status_file, METH_O),
METHOD(Repository, create_remote, METH_VARARGS),
METHOD(Repository, checkout_head, METH_VARARGS),
METHOD(Repository, checkout_index, METH_VARARGS),
METHOD(Repository, checkout_tree, METH_VARARGS),
@@ -1659,8 +1610,8 @@ PyGetSetDef Repository_getseters[] = {
GETTER(Repository, is_bare),
GETTER(Repository, config),
GETTER(Repository, workdir),
GETTER(Repository, remotes),
GETTER(Repository, default_signature),
GETTER(Repository, _pointer),
{NULL}
};

View File

@@ -189,9 +189,9 @@ class EmptyRepositoryTest(utils.EmptyRepoTestCase):
def test_fetch(self):
remote = self.repo.remotes[0]
stats = remote.fetch()
self.assertEqual(stats['received_bytes'], REMOTE_REPO_BYTES)
self.assertEqual(stats['indexed_objects'], REMOTE_REPO_OBJECTS)
self.assertEqual(stats['received_objects'], REMOTE_REPO_OBJECTS)
self.assertEqual(stats.received_bytes, REMOTE_REPO_BYTES)
self.assertEqual(stats.indexed_objects, REMOTE_REPO_OBJECTS)
self.assertEqual(stats.received_objects, REMOTE_REPO_OBJECTS)
def test_transfer_progress(self):
self.tp = None
@@ -201,9 +201,9 @@ class EmptyRepositoryTest(utils.EmptyRepoTestCase):
remote = self.repo.remotes[0]
remote.transfer_progress = tp_cb
stats = remote.fetch()
self.assertEqual(stats['received_bytes'], self.tp.received_bytes)
self.assertEqual(stats['indexed_objects'], self.tp.indexed_objects)
self.assertEqual(stats['received_objects'], self.tp.received_objects)
self.assertEqual(stats.received_bytes, self.tp.received_bytes)
self.assertEqual(stats.indexed_objects, self.tp.indexed_objects)
self.assertEqual(stats.received_objects, self.tp.received_objects)
def test_update_tips(self):
remote = self.repo.remotes[0]