github.com/whamcloud/lemur@v0.0.0-20190827193804-4655df8a52af/packaging/ci/lambda/GitPullS3/pygit2/index.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, unicode_literals
    30  
    31  # Import from pygit2
    32  from _pygit2 import Oid, Tree, Diff
    33  from .errors import check_error
    34  from .ffi import ffi, C
    35  from .utils import is_string, to_bytes, to_str
    36  from .utils import GenericIterator, StrArray
    37  
    38  
    39  class Index(object):
    40  
    41      def __init__(self, path=None):
    42          """Create a new Index
    43  
    44          If path is supplied, the read and write methods will use that path
    45          to read from and write to.
    46          """
    47          cindex = ffi.new('git_index **')
    48          err = C.git_index_open(cindex, to_bytes(path))
    49          check_error(err)
    50  
    51          self._repo = None
    52          self._index = cindex[0]
    53          self._cindex = cindex
    54  
    55      @classmethod
    56      def from_c(cls, repo, ptr):
    57          index = cls.__new__(cls)
    58          index._repo = repo
    59          index._index = ptr[0]
    60          index._cindex = ptr
    61  
    62          return index
    63  
    64      @property
    65      def _pointer(self):
    66          return bytes(ffi.buffer(self._cindex)[:])
    67  
    68      def __del__(self):
    69          C.git_index_free(self._index)
    70  
    71      def __len__(self):
    72          return C.git_index_entrycount(self._index)
    73  
    74      def __contains__(self, path):
    75          err = C.git_index_find(ffi.NULL, self._index, to_bytes(path))
    76          if err == C.GIT_ENOTFOUND:
    77              return False
    78  
    79          check_error(err)
    80          return True
    81  
    82      def __getitem__(self, key):
    83          centry = ffi.NULL
    84          if is_string(key):
    85              centry = C.git_index_get_bypath(self._index, to_bytes(key), 0)
    86          elif not key >= 0:
    87              raise ValueError(key)
    88          else:
    89              centry = C.git_index_get_byindex(self._index, key)
    90  
    91          if centry == ffi.NULL:
    92              raise KeyError(key)
    93  
    94          return IndexEntry._from_c(centry)
    95  
    96      def __iter__(self):
    97          return GenericIterator(self)
    98  
    99      def read(self, force=True):
   100          """Update the contents the Index
   101  
   102          Update the contents by reading from a file
   103  
   104          Arguments:
   105  
   106          force: if True (the default) allways reload. If False, only if
   107          the file has changed
   108          """
   109  
   110          err = C.git_index_read(self._index, force)
   111          check_error(err, True)
   112  
   113      def write(self):
   114          """Write the contents of the Index to disk."""
   115          err = C.git_index_write(self._index)
   116          check_error(err, True)
   117  
   118      def clear(self):
   119          err = C.git_index_clear(self._index)
   120          check_error(err)
   121  
   122      def read_tree(self, tree):
   123          """Replace the contents of the Index with those of the given tree,
   124          expressed either as a <Tree> object or as an oid (string or <Oid>).
   125  
   126          The tree will be read recursively and all its children will also be
   127          inserted into the Index.
   128          """
   129          repo = self._repo
   130          if is_string(tree):
   131              tree = repo[tree]
   132  
   133          if isinstance(tree, Oid):
   134              if repo is None:
   135                  raise TypeError("id given but no associated repository")
   136  
   137              tree = repo[tree]
   138          elif not isinstance(tree, Tree):
   139              raise TypeError("argument must be Oid or Tree")
   140  
   141          tree_cptr = ffi.new('git_tree **')
   142          ffi.buffer(tree_cptr)[:] = tree._pointer[:]
   143          err = C.git_index_read_tree(self._index, tree_cptr[0])
   144          check_error(err)
   145  
   146      def write_tree(self, repo=None):
   147          """Create a tree out of the Index. Return the <Oid> object of the
   148          written tree.
   149  
   150          The contents of the index will be written out to the object
   151          database. If there is no associated repository, 'repo' must be
   152          passed. If there is an associated repository and 'repo' is
   153          passed, then that repository will be used instead.
   154  
   155          It returns the id of the resulting tree.
   156          """
   157          coid = ffi.new('git_oid *')
   158          if repo:
   159              err = C.git_index_write_tree_to(coid, self._index, repo._repo)
   160          else:
   161              err = C.git_index_write_tree(coid, self._index)
   162  
   163          check_error(err)
   164          return Oid(raw=bytes(ffi.buffer(coid)[:]))
   165  
   166      def remove(self, path, level=0):
   167          """Remove an entry from the Index.
   168          """
   169          err = C.git_index_remove(self._index, to_bytes(path), level)
   170          check_error(err, True)
   171  
   172      def add_all(self, pathspecs=[]):
   173          """Add or update index entries matching files in the working directory.
   174  
   175          If pathspecs are specified, only files matching those pathspecs will
   176          be added.
   177          """
   178          with StrArray(pathspecs) as arr:
   179              err = C.git_index_add_all(self._index, arr, 0, ffi.NULL, ffi.NULL)
   180              check_error(err, True)
   181  
   182      def add(self, path_or_entry):
   183          """Add or update an entry in the Index.
   184  
   185          If a path is given, that file will be added. The path must be relative
   186          to the root of the worktree and the Index must be associated with a
   187          repository.
   188  
   189          If an IndexEntry is given, that entry will be added or update in the
   190          Index without checking for the existence of the path or id.
   191          """
   192  
   193          if is_string(path_or_entry):
   194              path = path_or_entry
   195              err = C.git_index_add_bypath(self._index, to_bytes(path))
   196          elif isinstance(path_or_entry, IndexEntry):
   197              entry = path_or_entry
   198              centry, str_ref = entry._to_c()
   199              err = C.git_index_add(self._index, centry)
   200          else:
   201              raise AttributeError('argument must be string or IndexEntry')
   202  
   203          check_error(err, True)
   204  
   205      def diff_to_workdir(self, flags=0, context_lines=3, interhunk_lines=0):
   206          """Diff the index against the working directory. Return a <Diff> object
   207          with the differences between the index and the working copy.
   208  
   209          Arguments:
   210  
   211          flags: a GIT_DIFF_* constant.
   212  
   213          context_lines: the number of unchanged lines that define the
   214          boundary of a hunk (and to display before and after)
   215  
   216          interhunk_lines: the maximum number of unchanged lines between hunk
   217          boundaries before the hunks will be merged into a one
   218          """
   219          repo = self._repo
   220          if repo is None:
   221              raise ValueError('diff needs an associated repository')
   222  
   223          copts = ffi.new('git_diff_options *')
   224          err = C.git_diff_init_options(copts, 1)
   225          check_error(err)
   226  
   227          copts.flags = flags
   228          copts.context_lines = context_lines
   229          copts.interhunk_lines = interhunk_lines
   230  
   231          cdiff = ffi.new('git_diff **')
   232          err = C.git_diff_index_to_workdir(cdiff, repo._repo, self._index,
   233                                            copts)
   234          check_error(err)
   235  
   236          return Diff.from_c(bytes(ffi.buffer(cdiff)[:]), repo)
   237  
   238      def diff_to_tree(self, tree, flags=0, context_lines=3, interhunk_lines=0):
   239          """Diff the index against a tree.  Return a <Diff> object with the
   240          differences between the index and the given tree.
   241  
   242          Arguments:
   243  
   244          tree: the tree to diff.
   245  
   246          flags: a GIT_DIFF_* constant.
   247  
   248          context_lines: the number of unchanged lines that define the boundary
   249          of a hunk (and to display before and after)
   250  
   251          interhunk_lines: the maximum number of unchanged lines between hunk
   252          boundaries before the hunks will be merged into a one.
   253          """
   254          repo = self._repo
   255          if repo is None:
   256              raise ValueError('diff needs an associated repository')
   257  
   258          if not isinstance(tree, Tree):
   259              raise TypeError('tree must be a Tree')
   260  
   261          copts = ffi.new('git_diff_options *')
   262          err = C.git_diff_init_options(copts, 1)
   263          check_error(err)
   264  
   265          copts.flags = flags
   266          copts.context_lines = context_lines
   267          copts.interhunk_lines = interhunk_lines
   268  
   269          ctree = ffi.new('git_tree **')
   270          ffi.buffer(ctree)[:] = tree._pointer[:]
   271  
   272          cdiff = ffi.new('git_diff **')
   273          err = C.git_diff_tree_to_index(cdiff, repo._repo, ctree[0],
   274                                         self._index, copts)
   275          check_error(err)
   276  
   277          return Diff.from_c(bytes(ffi.buffer(cdiff)[:]), repo)
   278  
   279  
   280      #
   281      # Conflicts
   282      #
   283      _conflicts = None
   284  
   285      @property
   286      def conflicts(self):
   287          """A collection of conflict information
   288  
   289          If there are no conflicts None is returned. Otherwise return an object
   290          that represents the conflicts in the index.
   291  
   292          This object presents a mapping interface with the paths as keys. You
   293          can use the ``del`` operator to remove a conflict form the Index.
   294  
   295          Each conflict is made up of three elements. Access or iteration
   296          of the conflicts returns a three-tuple of
   297          :py:class:`~pygit2.IndexEntry`. The first is the common
   298          ancestor, the second is the "ours" side of the conflict and the
   299          thirs is the "theirs" side.
   300  
   301          These elements may be None depending on which sides exist for
   302          the particular conflict.
   303          """
   304          if not C.git_index_has_conflicts(self._index):
   305              self._conflicts = None
   306              return None
   307  
   308          if self._conflicts is None:
   309              self._conflicts = ConflictCollection(self)
   310  
   311          return self._conflicts
   312  
   313  
   314  class IndexEntry(object):
   315      __slots__ = ['id', 'path', 'mode']
   316  
   317      def __init__(self, path, object_id, mode):
   318          self.path = path
   319          """The path of this entry"""
   320          self.id = object_id
   321          """The id of the referenced object"""
   322          self.mode = mode
   323          """The mode of this entry, a GIT_FILEMODE_* value"""
   324  
   325      @property
   326      def oid(self):
   327          # For backwards compatibility
   328          return self.id
   329  
   330      @property
   331      def hex(self):
   332          """The id of the referenced object as a hex string"""
   333          return self.id.hex
   334  
   335      def _to_c(self):
   336          """Convert this entry into the C structure
   337  
   338          The first returned arg is the pointer, the second is the reference to
   339          the string we allocated, which we need to exist past this function
   340          """
   341          centry = ffi.new('git_index_entry *')
   342          # basically memcpy()
   343          ffi.buffer(ffi.addressof(centry, 'id'))[:] = self.id.raw[:]
   344          centry.mode = self.mode
   345          path = ffi.new('char[]', to_bytes(self.path))
   346          centry.path = path
   347  
   348          return centry, path
   349  
   350      @classmethod
   351      def _from_c(cls, centry):
   352          if centry == ffi.NULL:
   353              return None
   354  
   355          entry = cls.__new__(cls)
   356          entry.path = to_str(ffi.string(centry.path))
   357          entry.mode = centry.mode
   358          entry.id = Oid(raw=bytes(ffi.buffer(ffi.addressof(centry, 'id'))[:]))
   359  
   360          return entry
   361  
   362  
   363  class ConflictCollection(object):
   364  
   365      def __init__(self, index):
   366          self._index = index
   367  
   368      def __getitem__(self, path):
   369          cancestor = ffi.new('git_index_entry **')
   370          cours = ffi.new('git_index_entry **')
   371          ctheirs = ffi.new('git_index_entry **')
   372  
   373          err = C.git_index_conflict_get(cancestor, cours, ctheirs,
   374                                         self._index._index, to_bytes(path))
   375          check_error(err)
   376  
   377          ancestor = IndexEntry._from_c(cancestor[0])
   378          ours = IndexEntry._from_c(cours[0])
   379          theirs = IndexEntry._from_c(ctheirs[0])
   380  
   381          return ancestor, ours, theirs
   382  
   383      def __delitem__(self, path):
   384          err = C.git_index_conflict_remove(self._index._index, to_bytes(path))
   385          check_error(err)
   386  
   387      def __iter__(self):
   388          return ConflictIterator(self._index)
   389  
   390  
   391  class ConflictIterator(object):
   392  
   393      def __init__(self, index):
   394          citer = ffi.new('git_index_conflict_iterator **')
   395          err = C.git_index_conflict_iterator_new(citer, index._index)
   396          check_error(err)
   397          self._index = index
   398          self._iter = citer[0]
   399  
   400      def __del__(self):
   401          C.git_index_conflict_iterator_free(self._iter)
   402  
   403      def next(self):
   404          return self.__next__()
   405  
   406      def __next__(self):
   407          cancestor = ffi.new('git_index_entry **')
   408          cours = ffi.new('git_index_entry **')
   409          ctheirs = ffi.new('git_index_entry **')
   410  
   411          err = C.git_index_conflict_next(cancestor, cours, ctheirs, self._iter)
   412          if err == C.GIT_ITEROVER:
   413              raise StopIteration
   414  
   415          check_error(err)
   416  
   417          ancestor = IndexEntry._from_c(cancestor[0])
   418          ours = IndexEntry._from_c(cours[0])
   419          theirs = IndexEntry._from_c(ctheirs[0])
   420  
   421          return ancestor, ours, theirs