/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/versionedfile.py

  • Committer: Robert Collins
  • Date: 2010-05-06 11:08:10 UTC
  • mto: This revision was merged to the branch mainline in revision 5223.
  • Revision ID: robertc@robertcollins.net-20100506110810-h3j07fh5gmw54s25
Cleaner matcher matching revised unlocking protocol.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006-2011 Canonical Ltd
 
1
# Copyright (C) 2006-2010 Canonical Ltd
 
2
#
 
3
# Authors:
 
4
#   Johan Rydberg <jrydberg@gnu.org>
2
5
#
3
6
# This program is free software; you can redistribute it and/or modify
4
7
# it under the terms of the GNU General Public License as published by
16
19
 
17
20
"""Versioned text file storage api."""
18
21
 
19
 
from __future__ import absolute_import
20
 
 
21
22
from copy import copy
22
 
import itertools
 
23
from cStringIO import StringIO
23
24
import os
24
25
import struct
25
26
from zlib import adler32
26
27
 
27
 
from ..lazy_import import lazy_import
 
28
from bzrlib.lazy_import import lazy_import
28
29
lazy_import(globals(), """
29
 
from breezy import (
 
30
import urllib
 
31
 
 
32
from bzrlib import (
30
33
    annotate,
31
 
    bencode,
32
34
    errors,
33
35
    graph as _mod_graph,
 
36
    groupcompress,
 
37
    index,
 
38
    knit,
34
39
    osutils,
35
40
    multiparent,
36
41
    tsort,
37
42
    revision,
38
 
    urlutils,
39
 
    )
40
 
from breezy.bzr import (
41
 
    groupcompress,
42
 
    index,
43
 
    knit,
44
 
    )
 
43
    ui,
 
44
    )
 
45
from bzrlib.graph import DictParentsProvider, Graph, StackedParentsProvider
 
46
from bzrlib.transport.memory import MemoryTransport
45
47
""")
46
 
from ..registry import Registry
47
 
from ..sixish import (
48
 
    BytesIO,
49
 
    viewitems,
50
 
    viewvalues,
51
 
    zip,
52
 
    )
53
 
from ..textmerge import TextMerge
 
48
from bzrlib.registry import Registry
 
49
from bzrlib.symbol_versioning import *
 
50
from bzrlib.textmerge import TextMerge
 
51
from bzrlib import bencode
54
52
 
55
53
 
56
54
adapter_registry = Registry()
57
 
adapter_registry.register_lazy(('knit-delta-gz', 'fulltext'), 'breezy.bzr.knit',
58
 
                               'DeltaPlainToFullText')
59
 
adapter_registry.register_lazy(('knit-ft-gz', 'fulltext'), 'breezy.bzr.knit',
60
 
                               'FTPlainToFullText')
 
55
adapter_registry.register_lazy(('knit-delta-gz', 'fulltext'), 'bzrlib.knit',
 
56
    'DeltaPlainToFullText')
 
57
adapter_registry.register_lazy(('knit-ft-gz', 'fulltext'), 'bzrlib.knit',
 
58
    'FTPlainToFullText')
61
59
adapter_registry.register_lazy(('knit-annotated-delta-gz', 'knit-delta-gz'),
62
 
                               'breezy.bzr.knit', 'DeltaAnnotatedToUnannotated')
 
60
    'bzrlib.knit', 'DeltaAnnotatedToUnannotated')
63
61
adapter_registry.register_lazy(('knit-annotated-delta-gz', 'fulltext'),
64
 
                               'breezy.bzr.knit', 'DeltaAnnotatedToFullText')
 
62
    'bzrlib.knit', 'DeltaAnnotatedToFullText')
65
63
adapter_registry.register_lazy(('knit-annotated-ft-gz', 'knit-ft-gz'),
66
 
                               'breezy.bzr.knit', 'FTAnnotatedToUnannotated')
 
64
    'bzrlib.knit', 'FTAnnotatedToUnannotated')
67
65
adapter_registry.register_lazy(('knit-annotated-ft-gz', 'fulltext'),
68
 
                               'breezy.bzr.knit', 'FTAnnotatedToFullText')
 
66
    'bzrlib.knit', 'FTAnnotatedToFullText')
69
67
# adapter_registry.register_lazy(('knit-annotated-ft-gz', 'chunked'),
70
 
#     'breezy.bzr.knit', 'FTAnnotatedToChunked')
 
68
#     'bzrlib.knit', 'FTAnnotatedToChunked')
71
69
 
72
70
 
73
71
class ContentFactory(object):
122
120
        if storage_kind == 'chunked':
123
121
            return self._chunks
124
122
        elif storage_kind == 'fulltext':
125
 
            return b''.join(self._chunks)
 
123
            return ''.join(self._chunks)
126
124
        raise errors.UnavailableRepresentation(self.key, storage_kind,
127
 
                                               self.storage_kind)
 
125
            self.storage_kind)
128
126
 
129
127
 
130
128
class FulltextContentFactory(ContentFactory):
149
147
        self.storage_kind = 'fulltext'
150
148
        self.key = key
151
149
        self.parents = parents
152
 
        if not isinstance(text, bytes):
153
 
            raise TypeError(text)
154
150
        self._text = text
155
151
 
156
152
    def get_bytes_as(self, storage_kind):
159
155
        elif storage_kind == 'chunked':
160
156
            return [self._text]
161
157
        raise errors.UnavailableRepresentation(self.key, storage_kind,
162
 
                                               self.storage_kind)
 
158
            self.storage_kind)
163
159
 
164
160
 
165
161
class AbsentContentFactory(ContentFactory):
210
206
            yield record
211
207
 
212
208
 
213
 
class _MPDiffGenerator(object):
214
 
    """Pull out the functionality for generating mp_diffs."""
215
 
 
216
 
    def __init__(self, vf, keys):
217
 
        self.vf = vf
218
 
        # This is the order the keys were requested in
219
 
        self.ordered_keys = tuple(keys)
220
 
        # keys + their parents, what we need to compute the diffs
221
 
        self.needed_keys = ()
222
 
        # Map from key: mp_diff
223
 
        self.diffs = {}
224
 
        # Map from key: parents_needed (may have ghosts)
225
 
        self.parent_map = {}
226
 
        # Parents that aren't present
227
 
        self.ghost_parents = ()
228
 
        # Map from parent_key => number of children for this text
229
 
        self.refcounts = {}
230
 
        # Content chunks that are cached while we still need them
231
 
        self.chunks = {}
232
 
 
233
 
    def _find_needed_keys(self):
234
 
        """Find the set of keys we need to request.
235
 
 
236
 
        This includes all the original keys passed in, and the non-ghost
237
 
        parents of those keys.
238
 
 
239
 
        :return: (needed_keys, refcounts)
240
 
            needed_keys is the set of all texts we need to extract
241
 
            refcounts is a dict of {key: num_children} letting us know when we
242
 
                no longer need to cache a given parent text
243
 
        """
244
 
        # All the keys and their parents
245
 
        needed_keys = set(self.ordered_keys)
246
 
        parent_map = self.vf.get_parent_map(needed_keys)
247
 
        self.parent_map = parent_map
248
 
        # TODO: Should we be using a different construct here? I think this
249
 
        #       uses difference_update internally, and we expect the result to
250
 
        #       be tiny
251
 
        missing_keys = needed_keys.difference(parent_map)
252
 
        if missing_keys:
253
 
            raise errors.RevisionNotPresent(list(missing_keys)[0], self.vf)
254
 
        # Parents that might be missing. They are allowed to be ghosts, but we
255
 
        # should check for them
256
 
        refcounts = {}
257
 
        setdefault = refcounts.setdefault
258
 
        just_parents = set()
259
 
        for child_key, parent_keys in viewitems(parent_map):
260
 
            if not parent_keys:
261
 
                # parent_keys may be None if a given VersionedFile claims to
262
 
                # not support graph operations.
263
 
                continue
264
 
            just_parents.update(parent_keys)
265
 
            needed_keys.update(parent_keys)
266
 
            for p in parent_keys:
267
 
                refcounts[p] = setdefault(p, 0) + 1
268
 
        just_parents.difference_update(parent_map)
269
 
        # Remove any parents that are actually ghosts from the needed set
270
 
        self.present_parents = set(self.vf.get_parent_map(just_parents))
271
 
        self.ghost_parents = just_parents.difference(self.present_parents)
272
 
        needed_keys.difference_update(self.ghost_parents)
273
 
        self.needed_keys = needed_keys
274
 
        self.refcounts = refcounts
275
 
        return needed_keys, refcounts
276
 
 
277
 
    def _compute_diff(self, key, parent_lines, lines):
278
 
        """Compute a single mp_diff, and store it in self._diffs"""
279
 
        if len(parent_lines) > 0:
280
 
            # XXX: _extract_blocks is not usefully defined anywhere...
281
 
            #      It was meant to extract the left-parent diff without
282
 
            #      having to recompute it for Knit content (pack-0.92,
283
 
            #      etc). That seems to have regressed somewhere
284
 
            left_parent_blocks = self.vf._extract_blocks(key,
285
 
                                                         parent_lines[0], lines)
286
 
        else:
287
 
            left_parent_blocks = None
288
 
        diff = multiparent.MultiParent.from_lines(lines,
289
 
                                                  parent_lines, left_parent_blocks)
290
 
        self.diffs[key] = diff
291
 
 
292
 
    def _process_one_record(self, key, this_chunks):
293
 
        parent_keys = None
294
 
        if key in self.parent_map:
295
 
            # This record should be ready to diff, since we requested
296
 
            # content in 'topological' order
297
 
            parent_keys = self.parent_map.pop(key)
298
 
            # If a VersionedFile claims 'no-graph' support, then it may return
299
 
            # None for any parent request, so we replace it with an empty tuple
300
 
            if parent_keys is None:
301
 
                parent_keys = ()
302
 
            parent_lines = []
303
 
            for p in parent_keys:
304
 
                # Alternatively we could check p not in self.needed_keys, but
305
 
                # ghost_parents should be tiny versus huge
306
 
                if p in self.ghost_parents:
307
 
                    continue
308
 
                refcount = self.refcounts[p]
309
 
                if refcount == 1:  # Last child reference
310
 
                    self.refcounts.pop(p)
311
 
                    parent_chunks = self.chunks.pop(p)
312
 
                else:
313
 
                    self.refcounts[p] = refcount - 1
314
 
                    parent_chunks = self.chunks[p]
315
 
                p_lines = osutils.chunks_to_lines(parent_chunks)
316
 
                # TODO: Should we cache the line form? We did the
317
 
                #       computation to get it, but storing it this way will
318
 
                #       be less memory efficient...
319
 
                parent_lines.append(p_lines)
320
 
                del p_lines
321
 
            lines = osutils.chunks_to_lines(this_chunks)
322
 
            # Since we needed the lines, we'll go ahead and cache them this way
323
 
            this_chunks = lines
324
 
            self._compute_diff(key, parent_lines, lines)
325
 
            del lines
326
 
        # Is this content required for any more children?
327
 
        if key in self.refcounts:
328
 
            self.chunks[key] = this_chunks
329
 
 
330
 
    def _extract_diffs(self):
331
 
        needed_keys, refcounts = self._find_needed_keys()
332
 
        for record in self.vf.get_record_stream(needed_keys,
333
 
                                                'topological', True):
334
 
            if record.storage_kind == 'absent':
335
 
                raise errors.RevisionNotPresent(record.key, self.vf)
336
 
            self._process_one_record(record.key,
337
 
                                     record.get_bytes_as('chunked'))
338
 
 
339
 
    def compute_diffs(self):
340
 
        self._extract_diffs()
341
 
        dpop = self.diffs.pop
342
 
        return [dpop(k) for k in self.ordered_keys]
343
 
 
344
 
 
345
209
class VersionedFile(object):
346
210
    """Versioned text file storage.
347
211
 
395
259
        raise NotImplementedError
396
260
 
397
261
    def add_lines(self, version_id, parents, lines, parent_texts=None,
398
 
                  left_matching_blocks=None, nostore_sha=None, random_id=False,
399
 
                  check_content=True):
 
262
        left_matching_blocks=None, nostore_sha=None, random_id=False,
 
263
        check_content=True):
400
264
        """Add a single text on top of the versioned file.
401
265
 
402
266
        Must raise RevisionAlreadyPresent if the new version is
436
300
        """
437
301
        self._check_write_ok()
438
302
        return self._add_lines(version_id, parents, lines, parent_texts,
439
 
                               left_matching_blocks, nostore_sha, random_id, check_content)
 
303
            left_matching_blocks, nostore_sha, random_id, check_content)
440
304
 
441
305
    def _add_lines(self, version_id, parents, lines, parent_texts,
442
 
                   left_matching_blocks, nostore_sha, random_id, check_content):
 
306
        left_matching_blocks, nostore_sha, random_id, check_content):
443
307
        """Helper to do the class specific add_lines."""
444
308
        raise NotImplementedError(self.add_lines)
445
309
 
446
310
    def add_lines_with_ghosts(self, version_id, parents, lines,
447
 
                              parent_texts=None, nostore_sha=None, random_id=False,
448
 
                              check_content=True, left_matching_blocks=None):
 
311
        parent_texts=None, nostore_sha=None, random_id=False,
 
312
        check_content=True, left_matching_blocks=None):
449
313
        """Add lines to the versioned file, allowing ghosts to be present.
450
314
 
451
315
        This takes the same parameters as add_lines and returns the same.
452
316
        """
453
317
        self._check_write_ok()
454
318
        return self._add_lines_with_ghosts(version_id, parents, lines,
455
 
                                           parent_texts, nostore_sha, random_id, check_content, left_matching_blocks)
 
319
            parent_texts, nostore_sha, random_id, check_content, left_matching_blocks)
456
320
 
457
321
    def _add_lines_with_ghosts(self, version_id, parents, lines, parent_texts,
458
 
                               nostore_sha, random_id, check_content, left_matching_blocks):
 
322
        nostore_sha, random_id, check_content, left_matching_blocks):
459
323
        """Helper to do class specific add_lines_with_ghosts."""
460
324
        raise NotImplementedError(self.add_lines_with_ghosts)
461
325
 
466
330
    def _check_lines_not_unicode(self, lines):
467
331
        """Check that lines being added to a versioned file are not unicode."""
468
332
        for line in lines:
469
 
            if not isinstance(line, bytes):
 
333
            if line.__class__ is not str:
470
334
                raise errors.BzrBadParameterUnicode("lines")
471
335
 
472
336
    def _check_lines_are_lines(self, lines):
473
337
        """Check that the lines really are full lines without inline EOL."""
474
338
        for line in lines:
475
 
            if b'\n' in line[:-1]:
 
339
            if '\n' in line[:-1]:
476
340
                raise errors.BzrBadParameterContainsNewline("lines")
477
341
 
478
342
    def get_format_signature(self):
484
348
 
485
349
    def make_mpdiffs(self, version_ids):
486
350
        """Create multiparent diffs for specified versions."""
487
 
        # XXX: Can't use _MPDiffGenerator just yet. This is because version_ids
488
 
        #      is a list of strings, not keys. And while self.get_record_stream
489
 
        #      is supported, it takes *keys*, while self.get_parent_map() takes
490
 
        #      strings... *sigh*
491
351
        knit_versions = set()
492
352
        knit_versions.update(version_ids)
493
353
        parent_map = self.get_parent_map(version_ids)
497
357
            except KeyError:
498
358
                raise errors.RevisionNotPresent(version_id, self)
499
359
        # We need to filter out ghosts, because we can't diff against them.
500
 
        knit_versions = set(self.get_parent_map(knit_versions))
 
360
        knit_versions = set(self.get_parent_map(knit_versions).keys())
501
361
        lines = dict(zip(knit_versions,
502
 
                         self._get_lf_split_line_list(knit_versions)))
 
362
            self._get_lf_split_line_list(knit_versions)))
503
363
        diffs = []
504
364
        for version_id in version_ids:
505
365
            target = lines[version_id]
506
366
            try:
507
367
                parents = [lines[p] for p in parent_map[version_id] if p in
508
 
                           knit_versions]
 
368
                    knit_versions]
509
369
            except KeyError:
510
370
                # I don't know how this could ever trigger.
511
371
                # parent_map[version_id] was already triggered in the previous
518
378
            else:
519
379
                left_parent_blocks = None
520
380
            diffs.append(multiparent.MultiParent.from_lines(target, parents,
521
 
                                                            left_parent_blocks))
 
381
                         left_parent_blocks))
522
382
        return diffs
523
383
 
524
384
    def _extract_blocks(self, version_id, source, target):
541
401
        for version, parent_ids, expected_sha1, mpdiff in records:
542
402
            needed_parents.update(p for p in parent_ids
543
403
                                  if not mpvf.has_version(p))
544
 
        present_parents = set(self.get_parent_map(needed_parents))
 
404
        present_parents = set(self.get_parent_map(needed_parents).keys())
545
405
        for parent_id, lines in zip(present_parents,
546
 
                                    self._get_lf_split_line_list(present_parents)):
 
406
                                 self._get_lf_split_line_list(present_parents)):
547
407
            mpvf.add_version(lines, parent_id, [])
548
 
        for (version, parent_ids, expected_sha1, mpdiff), lines in zip(
549
 
                records, mpvf.get_line_list(versions)):
 
408
        for (version, parent_ids, expected_sha1, mpdiff), lines in\
 
409
            zip(records, mpvf.get_line_list(versions)):
550
410
            if len(parent_ids) == 1:
551
411
                left_matching_blocks = list(mpdiff.get_matching_blocks(0,
552
 
                                                                       mpvf.get_diff(parent_ids[0]).num_lines()))
 
412
                    mpvf.get_diff(parent_ids[0]).num_lines()))
553
413
            else:
554
414
                left_matching_blocks = None
555
415
            try:
556
416
                _, _, version_text = self.add_lines_with_ghosts(version,
557
 
                                                                parent_ids, lines, vf_parents,
558
 
                                                                left_matching_blocks=left_matching_blocks)
 
417
                    parent_ids, lines, vf_parents,
 
418
                    left_matching_blocks=left_matching_blocks)
559
419
            except NotImplementedError:
560
420
                # The vf can't handle ghosts, so add lines normally, which will
561
421
                # (reasonably) fail if there are ghosts in the data.
562
422
                _, _, version_text = self.add_lines(version,
563
 
                                                    parent_ids, lines, vf_parents,
564
 
                                                    left_matching_blocks=left_matching_blocks)
 
423
                    parent_ids, lines, vf_parents,
 
424
                    left_matching_blocks=left_matching_blocks)
565
425
            vf_parents[version] = version_text
566
426
        sha1s = self.get_sha1s(versions)
567
427
        for version, parent_ids, expected_sha1, mpdiff in records:
574
434
        Raises RevisionNotPresent if version is not present in
575
435
        file history.
576
436
        """
577
 
        return b''.join(self.get_lines(version_id))
 
437
        return ''.join(self.get_lines(version_id))
578
438
    get_string = get_text
579
439
 
580
440
    def get_texts(self, version_ids):
583
443
        Raises RevisionNotPresent if version is not present in
584
444
        file history.
585
445
        """
586
 
        return [b''.join(self.get_lines(v)) for v in version_ids]
 
446
        return [''.join(self.get_lines(v)) for v in version_ids]
587
447
 
588
448
    def get_lines(self, version_id):
589
449
        """Return version contents as a sequence of lines.
594
454
        raise NotImplementedError(self.get_lines)
595
455
 
596
456
    def _get_lf_split_line_list(self, version_ids):
597
 
        return [BytesIO(t).readlines() for t in self.get_texts(version_ids)]
 
457
        return [StringIO(t).readlines() for t in self.get_texts(version_ids)]
598
458
 
599
459
    def get_ancestry(self, version_ids, topo_sorted=True):
600
460
        """Return a list of all ancestors of given version(s). This
605
465
 
606
466
        Must raise RevisionNotPresent if any of the given versions are
607
467
        not present in file history."""
 
468
        if isinstance(version_ids, basestring):
 
469
            version_ids = [version_ids]
608
470
        raise NotImplementedError(self.get_ancestry)
609
471
 
610
472
    def get_ancestry_with_ghosts(self, version_ids):
671
533
        """
672
534
        raise NotImplementedError(self.iter_lines_added_or_present_in_versions)
673
535
 
674
 
    def plan_merge(self, ver_a, ver_b, base=None):
 
536
    def plan_merge(self, ver_a, ver_b):
675
537
        """Return pseudo-annotation indicating how the two versions merge.
676
538
 
677
539
        This is computed between versions a and b and their common
716
578
        self.calls = []
717
579
 
718
580
    def add_lines(self, key, parents, lines, parent_texts=None,
719
 
                  left_matching_blocks=None, nostore_sha=None, random_id=False,
720
 
                  check_content=True):
 
581
        left_matching_blocks=None, nostore_sha=None, random_id=False,
 
582
        check_content=True):
721
583
        self.calls.append(("add_lines", key, parents, lines, parent_texts,
722
 
                           left_matching_blocks, nostore_sha, random_id, check_content))
 
584
            left_matching_blocks, nostore_sha, random_id, check_content))
723
585
        return self._backing_vf.add_lines(key, parents, lines, parent_texts,
724
 
                                          left_matching_blocks, nostore_sha, random_id, check_content)
 
586
            left_matching_blocks, nostore_sha, random_id, check_content)
725
587
 
726
588
    def check(self):
727
589
        self._backing_vf.check()
732
594
 
733
595
    def get_record_stream(self, keys, sort_order, include_delta_closure):
734
596
        self.calls.append(("get_record_stream", list(keys), sort_order,
735
 
                           include_delta_closure))
 
597
            include_delta_closure))
736
598
        return self._backing_vf.get_record_stream(keys, sort_order,
737
 
                                                  include_delta_closure)
 
599
            include_delta_closure)
738
600
 
739
601
    def get_sha1s(self, keys):
740
602
        self.calls.append(("get_sha1s", copy(keys)))
771
633
 
772
634
    def get_record_stream(self, keys, sort_order, include_delta_closure):
773
635
        self.calls.append(("get_record_stream", list(keys), sort_order,
774
 
                           include_delta_closure))
 
636
            include_delta_closure))
775
637
        if sort_order == 'unordered':
776
638
            def sort_key(key):
777
639
                return (self._key_priority.get(key, 0), key)
779
641
            # backing_vf
780
642
            for key in sorted(keys, key=sort_key):
781
643
                for record in self._backing_vf.get_record_stream([key],
782
 
                                                                 'unordered', include_delta_closure):
 
644
                                'unordered', include_delta_closure):
783
645
                    yield record
784
646
        else:
785
647
            for record in self._backing_vf.get_record_stream(keys, sort_order,
786
 
                                                             include_delta_closure):
 
648
                            include_delta_closure):
787
649
                yield record
788
650
 
789
651
 
793
655
    def map(self, key):
794
656
        """Map key to an underlying storage identifier.
795
657
 
796
 
        :param key: A key tuple e.g. (b'file-id', b'revision-id').
 
658
        :param key: A key tuple e.g. ('file-id', 'revision-id').
797
659
        :return: An underlying storage identifier, specific to the partitioning
798
660
            mechanism.
799
661
        """
830
692
 
831
693
    def map(self, key):
832
694
        """See KeyMapper.map()."""
833
 
        return urlutils.quote(self._map(key))
 
695
        return urllib.quote(self._map(key))
834
696
 
835
697
    def unmap(self, partition_id):
836
698
        """See KeyMapper.unmap()."""
837
 
        return self._unmap(urlutils.unquote(partition_id))
 
699
        return self._unmap(urllib.unquote(partition_id))
838
700
 
839
701
 
840
702
class PrefixMapper(URLEscapeMapper):
845
707
 
846
708
    def _map(self, key):
847
709
        """See KeyMapper.map()."""
848
 
        return key[0].decode('utf-8')
 
710
        return key[0]
849
711
 
850
712
    def _unmap(self, partition_id):
851
713
        """See KeyMapper.unmap()."""
852
 
        return (partition_id.encode('utf-8'),)
 
714
        return (partition_id,)
853
715
 
854
716
 
855
717
class HashPrefixMapper(URLEscapeMapper):
861
723
    def _map(self, key):
862
724
        """See KeyMapper.map()."""
863
725
        prefix = self._escape(key[0])
864
 
        return "%02x/%s" % (adler32(prefix) & 0xff, prefix.decode('utf-8'))
 
726
        return "%02x/%s" % (adler32(prefix) & 0xff, prefix)
865
727
 
866
728
    def _escape(self, prefix):
867
729
        """No escaping needed here."""
869
731
 
870
732
    def _unmap(self, partition_id):
871
733
        """See KeyMapper.unmap()."""
872
 
        return (self._unescape(osutils.basename(partition_id)).encode('utf-8'),)
 
734
        return (self._unescape(osutils.basename(partition_id)),)
873
735
 
874
736
    def _unescape(self, basename):
875
737
        """No unescaping needed for HashPrefixMapper."""
882
744
    This mapper is for use with a transport based backend.
883
745
    """
884
746
 
885
 
    _safe = bytearray(b"abcdefghijklmnopqrstuvwxyz0123456789-_@,.")
 
747
    _safe = "abcdefghijklmnopqrstuvwxyz0123456789-_@,."
886
748
 
887
749
    def _escape(self, prefix):
888
750
        """Turn a key element into a filesystem safe string.
889
751
 
890
 
        This is similar to a plain urlutils.quote, except
 
752
        This is similar to a plain urllib.quote, except
891
753
        it uses specific safe characters, so that it doesn't
892
754
        have to translate a lot of valid file ids.
893
755
        """
894
756
        # @ does not get escaped. This is because it is a valid
895
757
        # filesystem character we use all the time, and it looks
896
758
        # a lot better than seeing %40 all the time.
897
 
        r = [(c in self._safe) and chr(c) or ('%%%02x' % c)
898
 
             for c in bytearray(prefix)]
899
 
        return ''.join(r).encode('ascii')
 
759
        r = [((c in self._safe) and c or ('%%%02x' % ord(c)))
 
760
             for c in prefix]
 
761
        return ''.join(r)
900
762
 
901
763
    def _unescape(self, basename):
902
764
        """Escaped names are easily unescaped by urlutils."""
903
 
        return urlutils.unquote(basename)
 
765
        return urllib.unquote(basename)
904
766
 
905
767
 
906
768
def make_versioned_files_factory(versioned_file_factory, mapper):
912
774
    """
913
775
    def factory(transport):
914
776
        return ThunkedVersionedFiles(transport, versioned_file_factory, mapper,
915
 
                                     lambda: True)
 
777
            lambda:True)
916
778
    return factory
917
779
 
918
780
 
927
789
 
928
790
    The keyspace is expressed via simple tuples. Any instance of VersionedFiles
929
791
    may have a different length key-size, but that size will be constant for
930
 
    all texts added to or retrieved from it. For instance, breezy uses
 
792
    all texts added to or retrieved from it. For instance, bzrlib uses
931
793
    instances with a key-size of 2 for storing user files in a repository, with
932
794
    the first element the fileid, and the second the version of that file.
933
795
 
934
796
    The use of tuples allows a single code base to support several different
935
797
    uses with only the mapping logic changing from instance to instance.
936
 
 
937
 
    :ivar _immediate_fallback_vfs: For subclasses that support stacking,
938
 
        this is a list of other VersionedFiles immediately underneath this
939
 
        one.  They may in turn each have further fallbacks.
940
798
    """
941
799
 
942
800
    def add_lines(self, key, parents, lines, parent_texts=None,
943
 
                  left_matching_blocks=None, nostore_sha=None, random_id=False,
944
 
                  check_content=True):
 
801
        left_matching_blocks=None, nostore_sha=None, random_id=False,
 
802
        check_content=True):
945
803
        """Add a text to the store.
946
804
 
947
805
        :param key: The key tuple of the text to add. If the last element is
978
836
        """
979
837
        raise NotImplementedError(self.add_lines)
980
838
 
 
839
    def _add_text(self, key, parents, text, nostore_sha=None, random_id=False):
 
840
        """Add a text to the store.
 
841
 
 
842
        This is a private function for use by CommitBuilder.
 
843
 
 
844
        :param key: The key tuple of the text to add. If the last element is
 
845
            None, a CHK string will be generated during the addition.
 
846
        :param parents: The parents key tuples of the text to add.
 
847
        :param text: A string containing the text to be committed.
 
848
        :param nostore_sha: Raise ExistingContent and do not add the lines to
 
849
            the versioned file if the digest of the lines matches this.
 
850
        :param random_id: If True a random id has been selected rather than
 
851
            an id determined by some deterministic process such as a converter
 
852
            from a foreign VCS. When True the backend may choose not to check
 
853
            for uniqueness of the resulting key within the versioned file, so
 
854
            this should only be done when the result is expected to be unique
 
855
            anyway.
 
856
        :param check_content: If True, the lines supplied are verified to be
 
857
            bytestrings that are correctly formed lines.
 
858
        :return: The text sha1, the number of bytes in the text, and an opaque
 
859
                 representation of the inserted version which can be provided
 
860
                 back to future _add_text calls in the parent_texts dictionary.
 
861
        """
 
862
        # The default implementation just thunks over to .add_lines(),
 
863
        # inefficient, but it works.
 
864
        return self.add_lines(key, parents, osutils.split_lines(text),
 
865
                              nostore_sha=nostore_sha,
 
866
                              random_id=random_id,
 
867
                              check_content=True)
 
868
 
981
869
    def add_mpdiffs(self, records):
982
870
        """Add mpdiffs to this VersionedFile.
983
871
 
998
886
        # easily exhaust memory.
999
887
        chunks_to_lines = osutils.chunks_to_lines
1000
888
        for record in self.get_record_stream(needed_parents, 'unordered',
1001
 
                                             True):
 
889
            True):
1002
890
            if record.storage_kind == 'absent':
1003
891
                continue
1004
892
            mpvf.add_version(chunks_to_lines(record.get_bytes_as('chunked')),
1005
 
                             record.key, [])
1006
 
        for (key, parent_keys, expected_sha1, mpdiff), lines in zip(
1007
 
                records, mpvf.get_line_list(versions)):
 
893
                record.key, [])
 
894
        for (key, parent_keys, expected_sha1, mpdiff), lines in\
 
895
            zip(records, mpvf.get_line_list(versions)):
1008
896
            if len(parent_keys) == 1:
1009
897
                left_matching_blocks = list(mpdiff.get_matching_blocks(0,
1010
 
                                                                       mpvf.get_diff(parent_keys[0]).num_lines()))
 
898
                    mpvf.get_diff(parent_keys[0]).num_lines()))
1011
899
            else:
1012
900
                left_matching_blocks = None
1013
901
            version_sha1, _, version_text = self.add_lines(key,
1014
 
                                                           parent_keys, lines, vf_parents,
1015
 
                                                           left_matching_blocks=left_matching_blocks)
 
902
                parent_keys, lines, vf_parents,
 
903
                left_matching_blocks=left_matching_blocks)
1016
904
            if version_sha1 != expected_sha1:
1017
905
                raise errors.VersionedFileInvalidChecksum(version)
1018
906
            vf_parents[key] = version_text
1026
914
 
1027
915
    def check(self, progress_bar=None):
1028
916
        """Check this object for integrity.
1029
 
 
 
917
        
1030
918
        :param progress_bar: A progress bar to output as the check progresses.
1031
919
        :param keys: Specific keys within the VersionedFiles to check. When
1032
920
            this parameter is not None, check() becomes a generator as per
1051
939
    def _check_lines_not_unicode(self, lines):
1052
940
        """Check that lines being added to a versioned file are not unicode."""
1053
941
        for line in lines:
1054
 
            if line.__class__ is not bytes:
 
942
            if line.__class__ is not str:
1055
943
                raise errors.BzrBadParameterUnicode("lines")
1056
944
 
1057
945
    def _check_lines_are_lines(self, lines):
1058
946
        """Check that the lines really are full lines without inline EOL."""
1059
947
        for line in lines:
1060
 
            if b'\n' in line[:-1]:
 
948
            if '\n' in line[:-1]:
1061
949
                raise errors.BzrBadParameterContainsNewline("lines")
1062
950
 
1063
951
    def get_known_graph_ancestry(self, keys):
1068
956
        while pending:
1069
957
            this_parent_map = self.get_parent_map(pending)
1070
958
            parent_map.update(this_parent_map)
1071
 
            pending = set(itertools.chain.from_iterable(
1072
 
                viewvalues(this_parent_map)))
1073
 
            pending.difference_update(parent_map)
 
959
            pending = set()
 
960
            map(pending.update, this_parent_map.itervalues())
 
961
            pending = pending.difference(parent_map)
1074
962
        kg = _mod_graph.KnownGraph(parent_map)
1075
963
        return kg
1076
964
 
1107
995
        """
1108
996
        raise NotImplementedError(self.get_sha1s)
1109
997
 
1110
 
    __contains__ = index._has_key_from_parent_map
 
998
    has_key = index._has_key_from_parent_map
1111
999
 
1112
1000
    def get_missing_compression_parent_keys(self):
1113
1001
        """Return an iterable of keys of missing compression parents.
1159
1047
 
1160
1048
    def make_mpdiffs(self, keys):
1161
1049
        """Create multiparent diffs for specified keys."""
1162
 
        generator = _MPDiffGenerator(self, keys)
1163
 
        return generator.compute_diffs()
1164
 
 
1165
 
    def get_annotator(self):
1166
 
        return annotate.Annotator(self)
 
1050
        keys_order = tuple(keys)
 
1051
        keys = frozenset(keys)
 
1052
        knit_keys = set(keys)
 
1053
        parent_map = self.get_parent_map(keys)
 
1054
        for parent_keys in parent_map.itervalues():
 
1055
            if parent_keys:
 
1056
                knit_keys.update(parent_keys)
 
1057
        missing_keys = keys - set(parent_map)
 
1058
        if missing_keys:
 
1059
            raise errors.RevisionNotPresent(list(missing_keys)[0], self)
 
1060
        # We need to filter out ghosts, because we can't diff against them.
 
1061
        maybe_ghosts = knit_keys - keys
 
1062
        ghosts = maybe_ghosts - set(self.get_parent_map(maybe_ghosts))
 
1063
        knit_keys.difference_update(ghosts)
 
1064
        lines = {}
 
1065
        chunks_to_lines = osutils.chunks_to_lines
 
1066
        for record in self.get_record_stream(knit_keys, 'topological', True):
 
1067
            lines[record.key] = chunks_to_lines(record.get_bytes_as('chunked'))
 
1068
            # line_block_dict = {}
 
1069
            # for parent, blocks in record.extract_line_blocks():
 
1070
            #   line_blocks[parent] = blocks
 
1071
            # line_blocks[record.key] = line_block_dict
 
1072
        diffs = []
 
1073
        for key in keys_order:
 
1074
            target = lines[key]
 
1075
            parents = parent_map[key] or []
 
1076
            # Note that filtering knit_keys can lead to a parent difference
 
1077
            # between the creation and the application of the mpdiff.
 
1078
            parent_lines = [lines[p] for p in parents if p in knit_keys]
 
1079
            if len(parent_lines) > 0:
 
1080
                left_parent_blocks = self._extract_blocks(key, parent_lines[0],
 
1081
                    target)
 
1082
            else:
 
1083
                left_parent_blocks = None
 
1084
            diffs.append(multiparent.MultiParent.from_lines(target,
 
1085
                parent_lines, left_parent_blocks))
 
1086
        return diffs
1167
1087
 
1168
1088
    missing_keys = index._missing_keys_from_parent_map
1169
1089
 
1170
1090
    def _extract_blocks(self, version_id, source, target):
1171
1091
        return None
1172
1092
 
1173
 
    def _transitive_fallbacks(self):
1174
 
        """Return the whole stack of fallback versionedfiles.
1175
 
 
1176
 
        This VersionedFiles may have a list of fallbacks, but it doesn't
1177
 
        necessarily know about the whole stack going down, and it can't know
1178
 
        at open time because they may change after the objects are opened.
1179
 
        """
1180
 
        all_fallbacks = []
1181
 
        for a_vfs in self._immediate_fallback_vfs:
1182
 
            all_fallbacks.append(a_vfs)
1183
 
            all_fallbacks.extend(a_vfs._transitive_fallbacks())
1184
 
        return all_fallbacks
1185
 
 
1186
1093
 
1187
1094
class ThunkedVersionedFiles(VersionedFiles):
1188
1095
    """Storage for many versioned files thunked onto a 'VersionedFile' class.
1202
1109
        self._is_locked = is_locked
1203
1110
 
1204
1111
    def add_lines(self, key, parents, lines, parent_texts=None,
1205
 
                  left_matching_blocks=None, nostore_sha=None, random_id=False,
1206
 
                  check_content=True):
 
1112
        left_matching_blocks=None, nostore_sha=None, random_id=False,
 
1113
        check_content=True):
1207
1114
        """See VersionedFiles.add_lines()."""
1208
1115
        path = self._mapper.map(key)
1209
1116
        version_id = key[-1]
1212
1119
        try:
1213
1120
            try:
1214
1121
                return vf.add_lines_with_ghosts(version_id, parents, lines,
1215
 
                                                parent_texts=parent_texts,
1216
 
                                                left_matching_blocks=left_matching_blocks,
1217
 
                                                nostore_sha=nostore_sha, random_id=random_id,
1218
 
                                                check_content=check_content)
 
1122
                    parent_texts=parent_texts,
 
1123
                    left_matching_blocks=left_matching_blocks,
 
1124
                    nostore_sha=nostore_sha, random_id=random_id,
 
1125
                    check_content=check_content)
1219
1126
            except NotImplementedError:
1220
1127
                return vf.add_lines(version_id, parents, lines,
1221
 
                                    parent_texts=parent_texts,
1222
 
                                    left_matching_blocks=left_matching_blocks,
1223
 
                                    nostore_sha=nostore_sha, random_id=random_id,
1224
 
                                    check_content=check_content)
 
1128
                    parent_texts=parent_texts,
 
1129
                    left_matching_blocks=left_matching_blocks,
 
1130
                    nostore_sha=nostore_sha, random_id=random_id,
 
1131
                    check_content=check_content)
1225
1132
        except errors.NoSuchFile:
1226
1133
            # parent directory may be missing, try again.
1227
1134
            self._transport.mkdir(osutils.dirname(path))
1228
1135
            try:
1229
1136
                return vf.add_lines_with_ghosts(version_id, parents, lines,
1230
 
                                                parent_texts=parent_texts,
1231
 
                                                left_matching_blocks=left_matching_blocks,
1232
 
                                                nostore_sha=nostore_sha, random_id=random_id,
1233
 
                                                check_content=check_content)
 
1137
                    parent_texts=parent_texts,
 
1138
                    left_matching_blocks=left_matching_blocks,
 
1139
                    nostore_sha=nostore_sha, random_id=random_id,
 
1140
                    check_content=check_content)
1234
1141
            except NotImplementedError:
1235
1142
                return vf.add_lines(version_id, parents, lines,
1236
 
                                    parent_texts=parent_texts,
1237
 
                                    left_matching_blocks=left_matching_blocks,
1238
 
                                    nostore_sha=nostore_sha, random_id=random_id,
1239
 
                                    check_content=check_content)
 
1143
                    parent_texts=parent_texts,
 
1144
                    left_matching_blocks=left_matching_blocks,
 
1145
                    nostore_sha=nostore_sha, random_id=random_id,
 
1146
                    check_content=check_content)
1240
1147
 
1241
1148
    def annotate(self, key):
1242
1149
        """Return a list of (version-key, line) tuples for the text of key.
1252
1159
            result.append((prefix + (origin,), line))
1253
1160
        return result
1254
1161
 
 
1162
    def get_annotator(self):
 
1163
        return annotate.Annotator(self)
 
1164
 
1255
1165
    def check(self, progress_bar=None, keys=None):
1256
1166
        """See VersionedFiles.check()."""
1257
1167
        # XXX: This is over-enthusiastic but as we only thunk for Weaves today
1258
 
        # this is tolerable. Ideally we'd pass keys down to check() and
 
1168
        # this is tolerable. Ideally we'd pass keys down to check() and 
1259
1169
        # have the older VersiondFile interface updated too.
1260
1170
        for prefix, vf in self._iter_all_components():
1261
1171
            vf.check()
1271
1181
        """
1272
1182
        prefixes = self._partition_keys(keys)
1273
1183
        result = {}
1274
 
        for prefix, suffixes in viewitems(prefixes):
 
1184
        for prefix, suffixes in prefixes.items():
1275
1185
            path = self._mapper.map(prefix)
1276
1186
            vf = self._get_vf(path)
1277
1187
            parent_map = vf.get_parent_map(suffixes)
1278
 
            for key, parents in viewitems(parent_map):
 
1188
            for key, parents in parent_map.items():
1279
1189
                result[prefix + (key,)] = tuple(
1280
1190
                    prefix + (parent,) for parent in parents)
1281
1191
        return result
1284
1194
        if not self._is_locked():
1285
1195
            raise errors.ObjectNotLocked(self)
1286
1196
        return self._file_factory(path, self._transport, create=True,
1287
 
                                  get_scope=lambda: None)
 
1197
            get_scope=lambda:None)
1288
1198
 
1289
1199
    def _partition_keys(self, keys):
1290
1200
        """Turn keys into a dict of prefix:suffix_list."""
1294
1204
            prefix_keys.append(key[-1])
1295
1205
        return result
1296
1206
 
1297
 
    def _iter_all_prefixes(self):
 
1207
    def _get_all_prefixes(self):
1298
1208
        # Identify all key prefixes.
1299
1209
        # XXX: A bit hacky, needs polish.
1300
 
        if isinstance(self._mapper, ConstantMapper):
 
1210
        if type(self._mapper) == ConstantMapper:
1301
1211
            paths = [self._mapper.map(())]
1302
1212
            prefixes = [()]
1303
1213
        else:
1317
1227
        for prefix, suffixes, vf in self._iter_keys_vf(keys):
1318
1228
            suffixes = [(suffix,) for suffix in suffixes]
1319
1229
            for record in vf.get_record_stream(suffixes, ordering,
1320
 
                                               include_delta_closure):
 
1230
                include_delta_closure):
1321
1231
                if record.parents is not None:
1322
1232
                    record.parents = tuple(
1323
1233
                        prefix + parent for parent in record.parents)
1327
1237
    def _iter_keys_vf(self, keys):
1328
1238
        prefixes = self._partition_keys(keys)
1329
1239
        sha1s = {}
1330
 
        for prefix, suffixes in viewitems(prefixes):
 
1240
        for prefix, suffixes in prefixes.items():
1331
1241
            path = self._mapper.map(prefix)
1332
1242
            vf = self._get_vf(path)
1333
1243
            yield prefix, suffixes, vf
1335
1245
    def get_sha1s(self, keys):
1336
1246
        """See VersionedFiles.get_sha1s()."""
1337
1247
        sha1s = {}
1338
 
        for prefix, suffixes, vf in self._iter_keys_vf(keys):
 
1248
        for prefix,suffixes, vf in self._iter_keys_vf(keys):
1339
1249
            vf_sha1s = vf.get_sha1s(suffixes)
1340
 
            for suffix, sha1 in viewitems(vf_sha1s):
 
1250
            for suffix, sha1 in vf_sha1s.iteritems():
1341
1251
                sha1s[prefix + (suffix,)] = sha1
1342
1252
        return sha1s
1343
1253
 
1389
1299
                yield line, prefix + (version,)
1390
1300
 
1391
1301
    def _iter_all_components(self):
1392
 
        for path, prefix in self._iter_all_prefixes():
 
1302
        for path, prefix in self._get_all_prefixes():
1393
1303
            yield prefix, self._get_vf(path)
1394
1304
 
1395
1305
    def keys(self):
1401
1311
        return result
1402
1312
 
1403
1313
 
1404
 
class VersionedFilesWithFallbacks(VersionedFiles):
1405
 
 
1406
 
    def without_fallbacks(self):
1407
 
        """Return a clone of this object without any fallbacks configured."""
1408
 
        raise NotImplementedError(self.without_fallbacks)
1409
 
 
1410
 
    def add_fallback_versioned_files(self, a_versioned_files):
1411
 
        """Add a source of texts for texts not present in this knit.
1412
 
 
1413
 
        :param a_versioned_files: A VersionedFiles object.
1414
 
        """
1415
 
        raise NotImplementedError(self.add_fallback_versioned_files)
1416
 
 
1417
 
    def get_known_graph_ancestry(self, keys):
1418
 
        """Get a KnownGraph instance with the ancestry of keys."""
1419
 
        parent_map, missing_keys = self._index.find_ancestry(keys)
1420
 
        for fallback in self._transitive_fallbacks():
1421
 
            if not missing_keys:
1422
 
                break
1423
 
            (f_parent_map, f_missing_keys) = fallback._index.find_ancestry(
1424
 
                missing_keys)
1425
 
            parent_map.update(f_parent_map)
1426
 
            missing_keys = f_missing_keys
1427
 
        kg = _mod_graph.KnownGraph(parent_map)
1428
 
        return kg
1429
 
 
1430
 
 
1431
1314
class _PlanMergeVersionedFile(VersionedFiles):
1432
1315
    """A VersionedFile for uncommitted and committed texts.
1433
1316
 
1454
1337
        # line data for locally held keys.
1455
1338
        self._lines = {}
1456
1339
        # key lookup providers
1457
 
        self._providers = [_mod_graph.DictParentsProvider(self._parents)]
 
1340
        self._providers = [DictParentsProvider(self._parents)]
1458
1341
 
1459
1342
    def plan_merge(self, ver_a, ver_b, base=None):
1460
1343
        """See VersionedFile.plan_merge"""
1461
 
        from ..merge import _PlanMerge
 
1344
        from bzrlib.merge import _PlanMerge
1462
1345
        if base is None:
1463
1346
            return _PlanMerge(ver_a, ver_b, self, (self._file_id,)).plan_merge()
1464
 
        old_plan = list(_PlanMerge(ver_a, base, self,
1465
 
                                   (self._file_id,)).plan_merge())
1466
 
        new_plan = list(_PlanMerge(ver_a, ver_b, self,
1467
 
                                   (self._file_id,)).plan_merge())
 
1347
        old_plan = list(_PlanMerge(ver_a, base, self, (self._file_id,)).plan_merge())
 
1348
        new_plan = list(_PlanMerge(ver_a, ver_b, self, (self._file_id,)).plan_merge())
1468
1349
        return _PlanMerge._subtract_plans(old_plan, new_plan)
1469
1350
 
1470
1351
    def plan_lca_merge(self, ver_a, ver_b, base=None):
1471
 
        from ..merge import _PlanLCAMerge
1472
 
        graph = _mod_graph.Graph(self)
1473
 
        new_plan = _PlanLCAMerge(
1474
 
            ver_a, ver_b, self, (self._file_id,), graph).plan_merge()
 
1352
        from bzrlib.merge import _PlanLCAMerge
 
1353
        graph = Graph(self)
 
1354
        new_plan = _PlanLCAMerge(ver_a, ver_b, self, (self._file_id,), graph).plan_merge()
1475
1355
        if base is None:
1476
1356
            return new_plan
1477
 
        old_plan = _PlanLCAMerge(
1478
 
            ver_a, base, self, (self._file_id,), graph).plan_merge()
 
1357
        old_plan = _PlanLCAMerge(ver_a, base, self, (self._file_id,), graph).plan_merge()
1479
1358
        return _PlanLCAMerge._subtract_plans(list(old_plan), list(new_plan))
1480
1359
 
1481
1360
    def add_lines(self, key, parents, lines):
1484
1363
        Lines are added locally, not to fallback versionedfiles.  Also, ghosts
1485
1364
        are permitted.  Only reserved ids are permitted.
1486
1365
        """
1487
 
        if not isinstance(key, tuple):
 
1366
        if type(key) is not tuple:
1488
1367
            raise TypeError(key)
1489
1368
        if not revision.is_reserved_id(key[-1]):
1490
1369
            raise ValueError('Only reserved ids may be used')
1505
1384
                yield ChunkedContentFactory(key, parents, None, lines)
1506
1385
        for versionedfile in self.fallback_versionedfiles:
1507
1386
            for record in versionedfile.get_record_stream(
1508
 
                    pending, 'unordered', True):
 
1387
                pending, 'unordered', True):
1509
1388
                if record.storage_kind == 'absent':
1510
1389
                    continue
1511
1390
                else:
1529
1408
            result[revision.NULL_REVISION] = ()
1530
1409
        self._providers = self._providers[:1] + self.fallback_versionedfiles
1531
1410
        result.update(
1532
 
            _mod_graph.StackedParentsProvider(
1533
 
                self._providers).get_parent_map(keys))
1534
 
        for key, parents in viewitems(result):
 
1411
            StackedParentsProvider(self._providers).get_parent_map(keys))
 
1412
        for key, parents in result.iteritems():
1535
1413
            if parents == ():
1536
1414
                result[key] = (revision.NULL_REVISION,)
1537
1415
        return result
1606
1484
                ch_b = ch_a = True
1607
1485
            else:
1608
1486
                if state not in ('irrelevant', 'ghost-a', 'ghost-b',
1609
 
                                 'killed-base'):
 
1487
                        'killed-base'):
1610
1488
                    raise AssertionError(state)
1611
1489
        for struct in outstanding_struct():
1612
1490
            yield struct
1670
1548
    """Weave merge that takes a VersionedFile and two versions as its input."""
1671
1549
 
1672
1550
    def __init__(self, versionedfile, ver_a, ver_b,
1673
 
                 a_marker=PlanWeaveMerge.A_MARKER, b_marker=PlanWeaveMerge.B_MARKER):
 
1551
        a_marker=PlanWeaveMerge.A_MARKER, b_marker=PlanWeaveMerge.B_MARKER):
1674
1552
        plan = versionedfile.plan_merge(ver_a, ver_b)
1675
1553
        PlanWeaveMerge.__init__(self, plan, a_marker, b_marker)
1676
1554
 
1710
1588
 
1711
1589
    def get_parent_map(self, keys):
1712
1590
        """See VersionedFiles.get_parent_map."""
1713
 
        parent_view = viewitems(self._get_parent_map(k for (k,) in keys))
1714
 
        return dict(((k,), tuple((p,) for p in v)) for k, v in parent_view)
 
1591
        return dict([((k,), tuple([(p,) for p in v]))
 
1592
            for k,v in self._get_parent_map([k for (k,) in keys]).iteritems()])
1715
1593
 
1716
1594
    def get_sha1s(self, keys):
1717
1595
        """See VersionedFiles.get_sha1s."""
1732
1610
                if not isinstance(lines, list):
1733
1611
                    raise AssertionError
1734
1612
                yield ChunkedContentFactory((k,), None,
1735
 
                                            sha1=osutils.sha_strings(lines),
1736
 
                                            chunks=lines)
 
1613
                        sha1=osutils.sha_strings(lines),
 
1614
                        chunks=lines)
1737
1615
            else:
1738
1616
                yield AbsentContentFactory((k,))
1739
1617
 
1746
1624
                yield (l, key)
1747
1625
 
1748
1626
 
1749
 
class NoDupeAddLinesDecorator(object):
1750
 
    """Decorator for a VersionedFiles that skips doing an add_lines if the key
1751
 
    is already present.
1752
 
    """
1753
 
 
1754
 
    def __init__(self, store):
1755
 
        self._store = store
1756
 
 
1757
 
    def add_lines(self, key, parents, lines, parent_texts=None,
1758
 
                  left_matching_blocks=None, nostore_sha=None, random_id=False,
1759
 
                  check_content=True):
1760
 
        """See VersionedFiles.add_lines.
1761
 
 
1762
 
        This implementation may return None as the third element of the return
1763
 
        value when the original store wouldn't.
1764
 
        """
1765
 
        if nostore_sha:
1766
 
            raise NotImplementedError(
1767
 
                "NoDupeAddLinesDecorator.add_lines does not implement the "
1768
 
                "nostore_sha behaviour.")
1769
 
        if key[-1] is None:
1770
 
            sha1 = osutils.sha_strings(lines)
1771
 
            key = (b"sha1:" + sha1,)
1772
 
        else:
1773
 
            sha1 = None
1774
 
        if key in self._store.get_parent_map([key]):
1775
 
            # This key has already been inserted, so don't do it again.
1776
 
            if sha1 is None:
1777
 
                sha1 = osutils.sha_strings(lines)
1778
 
            return sha1, sum(map(len, lines)), None
1779
 
        return self._store.add_lines(key, parents, lines,
1780
 
                                     parent_texts=parent_texts,
1781
 
                                     left_matching_blocks=left_matching_blocks,
1782
 
                                     nostore_sha=nostore_sha, random_id=random_id,
1783
 
                                     check_content=check_content)
1784
 
 
1785
 
    def __getattr__(self, name):
1786
 
        return getattr(self._store, name)
1787
 
 
1788
 
 
1789
1627
def network_bytes_to_kind_and_offset(network_bytes):
1790
1628
    """Strip of a record kind from the front of network_bytes.
1791
1629
 
1792
1630
    :param network_bytes: The bytes of a record.
1793
1631
    :return: A tuple (storage_kind, offset_of_remaining_bytes)
1794
1632
    """
1795
 
    line_end = network_bytes.find(b'\n')
1796
 
    storage_kind = network_bytes[:line_end].decode('ascii')
 
1633
    line_end = network_bytes.find('\n')
 
1634
    storage_kind = network_bytes[:line_end]
1797
1635
    return storage_kind, line_end + 1
1798
1636
 
1799
1637
 
1826
1664
        for bytes in self._bytes_iterator:
1827
1665
            storage_kind, line_end = network_bytes_to_kind_and_offset(bytes)
1828
1666
            for record in self._kind_factory[storage_kind](
1829
 
                    storage_kind, bytes, line_end):
 
1667
                storage_kind, bytes, line_end):
1830
1668
                yield record
1831
1669
 
1832
1670
 
1833
1671
def fulltext_network_to_record(kind, bytes, line_end):
1834
1672
    """Convert a network fulltext record to record."""
1835
 
    meta_len, = struct.unpack('!L', bytes[line_end:line_end + 4])
1836
 
    record_meta = bytes[line_end + 4:line_end + 4 + meta_len]
 
1673
    meta_len, = struct.unpack('!L', bytes[line_end:line_end+4])
 
1674
    record_meta = bytes[line_end+4:line_end+4+meta_len]
1837
1675
    key, parents = bencode.bdecode_as_tuple(record_meta)
1838
 
    if parents == b'nil':
 
1676
    if parents == 'nil':
1839
1677
        parents = None
1840
 
    fulltext = bytes[line_end + 4 + meta_len:]
 
1678
    fulltext = bytes[line_end+4+meta_len:]
1841
1679
    return [FulltextContentFactory(key, parents, None, fulltext)]
1842
1680
 
1843
1681
 
1847
1685
 
1848
1686
def record_to_fulltext_bytes(record):
1849
1687
    if record.parents is None:
1850
 
        parents = b'nil'
 
1688
        parents = 'nil'
1851
1689
    else:
1852
1690
        parents = record.parents
1853
1691
    record_meta = bencode.bencode((record.key, parents))
1854
1692
    record_content = record.get_bytes_as('fulltext')
1855
 
    return b"fulltext\n%s%s%s" % (
 
1693
    return "fulltext\n%s%s%s" % (
1856
1694
        _length_prefix(record_meta), record_meta, record_content)
1857
1695
 
1858
1696
 
1867
1705
    # gc-optimal ordering is approximately reverse topological,
1868
1706
    # properly grouped by file-id.
1869
1707
    per_prefix_map = {}
1870
 
    for item in viewitems(parent_map):
 
1708
    for item in parent_map.iteritems():
1871
1709
        key = item[0]
1872
 
        if isinstance(key, bytes) or len(key) == 1:
1873
 
            prefix = b''
 
1710
        if isinstance(key, str) or len(key) == 1:
 
1711
            prefix = ''
1874
1712
        else:
1875
1713
            prefix = key[0]
1876
1714
        try:
1882
1720
    for prefix in sorted(per_prefix_map):
1883
1721
        present_keys.extend(reversed(tsort.topo_sort(per_prefix_map[prefix])))
1884
1722
    return present_keys
1885
 
 
1886
 
 
1887
 
class _KeyRefs(object):
1888
 
 
1889
 
    def __init__(self, track_new_keys=False):
1890
 
        # dict mapping 'key' to 'set of keys referring to that key'
1891
 
        self.refs = {}
1892
 
        if track_new_keys:
1893
 
            # set remembering all new keys
1894
 
            self.new_keys = set()
1895
 
        else:
1896
 
            self.new_keys = None
1897
 
 
1898
 
    def clear(self):
1899
 
        if self.refs:
1900
 
            self.refs.clear()
1901
 
        if self.new_keys:
1902
 
            self.new_keys.clear()
1903
 
 
1904
 
    def add_references(self, key, refs):
1905
 
        # Record the new references
1906
 
        for referenced in refs:
1907
 
            try:
1908
 
                needed_by = self.refs[referenced]
1909
 
            except KeyError:
1910
 
                needed_by = self.refs[referenced] = set()
1911
 
            needed_by.add(key)
1912
 
        # Discard references satisfied by the new key
1913
 
        self.add_key(key)
1914
 
 
1915
 
    def get_new_keys(self):
1916
 
        return self.new_keys
1917
 
 
1918
 
    def get_unsatisfied_refs(self):
1919
 
        return self.refs.keys()
1920
 
 
1921
 
    def _satisfy_refs_for_key(self, key):
1922
 
        try:
1923
 
            del self.refs[key]
1924
 
        except KeyError:
1925
 
            # No keys depended on this key.  That's ok.
1926
 
            pass
1927
 
 
1928
 
    def add_key(self, key):
1929
 
        # satisfy refs for key, and remember that we've seen this key.
1930
 
        self._satisfy_refs_for_key(key)
1931
 
        if self.new_keys is not None:
1932
 
            self.new_keys.add(key)
1933
 
 
1934
 
    def satisfy_refs_for_keys(self, keys):
1935
 
        for key in keys:
1936
 
            self._satisfy_refs_for_key(key)
1937
 
 
1938
 
    def get_referrers(self):
1939
 
        return set(itertools.chain.from_iterable(viewvalues(self.refs)))