diff --git a/.gitignore b/.gitignore index d802242..79889fe 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ pygit2/__pycache__ *.egg-info *.swp docs/_build +__pycache__ diff --git a/.travis.yml b/.travis.yml index f2ffa47..8440098 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,6 @@ language: python python: - - "2.6" - "2.7" - "3.2" - "3.3" @@ -11,6 +10,7 @@ env: LIBGIT2=~/libgit2/_install/ LD_LIBRARY_PATH=~/libgit2/_install/lib before_install: - sudo apt-get install cmake + - pip install cffi - "./.travis.sh" script: diff --git a/README.rst b/README.rst index 272b149..48ffc06 100644 --- a/README.rst +++ b/README.rst @@ -6,8 +6,12 @@ pygit2 - libgit2 bindings in Python :target: http://travis-ci.org/libgit2/pygit2 Pygit2 is a set of Python bindings to the libgit2 shared library, libgit2 -implements the core of Git. Pygit2 works with Python 2.6, 2.7, 3.1, 3.2 and -3.3 +implements the core of Git. Pygit2 works with Python 2.7, 3.2, 3.3, 3.4 and +pypy. + +It is likely to work with Python 2.6 and 3.1, but these versions are not +officially supported. + Pygit2 links: @@ -44,33 +48,35 @@ for the topic), send a pull request. Authors ============== -57 developers have contributed at least 1 commit to pygit2:: +62 developers have contributed at least 1 commit to pygit2:: - J. David Ibáñez Brodie Rao Adam Spiers - Nico von Geyso David Versmisse Alexander Bayandin - Carlos Martín Nieto Rémi Duraffort Andrew Chin - W. Trevor King Sebastian Thiel András Veres-Szentkirályi - Dave Borowitz Fraser Tweedale Benjamin Kircher - Daniel Rodríguez Troitiño Han-Wen Nienhuys Benjamin Pollack - Richo Healey Petr Viktorin Bryan O'Sullivan - Christian Boos Alex Chamberlain David Fischer - Julien Miotte Amit Bakshi David Sanders - Xu Tao Andrey Devyatkin Eric Davis - Jose Plana Ben Davis Erik van Zijst - Martin Lenders Eric Schrijver Ferengee - Petr Hosek Hervé Cauwelier Gustavo Di Pietro - Victor Garcia Huang Huang Hugh Cole-Baker - Xavier Delannoy Jared Flatow Josh Bleecher Snyder - Yonggang Luo Jiunn Haur Lim Jun Omae - Valentin Haenel Sarath Lakshman Óscar San José - Bernardo Heynemann Vicent Marti Ridge Kennedy - John Szakmeister Zoran Zaric Rui Abreu Ferreira + J. David Ibáñez Rémi Duraffort András Veres-Szentkirályi + Nico von Geyso Sebastian Thiel Benjamin Kircher + Carlos Martín Nieto Fraser Tweedale Benjamin Pollack + W. Trevor King Han-Wen Nienhuys Bryan O'Sullivan + Dave Borowitz Leonardo Rhodes David Fischer + Daniel Rodríguez Troitiño Petr Viktorin David Sanders + Richo Healey Alex Chamberlain Devaev Maxim + Christian Boos Amit Bakshi Eric Davis + Julien Miotte Andrey Devyatkin Erik Meusel + Xu Tao Ben Davis Erik van Zijst + Jose Plana Eric Schrijver Ferengee + Martin Lenders Hervé Cauwelier Gustavo Di Pietro + Petr Hosek Huang Huang Hugh Cole-Baker + Victor Garcia Jared Flatow Josh Bleecher Snyder + Xavier Delannoy Jiunn Haur Lim Jun Omae + Yonggang Luo Sarath Lakshman Óscar San José + Valentin Haenel Vicent Marti Ridge Kennedy + Bernardo Heynemann Zoran Zaric Rui Abreu Ferreira + John Szakmeister Adam Spiers Thomas Kluyver + Brodie Rao Alexander Bayandin earl + David Versmisse Andrew Chin Changelog ============== -0.20.3 (2014-04-XX) +0.20.3 (2014-04-02) ------------------- - A number of memory issues fixed diff --git a/docs/conf.py b/docs/conf.py index d7f2799..c432057 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -50,7 +50,7 @@ copyright = u'2010-2014 The pygit2 contributors' # The short X.Y version. version = '0.20' # The full version, including alpha/beta/rc tags. -release = '0.20.2' +release = '0.20.3' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/docs/config.rst b/docs/config.rst index 348c347..5c091e7 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -22,4 +22,13 @@ The Config type aware that this may return multiple versions of each entry if they are set multiple times in the configuration files. +.. currentmodule:: pygit2 + The :class:`Config` Mapping interface. + +When using the mapping interface, the value is returned as a +string. In order to apply the git-config parsing rules, you can use +:meth:`Config.get_bool` or :meth:`Config.get_int`. + +.. automethod:: pygit2.Config.get_bool +.. automethod:: pygit2.Config.get_int diff --git a/docs/index.rst b/docs/index.rst index 6eccbd5..62f112f 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -10,8 +10,11 @@ Welcome to pygit2's documentation! :target: http://travis-ci.org/libgit2/pygit2 Pygit2 is a set of Python bindings to the libgit2 shared library, libgit2 -implements the core of Git. Pygit2 works with Python 2.6, 2.7, 3.1, 3.2 and -3.3 +implements the core of Git. Pygit2 works with Python 2.7, 3.2, 3.3, 3.4 and +pypy. + +It is likely to work with Python 2.6 and 3.1, but these versions are not +officially supported. Pygit2 links: diff --git a/docs/install.rst b/docs/install.rst index f74e2d7..5f058f2 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -13,7 +13,7 @@ website: http://libgit2.github.com -Also, make sure you have Python 2.6+ installed together with the Python +Also, make sure you have Python 2.7 or 3.2+ installed together with the Python development headers. When those are installed, you can install pygit2: diff --git a/docs/remotes.rst b/docs/remotes.rst index 58ab8ef..445bef2 100644 --- a/docs/remotes.rst +++ b/docs/remotes.rst @@ -16,9 +16,9 @@ The Remote type .. autoattribute:: pygit2.Remote.refspec_count .. autoattribute:: pygit2.Remote.push_refspecs .. autoattribute:: pygit2.Remote.fetch_refspecs -.. autoattribute:: pygit2.Remote.progress -.. autoattribute:: pygit2.Remote.transfer_progress -.. autoattribute:: pygit2.Remote.update_tips +.. automethod:: pygit2.Remote.progress +.. automethod:: pygit2.Remote.transfer_progress +.. automethod:: pygit2.Remote.update_tips .. automethod:: pygit2.Remote.get_refspec .. automethod:: pygit2.Remote.fetch .. automethod:: pygit2.Remote.push diff --git a/pygit2/__init__.py b/pygit2/__init__.py index fc2e693..62954f6 100644 --- a/pygit2/__init__.py +++ b/pygit2/__init__.py @@ -37,6 +37,10 @@ from .repository import Repository from .version import __version__ from .settings import Settings from .credentials import * +from .remote import Remote, get_credentials +from .config import Config +from .errors import check_error +from .ffi import ffi, C, to_str def init_repository(path, bare=False): """ @@ -49,6 +53,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 as 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): @@ -74,8 +91,42 @@ def clone_repository( """ - _pygit2.clone_repository( - url, path, bare, ignore_cert_errors, remote_name, checkout_branch, credentials) + opts = ffi.new('git_clone_options *') + crepo = ffi.new('git_repository **') + + branch = checkout_branch or None + + # Data, let's use a dict as we don't really want much more + d = {} + d['callback'] = credentials + d_handle = ffi.new_handle(d) + + # We need to keep the ref alive ourselves + checkout_branch_ref = None + if branch: + checkout_branch_ref = ffi.new('char []', branch) + opts.checkout_branch = checkout_branch_ref + + remote_name_ref = ffi.new('char []', to_str(remote_name)) + opts.remote_name = remote_name_ref + + opts.version = 1 + opts.ignore_cert_errors = ignore_cert_errors + opts.bare = bare + opts.remote_callbacks.version = 1 + opts.checkout_opts.version = 1 + if credentials: + opts.remote_callbacks.credentials = _credentials_cb + opts.remote_callbacks.payload = d_handle + + err = C.git_clone(crepo, to_str(url), to_str(path), opts) + C.git_repository_free(crepo[0]) + + if 'exception' in d: + raise d['exception'] + + check_error(err) + return Repository(path) settings = Settings() diff --git a/pygit2/config.py b/pygit2/config.py new file mode 100644 index 0000000..6bbe6d2 --- /dev/null +++ b/pygit2/config.py @@ -0,0 +1,281 @@ +# -*- 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, unicode_literals + +from _pygit2 import Oid + +from .ffi import ffi, C, to_str, is_string +from .errors import check_error, GitError +from .refspec import Refspec + +def assert_string(v, desc): + if not is_string(v): + raise TypeError("%s must be a string" % desc) + +class ConfigIterator(object): + + def __init__(self, config, ptr): + self._iter = ptr + self._config = config + + def __del__(self): + C.git_config_iterator_free(self._iter) + + def __iter__(self): + return self + + def _next_entry(self): + centry = ffi.new('git_config_entry **') + err = C.git_config_next(centry, self._iter) + check_error(err) + + return centry[0] + + def next(self): + return self.__next__() + + def __next__(self): + entry = self._next_entry() + name = ffi.string(entry.name).decode('utf-8') + value = ffi.string(entry.value).decode('utf-8') + + return name, value + +class ConfigMultivarIterator(ConfigIterator): + def __next__(self): + entry = self._next_entry() + + return ffi.string(entry.value).decode('utf-8') + +class Config(object): + """Git configuration management""" + + def __init__(self, path=None): + cconfig = ffi.new('git_config **') + + if not path: + err = C.git_config_new(cconfig) + else: + assert_string(path, "path") + err = C.git_config_open_ondisk(cconfig, to_str(path)) + + check_error(err, True) + self._config = cconfig[0] + + @classmethod + def from_c(cls, repo, ptr): + config = cls.__new__(cls) + config._repo = repo + config._config = ptr + + return config + + def __del__(self): + C.git_config_free(self._config) + + def _get(self, key): + assert_string(key, "key") + + cstr = ffi.new('char **') + err = C.git_config_get_string(cstr, self._config, to_str(key)) + + return err, cstr + + def _get_string(self, key): + err, cstr = self._get(key) + + if err == C.GIT_ENOTFOUND: + raise KeyError(key) + + check_error(err) + return cstr[0] + + def __contains__(self, key): + err, cstr = self._get(key) + + if err == C.GIT_ENOTFOUND: + return False + + check_error(err) + + return True + + def __getitem__(self, key): + val = self._get_string(key) + + return ffi.string(val).decode() + + def __setitem__(self, key, value): + assert_string(key, "key") + + err = 0 + if isinstance(value, bool): + err = C.git_config_set_bool(self._config, to_str(key), value) + elif isinstance(value, int): + err = C.git_config_set_int64(self._config, to_str(key), value) + else: + err = C.git_config_set_string(self._config, to_str(key), to_str(value)) + + check_error(err) + + def __delitem__(self, key): + assert_string(key, "key") + + err = C.git_config_delete_entry(self._config, to_str(key)) + check_error(err) + + def __iter__(self): + citer = ffi.new('git_config_iterator **') + err = C.git_config_iterator_new(citer, self._config) + check_error(err) + + return ConfigIterator(self, citer[0]) + + def get_multivar(self, name, regex=None): + """get_multivar(name[, regex]) -> [str, ...] + + Get each value of a multivar ''name'' as a list. The optional ''regex'' + parameter is expected to be a regular expression to filter the variables + we're interested in.""" + + assert_string(name, "name") + + citer = ffi.new('git_config_iterator **') + err = C.git_config_multivar_iterator_new(citer, self._config, to_str(name), to_str(regex)) + check_error(err) + + return ConfigMultivarIterator(self, citer[0]) + + def set_multivar(self, name, regex, value): + """set_multivar(name, regex, value) + + Set a multivar ''name'' to ''value''. ''regexp'' is a regular expression + to indicate which values to replace""" + + assert_string(name, "name") + assert_string(regex, "regex") + assert_string(value, "value") + + err = C.git_config_set_multivar(self._config, to_str(name), to_str(regex), to_str(value)) + check_error(err) + + def get_bool(self, key): + """get_bool(key) -> Bool + + Look up *key* and parse its value as a boolean as per the git-config rules + + Truthy values are: 'true', 1, 'on' or 'yes'. Falsy values are: 'false', + 0, 'off' and 'no'""" + + val = self._get_string(key) + res = ffi.new('int *') + err = C.git_config_parse_bool(res, val) + check_error(err) + + return res[0] != 0 + + def get_int(self, key): + """get_int(key) -> int + + Look up *key* and parse its value as an integer as per the git-config rules. + + A value can have a suffix 'k', 'm' or 'g' which stand for 'kilo', 'mega' and + 'giga' respectively""" + + val = self._get_string(key) + res = ffi.new('int64_t *') + err = C.git_config_parse_int64(res, val) + check_error(err) + + return res[0] + + def add_file(self, path, level=0, force=0): + """add_file(path, level=0, force=0) + + Add a config file instance to an existing config.""" + + err = C.git_config_add_file_ondisk(self._config, to_str(path), level, force) + check_error(err) + + # + # Methods to parse a string according to the git-config rules + # + + @staticmethod + def parse_bool(text): + res = ffi.new('int *') + err = C.git_config_parse_bool(res, to_str(text)) + + return res[0] != 0 + + @staticmethod + def parse_int(text): + res = ffi.new('int64_t *') + err = C.git_config_parse_int64(res, to_str(text)) + check_error(err) + + return res[0] + + # + # Static methods to get specialized version of the config + # + + @staticmethod + def _from_found_config(fn): + buf = ffi.new('git_buf *', (ffi.NULL, 0)) + err = fn(buf) + check_error(err, True) + cpath = ffi.string(buf.ptr).decode() + C.git_buf_free(buf) + + return Config(cpath) + + @staticmethod + def get_system_config(): + """get_system_config() -> Config + + Return an object representing the system configuration file.""" + + return Config._from_found_config(C.git_config_find_system) + + @staticmethod + def get_global_config(): + """get_global_config() -> Config + + Return an object representing the global configuration file.""" + + return Config._from_found_config(C.git_config_find_global) + + @staticmethod + def get_xdg_config(): + """get_xdg_config() -> Config + + Return an object representing the global configuration file.""" + + return Config._from_found_config(C.git_config_find_xdg) diff --git a/pygit2/credentials.py b/pygit2/credentials.py index cad215a..9f060e2 100644 --- a/pygit2/credentials.py +++ b/pygit2/credentials.py @@ -25,8 +25,10 @@ # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. -# Import from pygit2 -from _pygit2 import GIT_CREDTYPE_USERPASS_PLAINTEXT, GIT_CREDTYPE_SSH_KEY +from .ffi import ffi, C + +GIT_CREDTYPE_USERPASS_PLAINTEXT = C.GIT_CREDTYPE_USERPASS_PLAINTEXT +GIT_CREDTYPE_SSH_KEY = C.GIT_CREDTYPE_SSH_KEY class UserPass(object): """Username/Password credentials diff --git a/pygit2/decl.h b/pygit2/decl.h new file mode 100644 index 0000000..a628f3b --- /dev/null +++ b/pygit2/decl.h @@ -0,0 +1,285 @@ +typedef ... git_repository; +typedef ... git_remote; +typedef ... git_refspec; +typedef ... git_push; +typedef ... git_cred; +typedef ... git_diff_file; +typedef ... git_tree; +typedef ... git_config; +typedef ... git_config_iterator; +typedef ... git_signature; + +#define GIT_OID_RAWSZ ... +#define GIT_PATH_MAX ... + +typedef struct git_oid { + unsigned char id[20]; +} git_oid; + +typedef struct { + char *ptr; + size_t asize, size; +} git_buf; +void git_buf_free(git_buf *buffer); + +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); +void git_repository_free(git_repository *repo); + +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 enum { + GIT_CREDTYPE_USERPASS_PLAINTEXT = ..., + GIT_CREDTYPE_SSH_KEY = ..., + GIT_CREDTYPE_SSH_CUSTOM = ..., + GIT_CREDTYPE_DEFAULT = ..., +} git_credtype_t; + +typedef int (*git_transport_message_cb)(const char *str, int len, void *data); +typedef int (*git_cred_acquire_cb)( + git_cred **cred, + const char *url, + const char *username_from_url, + unsigned int allowed_types, + void *payload); +typedef int (*git_transfer_progress_cb)(const git_transfer_progress *stats, void *payload); + +struct git_remote_callbacks { + unsigned int version; + git_transport_message_cb sideband_progress; + int (*completion)(git_remote_completion_type type, void *data); + git_cred_acquire_cb credentials; + git_transfer_progress_cb transfer_progress; + int (*update_tips)(const char *refname, const git_oid *a, const git_oid *b, void *data); + void *payload; +}; + +typedef struct git_remote_callbacks 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_signature *signature, const char *reflog_message); +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, + const git_signature *signature, + const char *reflog_message); +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(git_buf *buf, const git_refspec *spec, const char *name); +int git_refspec_rtransform(git_buf *buf, const git_refspec *spec, const char *name); + +int git_cred_userpass_plaintext_new( + git_cred **out, + const char *username, + const char *password); +int git_cred_ssh_key_new( + git_cred **out, + const char *username, + 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_options { + 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 *ancestor_label; + const char *our_label; + const char *their_label; +} git_checkout_options; + +typedef struct git_clone_options { + unsigned int version; + + git_checkout_options checkout_opts; + git_remote_callbacks remote_callbacks; + + int bare; + int ignore_cert_errors; + const char *remote_name; + const char* checkout_branch; + git_signature *signature; +} git_clone_options; + +int git_clone(git_repository **out, + const char *url, + const char *local_path, + const git_clone_options *options); + + +typedef enum { + GIT_CONFIG_LEVEL_SYSTEM = 1, + GIT_CONFIG_LEVEL_XDG = 2, + GIT_CONFIG_LEVEL_GLOBAL = 3, + GIT_CONFIG_LEVEL_LOCAL = 4, + GIT_CONFIG_LEVEL_APP = 5, + GIT_CONFIG_HIGHEST_LEVEL = -1, +} git_config_level_t; + +typedef struct { + const char *name; + const char *value; + git_config_level_t level; +} git_config_entry; + +int git_repository_config(git_config **out, git_repository *repo); +void git_config_free(git_config *cfg); + +int git_config_get_string(const char **out, const git_config *cfg, const char *name); +int git_config_set_string(git_config *cfg, const char *name, const char *value); +int git_config_set_bool(git_config *cfg, const char *name, int value); +int git_config_set_int64(git_config *cfg, const char *name, int64_t value); + +int git_config_parse_bool(int *out, const char *value); +int git_config_parse_int64(int64_t *out, const char *value); + +int git_config_delete_entry(git_config *cfg, const char *name); +int git_config_add_file_ondisk(git_config *cfg, + const char *path, + git_config_level_t level, + int force); + +int git_config_iterator_new(git_config_iterator **out, const git_config *cfg); +int git_config_next(git_config_entry **entry, git_config_iterator *iter); +void git_config_iterator_free(git_config_iterator *iter); + +int git_config_multivar_iterator_new(git_config_iterator **out, const git_config *cfg, const char *name, const char *regexp); +int git_config_set_multivar(git_config *cfg, const char *name, const char *regexp, const char *value); + +int git_config_new(git_config **out); +int git_config_open_ondisk(git_config **out, const char *path); +int git_config_find_system(git_buf *out); +int git_config_find_global(git_buf *out); +int git_config_find_xdg(git_buf *out); diff --git a/pygit2/errors.py b/pygit2/errors.py new file mode 100644 index 0000000..398d08c --- /dev/null +++ b/pygit2/errors.py @@ -0,0 +1,58 @@ +# -*- 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, io=False): + 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: + if io: + raise IOError(message) + else: + raise KeyError(message) + elif err == C.GIT_EINVALIDSPEC: + raise ValueError(message) + elif err == C.GIT_ITEROVER: + raise StopIteration() + + raise GitError(message) + diff --git a/pygit2/ffi.py b/pygit2/ffi.py new file mode 100644 index 0000000..468b9bc --- /dev/null +++ b/pygit2/ffi.py @@ -0,0 +1,121 @@ +# -*- 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 +import codecs +from os import path, getenv +from cffi import FFI +import sys + +(major_version, _, _, _, _) = sys.version_info + +if major_version < 3: + def to_str(s, encoding='utf-8', errors='strict'): + if s == ffi.NULL or s == None: + return ffi.NULL + + if isinstance(s, unicode): + encoding = encoding or 'utf-8' + return s.encode(encoding, errors) + + return s +else: + def to_str(s, encoding='utf-8', errors='strict'): + if s == ffi.NULL or s == None: + return ffi.NULL + + if isinstance(s, bytes): + return s + + return s.encode(encoding, errors) + +if major_version < 3: + def is_string(s): + return isinstance(s, basestring) +else: + def is_string(s): + return isinstance(s, str) + +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 []', to_str(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 codecs.open(decl_path, 'r', 'utf-8') as header: + ffi.cdef(header.read()) + +# if LIBGIT2 exists, set build and link against that version +libgit2_path = getenv('LIBGIT2') +include_dirs = [] +library_dirs = [] +if libgit2_path: + include_dirs = [path.join(libgit2_path, 'include')] + library_dirs = [path.join(libgit2_path, 'lib')] + +C = ffi.verify("#include ", libraries=["git2"], include_dirs=include_dirs, library_dirs=library_dirs) diff --git a/pygit2/refspec.py b/pygit2/refspec.py new file mode 100644 index 0000000..5a99d52 --- /dev/null +++ b/pygit2/refspec.py @@ -0,0 +1,96 @@ +# -*- 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, fn): + buf = ffi.new('git_buf *', (ffi.NULL, 0)) + err = fn(buf, self._refspec, to_str(ref)) + check_error(err) + + try: + return ffi.string(buf.ptr).decode() + finally: + C.git_buf_free(buf) + + def transform(self, ref): + """transform(str) -> str + + Transform a reference name according to this refspec from the lhs to the rhs.""" + return self._transform(ref, C.git_refspec_transform) + + def rtransform(self, ref): + """transform(str) -> str + + Transform a reference name according to this refspec from the lhs to the rhs""" + return self._transform(ref, C.git_refspec_rtransform) diff --git a/pygit2/remote.py b/pygit2/remote.py new file mode 100644 index 0000000..890e074 --- /dev/null +++ b/pygit2/remote.py @@ -0,0 +1,413 @@ +# -*- 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 _pygit2 import Oid + +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 + """Total number objects to download""" + + self.indexed_objects = tp.indexed_objects + """Objects which have been indexed""" + + self.received_objects = tp.received_objects + """Objects which have been received up to now""" + + self.local_objects = tp.local_objects + """Local objects which were used to fix the thin pack""" + + self.total_deltas = tp.total_deltas + """Total number of deltas in the pack""" + + self.indexed_deltas = tp.indexed_deltas + """Deltas which have been indexed""" + + self.received_bytes = tp.received_bytes + """"Number of bytes received up to now""" + +class Remote(object): + + def sideband_progress(self, string): + """Progress output callback + + Override this function with your own progress reporting function + + :param str string: Progress otuput from the remote + """ + pass + + def credentials(self, url, username_from_url, allowed_types): + """Credentials callback + + If the remote server requires authentication, this function will + be called and its return value used for authentication. Override + it if you want to be able to perform authentication. + + :param str url: The url of the remote + :param username_from_url: Username extracted from the url, if any + :type username_from_url: str or None + :param int allowed_types: credential types supported by the remote + :rtype: credential + """ + pass + + def transfer_progress(self, stats): + """Transfer progress callback + + Override with your own function to report transfer progress. + + :param TransferProgress stats: The progress up to now + """ + pass + + def update_tips(self, refname, old, new): + """Update tips callabck + + Override with your own function to report reference updates + + :param str refname: the name of the reference that's being updated + :param Oid old: the reference's old value + :param Oid new: the reference's new value + """ + + def __init__(self, repo, ptr): + """The constructor is for internal use only""" + + self._repo = repo + self._remote = ptr + self._stored_exception = None + + # Build the callback structure + callbacks = ffi.new('git_remote_callbacks *') + callbacks.version = 1 + callbacks.sideband_progress = self._sideband_progress_cb + callbacks.transfer_progress = self._transfer_progress_cb + callbacks.update_tips = self._update_tips_cb + callbacks.credentials = self._credentials_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): + """Name of the remote""" + + return maybe_string(C.git_remote_name(self._remote)) + + @name.setter + def name(self, value): + if not value: + raise ValueError("New remote name must be a non-empty string") + + err = C.git_remote_rename(self._remote, to_str(value), ffi.NULL, ffi.NULL) + check_error(err) + + @property + def url(self): + """Url of the remote""" + + 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): + """Push url of the remote""" + + 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): + """save() + + Save a remote to its repository's configuration""" + + err = C.git_remote_save(self._remote) + check_error(err) + + def fetch(self, signature=None, message=None): + """fetch(signature, message) -> TransferProgress + + Perform a fetch against this remote. + """ + + if signature: + ptr = signature._pointer[:] + else: + ptr = ffi.NULL + + self._stored_exception = None + err = C.git_remote_fetch(self._remote, ptr, to_str(message)) + if self._stored_exception: + raise self._stored_exception + + check_error(err) + + return TransferProgress(C.git_remote_stats(self._remote)) + + @property + def refspec_count(self): + """Total number of refspecs in this remote""" + + return C.git_remote_refspec_count(self._remote) + + def get_refspec(self, n): + """get_refspec(n) -> Refspec + + Return the refspec at the given position + """ + spec = C.git_remote_get_refspec(self._remote, n) + return Refspec(self, spec) + + @property + def fetch_refspecs(self): + """Refspecs that will be used for fetching""" + + 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): + """Refspecs that will be used for pushing""" + + 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): + """add_fetch(refspec) + + Add a fetch refspec to the remote""" + + err = C.git_remote_add_fetch(self._remote, to_str(spec)) + + def add_push(self, spec): + """add_push(refspec) + + Add a push refspec to the remote""" + + 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, signature=None, message=None): + """push(refspec, signature, message) + + Push the given refspec to the remote. Raises ``GitError`` on error + + :param str spec: push refspec to use + :param Signature signature: signature to use when updating the tips + :param str message: message to use when updating the tips + """ + + 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) + + if signature: + ptr = signature._pointer[:] + else: + ptr = ffi.NULL + + err = C.git_push_update_tips(push, ptr, to_str(message)) + 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('git_transfer_progress_cb') + def _transfer_progress_cb(stats_ptr, data): + self = ffi.from_handle(data) + + if not hasattr(self, 'transfer_progress') or not self.transfer_progress: + return 0 + + try: + self.transfer_progress(TransferProgress(stats_ptr)) + except Exception as e: + self._stored_exception = e + return C.GIT_EUSER + + return 0 + + @ffi.callback('git_transport_message_cb') + def _sideband_progress_cb(string, length, data): + self = ffi.from_handle(data) + + if not hasattr(self, 'progress') or not self.progress: + return 0 + + try: + s = ffi.string(string, length).decode() + self.progress(s) + except Exception as e: + self._stored_exception = e + return C.GIT_EUSER + + return 0 + + @ffi.callback('int (*update_tips)(const char *refname, const git_oid *a, const git_oid *b, void *data)') + def _update_tips_cb(refname, a, b, data): + self = ffi.from_handle(data) + + if not hasattr(self, 'update_tips') or not self.update_tips: + return 0 + + try: + s = maybe_string(refname) + a = Oid(raw=bytes(ffi.buffer(a)[:])) + b = Oid(raw=bytes(ffi.buffer(b)[:])) + + self.update_tips(s, a, b) + except Exception as e: + self._stored_exception = e + return C.GIT_EUSER + + return 0 + + @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, allowed, data): + self = ffi.from_handle(data) + + if not hasattr(self, 'credentials') or not self.credentials: + return 0 + + try: + ccred = get_credentials(self.credentials, url, username, allowed) + cred_out[0] = ccred[0] + + except Exception as e: + self._stored_exception = e + return C.GIT_EUSER + + return 0 + +def get_credentials(fn, url, username, allowed): + """Call fn and return the credentials object""" + + url_str = maybe_string(url) + username_str = maybe_string(username) + + creds = fn(url_str, username_str, allowed) + + if not hasattr(creds, 'credential_type') or not hasattr(creds, 'credential_tuple'): + raise TypeError("credential does not implement interface") + + cred_type = creds.credential_type + + if not (allowed & cred_type): + raise TypeError("invalid credential type") + + ccred = ffi.new('git_cred **') + if cred_type == C.GIT_CREDTYPE_USERPASS_PLAINTEXT: + name, passwd = creds.credential_tuple + err = C.git_cred_userpass_plaintext_new(ccred, to_str(name), to_str(passwd)) + + elif cred_type == C.GIT_CREDTYPE_SSH_KEY: + name, pubkey, privkey, passphrase = creds.credential_tuple + err = C.git_cred_ssh_key_new(ccred, to_str(name),to_str(pubkey), + to_str(privkey), to_str(passphrase)) + + else: + raise TypeError("unsupported credential type") + + check_error(err) + + return ccred diff --git a/pygit2/repository.py b/pygit2/repository.py index e6c750f..3f18c2c 100644 --- a/pygit2/repository.py +++ b/pygit2/repository.py @@ -35,9 +35,22 @@ 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 +from .config import Config class Repository(_Repository): + def __init__(self, *args, **kwargs): + super(Repository, self).__init__(*args, **kwargs) + + # Get the pointer as the contents of a buffer and store it for + # later access + repo_cptr = ffi.new('git_repository **') + ffi.buffer(repo_cptr)[:] = self._pointer[:] + self._repo = repo_cptr[0] + # # Mapping interface # @@ -59,6 +72,62 @@ 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. + """ + + cremote = ffi.new('git_remote **') + + err = C.git_remote_create(cremote, self._repo, to_str(name), to_str(url)) + check_error(err) + + return Remote(self, cremote[0]) + + @property + def remotes(self): + """Returns all configured remotes""" + + names = ffi.new('git_strarray *') + + try: + err = C.git_remote_list(names, self._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, self._repo, names.strings[i]) + check_error(err) + + l[i] = Remote(self, cremote[0]) + return l + finally: + C.git_strarray_free(names) + + + # + # Configuration + # + @property + def config(self): + """The configuration file for this repository + + If a the configuration hasn't been set yet, the default config for + repository will be returned, including global and system configurations + (if they are available).""" + + cconfig = ffi.new('git_config **') + err = C.git_repository_config(cconfig, self._repo) + check_error(err) + + return Config.from_c(self, cconfig[0]) + # # References # @@ -78,7 +147,7 @@ class Repository(_Repository): Examples:: - repo.create_reference('refs/heads/foo', repo.head.hex) + repo.create_reference('refs/heads/foo', repo.head.target) repo.create_reference('refs/tags/foo', 'refs/heads/master') repo.create_reference('refs/tags/foo', 'bbb78a9cec580') """ diff --git a/pygit2/version.py b/pygit2/version.py index af8676f..e68ec8d 100644 --- a/pygit2/version.py +++ b/pygit2/version.py @@ -23,4 +23,4 @@ # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. -__version__ = '0.20.2' +__version__ = '0.20.3' diff --git a/setup.py b/setup.py index eff54e1..27733f4 100644 --- a/setup.py +++ b/setup.py @@ -173,6 +173,11 @@ classifiers = [ with codecs.open('README.rst', 'r', 'utf-8') as readme: long_description = readme.read() +# This ffi is pygit2.ffi due to the path trick used in the beginning +# of the file +from ffi import ffi +ffi_ext = ffi.verifier.get_extension() + setup(name='pygit2', description='Python bindings for libgit2.', keywords='git', @@ -184,10 +189,13 @@ setup(name='pygit2', maintainer_email='jdavid.ibp@gmail.com', long_description=long_description, packages=['pygit2'], + package_data={'pygit2': ['decl.h']}, + install_requires=['cffi'], ext_modules=[ Extension('_pygit2', pygit2_exts, include_dirs=[libgit2_include, 'include'], library_dirs=[libgit2_lib], libraries=['git2']), + ffi_ext, ], cmdclass=cmdclass) diff --git a/src/blob.c b/src/blob.c index 133d494..11ec3cd 100644 --- a/src/blob.c +++ b/src/blob.c @@ -63,7 +63,7 @@ Blob_diff(Blob *self, PyObject *args, PyObject *kwds) int err; char *keywords[] = {"blob", "flag", "old_as_path", "new_as_path", NULL}; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "|O!ssI", keywords, + if (!PyArg_ParseTupleAndKeywords(args, kwds, "|O!Iss", keywords, &BlobType, &py_blob, &opts.flags, &old_as_path, &new_as_path)) return NULL; @@ -106,7 +106,7 @@ Blob_diff_to_buffer(Blob *self, PyObject *args, PyObject *kwds) char *keywords[] = {"buffer", "flag", "old_as_path", "buffer_as_path", NULL}; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "|s#ssI", keywords, + if (!PyArg_ParseTupleAndKeywords(args, kwds, "|s#Iss", keywords, &buffer, &buffer_len, &opts.flags, &old_as_path, &buffer_as_path)) return NULL; @@ -158,8 +158,58 @@ PyGetSetDef Blob_getseters[] = { {NULL} }; +static int +Blob_getbuffer(Blob *self, Py_buffer *view, int flags) +{ + return PyBuffer_FillInfo(view, (PyObject *) self, + (void *) git_blob_rawcontent(self->blob), + git_blob_rawsize(self->blob), 1, flags); +} -PyDoc_STRVAR(Blob__doc__, "Blob objects."); +#if PY_MAJOR_VERSION == 2 + +static Py_ssize_t +Blob_getreadbuffer(Blob *self, Py_ssize_t index, const void **ptr) +{ + if (index != 0) { + PyErr_SetString(PyExc_SystemError, + "accessing non-existent blob segment"); + return -1; + } + *ptr = (void *) git_blob_rawcontent(self->blob); + return git_blob_rawsize(self->blob); +} + +static Py_ssize_t +Blob_getsegcount(Blob *self, Py_ssize_t *lenp) +{ + if (lenp) + *lenp = git_blob_rawsize(self->blob); + + return 1; +} + +static PyBufferProcs Blob_as_buffer = { + (readbufferproc)Blob_getreadbuffer, + NULL, /* bf_getwritebuffer */ + (segcountproc)Blob_getsegcount, + NULL, /* charbufferproc */ + (getbufferproc)Blob_getbuffer, +}; + +#else + +static PyBufferProcs Blob_as_buffer = { + (getbufferproc)Blob_getbuffer, +}; + +#endif /* python 2 vs python 3 buffers */ + +PyDoc_STRVAR(Blob__doc__, "Blob object.\n" + "\n" + "Blobs implement the buffer interface, which means you can get access\n" + "to its data via `memoryview(blob)` without the need to create a copy." +); PyTypeObject BlobType = { PyVarObject_HEAD_INIT(NULL, 0) @@ -180,8 +230,14 @@ PyTypeObject BlobType = { 0, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ - 0, /* tp_as_buffer */ + &Blob_as_buffer, /* tp_as_buffer */ +#if PY_MAJOR_VERSION == 2 + Py_TPFLAGS_DEFAULT | /* tp_flags */ + Py_TPFLAGS_HAVE_GETCHARBUFFER | + Py_TPFLAGS_HAVE_NEWBUFFER, +#else Py_TPFLAGS_DEFAULT, /* tp_flags */ +#endif Blob__doc__, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ diff --git a/src/config.c b/src/config.c deleted file mode 100644 index 393fded..0000000 --- a/src/config.c +++ /dev/null @@ -1,495 +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 -#include "error.h" -#include "types.h" -#include "utils.h" -#include "config.h" - -extern PyTypeObject ConfigType; -extern PyTypeObject ConfigIterType; - - -PyObject * -wrap_config(char *c_path) { - int err; - PyObject *py_path; - Config *py_config; - - py_path = Py_BuildValue("(s)", c_path); - py_config = PyObject_New(Config, &ConfigType); - - err = Config_init(py_config, py_path, NULL); - if (err < 0) - return NULL; - - return (PyObject*) py_config; -} - - -int -Config_init(Config *self, PyObject *args, PyObject *kwds) -{ - char *path = NULL; - int err; - - if (kwds && PyDict_Size(kwds) > 0) { - PyErr_SetString(PyExc_TypeError, - "Config takes no keyword arguments"); - return -1; - } - - if (!PyArg_ParseTuple(args, "|s", &path)) - return -1; - - if (path == NULL) - err = git_config_new(&self->config); - else - err = git_config_open_ondisk(&self->config, path); - - if (err < 0) { - git_config_free(self->config); - - if (err == GIT_ENOTFOUND) - Error_set_exc(PyExc_IOError); - else - Error_set(err); - - return -1; - } - - return 0; -} - - -void -Config_dealloc(Config *self) -{ - git_config_free(self->config); - Py_TYPE(self)->tp_free(self); -} - -PyDoc_STRVAR(Config_get_global_config__doc__, - "get_global_config() -> Config\n" - "\n" - "Return an object representing the global configuration file."); - -PyObject * -Config_get_global_config(void) -{ - git_buf path = {NULL}; - PyObject *py_config; - int err; - - err = git_config_find_global(&path); - if (err < 0) { - if (err == GIT_ENOTFOUND) { - PyErr_SetString(PyExc_IOError, "Global config file not found."); - return NULL; - } - - return Error_set(err); - } - - py_config = wrap_config(path.ptr); - - git_buf_free(&path); - return py_config; -} - - -PyDoc_STRVAR(Config_get_system_config__doc__, - "get_system_config() -> Config\n" - "\n" - "Return an object representing the system configuration file."); - -PyObject * -Config_get_system_config(void) -{ - git_buf path = {NULL}; - PyObject *py_config; - int err; - - err = git_config_find_system(&path); - if (err < 0) { - if (err == GIT_ENOTFOUND) { - PyErr_SetString(PyExc_IOError, "System config file not found."); - return NULL; - } - return Error_set(err); - } - - py_config = wrap_config(path.ptr); - - git_buf_free(&path); - return py_config; -} - - -int -Config_contains(Config *self, PyObject *py_key) { - int err; - const char *c_value, *c_key; - PyObject *tkey; - - c_key = py_str_borrow_c_str(&tkey, py_key, NULL); - if (c_key == NULL) - return -1; - - err = git_config_get_string(&c_value, self->config, c_key); - Py_DECREF(tkey); - - if (err < 0) { - if (err == GIT_ENOTFOUND) - return 0; - - Error_set(err); - return -1; - } - - return 1; -} - - -PyObject * -Config_getitem(Config *self, PyObject *py_key) -{ - int64_t value_int; - int err, value_bool; - const char *value_str; - const char *key; - PyObject* py_value, *tmp; - - key = py_str_borrow_c_str(&tmp, py_key, NULL); - if (key == NULL) - return NULL; - - err = git_config_get_string(&value_str, self->config, key); - Py_CLEAR(tmp); - if (err < 0) - goto cleanup; - - if (git_config_parse_int64(&value_int, value_str) == 0) - py_value = PyLong_FromLongLong(value_int); - else if(git_config_parse_bool(&value_bool, value_str) == 0) - py_value = PyBool_FromLong(value_bool); - else - py_value = to_unicode(value_str, NULL, NULL); - -cleanup: - if (err < 0) { - if (err == GIT_ENOTFOUND) { - PyErr_SetObject(PyExc_KeyError, py_key); - return NULL; - } - - return Error_set(err); - } - - return py_value; -} - -int -Config_setitem(Config *self, PyObject *py_key, PyObject *py_value) -{ - int err; - const char *key, *value; - PyObject *tkey, *tvalue; - - key = py_str_borrow_c_str(&tkey, py_key, NULL); - if (key == NULL) - return -1; - - if (py_value == NULL) - err = git_config_delete_entry(self->config, key); - else if (PyBool_Check(py_value)) { - err = git_config_set_bool(self->config, key, - (int)PyObject_IsTrue(py_value)); - } else if (PyLong_Check(py_value)) { - err = git_config_set_int64(self->config, key, - (int64_t)PyLong_AsLong(py_value)); - } else { - value = py_str_borrow_c_str(&tvalue, py_value, NULL); - err = git_config_set_string(self->config, key, value); - Py_DECREF(tvalue); - } - - Py_DECREF(tkey); - if (err < 0) { - Error_set(err); - return -1; - } - return 0; -} - -PyDoc_STRVAR(Config_add_file__doc__, - "add_file(path, level=0, force=0)\n" - "\n" - "Add a config file instance to an existing config."); - -PyObject * -Config_add_file(Config *self, PyObject *args, PyObject *kwds) -{ - char *keywords[] = {"path", "level", "force", NULL}; - int err; - char *path; - unsigned int level = 0; - int force = 0; - - if (!PyArg_ParseTupleAndKeywords(args, kwds, "s|Ii", keywords, - &path, &level, &force)) - return NULL; - - err = git_config_add_file_ondisk(self->config, path, level, force); - if (err < 0) - return Error_set_str(err, path); - - Py_RETURN_NONE; -} - - -PyDoc_STRVAR(Config_get_multivar__doc__, - "get_multivar(name[, regex]) -> [str, ...]\n" - "\n" - "Get each value of a multivar ''name'' as a list. The optional ''regex''\n" - "parameter is expected to be a regular expression to filter the variables\n" - "we're interested in."); - -PyObject * -Config_get_multivar(Config *self, PyObject *args) -{ - int err; - PyObject *list; - const char *name = NULL; - const char *regex = NULL; - git_config_iterator *iter; - git_config_entry *entry; - - if (!PyArg_ParseTuple(args, "s|s", &name, ®ex)) - return NULL; - - list = PyList_New(0); - err = git_config_multivar_iterator_new(&iter, self->config, name, regex); - if (err < 0) - return Error_set(err); - - while ((err = git_config_next(&entry, iter)) == 0) { - PyObject *item; - - item = to_unicode(entry->value, NULL, NULL); - if (item == NULL) { - git_config_iterator_free(iter); - return NULL; - } - - PyList_Append(list, item); - Py_CLEAR(item); - } - - git_config_iterator_free(iter); - if (err == GIT_ITEROVER) - err = 0; - - if (err < 0) - return Error_set(err); - - return list; -} - - -PyDoc_STRVAR(Config_set_multivar__doc__, - "set_multivar(name, regex, value)\n" - "\n" - "Set a multivar ''name'' to ''value''. ''regexp'' is a regular expression\n" - "to indicate which values to replace"); - -PyObject * -Config_set_multivar(Config *self, PyObject *args) -{ - int err; - const char *name = NULL; - const char *regex = NULL; - const char *value = NULL; - - if (!PyArg_ParseTuple(args, "sss", &name, ®ex, &value)) - return NULL; - - err = git_config_set_multivar(self->config, name, regex, value); - if (err < 0) { - if (err == GIT_ENOTFOUND) - Error_set(err); - else - PyErr_SetNone(PyExc_TypeError); - return NULL; - } - - Py_RETURN_NONE; -} - -PyObject * -Config_iter(Config *self) -{ - ConfigIter *iter; - int err; - - iter = PyObject_New(ConfigIter, &ConfigIterType); - if (!iter) - return NULL; - - if ((err = git_config_iterator_new(&iter->iter, self->config)) < 0) - return Error_set(err); - - Py_INCREF(self); - iter->owner = self; - - return (PyObject*)iter; -} - -PyMethodDef Config_methods[] = { - METHOD(Config, get_system_config, METH_NOARGS | METH_STATIC), - METHOD(Config, get_global_config, METH_NOARGS | METH_STATIC), - METHOD(Config, add_file, METH_VARARGS | METH_KEYWORDS), - METHOD(Config, get_multivar, METH_VARARGS), - METHOD(Config, set_multivar, METH_VARARGS), - {NULL} -}; - -PySequenceMethods Config_as_sequence = { - 0, /* sq_length */ - 0, /* sq_concat */ - 0, /* sq_repeat */ - 0, /* sq_item */ - 0, /* sq_slice */ - 0, /* sq_ass_item */ - 0, /* sq_ass_slice */ - (objobjproc)Config_contains, /* sq_contains */ -}; - -PyMappingMethods Config_as_mapping = { - 0, /* mp_length */ - (binaryfunc)Config_getitem, /* mp_subscript */ - (objobjargproc)Config_setitem, /* mp_ass_subscript */ -}; - - -PyDoc_STRVAR(Config__doc__, "Configuration management."); - -PyTypeObject ConfigType = { - PyVarObject_HEAD_INIT(NULL, 0) - "_pygit2.Config", /* tp_name */ - sizeof(Config), /* tp_basicsize */ - 0, /* tp_itemsize */ - (destructor)Config_dealloc, /* tp_dealloc */ - 0, /* tp_print */ - 0, /* tp_getattr */ - 0, /* tp_setattr */ - 0, /* tp_compare */ - 0, /* tp_repr */ - 0, /* tp_as_number */ - &Config_as_sequence, /* tp_as_sequence */ - &Config_as_mapping, /* 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, /* tp_flags */ - Config__doc__, /* tp_doc */ - 0, /* tp_traverse */ - 0, /* tp_clear */ - 0, /* tp_richcompare */ - 0, /* tp_weaklistoffset */ - (getiterfunc)Config_iter, /* tp_iter */ - 0, /* tp_iternext */ - Config_methods, /* tp_methods */ - 0, /* tp_members */ - 0, /* tp_getset */ - 0, /* tp_base */ - 0, /* tp_dict */ - 0, /* tp_descr_get */ - 0, /* tp_descr_set */ - 0, /* tp_dictoffset */ - (initproc)Config_init, /* tp_init */ - 0, /* tp_alloc */ - 0, /* tp_new */ -}; - -void -ConfigIter_dealloc(ConfigIter *self) -{ - Py_CLEAR(self->owner); - git_config_iterator_free(self->iter); - PyObject_Del(self); -} - -PyObject * -ConfigIter_iternext(ConfigIter *self) -{ - int err; - git_config_entry *entry; - - if ((err = git_config_next(&entry, self->iter)) < 0) - return Error_set(err); - - return Py_BuildValue("ss", entry->name, entry->value); -} - -PyDoc_STRVAR(ConfigIter__doc__, "Configuration iterator."); - -PyTypeObject ConfigIterType = { - PyVarObject_HEAD_INIT(NULL, 0) - "_pygit2.ConfigIter", /* tp_name */ - sizeof(ConfigIter), /* tp_basicsize */ - 0, /* tp_itemsize */ - (destructor)ConfigIter_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 */ - ConfigIter__doc__, /* tp_doc */ - 0, /* tp_traverse */ - 0, /* tp_clear */ - 0, /* tp_richcompare */ - 0, /* tp_weaklistoffset */ - PyObject_SelfIter, /* tp_iter */ - (iternextfunc)ConfigIter_iternext, /* tp_iternext */ - -}; diff --git a/src/config.h b/src/config.h deleted file mode 100644 index ffa0733..0000000 --- a/src/config.h +++ /dev/null @@ -1,45 +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. - */ - -#ifndef INCLUDE_pygit2_config_h -#define INCLUDE_pygit2_config_h - -#define PY_SSIZE_T_CLEAN -#include -#include - -PyObject* wrap_config(char *c_path); -PyObject* Config_get_global_config(void); -PyObject* Config_get_system_config(void); -PyObject* Config_add_file(Config *self, PyObject *args, PyObject *kwds); -PyObject* Config_getitem(Config *self, PyObject *key); -PyObject* Config_foreach(Config *self, PyObject *args); -PyObject* Config_get_multivar(Config *self, PyObject *args); -PyObject* Config_set_multivar(Config *self, PyObject *args); -int Config_init(Config *self, PyObject *args, PyObject *kwds); -int Config_setitem(Config *self, PyObject *key, PyObject *value); -#endif diff --git a/src/pygit2.c b/src/pygit2.c index 6fedc5d..1b5b5d7 100644 --- a/src/pygit2.c +++ b/src/pygit2.c @@ -56,8 +56,6 @@ extern PyTypeObject IndexType; extern PyTypeObject IndexEntryType; extern PyTypeObject IndexIterType; extern PyTypeObject WalkerType; -extern PyTypeObject ConfigType; -extern PyTypeObject ConfigIterType; extern PyTypeObject ReferenceType; extern PyTypeObject RefLogIterType; extern PyTypeObject RefLogEntryType; @@ -65,7 +63,6 @@ extern PyTypeObject BranchType; extern PyTypeObject SignatureType; extern PyTypeObject RemoteType; extern PyTypeObject RefspecType; -extern PyTypeObject TransferProgressType; extern PyTypeObject NoteType; extern PyTypeObject NoteIterType; extern PyTypeObject BlameType; @@ -105,69 +102,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" @@ -246,8 +180,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__}, @@ -444,25 +376,6 @@ moduleinit(PyObject* m) ADD_CONSTANT_INT(m, GIT_CONFIG_LEVEL_XDG); ADD_CONSTANT_INT(m, GIT_CONFIG_LEVEL_SYSTEM); - INIT_TYPE(ConfigType, NULL, PyType_GenericNew) - INIT_TYPE(ConfigIterType, NULL, NULL) - ADD_TYPE(m, Config) - ADD_TYPE(m, ConfigIter) - - /* Remotes */ - INIT_TYPE(RemoteType, NULL, NULL) - INIT_TYPE(RefspecType, NULL, NULL) - INIT_TYPE(TransferProgressType, NULL, NULL) - ADD_TYPE(m, Remote) - ADD_TYPE(m, Refspec) - ADD_TYPE(m, TransferProgress) - /* Direction for the refspec */ - ADD_CONSTANT_INT(m, GIT_DIRECTION_FETCH) - ADD_CONSTANT_INT(m, GIT_DIRECTION_PUSH) - /* Credential types */ - ADD_CONSTANT_INT(m, GIT_CREDTYPE_USERPASS_PLAINTEXT) - ADD_CONSTANT_INT(m, GIT_CREDTYPE_SSH_KEY) - /* Blame */ INIT_TYPE(BlameType, NULL, NULL) INIT_TYPE(BlameIterType, NULL, NULL) diff --git a/src/refspec.c b/src/refspec.c deleted file mode 100644 index 008aa66..0000000 --- a/src/refspec.c +++ /dev/null @@ -1,275 +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 -#include -#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) -{ - git_buf trans = {NULL}; - const char *str; - int err; - PyObject *py_trans, *tstr; - - str = py_str_borrow_c_str(&tstr, py_str, NULL); - - err = git_refspec_transform(&trans, self->refspec, str); - Py_DECREF(tstr); - - if (err < 0) { - Error_set(err); - return NULL; - } - - py_trans = to_unicode(trans.ptr, NULL, NULL); - - git_buf_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) -{ - git_buf trans = {NULL}; - const char *str; - int err; - PyObject *py_trans, *tstr; - - str = py_str_borrow_c_str(&tstr, py_str, NULL); - - err = git_refspec_rtransform(&trans, self->refspec, str); - Py_DECREF(tstr); - - if (err < 0) { - Error_set(err); - return NULL; - } - - py_trans = to_unicode(trans.ptr, NULL, NULL); - - git_buf_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 */ -}; diff --git a/src/refspec.h b/src/refspec.h deleted file mode 100644 index 829823f..0000000 --- a/src/refspec.h +++ /dev/null @@ -1,38 +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. - */ - -#ifndef INCLUDE_pygit2_refspec_h -#define INCLUDE_pygit2_refspec_h - -#define PY_SSIZE_T_CLEAN -#include -#include -#include - -Refspec* wrap_refspec(const Remote *owner, const git_refspec *refspec); - -#endif diff --git a/src/remote.c b/src/remote.c deleted file mode 100644 index deb832d..0000000 --- a/src/remote.c +++ /dev/null @@ -1,728 +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 -#include -#include "error.h" -#include "types.h" -#include "utils.h" -#include "oid.h" -#include "refspec.h" -#include "remote.h" - - -extern PyObject *GitError; -extern PyTypeObject RepositoryType; -extern PyTypeObject TransferProgressType; - -PyObject * -wrap_transfer_progress(const git_transfer_progress *stats) -{ - TransferProgress *py_stats; - - py_stats = PyObject_New(TransferProgress, &TransferProgressType); - if (!py_stats) - return NULL; - - py_stats->total_objects = stats->total_objects; - py_stats->indexed_objects = stats->indexed_objects; - py_stats->received_objects = stats->received_objects; - py_stats->local_objects = stats->local_objects; - py_stats->total_deltas = stats->total_deltas; - py_stats->indexed_deltas = stats->indexed_deltas; - py_stats->received_bytes = stats->received_bytes; - - return (PyObject *) py_stats; -} - -void -TransferProgress_dealloc(TransferProgress *self) -{ - PyObject_Del(self); -} - -PyMemberDef TransferProgress_members[] = { - RMEMBER(TransferProgress, total_objects, T_UINT, - "Total number objects to download"), - RMEMBER(TransferProgress, indexed_objects, T_UINT, - "Objects which have been indexed"), - RMEMBER(TransferProgress, received_objects, T_UINT, - "Objects which have been received up to now"), - RMEMBER(TransferProgress, local_objects, T_UINT, - "Local objects which were used to fix the thin pack"), - RMEMBER(TransferProgress, total_deltas, T_UINT, - "Total number of deltas in the pack"), - RMEMBER(TransferProgress, indexed_deltas, T_UINT, - "Deltas which have been indexed"), - /* FIXME: technically this is unsigned, but there's no value for size_t - * here. */ - RMEMBER(TransferProgress, received_bytes, T_PYSSIZET, - "Number of bytes received up to now"), - {NULL}, -}; - -PyDoc_STRVAR(TransferProgress__doc__, - "Progress downloading and indexing data during a fetch"); - -PyTypeObject TransferProgressType = { - PyVarObject_HEAD_INIT(NULL, 0) - "_pygit2.TransferProgress", /* tp_name */ - sizeof(TransferProgress), /* tp_basicsize */ - 0, /* tp_itemsize */ - (destructor)TransferProgress_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, /* tp_flags */ - TransferProgress__doc__, /* tp_doc */ - 0, /* tp_traverse */ - 0, /* tp_clear */ - 0, /* tp_richcompare */ - 0, /* tp_weaklistoffset */ - 0, /* tp_iter */ - 0, /* tp_iternext */ - 0, /* tp_methods */ - TransferProgress_members, /* tp_members */ - 0, /* 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 */ -}; - -static int -progress_cb(const char *str, int len, void *data) -{ - Remote *remote = (Remote *) data; - PyObject *arglist, *ret; - - if (remote->progress == NULL) - return 0; - - if (!PyCallable_Check(remote->progress)) { - PyErr_SetString(PyExc_TypeError, "progress callback is not callable"); - return -1; - } - - arglist = Py_BuildValue("(s#)", str, len); - ret = PyObject_CallObject(remote->progress, arglist); - Py_DECREF(arglist); - - if (!ret) - return -1; - - Py_DECREF(ret); - - return 0; -} - -static int -credentials_cb(git_cred **out, const char *url, const char *username_from_url, unsigned int allowed_types, void *data) -{ - Remote *remote = (Remote *) data; - - return callable_to_credentials(out, url, username_from_url, allowed_types, remote->credentials); -} - -static int -transfer_progress_cb(const git_transfer_progress *stats, void *data) -{ - Remote *remote = (Remote *) data; - PyObject *py_stats, *ret; - - if (remote->transfer_progress == NULL) - return 0; - - if (!PyCallable_Check(remote->transfer_progress)) { - PyErr_SetString(PyExc_TypeError, "transfer progress callback is not callable"); - return -1; - } - - py_stats = wrap_transfer_progress(stats); - if (!py_stats) - return -1; - - ret = PyObject_CallFunctionObjArgs(remote->transfer_progress, py_stats, NULL); - Py_DECREF(py_stats); - if (!ret) - return -1; - - Py_DECREF(ret); - - return 0; -} - -static int -update_tips_cb(const char *refname, const git_oid *a, const git_oid *b, void *data) -{ - Remote *remote = (Remote *) data; - PyObject *ret; - PyObject *old, *new; - - if (remote->update_tips == NULL) - return 0; - - if (!PyCallable_Check(remote->update_tips)) { - PyErr_SetString(PyExc_TypeError, "update tips callback is not callable"); - return -1; - } - - old = git_oid_to_python(a); - new = git_oid_to_python(b); - - ret = PyObject_CallFunction(remote->update_tips, "(s,O,O)", refname, old ,new); - - Py_DECREF(old); - Py_DECREF(new); - - if (!ret) - return -1; - - Py_DECREF(ret); - - return 0; -} - -static void -Remote_dealloc(Remote *self) -{ - Py_CLEAR(self->repo); - Py_CLEAR(self->progress); - git_remote_free(self->remote); - PyObject_Del(self); -} - -PyDoc_STRVAR(Remote_name__doc__, "Name of the remote refspec"); - -PyObject * -Remote_name__get__(Remote *self) -{ - return to_unicode(git_remote_name(self->remote), NULL, NULL); -} - -int -Remote_name__set__(Remote *self, PyObject* py_name) -{ - int err; - const char* name; - PyObject *tname; - - name = py_str_borrow_c_str(&tname, py_name, NULL); - if (name != NULL) { - err = git_remote_rename(self->remote, name, NULL, NULL); - Py_DECREF(tname); - - if (err == GIT_OK) - return 0; - - Error_set(err); - } - - return -1; -} - -PyDoc_STRVAR(Remote_fetch_refspecs__doc__, "Fetch refspecs"); - -PyObject * -Remote_fetch_refspecs__get__(Remote *self) -{ - int err; - git_strarray refspecs; - PyObject *new_list; - - err = git_remote_get_fetch_refspecs(&refspecs, self->remote); - if (err != GIT_OK) - return Error_set(err); - - new_list = get_pylist_from_git_strarray(&refspecs); - - git_strarray_free(&refspecs); - return new_list; -} - -int -Remote_fetch_refspecs__set__(Remote *self, PyObject *py_list) -{ - int err; - git_strarray fetch_refspecs; - - if (get_strarraygit_from_pylist(&fetch_refspecs, py_list) < 0) - return -1; - - err = git_remote_set_fetch_refspecs(self->remote, &fetch_refspecs); - git_strarray_free(&fetch_refspecs); - - if (err < 0) { - Error_set(err); - return -1; - } - - return 0; -} - -PyDoc_STRVAR(Remote_push_refspecs__doc__, "Push refspecs"); - -PyObject * -Remote_push_refspecs__get__(Remote *self) -{ - int err; - git_strarray refspecs; - PyObject *new_list; - - err = git_remote_get_push_refspecs(&refspecs, self->remote); - if (err != GIT_OK) - return Error_set(err); - - new_list = get_pylist_from_git_strarray(&refspecs); - - git_strarray_free(&refspecs); - return new_list; -} - -int -Remote_push_refspecs__set__(Remote *self, PyObject *py_list) -{ - int err; - git_strarray push_refspecs; - - if (get_strarraygit_from_pylist(&push_refspecs, py_list) != 0) - return -1; - - err = git_remote_set_push_refspecs(self->remote, &push_refspecs); - git_strarray_free(&push_refspecs); - - if (err < 0) { - Error_set(err); - return -1; - } - - return 0; -} - - -PyDoc_STRVAR(Remote_url__doc__, "Url of the remote"); - - -PyObject * -Remote_url__get__(Remote *self) -{ - const char *url; - - url = git_remote_url(self->remote); - if (!url) - Py_RETURN_NONE; - - return to_unicode(url, NULL, NULL); -} - - -int -Remote_url__set__(Remote *self, PyObject* py_url) -{ - int err; - const char* url = NULL; - PyObject *turl; - - url = py_str_borrow_c_str(&turl, py_url, NULL); - if (url != NULL) { - err = git_remote_set_url(self->remote, url); - Py_DECREF(turl); - - if (err == GIT_OK) - return 0; - - Error_set(err); - } - - return -1; -} - -PyDoc_STRVAR(Remote_push_url__doc__, "Push url of the remote"); - - -PyObject * -Remote_push_url__get__(Remote *self) -{ - const char *url; - - url = git_remote_pushurl(self->remote); - if (!url) - Py_RETURN_NONE; - - return to_unicode(url, NULL, NULL); -} - - -int -Remote_push_url__set__(Remote *self, PyObject* py_url) -{ - int err; - const char* url = NULL; - PyObject *turl; - - url = py_str_borrow_c_str(&turl, py_url, NULL); - if (url != NULL) { - err = git_remote_set_pushurl(self->remote, url); - Py_DECREF(turl); - - if (err == GIT_OK) - return 0; - - Error_set(err); - } - - return -1; -} - - -PyDoc_STRVAR(Remote_refspec_count__doc__, "Number of refspecs."); - -PyObject * -Remote_refspec_count__get__(Remote *self) -{ - size_t count; - - count = git_remote_refspec_count(self->remote); - return PyLong_FromSize_t(count); -} - - -PyDoc_STRVAR(Remote_get_refspec__doc__, - "get_refspec(n) -> (str, str)\n" - "\n" - "Return the refspec at the given position."); - -PyObject * -Remote_get_refspec(Remote *self, PyObject *value) -{ - size_t n; - const git_refspec *refspec; - - n = PyLong_AsSize_t(value); - if (PyErr_Occurred()) - return NULL; - - refspec = git_remote_get_refspec(self->remote, n); - if (refspec == NULL) { - PyErr_SetObject(PyExc_IndexError, value); - return NULL; - } - - return (PyObject*) wrap_refspec(self, refspec); -} - - -PyDoc_STRVAR(Remote_fetch__doc__, - "fetch() -> {'indexed_objects': int, 'received_objects' : int," - " 'received_bytesa' : int}\n" - "\n" - "Negotiate what objects should be downloaded and download the\n" - "packfile with those objects"); - -PyObject * -Remote_fetch(Remote *self, PyObject *args) -{ - PyObject* py_stats = NULL; - const git_transfer_progress *stats; - int err; - - PyErr_Clear(); - err = git_remote_fetch(self->remote, NULL, NULL); - /* - * XXX: We should be checking for GIT_EUSER, but on v0.20, this does not - * make it all the way to us for update_tips - */ - if (err < 0 && PyErr_Occurred()) - return NULL; - if (err < 0) - return Error_set(err); - - stats = git_remote_stats(self->remote); - py_stats = Py_BuildValue("{s:I,s:I,s:n}", - "indexed_objects", stats->indexed_objects, - "received_objects", stats->received_objects, - "received_bytes", stats->received_bytes); - - return (PyObject*) py_stats; -} - - -PyDoc_STRVAR(Remote_save__doc__, - "save()\n\n" - "Save a remote to its repository configuration."); - -PyObject * -Remote_save(Remote *self, PyObject *args) -{ - int err; - - err = git_remote_save(self->remote); - if (err == GIT_OK) { - Py_RETURN_NONE; - } - else { - return Error_set(err); - } -} - - -int -push_status_foreach_callback(const char *ref, const char *msg, void *data) -{ - const char **msg_dst = (const char **)data; - if (msg != NULL && *msg_dst == NULL) - *msg_dst = msg; - return 0; -} - -PyDoc_STRVAR(Remote_push__doc__, - "push(refspec)\n" - "\n" - "Push the given refspec to the remote. Raises ``GitError`` on error."); - -PyObject * -Remote_push(Remote *self, PyObject *args) -{ - git_push *push = NULL; - const char *refspec = NULL; - const char *msg = NULL; - int err; - - if (!PyArg_ParseTuple(args, "s", &refspec)) - return NULL; - - err = git_push_new(&push, self->remote); - if (err < 0) - return Error_set(err); - - err = git_push_add_refspec(push, refspec); - if (err < 0) - goto error; - - err = git_push_finish(push); - if (err < 0) - goto error; - - if (!git_push_unpack_ok(push)) { - git_push_free(push); - PyErr_SetString(GitError, "Remote failed to unpack objects"); - return NULL; - } - - err = git_push_status_foreach(push, push_status_foreach_callback, &msg); - if (err < 0) - goto error; - if (msg != NULL) { - git_push_free(push); - PyErr_SetString(GitError, msg); - return NULL; - } - - err = git_push_update_tips(push, NULL, NULL); - if (err < 0) - goto error; - - git_push_free(push); - Py_RETURN_NONE; - -error: - git_push_free(push); - return Error_set(err); -} - - -PyDoc_STRVAR(Remote_add_push__doc__, - "add_push(refspec)\n" - "\n" - "Add a push refspec to the remote."); - -PyObject * -Remote_add_push(Remote *self, PyObject *args) -{ - git_remote *remote; - char *refspec = NULL; - int err = 0; - - if (!PyArg_ParseTuple(args, "s", &refspec)) - return NULL; - - remote = self->remote; - err = git_remote_add_push(remote, refspec); - if (err < 0) - return Error_set(err); - - Py_RETURN_NONE; -} - - -PyDoc_STRVAR(Remote_add_fetch__doc__, - "add_fetch(refspec)\n" - "\n" - "Add a fetch refspec to the remote."); - -PyObject * -Remote_add_fetch(Remote *self, PyObject *args) -{ - git_remote *remote; - char *refspec = NULL; - int err = 0; - - if (!PyArg_ParseTuple(args, "s", &refspec)) - return NULL; - - remote = self->remote; - err = git_remote_add_fetch(remote, refspec); - if (err < 0) - return Error_set(err); - - Py_RETURN_NONE; -} - -PyMethodDef Remote_methods[] = { - METHOD(Remote, fetch, METH_NOARGS), - METHOD(Remote, save, METH_NOARGS), - METHOD(Remote, get_refspec, METH_O), - METHOD(Remote, push, METH_VARARGS), - METHOD(Remote, add_push, METH_VARARGS), - METHOD(Remote, add_fetch, METH_VARARGS), - {NULL} -}; - -PyGetSetDef Remote_getseters[] = { - GETSET(Remote, name), - GETSET(Remote, url), - GETSET(Remote, push_url), - GETTER(Remote, refspec_count), - GETSET(Remote, fetch_refspecs), - GETSET(Remote, push_refspecs), - {NULL} -}; - -PyMemberDef Remote_members[] = { - MEMBER(Remote, progress, T_OBJECT_EX, "Progress output callback"), - MEMBER(Remote, credentials, T_OBJECT_EX, - "credentials(url, username_from_url, allowed_types) -> credential\n" - "\n" - "Credentials callback\n" - "\n" - "If the remote server requires authentication, this function will\n" - "be called and its return value used for authentication.\n" - "\n" - ":param str url: The url of the remote\n" - ":param username_from_url: Username extracted from the url, if any\n" - ":type username_from_url: str or None\n" - ":param int allowed_types: credential types supported by the remote "), - MEMBER(Remote, transfer_progress, T_OBJECT_EX, "Transfer progress callback"), - MEMBER(Remote, update_tips, T_OBJECT_EX, "update tips callback"), - {NULL}, -}; - -PyDoc_STRVAR(Remote__doc__, "Remote object."); - -PyTypeObject RemoteType = { - PyVarObject_HEAD_INIT(NULL, 0) - "_pygit2.Remote", /* tp_name */ - sizeof(Remote), /* tp_basicsize */ - 0, /* tp_itemsize */ - (destructor)Remote_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, /* tp_flags */ - Remote__doc__, /* tp_doc */ - 0, /* tp_traverse */ - 0, /* tp_clear */ - 0, /* tp_richcompare */ - 0, /* tp_weaklistoffset */ - 0, /* tp_iter */ - 0, /* tp_iternext */ - Remote_methods, /* tp_methods */ - Remote_members, /* tp_members */ - Remote_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 */ -}; - -PyObject * -wrap_remote(git_remote *c_remote, Repository *repo) -{ - Remote *py_remote = NULL; - git_remote_callbacks callbacks = GIT_REMOTE_CALLBACKS_INIT; - - py_remote = PyObject_New(Remote, &RemoteType); - if (py_remote) { - Py_INCREF(repo); - py_remote->repo = repo; - py_remote->remote = c_remote; - py_remote->progress = NULL; - py_remote->credentials = NULL; - py_remote->transfer_progress = NULL; - py_remote->update_tips = NULL; - - callbacks.progress = progress_cb; - callbacks.credentials = credentials_cb; - callbacks.transfer_progress = transfer_progress_cb; - callbacks.update_tips = update_tips_cb; - callbacks.payload = py_remote; - git_remote_set_callbacks(c_remote, &callbacks); - } - - return (PyObject *)py_remote; -} diff --git a/src/remote.h b/src/remote.h deleted file mode 100644 index ce6ee47..0000000 --- a/src/remote.h +++ /dev/null @@ -1,39 +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. - */ - -#ifndef INCLUDE_pygit2_remote_h -#define INCLUDE_pygit2_remote_h - -#define PY_SSIZE_T_CLEAN -#include -#include -#include - -PyObject* Remote_fetch(Remote *self, PyObject *args); -PyObject* wrap_remote(git_remote *c_remote, Repository *repo); - -#endif diff --git a/src/repository.c b/src/repository.c index ef9c8e3..b2409ec 100644 --- a/src/repository.c +++ b/src/repository.c @@ -35,7 +35,6 @@ #include "oid.h" #include "note.h" #include "repository.h" -#include "remote.h" #include "branch.h" #include "blame.h" #include "signature.h" @@ -53,7 +52,6 @@ extern PyTypeObject TreeType; extern PyTypeObject TreeBuilderType; extern PyTypeObject ConfigType; extern PyTypeObject DiffType; -extern PyTypeObject RemoteType; extern PyTypeObject ReferenceType; extern PyTypeObject NoteType; extern PyTypeObject NoteIterType; @@ -514,45 +512,6 @@ Repository_workdir__get__(Repository *self, void *closure) return to_path(c_path); } - -PyDoc_STRVAR(Repository_config__doc__, - "Get the configuration file for this repository.\n" - "\n" - "If a configuration file has not been set, the default config set for the\n" - "repository will be returned, including global and system configurations\n" - "(if they are available)."); - -PyObject * -Repository_config__get__(Repository *self) -{ - int err; - git_config *config; - Config *py_config; - - assert(self->repo); - - if (self->config == NULL) { - err = git_repository_config(&config, self->repo); - if (err < 0) - return Error_set(err); - - py_config = PyObject_New(Config, &ConfigType); - if (py_config == NULL) { - git_config_free(config); - return NULL; - } - - py_config->config = config; - self->config = (PyObject*)py_config; - /* We need 2 refs here. One is returned, one is kept internally. */ - Py_INCREF(self->config); - } else { - Py_INCREF(self->config); - } - - return self->config; -} - PyDoc_STRVAR(Repository_merge_base__doc__, "merge_base(oid, oid) -> Oid\n" "\n" @@ -948,7 +907,7 @@ PyDoc_STRVAR(Repository_create_branch__doc__, "\n" "Examples::\n" "\n" - " repo.create_branch('foo', repo.head.hex, force=False)"); + " repo.create_branch('foo', repo.head.get_object(), force=False)"); PyObject * Repository_create_branch(Repository *self, PyObject *args) @@ -1097,7 +1056,7 @@ Repository_lookup_reference(Repository *self, PyObject *py_name) } PyDoc_STRVAR(Repository_create_reference_direct__doc__, - "git_reference_create(name, target, force) -> Reference\n" + "create_reference_direct(name, target, force) -> Reference\n" "\n" "Create a new reference \"name\" which points to an object.\n" "\n" @@ -1109,7 +1068,7 @@ PyDoc_STRVAR(Repository_create_reference_direct__doc__, "\n" "Examples::\n" "\n" - " repo.git_reference_create('refs/heads/foo', repo.head.hex, False)"); + " repo.create_reference_direct('refs/heads/foo', repo.head.target, False)"); PyObject * Repository_create_reference_direct(Repository *self, PyObject *args, @@ -1136,7 +1095,7 @@ Repository_create_reference_direct(Repository *self, PyObject *args, } PyDoc_STRVAR(Repository_create_reference_symbolic__doc__, - "git_reference_symbolic_create(name, source, force) -> Reference\n" + "create_reference_symbolic(name, source, force) -> Reference\n" "\n" "Create a new reference \"name\" which points to another reference.\n" "\n" @@ -1148,7 +1107,7 @@ PyDoc_STRVAR(Repository_create_reference_symbolic__doc__, "\n" "Examples::\n" "\n" - " repo.git_reference_symbolic_create('refs/tags/foo', 'refs/heads/master', False)"); + " repo.create_reference_symbolic('refs/tags/foo', 'refs/heads/master', False)"); PyObject * Repository_create_reference_symbolic(Repository *self, PyObject *args, @@ -1309,67 +1268,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 * @@ -1384,6 +1282,14 @@ 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) +{ + /* Bytes means a raw buffer */ + return PyBytes_FromStringAndSize((char *) &self->repo, sizeof(git_repository *)); +} + PyDoc_STRVAR(Repository_checkout_head__doc__, "checkout_head(strategy)\n" "\n" @@ -1666,7 +1572,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), @@ -1690,10 +1595,9 @@ PyGetSetDef Repository_getseters[] = { GETTER(Repository, head_is_unborn), GETTER(Repository, is_empty), GETTER(Repository, is_bare), - GETTER(Repository, config), GETTER(Repository, workdir), - GETTER(Repository, remotes), GETTER(Repository, default_signature), + GETTER(Repository, _pointer), {NULL} }; diff --git a/src/signature.c b/src/signature.c index 727178d..e3f7d71 100644 --- a/src/signature.c +++ b/src/signature.c @@ -94,6 +94,13 @@ Signature_dealloc(Signature *self) PyObject_Del(self); } +PyDoc_STRVAR(Signature__pointer__doc__, "Get the signature's pointer. For internal use only."); +PyObject * +Signature__pointer__get__(Repository *self) +{ + /* Bytes means a raw buffer */ + return PyBytes_FromStringAndSize((char *) &self->repo, sizeof(git_repository *)); +} PyDoc_STRVAR(Signature__encoding__doc__, "Encoding."); @@ -171,6 +178,7 @@ PyGetSetDef Signature_getseters[] = { GETTER(Signature, email), GETTER(Signature, time), GETTER(Signature, offset), + GETTER(Signature, _pointer), {NULL} }; diff --git a/src/types.h b/src/types.h index 15ff740..806e589 100644 --- a/src/types.h +++ b/src/types.h @@ -32,6 +32,10 @@ #include #include +#if !(LIBGIT2_VER_MAJOR == 0 && LIBGIT2_VER_MINOR == 20) +#error You need a compatible libgit2 version (v0.20.x) +#endif + /* * Python objects * @@ -70,19 +74,6 @@ SIMPLE_TYPE(Tree, git_tree, tree) SIMPLE_TYPE(Blob, git_blob, blob) SIMPLE_TYPE(Tag, git_tag, tag) - -/* git_config */ -typedef struct { - PyObject_HEAD - git_config* config; -} Config; - -typedef struct { - PyObject_HEAD - Config *owner; - git_config_iterator *iter; -} ConfigIter; - /* git_note */ typedef struct { PyObject_HEAD @@ -194,38 +185,6 @@ typedef struct { char *encoding; } Signature; - -/* git_remote */ -typedef struct { - PyObject_HEAD - Repository *repo; - git_remote *remote; - /* Callbacks for network events */ - PyObject *progress; - PyObject *credentials; - PyObject *transfer_progress; - PyObject *update_tips; -} Remote; - -/* git_refspec */ -typedef struct { - PyObject_HEAD - const Remote *owner; - const git_refspec *refspec; -} Refspec; - -/* git_transfer_progress */ -typedef struct { - PyObject_HEAD - 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; -} TransferProgress; - /* git_blame */ SIMPLE_TYPE(Blame, git_blame, blame) diff --git a/src/utils.c b/src/utils.c index 24b6bbd..44acf5c 100644 --- a/src/utils.c +++ b/src/utils.c @@ -153,92 +153,3 @@ on_error: return -1; } - -static int -py_cred_to_git_cred(git_cred **out, PyObject *py_cred, unsigned int allowed) -{ - PyObject *py_type, *py_tuple; - long type; - int err = -1; - - py_type = PyObject_GetAttrString(py_cred, "credential_type"); - py_tuple = PyObject_GetAttrString(py_cred, "credential_tuple"); - - if (!py_type || !py_tuple) { - printf("py_type %p, py_tuple %p\n", py_type, py_tuple); - PyErr_SetString(PyExc_TypeError, "credential doesn't implement the interface"); - goto cleanup; - } - - if (!PyLong_Check(py_type)) { - PyErr_SetString(PyExc_TypeError, "credential type is not a long"); - goto cleanup; - } - - type = PyLong_AsLong(py_type); - - /* Sanity check, make sure we're given credentials we can use */ - if (!(allowed & type)) { - PyErr_SetString(PyExc_TypeError, "invalid credential type"); - goto cleanup; - } - - switch (type) { - case GIT_CREDTYPE_USERPASS_PLAINTEXT: - { - const char *username, *password; - - if (!PyArg_ParseTuple(py_tuple, "ss", &username, &password)) - goto cleanup; - - err = git_cred_userpass_plaintext_new(out, username, password); - break; - } - case GIT_CREDTYPE_SSH_KEY: - { - const char *username, *pubkey, *privkey, *passphrase; - - if (!PyArg_ParseTuple(py_tuple, "ssss", &username, &pubkey, &privkey, &passphrase)) - goto cleanup; - - err = git_cred_ssh_key_new(out, username, pubkey, privkey, passphrase); - break; - } - default: - PyErr_SetString(PyExc_TypeError, "unsupported credential type"); - break; - } - -cleanup: - Py_XDECREF(py_type); - Py_XDECREF(py_tuple); - - return err; -} - -int -callable_to_credentials(git_cred **out, const char *url, const char *username_from_url, unsigned int allowed_types, PyObject *credentials) -{ - int err; - PyObject *py_cred = NULL, *arglist = NULL; - - if (credentials == NULL || credentials == Py_None) - return 0; - - if (!PyCallable_Check(credentials)) { - PyErr_SetString(PyExc_TypeError, "credentials callback is not callable"); - return -1; - } - - arglist = Py_BuildValue("(szI)", url, username_from_url, allowed_types); - py_cred = PyObject_CallObject(credentials, arglist); - Py_DECREF(arglist); - - if (!py_cred) - return -1; - - err = py_cred_to_git_cred(out, py_cred, allowed_types); - Py_DECREF(py_cred); - - return err; -} diff --git a/src/utils.h b/src/utils.h index 7f95d73..e321ea1 100644 --- a/src/utils.h +++ b/src/utils.h @@ -47,6 +47,7 @@ #undef PyLong_Check #define PyLong_Check PyInt_Check #define PyLong_FromLong PyInt_FromLong + #define PyInteger_Type PyInt_Type #define PyBytes_AS_STRING PyString_AS_STRING #define PyBytes_AsString PyString_AsString #define PyBytes_AsStringAndSize PyString_AsStringAndSize @@ -57,6 +58,7 @@ #define to_path(x) to_bytes(x) #define to_encoding(x) to_bytes(x) #else + #define PyInteger_Type PyLong_Type #define to_path(x) to_unicode(x, Py_FileSystemDefaultEncoding, "strict") #define to_encoding(x) PyUnicode_DecodeASCII(x, strlen(x), "strict") #endif @@ -117,8 +119,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) diff --git a/test/test_blob.py b/test/test_blob.py index da3db3e..9fd1d4f 100644 --- a/test/test_blob.py +++ b/test/test_blob.py @@ -74,6 +74,13 @@ class BlobTest(utils.RepoTestCase): self.assertEqual(BLOB_NEW_CONTENT, blob.data) self.assertEqual(len(BLOB_NEW_CONTENT), blob.size) self.assertEqual(BLOB_NEW_CONTENT, blob.read_raw()) + blob_buffer = memoryview(blob) + self.assertEqual(len(BLOB_NEW_CONTENT), len(blob_buffer)) + self.assertEqual(BLOB_NEW_CONTENT, blob_buffer) + def set_content(): + blob_buffer[:2] = b'hi' + + self.assertRaises(TypeError, set_content) def test_create_blob_fromworkdir(self): diff --git a/test/test_config.py b/test/test_config.py index 9f4d460..9787c3e 100644 --- a/test/test_config.py +++ b/test/test_config.py @@ -74,7 +74,7 @@ class ConfigTest(utils.RepoTestCase): config_read = Config(CONFIG_FILENAME) self.assertTrue('core.bare' in config_read) - self.assertFalse(config_read['core.bare']) + self.assertFalse(config_read.get_bool('core.bare')) self.assertTrue('core.editor' in config_read) self.assertEqual(config_read['core.editor'], 'ed') @@ -88,9 +88,9 @@ class ConfigTest(utils.RepoTestCase): config.add_file(CONFIG_FILENAME, 0) self.assertTrue('this.that' in config) - self.assertTrue(config['this.that']) + self.assertTrue(config.get_bool('this.that')) self.assertTrue('something.other.here' in config) - self.assertFalse(config['something.other.here']) + self.assertFalse(config.get_bool('something.other.here')) def test_read(self): config = self.repo.config @@ -103,11 +103,11 @@ class ConfigTest(utils.RepoTestCase): lambda: config['abc.def']) self.assertTrue('core.bare' in config) - self.assertFalse(config['core.bare']) + self.assertFalse(config.get_bool('core.bare')) self.assertTrue('core.editor' in config) self.assertEqual(config['core.editor'], 'ed') self.assertTrue('core.repositoryformatversion' in config) - self.assertEqual(config['core.repositoryformatversion'], 0) + self.assertEqual(config.get_int('core.repositoryformatversion'), 0) new_file = open(CONFIG_FILENAME, "w") new_file.write("[this]\n\tthat = foobar\n\tthat = foobeer\n") @@ -115,9 +115,10 @@ class ConfigTest(utils.RepoTestCase): config.add_file(CONFIG_FILENAME, 0) self.assertTrue('this.that' in config) - self.assertEqual(len(config.get_multivar('this.that')), 2) - l = config.get_multivar('this.that', 'bar') - self.assertEqual(len(l), 1) + + self.assertEqual(2, len(list(config.get_multivar('this.that')))) + l = list(config.get_multivar('this.that', 'bar')) + self.assertEqual(1, len(l)) self.assertEqual(l[0], 'foobar') def test_write(self): @@ -129,7 +130,7 @@ class ConfigTest(utils.RepoTestCase): self.assertFalse('core.dummy1' in config) config['core.dummy1'] = 42 self.assertTrue('core.dummy1' in config) - self.assertEqual(config['core.dummy1'], 42) + self.assertEqual(config.get_int('core.dummy1'), 42) self.assertFalse('core.dummy2' in config) config['core.dummy2'] = 'foobar' @@ -155,16 +156,16 @@ class ConfigTest(utils.RepoTestCase): config.add_file(CONFIG_FILENAME, 5) self.assertTrue('this.that' in config) l = config.get_multivar('this.that', 'foo.*') - self.assertEqual(len(l), 2) + self.assertEqual(2, len(list(l))) config.set_multivar('this.that', '^.*beer', 'fool') - l = config.get_multivar('this.that', 'fool') + l = list(config.get_multivar('this.that', 'fool')) self.assertEqual(len(l), 1) self.assertEqual(l[0], 'fool') config.set_multivar('this.that', 'foo.*', 'foo-123456') l = config.get_multivar('this.that', 'foo.*') - self.assertEqual(len(l), 2) + self.assertEqual(2, len(list(l))) for i in l: self.assertEqual(i, 'foo-123456') @@ -178,5 +179,12 @@ class ConfigTest(utils.RepoTestCase): self.assertTrue('core.bare' in lst) self.assertTrue(lst['core.bare']) + def test_parsing(self): + self.assertTrue(Config.parse_bool("on")) + self.assertTrue(Config.parse_bool("1")) + + self.assertEqual(5, Config.parse_int("5")) + self.assertEqual(1024, Config.parse_int("1k")) + if __name__ == '__main__': unittest.main() diff --git a/test/test_remote.py b/test/test_remote.py index 797474c..9c95f80 100644 --- a/test/test_remote.py +++ b/test/test_remote.py @@ -65,6 +65,7 @@ class RepositoryTest(utils.RepoTestCase): self.assertEqual('new', remote.name) self.assertRaisesAssign(ValueError, remote, 'name', '') + self.assertRaisesAssign(ValueError, remote, 'name', None) def test_remote_set_url(self): @@ -189,9 +190,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 +202,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]