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