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:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -10,3 +10,4 @@ pygit2/__pycache__
|
||||
*.egg-info
|
||||
*.swp
|
||||
docs/_build
|
||||
__pycache__
|
||||
|
@@ -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
127
pygit2/decl.h
Normal 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
55
pygit2/errors.py
Normal 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
106
pygit2/ffi.py
Normal 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
110
pygit2/refspec.py
Normal 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
207
pygit2/remote.py
Normal 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
|
@@ -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
|
||||
#
|
||||
|
297
src/refspec.c
297
src/refspec.c
@@ -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 */
|
||||
};
|
@@ -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}
|
||||
};
|
||||
|
||||
|
@@ -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]
|
||||
|
Reference in New Issue
Block a user