~ubuntu-branches/ubuntu/vivid/dulwich/vivid-proposed

« back to all changes in this revision

Viewing changes to dulwich/refs.py

  • Committer: Package Import Robot
  • Author(s): Jelmer Vernooij
  • Date: 2013-11-30 16:21:10 UTC
  • mfrom: (1.5.3)
  • Revision ID: package-import@ubuntu.com-20131130162110-8sm1dag21auasyc8
Tags: 0.9.4-1
New upstream release.

Show diffs side-by-side

added added

removed removed

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