89c4cfdbb79ebeea87d694d14b0ea3f8836d3da4
[jelmer/dulwich.git] / dulwich / refs.py
1 # refs.py -- For dealing with git refs
2 # Copyright (C) 2008-2013 Jelmer Vernooij <jelmer@jelmer.uk>
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 LOCAL_TAG_PREFIX = b'refs/tags/'
47 BAD_REF_CHARS = set(b'\177 ~^:?*[')
48 ANNOTATED_TAG_SUFFIX = b'^{}'
49
50
51 def parse_symref_value(contents):
52     """Parse a symref value.
53
54     :param contents: Contents to parse
55     :return: Destination
56     """
57     if contents.startswith(SYMREF):
58         return contents[len(SYMREF):].rstrip(b'\r\n')
59     raise ValueError(contents)
60
61
62 def check_ref_format(refname):
63     """Check if a refname is correctly formatted.
64
65     Implements all the same rules as git-check-ref-format[1].
66
67     [1]
68     http://www.kernel.org/pub/software/scm/git/docs/git-check-ref-format.html
69
70     :param refname: The refname to check
71     :return: True if refname is valid, False otherwise
72     """
73     # These could be combined into one big expression, but are listed
74     # separately to parallel [1].
75     if b'/.' in refname or refname.startswith(b'.'):
76         return False
77     if b'/' not in refname:
78         return False
79     if b'..' in refname:
80         return False
81     for i, c in enumerate(refname):
82         if ord(refname[i:i+1]) < 0o40 or c in BAD_REF_CHARS:
83             return False
84     if refname[-1] in b'/.':
85         return False
86     if refname.endswith(b'.lock'):
87         return False
88     if b'@{' in refname:
89         return False
90     if b'\\' in refname:
91         return False
92     return True
93
94
95 class RefsContainer(object):
96     """A container for refs."""
97
98     def __init__(self, logger=None):
99         self._logger = logger
100
101     def _log(self, ref, old_sha, new_sha, committer=None, timestamp=None,
102              timezone=None, message=None):
103         if self._logger is None:
104             return
105         if message is None:
106             return
107         self._logger(ref, old_sha, new_sha, committer, timestamp,
108                      timezone, message)
109
110     def set_symbolic_ref(self, name, other, committer=None, timestamp=None,
111                          timezone=None, message=None):
112         """Make a ref point at another ref.
113
114         :param name: Name of the ref to set
115         :param other: Name of the ref to point at
116         :param message: Optional message
117         """
118         raise NotImplementedError(self.set_symbolic_ref)
119
120     def get_packed_refs(self):
121         """Get contents of the packed-refs file.
122
123         :return: Dictionary mapping ref names to SHA1s
124
125         :note: Will return an empty dictionary when no packed-refs file is
126             present.
127         """
128         raise NotImplementedError(self.get_packed_refs)
129
130     def get_peeled(self, name):
131         """Return the cached peeled value of a ref, if available.
132
133         :param name: Name of the ref to peel
134         :return: The peeled value of the ref. If the ref is known not point to
135             a tag, this will be the SHA the ref refers to. If the ref may point
136             to a tag, but no cached information is available, None is returned.
137         """
138         return None
139
140     def import_refs(self, base, other, committer=None, timestamp=None,
141                     timezone=None, message=None):
142         for name, value in other.items():
143             self.set_if_equals(b'/'.join((base, name)), None, value,
144                                message=message)
145
146     def allkeys(self):
147         """All refs present in this container."""
148         raise NotImplementedError(self.allkeys)
149
150     def keys(self, base=None):
151         """Refs present in this container.
152
153         :param base: An optional base to return refs under.
154         :return: An unsorted set of valid refs in this container, including
155             packed refs.
156         """
157         if base is not None:
158             return self.subkeys(base)
159         else:
160             return self.allkeys()
161
162     def subkeys(self, base):
163         """Refs present in this container under a base.
164
165         :param base: The base to return refs under.
166         :return: A set of valid refs in this container under the base; the base
167             prefix is stripped from the ref names returned.
168         """
169         keys = set()
170         base_len = len(base) + 1
171         for refname in self.allkeys():
172             if refname.startswith(base):
173                 keys.add(refname[base_len:])
174         return keys
175
176     def as_dict(self, base=None):
177         """Return the contents of this container as a dictionary.
178
179         """
180         ret = {}
181         keys = self.keys(base)
182         if base is None:
183             base = b''
184         else:
185             base = base.rstrip(b'/')
186         for key in keys:
187             try:
188                 ret[key] = self[(base + b'/' + key).strip(b'/')]
189             except KeyError:
190                 continue  # Unable to resolve
191
192         return ret
193
194     def _check_refname(self, name):
195         """Ensure a refname is valid and lives in refs or is HEAD.
196
197         HEAD is not a valid refname according to git-check-ref-format, but this
198         class needs to be able to touch HEAD. Also, check_ref_format expects
199         refnames without the leading 'refs/', but this class requires that
200         so it cannot touch anything outside the refs dir (or HEAD).
201
202         :param name: The name of the reference.
203         :raises KeyError: if a refname is not HEAD or is otherwise not valid.
204         """
205         if name in (b'HEAD', b'refs/stash'):
206             return
207         if not name.startswith(b'refs/') or not check_ref_format(name[5:]):
208             raise RefFormatError(name)
209
210     def read_ref(self, refname):
211         """Read a reference without following any references.
212
213         :param refname: The name of the reference
214         :return: The contents of the ref file, or None if it does
215             not exist.
216         """
217         contents = self.read_loose_ref(refname)
218         if not contents:
219             contents = self.get_packed_refs().get(refname, None)
220         return contents
221
222     def read_loose_ref(self, name):
223         """Read a loose reference and return its contents.
224
225         :param name: the refname to read
226         :return: The contents of the ref file, or None if it does
227             not exist.
228         """
229         raise NotImplementedError(self.read_loose_ref)
230
231     def follow(self, name):
232         """Follow a reference name.
233
234         :return: a tuple of (refnames, sha), wheres refnames are the names of
235             references in the chain
236         """
237         contents = SYMREF + name
238         depth = 0
239         refnames = []
240         while contents.startswith(SYMREF):
241             refname = contents[len(SYMREF):]
242             refnames.append(refname)
243             contents = self.read_ref(refname)
244             if not contents:
245                 break
246             depth += 1
247             if depth > 5:
248                 raise KeyError(name)
249         return refnames, contents
250
251     def _follow(self, name):
252         import warnings
253         warnings.warn(
254             "RefsContainer._follow is deprecated. Use RefsContainer.follow "
255             "instead.", DeprecationWarning)
256         refnames, contents = self.follow(name)
257         if not refnames:
258             return (None, contents)
259         return (refnames[-1], contents)
260
261     def __contains__(self, refname):
262         if self.read_ref(refname):
263             return True
264         return False
265
266     def __getitem__(self, name):
267         """Get the SHA1 for a reference name.
268
269         This method follows all symbolic references.
270         """
271         _, sha = self.follow(name)
272         if sha is None:
273             raise KeyError(name)
274         return sha
275
276     def set_if_equals(self, name, old_ref, new_ref, committer=None,
277                       timestamp=None, timezone=None, message=None):
278         """Set a refname to new_ref only if it currently equals old_ref.
279
280         This method follows all symbolic references if applicable for the
281         subclass, and can be used to perform an atomic compare-and-swap
282         operation.
283
284         :param name: The refname to set.
285         :param old_ref: The old sha the refname must refer to, or None to set
286             unconditionally.
287         :param new_ref: The new sha the refname will refer to.
288         :param message: Message for reflog
289         :return: True if the set was successful, False otherwise.
290         """
291         raise NotImplementedError(self.set_if_equals)
292
293     def add_if_new(self, name, ref):
294         """Add a new reference only if it does not already exist.
295
296         :param name: Ref name
297         :param ref: Ref value
298         :param message: Message for reflog
299         """
300         raise NotImplementedError(self.add_if_new)
301
302     def __setitem__(self, name, ref):
303         """Set a reference name to point to the given SHA1.
304
305         This method follows all symbolic references if applicable for the
306         subclass.
307
308         :note: This method unconditionally overwrites the contents of a
309             reference. To update atomically only if the reference has not
310             changed, use set_if_equals().
311         :param name: The refname to set.
312         :param ref: The new sha the refname will refer to.
313         """
314         self.set_if_equals(name, None, ref)
315
316     def remove_if_equals(self, name, old_ref, committer=None,
317                          timestamp=None, timezone=None, message=None):
318         """Remove a refname only if it currently equals old_ref.
319
320         This method does not follow symbolic references, even if applicable for
321         the subclass. It can be used to perform an atomic compare-and-delete
322         operation.
323
324         :param name: The refname to delete.
325         :param old_ref: The old sha the refname must refer to, or None to
326             delete unconditionally.
327         :param message: Message for reflog
328         :return: True if the delete was successful, False otherwise.
329         """
330         raise NotImplementedError(self.remove_if_equals)
331
332     def __delitem__(self, name):
333         """Remove a refname.
334
335         This method does not follow symbolic references, even if applicable for
336         the subclass.
337
338         :note: This method unconditionally deletes the contents of a reference.
339             To delete atomically only if the reference has not changed, use
340             remove_if_equals().
341
342         :param name: The refname to delete.
343         """
344         self.remove_if_equals(name, None)
345
346     def get_symrefs(self):
347         """Get a dict with all symrefs in this container.
348
349         :return: Dictionary mapping source ref to target ref
350         """
351         ret = {}
352         for src in self.allkeys():
353             try:
354                 dst = parse_symref_value(self.read_ref(src))
355             except ValueError:
356                 pass
357             else:
358                 ret[src] = dst
359         return ret
360
361
362 class DictRefsContainer(RefsContainer):
363     """RefsContainer backed by a simple dict.
364
365     This container does not support symbolic or packed references and is not
366     threadsafe.
367     """
368
369     def __init__(self, refs, logger=None):
370         super(DictRefsContainer, self).__init__(logger=logger)
371         self._refs = refs
372         self._peeled = {}
373
374     def allkeys(self):
375         return self._refs.keys()
376
377     def read_loose_ref(self, name):
378         return self._refs.get(name, None)
379
380     def get_packed_refs(self):
381         return {}
382
383     def set_symbolic_ref(self, name, other, committer=None,
384                          timestamp=None, timezone=None, message=None):
385         old = self.follow(name)[-1]
386         self._refs[name] = SYMREF + other
387         self._log(name, old, old, committer=committer, timestamp=timestamp,
388                   timezone=timezone, message=message)
389
390     def set_if_equals(self, name, old_ref, new_ref, committer=None,
391                       timestamp=None, timezone=None, message=None):
392         if old_ref is not None and self._refs.get(name, ZERO_SHA) != old_ref:
393             return False
394         realnames, _ = self.follow(name)
395         for realname in realnames:
396             self._check_refname(realname)
397             old = self._refs.get(realname)
398             self._refs[realname] = new_ref
399             self._log(realname, old, new_ref, committer=committer,
400                       timestamp=timestamp, timezone=timezone, message=message)
401         return True
402
403     def add_if_new(self, name, ref, committer=None, timestamp=None,
404                    timezone=None, message=None):
405         if name in self._refs:
406             return False
407         self._refs[name] = ref
408         self._log(name, None, ref, committer=committer, timestamp=timestamp,
409                   timezone=timezone, message=message)
410         return True
411
412     def remove_if_equals(self, name, old_ref, committer=None, timestamp=None,
413                          timezone=None, message=None):
414         if old_ref is not None and self._refs.get(name, ZERO_SHA) != old_ref:
415             return False
416         try:
417             old = self._refs.pop(name)
418         except KeyError:
419             pass
420         else:
421             self._log(name, old, None, committer=committer,
422                       timestamp=timestamp, timezone=timezone, message=message)
423         return True
424
425     def get_peeled(self, name):
426         return self._peeled.get(name)
427
428     def _update(self, refs):
429         """Update multiple refs; intended only for testing."""
430         # TODO(dborowitz): replace this with a public function that uses
431         # set_if_equal.
432         self._refs.update(refs)
433
434     def _update_peeled(self, peeled):
435         """Update cached peeled refs; intended only for testing."""
436         self._peeled.update(peeled)
437
438
439 class InfoRefsContainer(RefsContainer):
440     """Refs container that reads refs from a info/refs file."""
441
442     def __init__(self, f):
443         self._refs = {}
444         self._peeled = {}
445         for l in f.readlines():
446             sha, name = l.rstrip(b'\n').split(b'\t')
447             if name.endswith(ANNOTATED_TAG_SUFFIX):
448                 name = name[:-3]
449                 if not check_ref_format(name):
450                     raise ValueError("invalid ref name %r" % name)
451                 self._peeled[name] = sha
452             else:
453                 if not check_ref_format(name):
454                     raise ValueError("invalid ref name %r" % name)
455                 self._refs[name] = sha
456
457     def allkeys(self):
458         return self._refs.keys()
459
460     def read_loose_ref(self, name):
461         return self._refs.get(name, None)
462
463     def get_packed_refs(self):
464         return {}
465
466     def get_peeled(self, name):
467         try:
468             return self._peeled[name]
469         except KeyError:
470             return self._refs[name]
471
472
473 class DiskRefsContainer(RefsContainer):
474     """Refs container that reads refs from disk."""
475
476     def __init__(self, path, worktree_path=None, logger=None):
477         super(DiskRefsContainer, self).__init__(logger=logger)
478         if getattr(path, 'encode', None) is not None:
479             path = path.encode(sys.getfilesystemencoding())
480         self.path = path
481         if worktree_path is None:
482             worktree_path = path
483         if getattr(worktree_path, 'encode', None) is not None:
484             worktree_path = worktree_path.encode(sys.getfilesystemencoding())
485         self.worktree_path = worktree_path
486         self._packed_refs = None
487         self._peeled_refs = None
488
489     def __repr__(self):
490         return "%s(%r)" % (self.__class__.__name__, self.path)
491
492     def subkeys(self, base):
493         subkeys = set()
494         path = self.refpath(base)
495         for root, unused_dirs, files in os.walk(path):
496             dir = root[len(path):]
497             if os.path.sep != '/':
498                 dir = dir.replace(os.path.sep.encode(
499                     sys.getfilesystemencoding()), b"/")
500             dir = dir.strip(b'/')
501             for filename in files:
502                 refname = b"/".join(([dir] if dir else []) + [filename])
503                 # check_ref_format requires at least one /, so we prepend the
504                 # base before calling it.
505                 if check_ref_format(base + b'/' + refname):
506                     subkeys.add(refname)
507         for key in self.get_packed_refs():
508             if key.startswith(base):
509                 subkeys.add(key[len(base):].strip(b'/'))
510         return subkeys
511
512     def allkeys(self):
513         allkeys = set()
514         if os.path.exists(self.refpath(b'HEAD')):
515             allkeys.add(b'HEAD')
516         path = self.refpath(b'')
517         refspath = self.refpath(b'refs')
518         for root, unused_dirs, files in os.walk(refspath):
519             dir = root[len(path):]
520             if os.path.sep != '/':
521                 dir = dir.replace(
522                     os.path.sep.encode(sys.getfilesystemencoding()), b"/")
523             for filename in files:
524                 refname = b"/".join([dir, filename])
525                 if check_ref_format(refname):
526                     allkeys.add(refname)
527         allkeys.update(self.get_packed_refs())
528         return allkeys
529
530     def refpath(self, name):
531         """Return the disk path of a ref.
532
533         """
534         if os.path.sep != "/":
535             name = name.replace(
536                     b"/",
537                     os.path.sep.encode(sys.getfilesystemencoding()))
538         # TODO: as the 'HEAD' reference is working tree specific, it
539         # should actually not be a part of RefsContainer
540         if name == b'HEAD':
541             return os.path.join(self.worktree_path, name)
542         else:
543             return os.path.join(self.path, name)
544
545     def get_packed_refs(self):
546         """Get contents of the packed-refs file.
547
548         :return: Dictionary mapping ref names to SHA1s
549
550         :note: Will return an empty dictionary when no packed-refs file is
551             present.
552         """
553         # TODO: invalidate the cache on repacking
554         if self._packed_refs is None:
555             # set both to empty because we want _peeled_refs to be
556             # None if and only if _packed_refs is also None.
557             self._packed_refs = {}
558             self._peeled_refs = {}
559             path = os.path.join(self.path, b'packed-refs')
560             try:
561                 f = GitFile(path, 'rb')
562             except IOError as e:
563                 if e.errno == errno.ENOENT:
564                     return {}
565                 raise
566             with f:
567                 first_line = next(iter(f)).rstrip()
568                 if (first_line.startswith(b'# pack-refs') and b' peeled' in
569                         first_line):
570                     for sha, name, peeled in read_packed_refs_with_peeled(f):
571                         self._packed_refs[name] = sha
572                         if peeled:
573                             self._peeled_refs[name] = peeled
574                 else:
575                     f.seek(0)
576                     for sha, name in read_packed_refs(f):
577                         self._packed_refs[name] = sha
578         return self._packed_refs
579
580     def get_peeled(self, name):
581         """Return the cached peeled value of a ref, if available.
582
583         :param name: Name of the ref to peel
584         :return: The peeled value of the ref. If the ref is known not point to
585             a tag, this will be the SHA the ref refers to. If the ref may point
586             to a tag, but no cached information is available, None is returned.
587         """
588         self.get_packed_refs()
589         if self._peeled_refs is None or name not in self._packed_refs:
590             # No cache: no peeled refs were read, or this ref is loose
591             return None
592         if name in self._peeled_refs:
593             return self._peeled_refs[name]
594         else:
595             # Known not peelable
596             return self[name]
597
598     def read_loose_ref(self, name):
599         """Read a reference file and return its contents.
600
601         If the reference file a symbolic reference, only read the first line of
602         the file. Otherwise, only read the first 40 bytes.
603
604         :param name: the refname to read, relative to refpath
605         :return: The contents of the ref file, or None if the file does not
606             exist.
607         :raises IOError: if any other error occurs
608         """
609         filename = self.refpath(name)
610         try:
611             with GitFile(filename, 'rb') as f:
612                 header = f.read(len(SYMREF))
613                 if header == SYMREF:
614                     # Read only the first line
615                     return header + next(iter(f)).rstrip(b'\r\n')
616                 else:
617                     # Read only the first 40 bytes
618                     return header + f.read(40 - len(SYMREF))
619         except IOError as e:
620             if e.errno in (errno.ENOENT, errno.EISDIR):
621                 return None
622             raise
623
624     def _remove_packed_ref(self, name):
625         if self._packed_refs is None:
626             return
627         filename = os.path.join(self.path, b'packed-refs')
628         # reread cached refs from disk, while holding the lock
629         f = GitFile(filename, 'wb')
630         try:
631             self._packed_refs = None
632             self.get_packed_refs()
633
634             if name not in self._packed_refs:
635                 return
636
637             del self._packed_refs[name]
638             if name in self._peeled_refs:
639                 del self._peeled_refs[name]
640             write_packed_refs(f, self._packed_refs, self._peeled_refs)
641             f.close()
642         finally:
643             f.abort()
644
645     def set_symbolic_ref(self, name, other, committer=None, timestamp=None,
646                          timezone=None, message=None):
647         """Make a ref point at another ref.
648
649         :param name: Name of the ref to set
650         :param other: Name of the ref to point at
651         :param message: Optional message to describe the change
652         """
653         self._check_refname(name)
654         self._check_refname(other)
655         filename = self.refpath(name)
656         f = GitFile(filename, 'wb')
657         try:
658             f.write(SYMREF + other + b'\n')
659             sha = self.follow(name)[-1]
660             self._log(name, sha, sha, committer=committer,
661                       timestamp=timestamp, timezone=timezone,
662                       message=message)
663         except BaseException:
664             f.abort()
665             raise
666         else:
667             f.close()
668
669     def set_if_equals(self, name, old_ref, new_ref, committer=None,
670                       timestamp=None, timezone=None, message=None):
671         """Set a refname to new_ref only if it currently equals old_ref.
672
673         This method follows all symbolic references, and can be used to perform
674         an atomic compare-and-swap operation.
675
676         :param name: The refname to set.
677         :param old_ref: The old sha the refname must refer to, or None to set
678             unconditionally.
679         :param new_ref: The new sha the refname will refer to.
680         :param message: Set message for reflog
681         :return: True if the set was successful, False otherwise.
682         """
683         self._check_refname(name)
684         try:
685             realnames, _ = self.follow(name)
686             realname = realnames[-1]
687         except (KeyError, IndexError):
688             realname = name
689         filename = self.refpath(realname)
690         ensure_dir_exists(os.path.dirname(filename))
691         with GitFile(filename, 'wb') as f:
692             if old_ref is not None:
693                 try:
694                     # read again while holding the lock
695                     orig_ref = self.read_loose_ref(realname)
696                     if orig_ref is None:
697                         orig_ref = self.get_packed_refs().get(
698                                 realname, ZERO_SHA)
699                     if orig_ref != old_ref:
700                         f.abort()
701                         return False
702                 except (OSError, IOError):
703                     f.abort()
704                     raise
705             try:
706                 f.write(new_ref + b'\n')
707             except (OSError, IOError):
708                 f.abort()
709                 raise
710             self._log(realname, old_ref, new_ref, committer=committer,
711                       timestamp=timestamp, timezone=timezone, message=message)
712         return True
713
714     def add_if_new(self, name, ref, committer=None, timestamp=None,
715                    timezone=None, message=None):
716         """Add a new reference only if it does not already exist.
717
718         This method follows symrefs, and only ensures that the last ref in the
719         chain does not exist.
720
721         :param name: The refname to set.
722         :param ref: The new sha the refname will refer to.
723         :param message: Optional message for reflog
724         :return: True if the add was successful, False otherwise.
725         """
726         try:
727             realnames, contents = self.follow(name)
728             if contents is not None:
729                 return False
730             realname = realnames[-1]
731         except (KeyError, IndexError):
732             realname = name
733         self._check_refname(realname)
734         filename = self.refpath(realname)
735         ensure_dir_exists(os.path.dirname(filename))
736         with GitFile(filename, 'wb') as f:
737             if os.path.exists(filename) or name in self.get_packed_refs():
738                 f.abort()
739                 return False
740             try:
741                 f.write(ref + b'\n')
742             except (OSError, IOError):
743                 f.abort()
744                 raise
745             else:
746                 self._log(name, None, ref, committer=committer,
747                           timestamp=timestamp, timezone=timezone,
748                           message=message)
749         return True
750
751     def remove_if_equals(self, name, old_ref, committer=None, timestamp=None,
752                          timezone=None, message=None):
753         """Remove a refname only if it currently equals old_ref.
754
755         This method does not follow symbolic references. It can be used to
756         perform an atomic compare-and-delete operation.
757
758         :param name: The refname to delete.
759         :param old_ref: The old sha the refname must refer to, or None to
760             delete unconditionally.
761         :param message: Optional message
762         :return: True if the delete was successful, False otherwise.
763         """
764         self._check_refname(name)
765         filename = self.refpath(name)
766         ensure_dir_exists(os.path.dirname(filename))
767         f = GitFile(filename, 'wb')
768         try:
769             if old_ref is not None:
770                 orig_ref = self.read_loose_ref(name)
771                 if orig_ref is None:
772                     orig_ref = self.get_packed_refs().get(name, ZERO_SHA)
773                 if orig_ref != old_ref:
774                     return False
775
776             # remove the reference file itself
777             try:
778                 os.remove(filename)
779             except OSError as e:
780                 if e.errno != errno.ENOENT:  # may only be packed
781                     raise
782
783             self._remove_packed_ref(name)
784             self._log(name, old_ref, None, committer=committer,
785                       timestamp=timestamp, timezone=timezone, message=message)
786         finally:
787             # never write, we just wanted the lock
788             f.abort()
789
790         # outside of the lock, clean-up any parent directory that might now
791         # be empty. this ensures that re-creating a reference of the same
792         # name of what was previously a directory works as expected
793         parent = name
794         while True:
795             try:
796                 parent, _ = parent.rsplit(b'/', 1)
797             except ValueError:
798                 break
799
800             parent_filename = self.refpath(parent)
801             try:
802                 os.rmdir(parent_filename)
803             except OSError as e:
804                 # this can be caused by the parent directory being
805                 # removed by another process, being not empty, etc.
806                 # in any case, this is non fatal because we already
807                 # removed the reference, just ignore it
808                 break
809
810         return True
811
812
813 def _split_ref_line(line):
814     """Split a single ref line into a tuple of SHA1 and name."""
815     fields = line.rstrip(b'\n\r').split(b' ')
816     if len(fields) != 2:
817         raise PackedRefsException("invalid ref line %r" % line)
818     sha, name = fields
819     if not valid_hexsha(sha):
820         raise PackedRefsException("Invalid hex sha %r" % sha)
821     if not check_ref_format(name):
822         raise PackedRefsException("invalid ref name %r" % name)
823     return (sha, name)
824
825
826 def read_packed_refs(f):
827     """Read a packed refs file.
828
829     :param f: file-like object to read from
830     :return: Iterator over tuples with SHA1s and ref names.
831     """
832     for l in f:
833         if l.startswith(b'#'):
834             # Comment
835             continue
836         if l.startswith(b'^'):
837             raise PackedRefsException(
838               "found peeled ref in packed-refs without peeled")
839         yield _split_ref_line(l)
840
841
842 def read_packed_refs_with_peeled(f):
843     """Read a packed refs file including peeled refs.
844
845     Assumes the "# pack-refs with: peeled" line was already read. Yields tuples
846     with ref names, SHA1s, and peeled SHA1s (or None).
847
848     :param f: file-like object to read from, seek'ed to the second line
849     """
850     last = None
851     for line in f:
852         if line[0] == b'#':
853             continue
854         line = line.rstrip(b'\r\n')
855         if line.startswith(b'^'):
856             if not last:
857                 raise PackedRefsException("unexpected peeled ref line")
858             if not valid_hexsha(line[1:]):
859                 raise PackedRefsException("Invalid hex sha %r" % line[1:])
860             sha, name = _split_ref_line(last)
861             last = None
862             yield (sha, name, line[1:])
863         else:
864             if last:
865                 sha, name = _split_ref_line(last)
866                 yield (sha, name, None)
867             last = line
868     if last:
869         sha, name = _split_ref_line(last)
870         yield (sha, name, None)
871
872
873 def write_packed_refs(f, packed_refs, peeled_refs=None):
874     """Write a packed refs file.
875
876     :param f: empty file-like object to write to
877     :param packed_refs: dict of refname to sha of packed refs to write
878     :param peeled_refs: dict of refname to peeled value of sha
879     """
880     if peeled_refs is None:
881         peeled_refs = {}
882     else:
883         f.write(b'# pack-refs with: peeled\n')
884     for refname in sorted(packed_refs.keys()):
885         f.write(git_line(packed_refs[refname], refname))
886         if refname in peeled_refs:
887             f.write(b'^' + peeled_refs[refname] + b'\n')
888
889
890 def read_info_refs(f):
891     ret = {}
892     for l in f.readlines():
893         (sha, name) = l.rstrip(b"\r\n").split(b"\t", 1)
894         ret[name] = sha
895     return ret
896
897
898 def write_info_refs(refs, store):
899     """Generate info refs."""
900     for name, sha in sorted(refs.items()):
901         # get_refs() includes HEAD as a special case, but we don't want to
902         # advertise it
903         if name == b'HEAD':
904             continue
905         try:
906             o = store[sha]
907         except KeyError:
908             continue
909         peeled = store.peel_sha(sha)
910         yield o.id + b'\t' + name + b'\n'
911         if o.id != peeled.id:
912             yield peeled.id + b'\t' + name + ANNOTATED_TAG_SUFFIX + b'\n'
913
914
915 def is_local_branch(x):
916     return x.startswith(LOCAL_BRANCH_PREFIX)
917
918
919 def strip_peeled_refs(refs):
920     """Remove all peeled refs"""
921     return {ref: sha for (ref, sha) in refs.items()
922             if not ref.endswith(ANNOTATED_TAG_SUFFIX)}