github.com/whamcloud/lemur@v0.0.0-20190827193804-4655df8a52af/packaging/ci/lambda/GitPullS3/pygit2/remote.py (about)

     1  # -*- coding: utf-8 -*-
     2  #
     3  # Copyright 2010-2015 The pygit2 contributors
     4  #
     5  # This file is free software; you can redistribute it and/or modify
     6  # it under the terms of the GNU General Public License, version 2,
     7  # as published by the Free Software Foundation.
     8  #
     9  # In addition to the permissions in the GNU General Public License,
    10  # the authors give you unlimited permission to link the compiled
    11  # version of this file into combinations with other programs,
    12  # and to distribute those combinations without any restriction
    13  # coming from the use of this file.  (The General Public License
    14  # restrictions do apply in other respects; for example, they cover
    15  # modification of the file, and distribution when not linked into
    16  # a combined executable.)
    17  #
    18  # This file is distributed in the hope that it will be useful, but
    19  # WITHOUT ANY WARRANTY; without even the implied warranty of
    20  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    21  # General Public License for more details.
    22  #
    23  # You should have received a copy of the GNU General Public License
    24  # along with this program; see the file COPYING.  If not, write to
    25  # the Free Software Foundation, 51 Franklin Street, Fifth Floor,
    26  # Boston, MA 02110-1301, USA.
    27  
    28  # Import from the future
    29  from __future__ import absolute_import
    30  
    31  # Import from pygit2
    32  from _pygit2 import Oid
    33  from .errors import check_error, Passthrough
    34  from .ffi import ffi, C
    35  from .refspec import Refspec
    36  from .utils import to_bytes, strarray_to_strings, StrArray
    37  
    38  
    39  def maybe_string(ptr):
    40      if not ptr:
    41          return None
    42  
    43      return ffi.string(ptr).decode()
    44  
    45  
    46  class TransferProgress(object):
    47      """Progress downloading and indexing data during a fetch"""
    48  
    49      def __init__(self, tp):
    50  
    51          self.total_objects = tp.total_objects
    52          """Total number of objects to download"""
    53  
    54          self.indexed_objects = tp.indexed_objects
    55          """Objects which have been indexed"""
    56  
    57          self.received_objects = tp.received_objects
    58          """Objects which have been received up to now"""
    59  
    60          self.local_objects = tp.local_objects
    61          """Local objects which were used to fix the thin pack"""
    62  
    63          self.total_deltas = tp.total_deltas
    64          """Total number of deltas in the pack"""
    65  
    66          self.indexed_deltas = tp.indexed_deltas
    67          """Deltas which have been indexed"""
    68  
    69          self.received_bytes = tp.received_bytes
    70          """"Number of bytes received up to now"""
    71  
    72  
    73  class RemoteCallbacks(object):
    74      """Base class for pygit2 remote callbacks.
    75  
    76      Inherit from this class and override the callbacks which you want to use
    77      in your class, which you can then pass to the network operations.
    78      """
    79  
    80      def __init__(self, credentials=None, certificate=None):
    81          """Initialize some callbacks in-line
    82  
    83          Use this constructor to provide credentials and certificate
    84          callbacks in-line, instead of defining your own class for these ones.
    85  
    86          You can e.g. also pass in one of the credential objects as 'credentials'
    87          instead of creating a function which returns a hard-coded object.
    88          """
    89  
    90          if credentials is not None:
    91              self.credentials = credentials
    92          if certificate is not None:
    93              self.certificate = certificate
    94  
    95      def sideband_progress(self, string):
    96          """Progress output callback
    97  
    98          Override this function with your own progress reporting function
    99  
   100          :param str string: Progress output from the remote
   101          """
   102  
   103      def credentials(self, url, username_from_url, allowed_types):
   104          """Credentials callback
   105  
   106          If the remote server requires authentication, this function will
   107          be called and its return value used for authentication. Override
   108          it if you want to be able to perform authentication.
   109  
   110          Parameters:
   111  
   112          - url (str) -- The url of the remote.
   113  
   114          - username_from_url (str or None) -- Username extracted from the url,
   115            if any.
   116  
   117          - allowed_types (int) -- Credential types supported by the remote.
   118  
   119          Return value: credential
   120          """
   121          raise Passthrough
   122  
   123      def certificate_check(self, certificate, valid, host):
   124          """Certificate callback
   125  
   126          Override with your own function to determine whether the accept
   127          the server's certificate.
   128  
   129          :param None certificate: The certificate. It is currently always None
   130           while we figure out how to represent it cross-platform
   131  
   132          :param bool valid: Whether the TLS/SSH library thinks the certificate
   133           is valid
   134  
   135          :param str host: The hostname we want to connect to
   136  
   137          Return value: True to connect, False to abort
   138          """
   139  
   140          raise Passthrough
   141  
   142      def transfer_progress(self, stats):
   143          """Transfer progress callback
   144  
   145          Override with your own function to report transfer progress.
   146  
   147          :param TransferProgress stats: The progress up to now
   148          """
   149  
   150      def update_tips(self, refname, old, new):
   151          """Update tips callabck
   152  
   153          Override with your own function to report reference updates
   154  
   155          :param str refname: the name of the reference that's being updated
   156          :param Oid old: the reference's old value
   157          :param Oid new: the reference's new value
   158          """
   159  
   160      def push_update_reference(self, refname, message):
   161          """Push update reference callback
   162  
   163          Override with your own function to report the remote's
   164          acceptace or rejection of reference updates.
   165  
   166          :param str refname: the name of the reference (on the remote)
   167          :param str messsage: rejection message from the remote. If None, the update was accepted.
   168          """
   169  
   170      def _fill_fetch_options(self, fetch_opts):
   171          fetch_opts.callbacks.sideband_progress = self._sideband_progress_cb
   172          fetch_opts.callbacks.transfer_progress = self._transfer_progress_cb
   173          fetch_opts.callbacks.update_tips = self._update_tips_cb
   174          fetch_opts.callbacks.credentials = self._credentials_cb
   175          fetch_opts.callbacks.certificate_check = self._certificate_cb
   176          # We need to make sure that this handle stays alive
   177          self._self_handle = ffi.new_handle(self)
   178          fetch_opts.callbacks.payload = self._self_handle
   179  
   180          self._stored_exception = None
   181  
   182      def _fill_push_options(self, push_opts):
   183          push_opts.callbacks.sideband_progress = self._sideband_progress_cb
   184          push_opts.callbacks.transfer_progress = self._transfer_progress_cb
   185          push_opts.callbacks.update_tips = self._update_tips_cb
   186          push_opts.callbacks.credentials = self._credentials_cb
   187          push_opts.callbacks.certificate_check = self._certificate_cb
   188          push_opts.callbacks.push_update_reference = self._push_update_reference_cb
   189          # We need to make sure that this handle stays alive
   190          self._self_handle = ffi.new_handle(self)
   191          push_opts.callbacks.payload = self._self_handle
   192  
   193      # These functions exist to be called by the git_remote as
   194      # callbacks. They proxy the call to whatever the user set
   195  
   196      @ffi.callback('git_transfer_progress_cb')
   197      def _transfer_progress_cb(stats_ptr, data):
   198          self = ffi.from_handle(data)
   199  
   200          transfer_progress = getattr(self, 'transfer_progress', None)
   201          if not transfer_progress:
   202              return 0
   203  
   204          try:
   205              transfer_progress(TransferProgress(stats_ptr))
   206          except Exception as e:
   207              self._stored_exception = e
   208              return C.GIT_EUSER
   209  
   210          return 0
   211  
   212      @ffi.callback('git_transport_message_cb')
   213      def _sideband_progress_cb(string, length, data):
   214          self = ffi.from_handle(data)
   215  
   216          progress = getattr(self, 'progress', None)
   217          if not progress:
   218              return 0
   219  
   220          try:
   221              s = ffi.string(string, length).decode()
   222              progress(s)
   223          except Exception as e:
   224              self._stored_exception = e
   225              return C.GIT_EUSER
   226  
   227          return 0
   228  
   229      @ffi.callback('int (*update_tips)(const char *refname, const git_oid *a,'
   230                    'const git_oid *b, void *data)')
   231      def _update_tips_cb(refname, a, b, data):
   232          self = ffi.from_handle(data)
   233  
   234          update_tips = getattr(self, 'update_tips', None)
   235          if not update_tips:
   236              return 0
   237  
   238          try:
   239              s = maybe_string(refname)
   240              a = Oid(raw=bytes(ffi.buffer(a)[:]))
   241              b = Oid(raw=bytes(ffi.buffer(b)[:]))
   242  
   243              update_tips(s, a, b)
   244          except Exception as e:
   245              self._stored_exception = e
   246              return C.GIT_EUSER
   247  
   248          return 0
   249  
   250      @ffi.callback("int (*push_update_reference)(const char *ref, const char *msg, void *data)")
   251      def _push_update_reference_cb(ref, msg, data):
   252          self = ffi.from_handle(data)
   253  
   254          push_update_reference = getattr(self, 'push_update_reference', None)
   255          if not push_update_reference:
   256              return 0
   257  
   258          try:
   259              refname = ffi.string(ref)
   260              message = maybe_string(msg)
   261              push_update_reference(refname, message)
   262          except Exception as e:
   263              self._stored_exception = e
   264              return C.GIT_EUSER
   265  
   266          return 0
   267  
   268      @ffi.callback('int (*credentials)(git_cred **cred, const char *url,'
   269                    'const char *username_from_url, unsigned int allowed_types,'
   270                    'void *data)')
   271      def _credentials_cb(cred_out, url, username, allowed, data):
   272          self = ffi.from_handle(data)
   273  
   274          credentials = getattr(self, 'credentials', None)
   275          if not credentials:
   276              return 0
   277  
   278          try:
   279              ccred = get_credentials(credentials, url, username, allowed)
   280              cred_out[0] = ccred[0]
   281  
   282          except Exception as e:
   283              if e is Passthrough:
   284                  return C.GIT_PASSTHROUGH
   285  
   286              self._stored_exception = e
   287              return C.GIT_EUSER
   288  
   289          return 0
   290  
   291      @ffi.callback('int (*git_transport_certificate_check_cb)'
   292                    '(git_cert *cert, int valid, const char *host, void *payload)')
   293      def _certificate_cb(cert_i, valid, host, data):
   294          self = ffi.from_handle(data)
   295  
   296          # We want to simulate what should happen if libgit2 supported pass-through for
   297          # this callback. For SSH, 'valid' is always False, because it doesn't look
   298          # at known_hosts, but we do want to let it through in order to do what libgit2 would
   299          # if the callback were not set.
   300          try:
   301              is_ssh = cert_i.cert_type == C.GIT_CERT_HOSTKEY_LIBSSH2
   302  
   303              certificate_check = getattr(self, 'certificate_check', None)
   304              if not certificate_check:
   305                  raise Passthrough
   306  
   307              # python's parsing is deep in the libraries and assumes an OpenSSL-owned cert
   308              val = certificate_check(None, bool(valid), ffi.string(host))
   309              if not val:
   310                  return C.GIT_ECERTIFICATE
   311          except Exception as e:
   312              if e is Passthrough:
   313                  if is_ssh:
   314                      return 0
   315                  elif valid:
   316                      return 0
   317                  else:
   318                      return C.GIT_ECERTIFICATE
   319  
   320              self._stored_exception = e
   321              return C.GIT_EUSER
   322  
   323          return 0
   324  
   325  class Remote(object):
   326      def __init__(self, repo, ptr):
   327          """The constructor is for internal use only"""
   328  
   329          self._repo = repo
   330          self._remote = ptr
   331          self._stored_exception = None
   332  
   333      def __del__(self):
   334          C.git_remote_free(self._remote)
   335  
   336      @property
   337      def name(self):
   338          """Name of the remote"""
   339  
   340          return maybe_string(C.git_remote_name(self._remote))
   341  
   342      @property
   343      def url(self):
   344          """Url of the remote"""
   345  
   346          return maybe_string(C.git_remote_url(self._remote))
   347  
   348      @property
   349      def push_url(self):
   350          """Push url of the remote"""
   351  
   352          return maybe_string(C.git_remote_pushurl(self._remote))
   353  
   354      def save(self):
   355          """Save a remote to its repository's configuration."""
   356  
   357          err = C.git_remote_save(self._remote)
   358          check_error(err)
   359  
   360      def fetch(self, refspecs=None, message=None, callbacks=None):
   361          """Perform a fetch against this remote. Returns a <TransferProgress>
   362          object.
   363          """
   364  
   365          fetch_opts = ffi.new('git_fetch_options *')
   366          err = C.git_fetch_init_options(fetch_opts, C.GIT_FETCH_OPTIONS_VERSION)
   367  
   368          if callbacks is None:
   369              callbacks = RemoteCallbacks()
   370  
   371          callbacks._fill_fetch_options(fetch_opts)
   372  
   373          try:
   374              with StrArray(refspecs) as arr:
   375                  err = C.git_remote_fetch(self._remote, arr, fetch_opts, to_bytes(message))
   376                  if callbacks._stored_exception:
   377                      raise callbacks._stored_exception
   378                  check_error(err)
   379          finally:
   380              callbacks._self_handle = None
   381  
   382          return TransferProgress(C.git_remote_stats(self._remote))
   383  
   384      @property
   385      def refspec_count(self):
   386          """Total number of refspecs in this remote"""
   387  
   388          return C.git_remote_refspec_count(self._remote)
   389  
   390      def get_refspec(self, n):
   391          """Return the <Refspec> object at the given position."""
   392          spec = C.git_remote_get_refspec(self._remote, n)
   393          return Refspec(self, spec)
   394  
   395      @property
   396      def fetch_refspecs(self):
   397          """Refspecs that will be used for fetching"""
   398  
   399          specs = ffi.new('git_strarray *')
   400          err = C.git_remote_get_fetch_refspecs(specs, self._remote)
   401          check_error(err)
   402  
   403          return strarray_to_strings(specs)
   404  
   405      @property
   406      def push_refspecs(self):
   407          """Refspecs that will be used for pushing"""
   408  
   409          specs = ffi.new('git_strarray *')
   410          err = C.git_remote_get_push_refspecs(specs, self._remote)
   411          check_error(err)
   412  
   413          return strarray_to_strings(specs)
   414  
   415      def push(self, specs, callbacks=None):
   416          """Push the given refspec to the remote. Raises ``GitError`` on
   417          protocol error or unpack failure.
   418  
   419          :param [str] specs: push refspecs to use
   420          """
   421          push_opts = ffi.new('git_push_options *')
   422          err = C.git_push_init_options(push_opts, C.GIT_PUSH_OPTIONS_VERSION)
   423  
   424          if callbacks is None:
   425              callbacks = RemoteCallbacks()
   426  
   427          callbacks._fill_push_options(push_opts)
   428          # Build custom callback structure
   429  
   430          try:
   431              with StrArray(specs) as refspecs:
   432                  err = C.git_remote_push(self._remote, refspecs, push_opts)
   433                  check_error(err)
   434          finally:
   435              callbacks._self_handle = None
   436  
   437  def get_credentials(fn, url, username, allowed):
   438      """Call fn and return the credentials object"""
   439  
   440      url_str = maybe_string(url)
   441      username_str = maybe_string(username)
   442  
   443      creds = fn(url_str, username_str, allowed)
   444  
   445      credential_type = getattr(creds, 'credential_type', None)
   446      credential_tuple = getattr(creds, 'credential_tuple', None)
   447      if not credential_type or not credential_tuple:
   448          raise TypeError("credential does not implement interface")
   449  
   450      cred_type = credential_type
   451  
   452      if not (allowed & cred_type):
   453          raise TypeError("invalid credential type")
   454  
   455      ccred = ffi.new('git_cred **')
   456      if cred_type == C.GIT_CREDTYPE_USERPASS_PLAINTEXT:
   457          name, passwd = credential_tuple
   458          err = C.git_cred_userpass_plaintext_new(ccred, to_bytes(name),
   459                                                  to_bytes(passwd))
   460  
   461      elif cred_type == C.GIT_CREDTYPE_SSH_KEY:
   462          name, pubkey, privkey, passphrase = credential_tuple
   463          if pubkey is None and privkey is None:
   464              err = C.git_cred_ssh_key_from_agent(ccred, to_bytes(name))
   465          else:
   466              err = C.git_cred_ssh_key_new(ccred, to_bytes(name),
   467                                           to_bytes(pubkey), to_bytes(privkey),
   468                                           to_bytes(passphrase))
   469      else:
   470          raise TypeError("unsupported credential type")
   471  
   472      check_error(err)
   473  
   474      return ccred
   475  
   476  class RemoteCollection(object):
   477      """Collection of configured remotes
   478  
   479      You can use this class to look up and manage the remotes configured
   480      in a repository.  You can access repositories using index
   481      access. E.g. to look up the "origin" remote, you can use
   482  
   483      >>> repo.remotes["origin"]
   484      """
   485  
   486      def __init__(self, repo):
   487          self._repo = repo;
   488  
   489      def __len__(self):
   490          names = ffi.new('git_strarray *')
   491  
   492          try:
   493              err = C.git_remote_list(names, self._repo._repo)
   494              check_error(err)
   495  
   496              return names.count
   497          finally:
   498              C.git_strarray_free(names)
   499  
   500      def __iter__(self):
   501          names = ffi.new('git_strarray *')
   502  
   503          try:
   504              err = C.git_remote_list(names, self._repo._repo)
   505              check_error(err)
   506  
   507              cremote = ffi.new('git_remote **')
   508              for i in range(names.count):
   509                  err = C.git_remote_lookup(cremote, self._repo._repo, names.strings[i])
   510                  check_error(err)
   511  
   512                  yield Remote(self._repo, cremote[0])
   513          finally:
   514              C.git_strarray_free(names)
   515  
   516      def __getitem__(self, name):
   517          if isinstance(name, int):
   518              return list(self)[name]
   519  
   520          cremote = ffi.new('git_remote **')
   521          err = C.git_remote_lookup(cremote, self._repo._repo, to_bytes(name))
   522          check_error(err)
   523  
   524          return Remote(self._repo, cremote[0])
   525  
   526      def create(self, name, url, fetch=None):
   527          """Create a new remote with the given name and url. Returns a <Remote>
   528          object.
   529  
   530          If 'fetch' is provided, this fetch refspec will be used instead of the default
   531          """
   532  
   533          cremote = ffi.new('git_remote **')
   534  
   535          if fetch:
   536              err = C.git_remote_create_with_fetchspec(cremote, self._repo._repo, to_bytes(name), to_bytes(url), to_bytes(fetch))
   537          else:
   538              err = C.git_remote_create(cremote, self._repo._repo, to_bytes(name), to_bytes(url))
   539  
   540          check_error(err)
   541  
   542          return Remote(self._repo, cremote[0])
   543  
   544      def rename(self, name, new_name):
   545          """Rename a remote in the configuration. The refspecs in standard
   546          format will be renamed.
   547  
   548          Returns a list of fetch refspecs (list of strings) which were not in
   549          the standard format and thus could not be remapped.
   550          """
   551  
   552          if not new_name:
   553              raise ValueError("Current remote name must be a non-empty string")
   554  
   555          if not new_name:
   556              raise ValueError("New remote name must be a non-empty string")
   557  
   558          problems = ffi.new('git_strarray *')
   559          err = C.git_remote_rename(problems, self._repo._repo, to_bytes(name), to_bytes(new_name))
   560          check_error(err)
   561  
   562          ret = strarray_to_strings(problems)
   563          C.git_strarray_free(problems)
   564  
   565          return ret
   566  
   567      def delete(self, name):
   568          """Remove a remote from the configuration
   569  
   570          All remote-tracking branches and configuration settings for the remote will be removed.
   571          """
   572          err = C.git_remote_delete(self._repo._repo, to_bytes(name))
   573          check_error(err)
   574  
   575      def set_url(self, name, url):
   576          """ Set the URL for a remote
   577          """
   578          err = C.git_remote_set_url(self._repo._repo, to_bytes(name), to_bytes(url))
   579          check_error(err)
   580  
   581      def set_push_url(self, name, url):
   582          """Set the push-URL for a remote
   583          """
   584          err = C.git_remote_set_pushurl(self._repo._repo, to_bytes(name), to_bytes(url))
   585          check_error(err)
   586  
   587      def add_fetch(self, name, refspec):
   588          """Add a fetch refspec (str) to the remote
   589          """
   590  
   591          err = C.git_remote_add_fetch(self._repo._repo, to_bytes(name), to_bytes(refspec))
   592          check_error(err)
   593  
   594      def add_push(self, name, refspec):
   595          """Add a push refspec (str) to the remote
   596          """
   597  
   598          err = C.git_remote_add_push(self._repo._repo, to_bytes(name), to_bytes(refspec))
   599          check_error(err)