gvsig-scripting / org.gvsig.scripting / trunk / org.gvsig.scripting / org.gvsig.scripting.app / org.gvsig.scripting.app.mainplugin / src / main / resources-plugin / scripting / lib / dulwich / refs.py @ 959
History | View | Annotate | Download (26 KB)
1 |
# refs.py -- For dealing with git refs
|
---|---|
2 |
# Copyright (C) 2008-2013 Jelmer Vernooij <jelmer@samba.org>
|
3 |
#
|
4 |
# Dulwich is dual-licensed under the Apache License, Version 2.0 and the GNU
|
5 |
# General Public License as public by the Free Software Foundation; version 2.0
|
6 |
# or (at your option) any later version. You can redistribute it and/or
|
7 |
# modify it under the terms of either of these two licenses.
|
8 |
#
|
9 |
# Unless required by applicable law or agreed to in writing, software
|
10 |
# distributed under the License is distributed on an "AS IS" BASIS,
|
11 |
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12 |
# See the License for the specific language governing permissions and
|
13 |
# limitations under the License.
|
14 |
#
|
15 |
# You should have received a copy of the licenses; if not, see
|
16 |
# <http://www.gnu.org/licenses/> for a copy of the GNU General Public License
|
17 |
# and <http://www.apache.org/licenses/LICENSE-2.0> for a copy of the Apache
|
18 |
# License, Version 2.0.
|
19 |
#
|
20 |
|
21 |
|
22 |
"""Ref handling.
|
23 |
|
24 |
"""
|
25 |
import errno |
26 |
import os |
27 |
import sys |
28 |
|
29 |
from dulwich.errors import ( |
30 |
PackedRefsException, |
31 |
RefFormatError, |
32 |
) |
33 |
from dulwich.objects import ( |
34 |
git_line, |
35 |
valid_hexsha, |
36 |
ZERO_SHA, |
37 |
) |
38 |
from dulwich.file import ( |
39 |
GitFile, |
40 |
ensure_dir_exists, |
41 |
) |
42 |
|
43 |
|
44 |
SYMREF = b'ref: '
|
45 |
LOCAL_BRANCH_PREFIX = b'refs/heads/'
|
46 |
BAD_REF_CHARS = set(b'\177 ~^:?*[') |
47 |
ANNOTATED_TAG_SUFFIX = b'^{}'
|
48 |
|
49 |
|
50 |
def check_ref_format(refname): |
51 |
"""Check if a refname is correctly formatted.
|
52 |
|
53 |
Implements all the same rules as git-check-ref-format[1].
|
54 |
|
55 |
[1] http://www.kernel.org/pub/software/scm/git/docs/git-check-ref-format.html
|
56 |
|
57 |
:param refname: The refname to check
|
58 |
:return: True if refname is valid, False otherwise
|
59 |
"""
|
60 |
# These could be combined into one big expression, but are listed separately
|
61 |
# to parallel [1].
|
62 |
if b'/.' in refname or refname.startswith(b'.'): |
63 |
return False |
64 |
if b'/' not in refname: |
65 |
return False |
66 |
if b'..' in refname: |
67 |
return False |
68 |
for i, c in enumerate(refname): |
69 |
if ord(refname[i:i+1]) < 0o40 or c in BAD_REF_CHARS: |
70 |
return False |
71 |
if refname[-1] in b'/.': |
72 |
return False |
73 |
if refname.endswith(b'.lock'): |
74 |
return False |
75 |
if b'@{' in refname: |
76 |
return False |
77 |
if b'\\' in refname: |
78 |
return False |
79 |
return True |
80 |
|
81 |
|
82 |
class RefsContainer(object): |
83 |
"""A container for refs."""
|
84 |
|
85 |
def set_symbolic_ref(self, name, other): |
86 |
"""Make a ref point at another ref.
|
87 |
|
88 |
:param name: Name of the ref to set
|
89 |
:param other: Name of the ref to point at
|
90 |
"""
|
91 |
raise NotImplementedError(self.set_symbolic_ref) |
92 |
|
93 |
def get_packed_refs(self): |
94 |
"""Get contents of the packed-refs file.
|
95 |
|
96 |
:return: Dictionary mapping ref names to SHA1s
|
97 |
|
98 |
:note: Will return an empty dictionary when no packed-refs file is
|
99 |
present.
|
100 |
"""
|
101 |
raise NotImplementedError(self.get_packed_refs) |
102 |
|
103 |
def get_peeled(self, name): |
104 |
"""Return the cached peeled value of a ref, if available.
|
105 |
|
106 |
:param name: Name of the ref to peel
|
107 |
:return: The peeled value of the ref. If the ref is known not point to a
|
108 |
tag, this will be the SHA the ref refers to. If the ref may point to
|
109 |
a tag, but no cached information is available, None is returned.
|
110 |
"""
|
111 |
return None |
112 |
|
113 |
def import_refs(self, base, other): |
114 |
for name, value in other.items(): |
115 |
self[b'/'.join((base, name))] = value |
116 |
|
117 |
def allkeys(self): |
118 |
"""All refs present in this container."""
|
119 |
raise NotImplementedError(self.allkeys) |
120 |
|
121 |
def keys(self, base=None): |
122 |
"""Refs present in this container.
|
123 |
|
124 |
:param base: An optional base to return refs under.
|
125 |
:return: An unsorted set of valid refs in this container, including
|
126 |
packed refs.
|
127 |
"""
|
128 |
if base is not None: |
129 |
return self.subkeys(base) |
130 |
else:
|
131 |
return self.allkeys() |
132 |
|
133 |
def subkeys(self, base): |
134 |
"""Refs present in this container under a base.
|
135 |
|
136 |
:param base: The base to return refs under.
|
137 |
:return: A set of valid refs in this container under the base; the base
|
138 |
prefix is stripped from the ref names returned.
|
139 |
"""
|
140 |
keys = set()
|
141 |
base_len = len(base) + 1 |
142 |
for refname in self.allkeys(): |
143 |
if refname.startswith(base):
|
144 |
keys.add(refname[base_len:]) |
145 |
return keys
|
146 |
|
147 |
def as_dict(self, base=None): |
148 |
"""Return the contents of this container as a dictionary.
|
149 |
|
150 |
"""
|
151 |
ret = {} |
152 |
keys = self.keys(base)
|
153 |
if base is None: |
154 |
base = b''
|
155 |
else:
|
156 |
base = base.rstrip(b'/')
|
157 |
for key in keys: |
158 |
try:
|
159 |
ret[key] = self[(base + b'/' + key).strip(b'/')] |
160 |
except KeyError: |
161 |
continue # Unable to resolve |
162 |
|
163 |
return ret
|
164 |
|
165 |
def _check_refname(self, name): |
166 |
"""Ensure a refname is valid and lives in refs or is HEAD.
|
167 |
|
168 |
HEAD is not a valid refname according to git-check-ref-format, but this
|
169 |
class needs to be able to touch HEAD. Also, check_ref_format expects
|
170 |
refnames without the leading 'refs/', but this class requires that
|
171 |
so it cannot touch anything outside the refs dir (or HEAD).
|
172 |
|
173 |
:param name: The name of the reference.
|
174 |
:raises KeyError: if a refname is not HEAD or is otherwise not valid.
|
175 |
"""
|
176 |
if name in (b'HEAD', b'refs/stash'): |
177 |
return
|
178 |
if not name.startswith(b'refs/') or not check_ref_format(name[5:]): |
179 |
raise RefFormatError(name)
|
180 |
|
181 |
def read_ref(self, refname): |
182 |
"""Read a reference without following any references.
|
183 |
|
184 |
:param refname: The name of the reference
|
185 |
:return: The contents of the ref file, or None if it does
|
186 |
not exist.
|
187 |
"""
|
188 |
contents = self.read_loose_ref(refname)
|
189 |
if not contents: |
190 |
contents = self.get_packed_refs().get(refname, None) |
191 |
return contents
|
192 |
|
193 |
def read_loose_ref(self, name): |
194 |
"""Read a loose reference and return its contents.
|
195 |
|
196 |
:param name: the refname to read
|
197 |
:return: The contents of the ref file, or None if it does
|
198 |
not exist.
|
199 |
"""
|
200 |
raise NotImplementedError(self.read_loose_ref) |
201 |
|
202 |
def follow(self, name): |
203 |
"""Follow a reference name.
|
204 |
|
205 |
:return: a tuple of (refnames, sha), wheres refnames are the names of
|
206 |
references in the chain
|
207 |
"""
|
208 |
contents = SYMREF + name |
209 |
depth = 0
|
210 |
refnames = [] |
211 |
while contents.startswith(SYMREF):
|
212 |
refname = contents[len(SYMREF):]
|
213 |
refnames.append(refname) |
214 |
contents = self.read_ref(refname)
|
215 |
if not contents: |
216 |
break
|
217 |
depth += 1
|
218 |
if depth > 5: |
219 |
raise KeyError(name) |
220 |
return refnames, contents
|
221 |
|
222 |
def _follow(self, name): |
223 |
import warnings |
224 |
warnings.warn( |
225 |
"RefsContainer._follow is deprecated. Use RefsContainer.follow instead.",
|
226 |
DeprecationWarning)
|
227 |
refnames, contents = self.follow(name)
|
228 |
if not refnames: |
229 |
return (None, contents) |
230 |
return (refnames[-1], contents) |
231 |
|
232 |
def __contains__(self, refname): |
233 |
if self.read_ref(refname): |
234 |
return True |
235 |
return False |
236 |
|
237 |
def __getitem__(self, name): |
238 |
"""Get the SHA1 for a reference name.
|
239 |
|
240 |
This method follows all symbolic references.
|
241 |
"""
|
242 |
_, sha = self.follow(name)
|
243 |
if sha is None: |
244 |
raise KeyError(name) |
245 |
return sha
|
246 |
|
247 |
def set_if_equals(self, name, old_ref, new_ref): |
248 |
"""Set a refname to new_ref only if it currently equals old_ref.
|
249 |
|
250 |
This method follows all symbolic references if applicable for the
|
251 |
subclass, and can be used to perform an atomic compare-and-swap
|
252 |
operation.
|
253 |
|
254 |
:param name: The refname to set.
|
255 |
:param old_ref: The old sha the refname must refer to, or None to set
|
256 |
unconditionally.
|
257 |
:param new_ref: The new sha the refname will refer to.
|
258 |
:return: True if the set was successful, False otherwise.
|
259 |
"""
|
260 |
raise NotImplementedError(self.set_if_equals) |
261 |
|
262 |
def add_if_new(self, name, ref): |
263 |
"""Add a new reference only if it does not already exist."""
|
264 |
raise NotImplementedError(self.add_if_new) |
265 |
|
266 |
def __setitem__(self, name, ref): |
267 |
"""Set a reference name to point to the given SHA1.
|
268 |
|
269 |
This method follows all symbolic references if applicable for the
|
270 |
subclass.
|
271 |
|
272 |
:note: This method unconditionally overwrites the contents of a
|
273 |
reference. To update atomically only if the reference has not
|
274 |
changed, use set_if_equals().
|
275 |
:param name: The refname to set.
|
276 |
:param ref: The new sha the refname will refer to.
|
277 |
"""
|
278 |
self.set_if_equals(name, None, ref) |
279 |
|
280 |
def remove_if_equals(self, name, old_ref): |
281 |
"""Remove a refname only if it currently equals old_ref.
|
282 |
|
283 |
This method does not follow symbolic references, even if applicable for
|
284 |
the subclass. It can be used to perform an atomic compare-and-delete
|
285 |
operation.
|
286 |
|
287 |
:param name: The refname to delete.
|
288 |
:param old_ref: The old sha the refname must refer to, or None to delete
|
289 |
unconditionally.
|
290 |
:return: True if the delete was successful, False otherwise.
|
291 |
"""
|
292 |
raise NotImplementedError(self.remove_if_equals) |
293 |
|
294 |
def __delitem__(self, name): |
295 |
"""Remove a refname.
|
296 |
|
297 |
This method does not follow symbolic references, even if applicable for
|
298 |
the subclass.
|
299 |
|
300 |
:note: This method unconditionally deletes the contents of a reference.
|
301 |
To delete atomically only if the reference has not changed, use
|
302 |
remove_if_equals().
|
303 |
|
304 |
:param name: The refname to delete.
|
305 |
"""
|
306 |
self.remove_if_equals(name, None) |
307 |
|
308 |
|
309 |
class DictRefsContainer(RefsContainer): |
310 |
"""RefsContainer backed by a simple dict.
|
311 |
|
312 |
This container does not support symbolic or packed references and is not
|
313 |
threadsafe.
|
314 |
"""
|
315 |
|
316 |
def __init__(self, refs): |
317 |
self._refs = refs
|
318 |
self._peeled = {}
|
319 |
|
320 |
def allkeys(self): |
321 |
return self._refs.keys() |
322 |
|
323 |
def read_loose_ref(self, name): |
324 |
return self._refs.get(name, None) |
325 |
|
326 |
def get_packed_refs(self): |
327 |
return {}
|
328 |
|
329 |
def set_symbolic_ref(self, name, other): |
330 |
self._refs[name] = SYMREF + other
|
331 |
|
332 |
def set_if_equals(self, name, old_ref, new_ref): |
333 |
if old_ref is not None and self._refs.get(name, ZERO_SHA) != old_ref: |
334 |
return False |
335 |
realnames, _ = self.follow(name)
|
336 |
for realname in realnames: |
337 |
self._check_refname(realname)
|
338 |
self._refs[realname] = new_ref
|
339 |
return True |
340 |
|
341 |
def add_if_new(self, name, ref): |
342 |
if name in self._refs: |
343 |
return False |
344 |
self._refs[name] = ref
|
345 |
return True |
346 |
|
347 |
def remove_if_equals(self, name, old_ref): |
348 |
if old_ref is not None and self._refs.get(name, ZERO_SHA) != old_ref: |
349 |
return False |
350 |
try:
|
351 |
del self._refs[name] |
352 |
except KeyError: |
353 |
pass
|
354 |
return True |
355 |
|
356 |
def get_peeled(self, name): |
357 |
return self._peeled.get(name) |
358 |
|
359 |
def _update(self, refs): |
360 |
"""Update multiple refs; intended only for testing."""
|
361 |
# TODO(dborowitz): replace this with a public function that uses
|
362 |
# set_if_equal.
|
363 |
self._refs.update(refs)
|
364 |
|
365 |
def _update_peeled(self, peeled): |
366 |
"""Update cached peeled refs; intended only for testing."""
|
367 |
self._peeled.update(peeled)
|
368 |
|
369 |
|
370 |
class InfoRefsContainer(RefsContainer): |
371 |
"""Refs container that reads refs from a info/refs file."""
|
372 |
|
373 |
def __init__(self, f): |
374 |
self._refs = {}
|
375 |
self._peeled = {}
|
376 |
for l in f.readlines(): |
377 |
sha, name = l.rstrip(b'\n').split(b'\t') |
378 |
if name.endswith(ANNOTATED_TAG_SUFFIX):
|
379 |
name = name[:-3]
|
380 |
if not check_ref_format(name): |
381 |
raise ValueError("invalid ref name %r" % name) |
382 |
self._peeled[name] = sha
|
383 |
else:
|
384 |
if not check_ref_format(name): |
385 |
raise ValueError("invalid ref name %r" % name) |
386 |
self._refs[name] = sha
|
387 |
|
388 |
def allkeys(self): |
389 |
return self._refs.keys() |
390 |
|
391 |
def read_loose_ref(self, name): |
392 |
return self._refs.get(name, None) |
393 |
|
394 |
def get_packed_refs(self): |
395 |
return {}
|
396 |
|
397 |
def get_peeled(self, name): |
398 |
try:
|
399 |
return self._peeled[name] |
400 |
except KeyError: |
401 |
return self._refs[name] |
402 |
|
403 |
|
404 |
class DiskRefsContainer(RefsContainer): |
405 |
"""Refs container that reads refs from disk."""
|
406 |
|
407 |
def __init__(self, path, worktree_path=None): |
408 |
self.path = path
|
409 |
self.worktree_path = worktree_path or path |
410 |
self._packed_refs = None |
411 |
self._peeled_refs = None |
412 |
|
413 |
def __repr__(self): |
414 |
return "%s(%r)" % (self.__class__.__name__, self.path) |
415 |
|
416 |
def subkeys(self, base): |
417 |
subkeys = set()
|
418 |
path = self.refpath(base)
|
419 |
for root, dirs, files in os.walk(path): |
420 |
dir = root[len(path):].strip(os.path.sep).replace(os.path.sep, "/") |
421 |
for filename in files: |
422 |
refname = (("%s/%s" % (dir, filename)) |
423 |
.strip("/").encode(sys.getfilesystemencoding()))
|
424 |
# check_ref_format requires at least one /, so we prepend the
|
425 |
# base before calling it.
|
426 |
if check_ref_format(base + b'/' + refname): |
427 |
subkeys.add(refname) |
428 |
for key in self.get_packed_refs(): |
429 |
if key.startswith(base):
|
430 |
subkeys.add(key[len(base):].strip(b'/')) |
431 |
return subkeys
|
432 |
|
433 |
def allkeys(self): |
434 |
allkeys = set()
|
435 |
if os.path.exists(self.refpath(b'HEAD')): |
436 |
allkeys.add(b'HEAD')
|
437 |
path = self.refpath(b'') |
438 |
for root, dirs, files in os.walk(self.refpath(b'refs')): |
439 |
dir = root[len(path):].strip(os.path.sep).replace(os.path.sep, "/") |
440 |
for filename in files: |
441 |
refname = ("%s/%s" % (dir, filename)).encode(sys.getfilesystemencoding()) |
442 |
if check_ref_format(refname):
|
443 |
allkeys.add(refname) |
444 |
allkeys.update(self.get_packed_refs())
|
445 |
return allkeys
|
446 |
|
447 |
def refpath(self, name): |
448 |
"""Return the disk path of a ref.
|
449 |
|
450 |
"""
|
451 |
if getattr(self.path, "encode", None) and getattr(name, "decode", None): |
452 |
name = name.decode(sys.getfilesystemencoding()) |
453 |
if os.path.sep != "/": |
454 |
name = name.replace("/", os.path.sep)
|
455 |
# TODO: as the 'HEAD' reference is working tree specific, it
|
456 |
# should actually not be a part of RefsContainer
|
457 |
if name == 'HEAD': |
458 |
return os.path.join(self.worktree_path, name) |
459 |
else:
|
460 |
return os.path.join(self.path, name) |
461 |
|
462 |
def get_packed_refs(self): |
463 |
"""Get contents of the packed-refs file.
|
464 |
|
465 |
:return: Dictionary mapping ref names to SHA1s
|
466 |
|
467 |
:note: Will return an empty dictionary when no packed-refs file is
|
468 |
present.
|
469 |
"""
|
470 |
# TODO: invalidate the cache on repacking
|
471 |
if self._packed_refs is None: |
472 |
# set both to empty because we want _peeled_refs to be
|
473 |
# None if and only if _packed_refs is also None.
|
474 |
self._packed_refs = {}
|
475 |
self._peeled_refs = {}
|
476 |
path = os.path.join(self.path, 'packed-refs') |
477 |
try:
|
478 |
f = GitFile(path, 'rb')
|
479 |
except IOError as e: |
480 |
if e.errno == errno.ENOENT:
|
481 |
return {}
|
482 |
raise
|
483 |
with f:
|
484 |
first_line = next(iter(f)).rstrip() |
485 |
if (first_line.startswith(b'# pack-refs') and b' peeled' in |
486 |
first_line): |
487 |
for sha, name, peeled in read_packed_refs_with_peeled(f): |
488 |
self._packed_refs[name] = sha
|
489 |
if peeled:
|
490 |
self._peeled_refs[name] = peeled
|
491 |
else:
|
492 |
f.seek(0)
|
493 |
for sha, name in read_packed_refs(f): |
494 |
self._packed_refs[name] = sha
|
495 |
return self._packed_refs |
496 |
|
497 |
def get_peeled(self, name): |
498 |
"""Return the cached peeled value of a ref, if available.
|
499 |
|
500 |
:param name: Name of the ref to peel
|
501 |
:return: The peeled value of the ref. If the ref is known not point to a
|
502 |
tag, this will be the SHA the ref refers to. If the ref may point to
|
503 |
a tag, but no cached information is available, None is returned.
|
504 |
"""
|
505 |
self.get_packed_refs()
|
506 |
if self._peeled_refs is None or name not in self._packed_refs: |
507 |
# No cache: no peeled refs were read, or this ref is loose
|
508 |
return None |
509 |
if name in self._peeled_refs: |
510 |
return self._peeled_refs[name] |
511 |
else:
|
512 |
# Known not peelable
|
513 |
return self[name] |
514 |
|
515 |
def read_loose_ref(self, name): |
516 |
"""Read a reference file and return its contents.
|
517 |
|
518 |
If the reference file a symbolic reference, only read the first line of
|
519 |
the file. Otherwise, only read the first 40 bytes.
|
520 |
|
521 |
:param name: the refname to read, relative to refpath
|
522 |
:return: The contents of the ref file, or None if the file does not
|
523 |
exist.
|
524 |
:raises IOError: if any other error occurs
|
525 |
"""
|
526 |
filename = self.refpath(name)
|
527 |
try:
|
528 |
with GitFile(filename, 'rb') as f: |
529 |
header = f.read(len(SYMREF))
|
530 |
if header == SYMREF:
|
531 |
# Read only the first line
|
532 |
return header + next(iter(f)).rstrip(b'\r\n') |
533 |
else:
|
534 |
# Read only the first 40 bytes
|
535 |
return header + f.read(40 - len(SYMREF)) |
536 |
except IOError as e: |
537 |
if e.errno == errno.ENOENT:
|
538 |
return None |
539 |
raise
|
540 |
|
541 |
def _remove_packed_ref(self, name): |
542 |
if self._packed_refs is None: |
543 |
return
|
544 |
filename = os.path.join(self.path, 'packed-refs') |
545 |
# reread cached refs from disk, while holding the lock
|
546 |
f = GitFile(filename, 'wb')
|
547 |
try:
|
548 |
self._packed_refs = None |
549 |
self.get_packed_refs()
|
550 |
|
551 |
if name not in self._packed_refs: |
552 |
return
|
553 |
|
554 |
del self._packed_refs[name] |
555 |
if name in self._peeled_refs: |
556 |
del self._peeled_refs[name] |
557 |
write_packed_refs(f, self._packed_refs, self._peeled_refs) |
558 |
f.close() |
559 |
finally:
|
560 |
f.abort() |
561 |
|
562 |
def set_symbolic_ref(self, name, other): |
563 |
"""Make a ref point at another ref.
|
564 |
|
565 |
:param name: Name of the ref to set
|
566 |
:param other: Name of the ref to point at
|
567 |
"""
|
568 |
self._check_refname(name)
|
569 |
self._check_refname(other)
|
570 |
filename = self.refpath(name)
|
571 |
try:
|
572 |
f = GitFile(filename, 'wb')
|
573 |
try:
|
574 |
f.write(SYMREF + other + b'\n')
|
575 |
except (IOError, OSError): |
576 |
f.abort() |
577 |
raise
|
578 |
finally:
|
579 |
f.close() |
580 |
|
581 |
def set_if_equals(self, name, old_ref, new_ref): |
582 |
"""Set a refname to new_ref only if it currently equals old_ref.
|
583 |
|
584 |
This method follows all symbolic references, and can be used to perform
|
585 |
an atomic compare-and-swap operation.
|
586 |
|
587 |
:param name: The refname to set.
|
588 |
:param old_ref: The old sha the refname must refer to, or None to set
|
589 |
unconditionally.
|
590 |
:param new_ref: The new sha the refname will refer to.
|
591 |
:return: True if the set was successful, False otherwise.
|
592 |
"""
|
593 |
self._check_refname(name)
|
594 |
try:
|
595 |
realnames, _ = self.follow(name)
|
596 |
realname = realnames[-1]
|
597 |
except (KeyError, IndexError): |
598 |
realname = name |
599 |
filename = self.refpath(realname)
|
600 |
ensure_dir_exists(os.path.dirname(filename)) |
601 |
with GitFile(filename, 'wb') as f: |
602 |
if old_ref is not None: |
603 |
try:
|
604 |
# read again while holding the lock
|
605 |
orig_ref = self.read_loose_ref(realname)
|
606 |
if orig_ref is None: |
607 |
orig_ref = self.get_packed_refs().get(realname, ZERO_SHA)
|
608 |
if orig_ref != old_ref:
|
609 |
f.abort() |
610 |
return False |
611 |
except (OSError, IOError): |
612 |
f.abort() |
613 |
raise
|
614 |
try:
|
615 |
f.write(new_ref + b'\n')
|
616 |
except (OSError, IOError): |
617 |
f.abort() |
618 |
raise
|
619 |
return True |
620 |
|
621 |
def add_if_new(self, name, ref): |
622 |
"""Add a new reference only if it does not already exist.
|
623 |
|
624 |
This method follows symrefs, and only ensures that the last ref in the
|
625 |
chain does not exist.
|
626 |
|
627 |
:param name: The refname to set.
|
628 |
:param ref: The new sha the refname will refer to.
|
629 |
:return: True if the add was successful, False otherwise.
|
630 |
"""
|
631 |
try:
|
632 |
realnames, contents = self.follow(name)
|
633 |
if contents is not None: |
634 |
return False |
635 |
realname = realnames[-1]
|
636 |
except (KeyError, IndexError): |
637 |
realname = name |
638 |
self._check_refname(realname)
|
639 |
filename = self.refpath(realname)
|
640 |
ensure_dir_exists(os.path.dirname(filename)) |
641 |
with GitFile(filename, 'wb') as f: |
642 |
if os.path.exists(filename) or name in self.get_packed_refs(): |
643 |
f.abort() |
644 |
return False |
645 |
try:
|
646 |
f.write(ref + b'\n')
|
647 |
except (OSError, IOError): |
648 |
f.abort() |
649 |
raise
|
650 |
return True |
651 |
|
652 |
def remove_if_equals(self, name, old_ref): |
653 |
"""Remove a refname only if it currently equals old_ref.
|
654 |
|
655 |
This method does not follow symbolic references. It can be used to
|
656 |
perform an atomic compare-and-delete operation.
|
657 |
|
658 |
:param name: The refname to delete.
|
659 |
:param old_ref: The old sha the refname must refer to, or None to delete
|
660 |
unconditionally.
|
661 |
:return: True if the delete was successful, False otherwise.
|
662 |
"""
|
663 |
self._check_refname(name)
|
664 |
filename = self.refpath(name)
|
665 |
ensure_dir_exists(os.path.dirname(filename)) |
666 |
f = GitFile(filename, 'wb')
|
667 |
try:
|
668 |
if old_ref is not None: |
669 |
orig_ref = self.read_loose_ref(name)
|
670 |
if orig_ref is None: |
671 |
orig_ref = self.get_packed_refs().get(name, ZERO_SHA)
|
672 |
if orig_ref != old_ref:
|
673 |
return False |
674 |
# may only be packed
|
675 |
try:
|
676 |
os.remove(filename) |
677 |
except OSError as e: |
678 |
if e.errno != errno.ENOENT:
|
679 |
raise
|
680 |
self._remove_packed_ref(name)
|
681 |
finally:
|
682 |
# never write, we just wanted the lock
|
683 |
f.abort() |
684 |
return True |
685 |
|
686 |
|
687 |
def _split_ref_line(line): |
688 |
"""Split a single ref line into a tuple of SHA1 and name."""
|
689 |
fields = line.rstrip(b'\n\r').split(b' ') |
690 |
if len(fields) != 2: |
691 |
raise PackedRefsException("invalid ref line %r" % line) |
692 |
sha, name = fields |
693 |
if not valid_hexsha(sha): |
694 |
raise PackedRefsException("Invalid hex sha %r" % sha) |
695 |
if not check_ref_format(name): |
696 |
raise PackedRefsException("invalid ref name %r" % name) |
697 |
return (sha, name)
|
698 |
|
699 |
|
700 |
def read_packed_refs(f): |
701 |
"""Read a packed refs file.
|
702 |
|
703 |
:param f: file-like object to read from
|
704 |
:return: Iterator over tuples with SHA1s and ref names.
|
705 |
"""
|
706 |
for l in f: |
707 |
if l.startswith(b'#'): |
708 |
# Comment
|
709 |
continue
|
710 |
if l.startswith(b'^'): |
711 |
raise PackedRefsException(
|
712 |
"found peeled ref in packed-refs without peeled")
|
713 |
yield _split_ref_line(l)
|
714 |
|
715 |
|
716 |
def read_packed_refs_with_peeled(f): |
717 |
"""Read a packed refs file including peeled refs.
|
718 |
|
719 |
Assumes the "# pack-refs with: peeled" line was already read. Yields tuples
|
720 |
with ref names, SHA1s, and peeled SHA1s (or None).
|
721 |
|
722 |
:param f: file-like object to read from, seek'ed to the second line
|
723 |
"""
|
724 |
last = None
|
725 |
for l in f: |
726 |
if l[0] == b'#': |
727 |
continue
|
728 |
l = l.rstrip(b'\r\n')
|
729 |
if l.startswith(b'^'): |
730 |
if not last: |
731 |
raise PackedRefsException("unexpected peeled ref line") |
732 |
if not valid_hexsha(l[1:]): |
733 |
raise PackedRefsException("Invalid hex sha %r" % l[1:]) |
734 |
sha, name = _split_ref_line(last) |
735 |
last = None
|
736 |
yield (sha, name, l[1:]) |
737 |
else:
|
738 |
if last:
|
739 |
sha, name = _split_ref_line(last) |
740 |
yield (sha, name, None) |
741 |
last = l |
742 |
if last:
|
743 |
sha, name = _split_ref_line(last) |
744 |
yield (sha, name, None) |
745 |
|
746 |
|
747 |
def write_packed_refs(f, packed_refs, peeled_refs=None): |
748 |
"""Write a packed refs file.
|
749 |
|
750 |
:param f: empty file-like object to write to
|
751 |
:param packed_refs: dict of refname to sha of packed refs to write
|
752 |
:param peeled_refs: dict of refname to peeled value of sha
|
753 |
"""
|
754 |
if peeled_refs is None: |
755 |
peeled_refs = {} |
756 |
else:
|
757 |
f.write(b'# pack-refs with: peeled\n')
|
758 |
for refname in sorted(packed_refs.keys()): |
759 |
f.write(git_line(packed_refs[refname], refname)) |
760 |
if refname in peeled_refs: |
761 |
f.write(b'^' + peeled_refs[refname] + b'\n') |
762 |
|
763 |
|
764 |
def read_info_refs(f): |
765 |
ret = {} |
766 |
for l in f.readlines(): |
767 |
(sha, name) = l.rstrip(b"\r\n").split(b"\t", 1) |
768 |
ret[name] = sha |
769 |
return ret
|
770 |
|
771 |
|
772 |
def write_info_refs(refs, store): |
773 |
"""Generate info refs."""
|
774 |
for name, sha in sorted(refs.items()): |
775 |
# get_refs() includes HEAD as a special case, but we don't want to
|
776 |
# advertise it
|
777 |
if name == b'HEAD': |
778 |
continue
|
779 |
try:
|
780 |
o = store[sha] |
781 |
except KeyError: |
782 |
continue
|
783 |
peeled = store.peel_sha(sha) |
784 |
yield o.id + b'\t' + name + b'\n' |
785 |
if o.id != peeled.id:
|
786 |
yield peeled.id + b'\t' + name + ANNOTATED_TAG_SUFFIX + b'\n' |
787 |
|
788 |
|
789 |
is_local_branch = lambda x: x.startswith(b'refs/heads/') |