Statistics
| Revision:

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/')