~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/versionedfile.py

  • Committer: Alexander Belchenko
  • Date: 2007-01-30 23:05:35 UTC
  • mto: This revision was merged to the branch mainline in revision 2259.
  • Revision ID: bialix@ukr.net-20070130230535-kx1rd478rtigyc3v
standalone installer: win98 support

Show diffs side-by-side

added added

removed removed

Lines of Context:
21
21
 
22
22
from bzrlib.lazy_import import lazy_import
23
23
lazy_import(globals(), """
 
24
from copy import deepcopy
 
25
import unittest
24
26
 
25
27
from bzrlib import (
26
28
    errors,
27
 
    osutils,
28
 
    multiparent,
29
29
    tsort,
30
30
    revision,
31
31
    ui,
33
33
from bzrlib.transport.memory import MemoryTransport
34
34
""")
35
35
 
36
 
from cStringIO import StringIO
37
 
 
38
36
from bzrlib.inter import InterObject
39
37
from bzrlib.textmerge import TextMerge
 
38
from bzrlib.symbol_versioning import (deprecated_function,
 
39
        deprecated_method,
 
40
        zero_eight,
 
41
        )
40
42
 
41
43
 
42
44
class VersionedFile(object):
65
67
        """Copy this versioned file to name on transport."""
66
68
        raise NotImplementedError(self.copy_to)
67
69
 
 
70
    @deprecated_method(zero_eight)
 
71
    def names(self):
 
72
        """Return a list of all the versions in this versioned file.
 
73
 
 
74
        Please use versionedfile.versions() now.
 
75
        """
 
76
        return self.versions()
 
77
 
68
78
    def versions(self):
69
79
        """Return a unsorted list of versions."""
70
80
        raise NotImplementedError(self.versions)
77
87
        """Returns whether version is present."""
78
88
        raise NotImplementedError(self.has_version)
79
89
 
80
 
    def add_lines(self, version_id, parents, lines, parent_texts=None,
81
 
        left_matching_blocks=None, nostore_sha=None, random_id=False,
82
 
        check_content=True):
 
90
    def add_delta(self, version_id, parents, delta_parent, sha1, noeol, delta):
 
91
        """Add a text to the versioned file via a pregenerated delta.
 
92
 
 
93
        :param version_id: The version id being added.
 
94
        :param parents: The parents of the version_id.
 
95
        :param delta_parent: The parent this delta was created against.
 
96
        :param sha1: The sha1 of the full text.
 
97
        :param delta: The delta instructions. See get_delta for details.
 
98
        """
 
99
        self._check_write_ok()
 
100
        if self.has_version(version_id):
 
101
            raise errors.RevisionAlreadyPresent(version_id, self)
 
102
        return self._add_delta(version_id, parents, delta_parent, sha1, noeol, delta)
 
103
 
 
104
    def _add_delta(self, version_id, parents, delta_parent, sha1, noeol, delta):
 
105
        """Class specific routine to add a delta.
 
106
 
 
107
        This generic version simply applies the delta to the delta_parent and
 
108
        then inserts it.
 
109
        """
 
110
        # strip annotation from delta
 
111
        new_delta = []
 
112
        for start, stop, delta_len, delta_lines in delta:
 
113
            new_delta.append((start, stop, delta_len, [text for origin, text in delta_lines]))
 
114
        if delta_parent is not None:
 
115
            parent_full = self.get_lines(delta_parent)
 
116
        else:
 
117
            parent_full = []
 
118
        new_full = self._apply_delta(parent_full, new_delta)
 
119
        # its impossible to have noeol on an empty file
 
120
        if noeol and new_full[-1][-1] == '\n':
 
121
            new_full[-1] = new_full[-1][:-1]
 
122
        self.add_lines(version_id, parents, new_full)
 
123
 
 
124
    def add_lines(self, version_id, parents, lines, parent_texts=None):
83
125
        """Add a single text on top of the versioned file.
84
126
 
85
127
        Must raise RevisionAlreadyPresent if the new version is
87
129
 
88
130
        Must raise RevisionNotPresent if any of the given parents are
89
131
        not present in file history.
90
 
 
91
 
        :param lines: A list of lines. Each line must be a bytestring. And all
92
 
            of them except the last must be terminated with \n and contain no
93
 
            other \n's. The last line may either contain no \n's or a single
94
 
            terminated \n. If the lines list does meet this constraint the add
95
 
            routine may error or may succeed - but you will be unable to read
96
 
            the data back accurately. (Checking the lines have been split
97
 
            correctly is expensive and extremely unlikely to catch bugs so it
98
 
            is not done at runtime unless check_content is True.)
99
132
        :param parent_texts: An optional dictionary containing the opaque 
100
 
            representations of some or all of the parents of version_id to
101
 
            allow delta optimisations.  VERY IMPORTANT: the texts must be those
102
 
            returned by add_lines or data corruption can be caused.
103
 
        :param left_matching_blocks: a hint about which areas are common
104
 
            between the text and its left-hand-parent.  The format is
105
 
            the SequenceMatcher.get_matching_blocks format.
106
 
        :param nostore_sha: Raise ExistingContent and do not add the lines to
107
 
            the versioned file if the digest of the lines matches this.
108
 
        :param random_id: If True a random id has been selected rather than
109
 
            an id determined by some deterministic process such as a converter
110
 
            from a foreign VCS. When True the backend may choose not to check
111
 
            for uniqueness of the resulting key within the versioned file, so
112
 
            this should only be done when the result is expected to be unique
113
 
            anyway.
114
 
        :param check_content: If True, the lines supplied are verified to be
115
 
            bytestrings that are correctly formed lines.
116
 
        :return: The text sha1, the number of bytes in the text, and an opaque
117
 
                 representation of the inserted version which can be provided
118
 
                 back to future add_lines calls in the parent_texts dictionary.
 
133
             representations of some or all of the parents of 
 
134
             version_id to allow delta optimisations. 
 
135
             VERY IMPORTANT: the texts must be those returned
 
136
             by add_lines or data corruption can be caused.
 
137
        :return: An opaque representation of the inserted version which can be
 
138
                 provided back to future add_lines calls in the parent_texts
 
139
                 dictionary.
119
140
        """
120
141
        self._check_write_ok()
121
 
        return self._add_lines(version_id, parents, lines, parent_texts,
122
 
            left_matching_blocks, nostore_sha, random_id, check_content)
 
142
        return self._add_lines(version_id, parents, lines, parent_texts)
123
143
 
124
 
    def _add_lines(self, version_id, parents, lines, parent_texts,
125
 
        left_matching_blocks, nostore_sha, random_id, check_content):
 
144
    def _add_lines(self, version_id, parents, lines, parent_texts):
126
145
        """Helper to do the class specific add_lines."""
127
146
        raise NotImplementedError(self.add_lines)
128
147
 
129
148
    def add_lines_with_ghosts(self, version_id, parents, lines,
130
 
        parent_texts=None, nostore_sha=None, random_id=False,
131
 
        check_content=True):
 
149
                              parent_texts=None):
132
150
        """Add lines to the versioned file, allowing ghosts to be present.
133
151
        
134
 
        This takes the same parameters as add_lines and returns the same.
 
152
        This takes the same parameters as add_lines.
135
153
        """
136
154
        self._check_write_ok()
137
155
        return self._add_lines_with_ghosts(version_id, parents, lines,
138
 
            parent_texts, nostore_sha, random_id, check_content)
 
156
                                           parent_texts)
139
157
 
140
 
    def _add_lines_with_ghosts(self, version_id, parents, lines, parent_texts,
141
 
        nostore_sha, random_id, check_content):
 
158
    def _add_lines_with_ghosts(self, version_id, parents, lines, parent_texts):
142
159
        """Helper to do class specific add_lines_with_ghosts."""
143
160
        raise NotImplementedError(self.add_lines_with_ghosts)
144
161
 
203
220
        """
204
221
        raise NotImplementedError(self.create_empty)
205
222
 
206
 
    def get_format_signature(self):
207
 
        """Get a text description of the data encoding in this file.
208
 
        
209
 
        :since: 0.90
210
 
        """
211
 
        raise NotImplementedError(self.get_format_signature)
212
 
 
213
 
    def make_mpdiffs(self, version_ids):
214
 
        """Create multiparent diffs for specified versions."""
215
 
        knit_versions = set()
216
 
        for version_id in version_ids:
217
 
            knit_versions.add(version_id)
218
 
            knit_versions.update(self.get_parents(version_id))
219
 
        lines = dict(zip(knit_versions,
220
 
            self._get_lf_split_line_list(knit_versions)))
221
 
        diffs = []
222
 
        for version_id in version_ids:
223
 
            target = lines[version_id]
224
 
            parents = [lines[p] for p in self.get_parents(version_id)]
225
 
            if len(parents) > 0:
226
 
                left_parent_blocks = self._extract_blocks(version_id,
227
 
                                                          parents[0], target)
228
 
            else:
229
 
                left_parent_blocks = None
230
 
            diffs.append(multiparent.MultiParent.from_lines(target, parents,
231
 
                         left_parent_blocks))
232
 
        return diffs
233
 
 
234
 
    def _extract_blocks(self, version_id, source, target):
235
 
        return None
236
 
 
237
 
    def add_mpdiffs(self, records):
238
 
        """Add mpdiffs to this VersionedFile.
239
 
 
240
 
        Records should be iterables of version, parents, expected_sha1,
241
 
        mpdiff. mpdiff should be a MultiParent instance.
242
 
        """
243
 
        # Does this need to call self._check_write_ok()? (IanC 20070919)
244
 
        vf_parents = {}
245
 
        mpvf = multiparent.MultiMemoryVersionedFile()
246
 
        versions = []
247
 
        for version, parent_ids, expected_sha1, mpdiff in records:
248
 
            versions.append(version)
249
 
            mpvf.add_diff(mpdiff, version, parent_ids)
250
 
        needed_parents = set()
251
 
        for version, parent_ids, expected_sha1, mpdiff in records:
252
 
            needed_parents.update(p for p in parent_ids
253
 
                                  if not mpvf.has_version(p))
254
 
        for parent_id, lines in zip(needed_parents,
255
 
                                 self._get_lf_split_line_list(needed_parents)):
256
 
            mpvf.add_version(lines, parent_id, [])
257
 
        for (version, parent_ids, expected_sha1, mpdiff), lines in\
258
 
            zip(records, mpvf.get_line_list(versions)):
259
 
            if len(parent_ids) == 1:
260
 
                left_matching_blocks = list(mpdiff.get_matching_blocks(0,
261
 
                    mpvf.get_diff(parent_ids[0]).num_lines()))
262
 
            else:
263
 
                left_matching_blocks = None
264
 
            _, _, version_text = self.add_lines(version, parent_ids, lines,
265
 
                vf_parents, left_matching_blocks=left_matching_blocks)
266
 
            vf_parents[version] = version_text
267
 
        for (version, parent_ids, expected_sha1, mpdiff), sha1 in\
268
 
             zip(records, self.get_sha1s(versions)):
269
 
            if expected_sha1 != sha1:
270
 
                raise errors.VersionedFileInvalidChecksum(version)
 
223
    def fix_parents(self, version, new_parents):
 
224
        """Fix the parents list for version.
 
225
        
 
226
        This is done by appending a new version to the index
 
227
        with identical data except for the parents list.
 
228
        the parents list must be a superset of the current
 
229
        list.
 
230
        """
 
231
        self._check_write_ok()
 
232
        return self._fix_parents(version, new_parents)
 
233
 
 
234
    def _fix_parents(self, version, new_parents):
 
235
        """Helper for fix_parents."""
 
236
        raise NotImplementedError(self.fix_parents)
 
237
 
 
238
    def get_delta(self, version):
 
239
        """Get a delta for constructing version from some other version.
 
240
        
 
241
        :return: (delta_parent, sha1, noeol, delta)
 
242
        Where delta_parent is a version id or None to indicate no parent.
 
243
        """
 
244
        raise NotImplementedError(self.get_delta)
 
245
 
 
246
    def get_deltas(self, versions):
 
247
        """Get multiple deltas at once for constructing versions.
 
248
        
 
249
        :return: dict(version_id:(delta_parent, sha1, noeol, delta))
 
250
        Where delta_parent is a version id or None to indicate no parent, and
 
251
        version_id is the version_id created by that delta.
 
252
        """
 
253
        result = {}
 
254
        for version in versions:
 
255
            result[version] = self.get_delta(version)
 
256
        return result
271
257
 
272
258
    def get_sha1(self, version_id):
273
259
        """Get the stored sha1 sum for the given revision.
274
260
        
275
 
        :param version_id: The name of the version to lookup
 
261
        :param name: The name of the version to lookup
276
262
        """
277
263
        raise NotImplementedError(self.get_sha1)
278
264
 
279
 
    def get_sha1s(self, version_ids):
280
 
        """Get the stored sha1 sums for the given revisions.
281
 
 
282
 
        :param version_ids: The names of the versions to lookup
283
 
        :return: a list of sha1s in order according to the version_ids
284
 
        """
285
 
        raise NotImplementedError(self.get_sha1s)
286
 
 
287
265
    def get_suffixes(self):
288
266
        """Return the file suffixes associated with this versioned file."""
289
267
        raise NotImplementedError(self.get_suffixes)
313
291
        """
314
292
        raise NotImplementedError(self.get_lines)
315
293
 
316
 
    def _get_lf_split_line_list(self, version_ids):
317
 
        return [StringIO(t).readlines() for t in self.get_texts(version_ids)]
318
 
 
319
 
    def get_ancestry(self, version_ids, topo_sorted=True):
 
294
    def get_ancestry(self, version_ids):
320
295
        """Return a list of all ancestors of given version(s). This
321
296
        will not include the null revision.
322
297
 
323
 
        This list will not be topologically sorted if topo_sorted=False is
324
 
        passed.
325
 
 
326
298
        Must raise RevisionNotPresent if any of the given versions are
327
299
        not present in file history."""
328
300
        if isinstance(version_ids, basestring):
348
320
        :param version_ids: Versions to select.
349
321
                            None means retrieve all versions.
350
322
        """
 
323
        result = {}
351
324
        if version_ids is None:
352
 
            return dict(self.iter_parents(self.versions()))
353
 
        result = {}
354
 
        pending = set(version_ids)
355
 
        while pending:
356
 
            this_iteration = pending
357
 
            pending = set()
358
 
            for version, parents in self.iter_parents(this_iteration):
359
 
                result[version] = parents
 
325
            for version in self.versions():
 
326
                result[version] = self.get_parents(version)
 
327
        else:
 
328
            pending = set(version_ids)
 
329
            while pending:
 
330
                version = pending.pop()
 
331
                if version in result:
 
332
                    continue
 
333
                parents = self.get_parents(version)
360
334
                for parent in parents:
361
335
                    if parent in result:
362
336
                        continue
363
337
                    pending.add(parent)
 
338
                result[version] = parents
364
339
        return result
365
340
 
366
341
    def get_graph_with_ghosts(self):
371
346
        """
372
347
        raise NotImplementedError(self.get_graph_with_ghosts)
373
348
 
 
349
    @deprecated_method(zero_eight)
 
350
    def parent_names(self, version):
 
351
        """Return version names for parents of a version.
 
352
        
 
353
        See get_parents for the current api.
 
354
        """
 
355
        return self.get_parents(version)
 
356
 
374
357
    def get_parents(self, version_id):
375
358
        """Return version names for parents of a version.
376
359
 
394
377
        """Yield list of (version-id, line) pairs for the specified
395
378
        version.
396
379
 
397
 
        Must raise RevisionNotPresent if the given version is
 
380
        Must raise RevisionNotPresent if any of the given versions are
398
381
        not present in file history.
399
382
        """
400
383
        raise NotImplementedError(self.annotate_iter)
402
385
    def annotate(self, version_id):
403
386
        return list(self.annotate_iter(version_id))
404
387
 
 
388
    def _apply_delta(self, lines, delta):
 
389
        """Apply delta to lines."""
 
390
        lines = list(lines)
 
391
        offset = 0
 
392
        for start, end, count, delta_lines in delta:
 
393
            lines[offset+start:offset+end] = delta_lines
 
394
            offset = offset + (start - end) + count
 
395
        return lines
 
396
 
405
397
    def join(self, other, pb=None, msg=None, version_ids=None,
406
398
             ignore_missing=False):
407
399
        """Integrate versions from other into this versioned file.
410
402
        incorporated into this versioned file.
411
403
 
412
404
        Must raise RevisionNotPresent if any of the specified versions
413
 
        are not present in the other file's history unless ignore_missing
414
 
        is supplied in which case they are silently skipped.
 
405
        are not present in the other files history unless ignore_missing
 
406
        is supplied when they are silently skipped.
415
407
        """
416
408
        self._check_write_ok()
417
409
        return InterVersionedFile.get(other, self).join(
420
412
            version_ids,
421
413
            ignore_missing)
422
414
 
423
 
    def iter_lines_added_or_present_in_versions(self, version_ids=None,
 
415
    def iter_lines_added_or_present_in_versions(self, version_ids=None, 
424
416
                                                pb=None):
425
417
        """Iterate over the lines in the versioned file from version_ids.
426
418
 
427
 
        This may return lines from other versions. Each item the returned
428
 
        iterator yields is a tuple of a line and a text version that that line
429
 
        is present in (not introduced in).
430
 
 
431
 
        Ordering of results is in whatever order is most suitable for the
432
 
        underlying storage format.
 
419
        This may return lines from other versions, and does not return the
 
420
        specific version marker at this point. The api may be changed
 
421
        during development to include the version that the versioned file
 
422
        thinks is relevant, but given that such hints are just guesses,
 
423
        its better not to have it if we don't need it.
433
424
 
434
425
        If a progress bar is supplied, it may be used to indicate progress.
435
426
        The caller is responsible for cleaning up progress bars (because this
437
428
 
438
429
        NOTES: Lines are normalised: they will all have \n terminators.
439
430
               Lines are returned in arbitrary order.
440
 
 
441
 
        :return: An iterator over (line, version_id).
442
431
        """
443
432
        raise NotImplementedError(self.iter_lines_added_or_present_in_versions)
444
433
 
445
 
    def iter_parents(self, version_ids):
446
 
        """Iterate through the parents for many version ids.
447
 
 
448
 
        :param version_ids: An iterable yielding version_ids.
449
 
        :return: An iterator that yields (version_id, parents). Requested 
450
 
            version_ids not present in the versioned file are simply skipped.
451
 
            The order is undefined, allowing for different optimisations in
452
 
            the underlying implementation.
453
 
        """
454
 
        for version_id in version_ids:
455
 
            try:
456
 
                yield version_id, tuple(self.get_parents(version_id))
457
 
            except errors.RevisionNotPresent:
458
 
                pass
459
 
 
460
434
    def transaction_finished(self):
461
435
        """The transaction that this file was opened in has finished.
462
436
 
465
439
        """
466
440
        self.finished = True
467
441
 
 
442
    @deprecated_method(zero_eight)
 
443
    def walk(self, version_ids=None):
 
444
        """Walk the versioned file as a weave-like structure, for
 
445
        versions relative to version_ids.  Yields sequence of (lineno,
 
446
        insert, deletes, text) for each relevant line.
 
447
 
 
448
        Must raise RevisionNotPresent if any of the specified versions
 
449
        are not present in the file history.
 
450
 
 
451
        :param version_ids: the version_ids to walk with respect to. If not
 
452
                            supplied the entire weave-like structure is walked.
 
453
 
 
454
        walk is deprecated in favour of iter_lines_added_or_present_in_versions
 
455
        """
 
456
        raise NotImplementedError(self.walk)
 
457
 
 
458
    @deprecated_method(zero_eight)
 
459
    def iter_names(self):
 
460
        """Walk the names list."""
 
461
        return iter(self.versions())
 
462
 
468
463
    def plan_merge(self, ver_a, ver_b):
469
464
        """Return pseudo-annotation indicating how the two versions merge.
470
465
 
492
487
        return PlanWeaveMerge(plan, a_marker, b_marker).merge_lines()[0]
493
488
 
494
489
 
495
 
class _PlanMergeVersionedFile(object):
496
 
    """A VersionedFile for uncommitted and committed texts.
497
 
 
498
 
    It is intended to allow merges to be planned with working tree texts.
499
 
    It implements only the small part of the VersionedFile interface used by
500
 
    PlanMerge.  It falls back to multiple versionedfiles for data not stored in
501
 
    _PlanMergeVersionedFile itself.
502
 
    """
503
 
 
504
 
    def __init__(self, file_id, fallback_versionedfiles=None):
505
 
        """Constuctor
506
 
 
507
 
        :param file_id: Used when raising exceptions.
508
 
        :param fallback_versionedfiles: If supplied, the set of fallbacks to
509
 
            use.  Otherwise, _PlanMergeVersionedFile.fallback_versionedfiles
510
 
            can be appended to later.
511
 
        """
512
 
        self._file_id = file_id
513
 
        if fallback_versionedfiles is None:
514
 
            self.fallback_versionedfiles = []
515
 
        else:
516
 
            self.fallback_versionedfiles = fallback_versionedfiles
517
 
        self._parents = {}
518
 
        self._lines = {}
519
 
 
520
 
    def plan_merge(self, ver_a, ver_b, base=None):
521
 
        """See VersionedFile.plan_merge"""
522
 
        from bzrlib.merge import _PlanMerge
523
 
        if base is None:
524
 
            return _PlanMerge(ver_a, ver_b, self).plan_merge()
525
 
        old_plan = list(_PlanMerge(ver_a, base, self).plan_merge())
526
 
        new_plan = list(_PlanMerge(ver_a, ver_b, self).plan_merge())
527
 
        return _PlanMerge._subtract_plans(old_plan, new_plan)
528
 
 
529
 
    def plan_lca_merge(self, ver_a, ver_b, base=None):
530
 
        from bzrlib.merge import _PlanLCAMerge
531
 
        graph = self._get_graph()
532
 
        new_plan = _PlanLCAMerge(ver_a, ver_b, self, graph).plan_merge()
533
 
        if base is None:
534
 
            return new_plan
535
 
        old_plan = _PlanLCAMerge(ver_a, base, self, graph).plan_merge()
536
 
        return _PlanLCAMerge._subtract_plans(list(old_plan), list(new_plan))
537
 
 
538
 
    def add_lines(self, version_id, parents, lines):
539
 
        """See VersionedFile.add_lines
540
 
 
541
 
        Lines are added locally, not fallback versionedfiles.  Also, ghosts are
542
 
        permitted.  Only reserved ids are permitted.
543
 
        """
544
 
        if not revision.is_reserved_id(version_id):
545
 
            raise ValueError('Only reserved ids may be used')
546
 
        if parents is None:
547
 
            raise ValueError('Parents may not be None')
548
 
        if lines is None:
549
 
            raise ValueError('Lines may not be None')
550
 
        self._parents[version_id] = parents
551
 
        self._lines[version_id] = lines
552
 
 
553
 
    def get_lines(self, version_id):
554
 
        """See VersionedFile.get_ancestry"""
555
 
        lines = self._lines.get(version_id)
556
 
        if lines is not None:
557
 
            return lines
558
 
        for versionedfile in self.fallback_versionedfiles:
559
 
            try:
560
 
                return versionedfile.get_lines(version_id)
561
 
            except errors.RevisionNotPresent:
562
 
                continue
563
 
        else:
564
 
            raise errors.RevisionNotPresent(version_id, self._file_id)
565
 
 
566
 
    def get_ancestry(self, version_id, topo_sorted=False):
567
 
        """See VersionedFile.get_ancestry.
568
 
 
569
 
        Note that this implementation assumes that if a VersionedFile can
570
 
        answer get_ancestry at all, it can give an authoritative answer.  In
571
 
        fact, ghosts can invalidate this assumption.  But it's good enough
572
 
        99% of the time, and far cheaper/simpler.
573
 
 
574
 
        Also note that the results of this version are never topologically
575
 
        sorted, and are a set.
576
 
        """
577
 
        if topo_sorted:
578
 
            raise ValueError('This implementation does not provide sorting')
579
 
        parents = self._parents.get(version_id)
580
 
        if parents is None:
581
 
            for vf in self.fallback_versionedfiles:
582
 
                try:
583
 
                    return vf.get_ancestry(version_id, topo_sorted=False)
584
 
                except errors.RevisionNotPresent:
585
 
                    continue
586
 
            else:
587
 
                raise errors.RevisionNotPresent(version_id, self._file_id)
588
 
        ancestry = set([version_id])
589
 
        for parent in parents:
590
 
            ancestry.update(self.get_ancestry(parent, topo_sorted=False))
591
 
        return ancestry
592
 
 
593
 
    def get_parents(self, version_id):
594
 
        """See VersionedFile.get_parents"""
595
 
        parents = self._parents.get(version_id)
596
 
        if parents is not None:
597
 
            return parents
598
 
        for versionedfile in self.fallback_versionedfiles:
599
 
            try:
600
 
                return versionedfile.get_parents(version_id)
601
 
            except errors.RevisionNotPresent:
602
 
                continue
603
 
        else:
604
 
            raise errors.RevisionNotPresent(version_id, self._file_id)
605
 
 
606
 
    def _get_graph(self):
607
 
        from bzrlib.graph import (
608
 
            DictParentsProvider,
609
 
            Graph,
610
 
            _StackedParentsProvider,
611
 
            )
612
 
        from bzrlib.repofmt.knitrepo import _KnitParentsProvider
613
 
        parent_providers = [DictParentsProvider(self._parents)]
614
 
        for vf in self.fallback_versionedfiles:
615
 
            parent_providers.append(_KnitParentsProvider(vf))
616
 
        return Graph(_StackedParentsProvider(parent_providers))
617
 
 
618
 
 
619
490
class PlanWeaveMerge(TextMerge):
620
491
    """Weave merge that takes a plan as its input.
621
492
    
673
544
            elif state == 'new-b':
674
545
                ch_b = True
675
546
                lines_b.append(line)
676
 
            elif state == 'conflicted-a':
677
 
                ch_b = ch_a = True
678
 
                lines_a.append(line)
679
 
            elif state == 'conflicted-b':
680
 
                ch_b = ch_a = True
681
 
                lines_b.append(line)
682
547
            else:
683
548
                assert state in ('irrelevant', 'ghost-a', 'ghost-b', 
684
549
                                 'killed-base', 'killed-both'), state
687
552
 
688
553
 
689
554
class WeaveMerge(PlanWeaveMerge):
690
 
    """Weave merge that takes a VersionedFile and two versions as its input."""
 
555
    """Weave merge that takes a VersionedFile and two versions as its input"""
691
556
 
692
557
    def __init__(self, versionedfile, ver_a, ver_b, 
693
558
        a_marker=PlanWeaveMerge.A_MARKER, b_marker=PlanWeaveMerge.B_MARKER):
696
561
 
697
562
 
698
563
class InterVersionedFile(InterObject):
699
 
    """This class represents operations taking place between two VersionedFiles.
 
564
    """This class represents operations taking place between two versionedfiles..
700
565
 
701
566
    Its instances have methods like join, and contain
702
567
    references to the source and target versionedfiles these operations can be 
717
582
        incorporated into this versioned file.
718
583
 
719
584
        Must raise RevisionNotPresent if any of the specified versions
720
 
        are not present in the other file's history unless ignore_missing is 
721
 
        supplied in which case they are silently skipped.
 
585
        are not present in the other files history unless ignore_missing is 
 
586
        supplied when they are silently skipped.
722
587
        """
723
588
        # the default join: 
724
589
        # - if the target is empty, just add all the versions from 
751
616
            # TODO: remove parent texts when they are not relevant any more for 
752
617
            # memory pressure reduction. RBC 20060313
753
618
            # pb.update('Converting versioned data', 0, len(order))
754
 
            total = len(order)
 
619
            # deltas = self.source.get_deltas(order)
755
620
            for index, version in enumerate(order):
756
 
                pb.update('Converting versioned data', index, total)
757
 
                _, _, parent_text = target.add_lines(version,
 
621
                pb.update('Converting versioned data', index, len(order))
 
622
                parent_text = target.add_lines(version,
758
623
                                               self.source.get_parents(version),
759
624
                                               self.source.get_lines(version),
760
625
                                               parent_texts=parent_texts)
761
626
                parent_texts[version] = parent_text
 
627
                #delta_parent, sha1, noeol, delta = deltas[version]
 
628
                #target.add_delta(version,
 
629
                #                 self.source.get_parents(version),
 
630
                #                 delta_parent,
 
631
                #                 sha1,
 
632
                #                 noeol,
 
633
                #                 delta)
 
634
                #target.get_lines(version)
762
635
            
763
636
            # this should hit the native code path for target
764
637
            if target is not self.target:
767
640
                                        msg,
768
641
                                        version_ids,
769
642
                                        ignore_missing)
770
 
            else:
771
 
                return total
772
643
        finally:
773
644
            pb.finished()
774
645
 
798
669
                    else:
799
670
                        new_version_ids.add(version)
800
671
                return new_version_ids
 
672
 
 
673
 
 
674
class InterVersionedFileTestProviderAdapter(object):
 
675
    """A tool to generate a suite testing multiple inter versioned-file classes.
 
676
 
 
677
    This is done by copying the test once for each InterVersionedFile provider
 
678
    and injecting the transport_server, transport_readonly_server,
 
679
    versionedfile_factory and versionedfile_factory_to classes into each copy.
 
680
    Each copy is also given a new id() to make it easy to identify.
 
681
    """
 
682
 
 
683
    def __init__(self, transport_server, transport_readonly_server, formats):
 
684
        self._transport_server = transport_server
 
685
        self._transport_readonly_server = transport_readonly_server
 
686
        self._formats = formats
 
687
    
 
688
    def adapt(self, test):
 
689
        result = unittest.TestSuite()
 
690
        for (interversionedfile_class,
 
691
             versionedfile_factory,
 
692
             versionedfile_factory_to) in self._formats:
 
693
            new_test = deepcopy(test)
 
694
            new_test.transport_server = self._transport_server
 
695
            new_test.transport_readonly_server = self._transport_readonly_server
 
696
            new_test.interversionedfile_class = interversionedfile_class
 
697
            new_test.versionedfile_factory = versionedfile_factory
 
698
            new_test.versionedfile_factory_to = versionedfile_factory_to
 
699
            def make_new_test_id():
 
700
                new_id = "%s(%s)" % (new_test.id(), interversionedfile_class.__name__)
 
701
                return lambda: new_id
 
702
            new_test.id = make_new_test_id()
 
703
            result.addTest(new_test)
 
704
        return result
 
705
 
 
706
    @staticmethod
 
707
    def default_test_list():
 
708
        """Generate the default list of interversionedfile permutations to test."""
 
709
        from bzrlib.weave import WeaveFile
 
710
        from bzrlib.knit import KnitVersionedFile
 
711
        result = []
 
712
        # test the fallback InterVersionedFile from annotated knits to weave
 
713
        result.append((InterVersionedFile, 
 
714
                       KnitVersionedFile,
 
715
                       WeaveFile))
 
716
        for optimiser in InterVersionedFile._optimisers:
 
717
            result.append((optimiser,
 
718
                           optimiser._matching_file_from_factory,
 
719
                           optimiser._matching_file_to_factory
 
720
                           ))
 
721
        # if there are specific combinations we want to use, we can add them 
 
722
        # here.
 
723
        return result