# -*- 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 self.indexed_objects = tp.indexed_objects self.received_objects = tp.received_objects self.local_objects = tp.local_objects self.total_deltas = tp.total_deltas self.indexed_deltas = tp.indexed_deltas self.received_bytes = tp.received_bytes class Remote(object): def __init__(self, repo, ptr): """The constructor is for internal use only""" self._repo = repo self._remote = ptr self._stored_exception = None # Build the callback structure callbacks = ffi.new('git_remote_callbacks *') callbacks.version = 1 callbacks.progress = self._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): return maybe_string(C.git_remote_name(self._remote)) @name.setter def name(self, value): err = C.git_remote_rename(self._remote, to_str(value), ffi.NULL, ffi.NULL) check_error(err) @property def url(self): return maybe_string(C.git_remote_url(self._remote)) @url.setter def url(self, value): err = C.git_remote_set_url(self._remote, to_str(value)) @property def push_url(self): return maybe_string(C.git_remote_pushurl(self._remote)) @push_url.setter def push_url(self, value): err = C.git_remote_set_pushurl(self._remote, to_str(value)) check_error(err) def save(self): err = C.git_remote_save(self._remote) check_error(err) def fetch(self): self._stored_exception = None err = C.git_remote_fetch(self._remote) if self._stored_exception: raise self._stored_exception check_error(err) return TransferProgress(C.git_remote_stats(self._remote)) @property def refspec_count(self): return C.git_remote_refspec_count(self._remote) def get_refspec(self, n): spec = C.git_remote_get_refspec(self._remote, n) return Refspec(self, spec) @property def fetch_refspecs(self): specs = ffi.new('git_strarray *') err = C.git_remote_get_fetch_refspecs(specs, self._remote) check_error(err) return strarray_to_strings(specs) @fetch_refspecs.setter def fetch_refspecs(self, l): arr, refs = strings_to_strarray(l) err = C.git_remote_set_fetch_refspecs(self._remote, arr) check_error(err) @property def push_refspecs(self): specs = ffi.new('git_strarray *') err = C.git_remote_get_push_refspecs(specs, self._remote) check_error(err) return strarray_to_strings(specs) @push_refspecs.setter def push_refspecs(self, l): arr, refs = strings_to_strarray(l) err = C.git_remote_set_push_refspecs(self._remote, arr) check_error(err) def add_fetch(self, spec): err = C.git_remote_add_fetch(self._remote, to_str(spec)) def add_push(self, spec): err = C.git_remote_add_push(self._remote, to_str(spec)) @ffi.callback("int (*cb)(const char *ref, const char *msg, void *data)") def _push_cb(ref, msg, data): self = ffi.from_handle(data) if msg: self._bad_message = ffi.string(msg).decode() return 0 def push(self, spec): cpush = ffi.new('git_push **') err = C.git_push_new(cpush, self._remote) check_error(err) push = cpush[0] try: err = C.git_push_add_refspec(push, to_str(spec)) check_error(err) err = C.git_push_finish(push) check_error(err) if not C.git_push_unpack_ok(push): raise GitError("remote failed to unpack objects") err = C.git_push_status_foreach(push, self._push_cb, ffi.new_handle(self)) check_error(err) if hasattr(self, '_bad_message'): raise GitError(self._bad_message) err = C.git_push_update_tips(push) check_error(err) finally: C.git_push_free(push) # These functions exist to be called by the git_remote as # callbacks. They proxy the call to whatever the user set @ffi.callback('int (*transfer_progress)(const git_transfer_progress *stats, void *data)') def _transfer_progress_cb(stats_ptr, data): self = ffi.from_handle(data) if not hasattr(self, 'transfer_progress'): return 0 try: self.transfer_progress(TransferProgress(stats_ptr)) except Exception, e: self._stored_exception = e return C.GIT_EUSER return 0 @ffi.callback('int (*progress)(const char *str, int len, void *data)') def _progress_cb(string, length, data): self = ffi.from_handle(data) if not hasattr(self, 'progress'): return 0 try: s = ffi.string(string, length).decode() self.progress(s) except Exception, 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'): 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, 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'): return 0 try: url_str = maybe_string(url) username_str = maybe_string(username) creds = self.credentials(url_str, username_str, allowed) if not hasattr(creds, 'credential_type') or not hasattr(creds, 'credential_tuple'): raise TypeError("credential does not implement interface") cred_type = creds.credential_type if not (allowed & cred_type): raise TypeError("invalid credential type") ccred = ffi.new('git_cred **') if cred_type == C.GIT_CREDTYPE_USERPASS_PLAINTEXT: name, passwd = creds.credential_tuple err = C.git_cred_userpass_plaintext_new(ccred, to_str(name), to_str(passwd)) elif cred_type == C.GIT_CREDTYPE_SSH_KEY: name, pubkey, privkey, passphrase = creds.credential_tuple err = C.git_cred_ssh_key_new(ccred, to_str(name),to_str(pubkey), to_str(privkey), to_str(passphrase)) else: raise TypeError("unsupported credential type") check_error(err) cred_out[0] = ccred[0] except Exception, e: self._stored_exception = e return C.GIT_EUSER return 0