7
7
# it under the terms of the GNU General Public License as published by
8
8
# the Free Software Foundation; either version 2 of the License, or
9
9
# (at your option) any later version.
11
11
# This program is distributed in the hope that it will be useful,
12
12
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
14
# GNU General Public License for more details.
16
16
# You should have received a copy of the GNU General Public License
17
17
# along with this program; if not, write to the Free Software
18
18
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20
# Remaing to do is to figure out if get_graph should return a simple
21
# map, or a graph object of some kind.
24
20
"""Versioned text file storage api."""
22
from bzrlib.lazy_import import lazy_import
23
lazy_import(globals(), """
27
24
from copy import deepcopy
28
from unittest import TestSuite
31
import bzrlib.errors as errors
33
from bzrlib.transport.memory import MemoryTransport
32
36
from bzrlib.inter import InterObject
33
from bzrlib.symbol_versioning import *
34
from bzrlib.transport.memory import MemoryTransport
35
from bzrlib.tsort import topo_sort
37
from bzrlib.textmerge import TextMerge
38
from bzrlib.symbol_versioning import (deprecated_function,
39
44
class VersionedFile(object):
54
59
self.finished = False
55
60
self._access_mode = access_mode
63
def check_not_reserved_id(version_id):
64
revision.check_not_reserved_id(version_id)
57
66
def copy_to(self, name, transport):
58
67
"""Copy this versioned file to name on transport."""
59
68
raise NotImplementedError(self.copy_to)
61
70
@deprecated_method(zero_eight)
63
72
"""Return a list of all the versions in this versioned file.
154
163
"""Check the versioned file for integrity."""
155
164
raise NotImplementedError(self.check)
166
def _check_lines_not_unicode(self, lines):
167
"""Check that lines being added to a versioned file are not unicode."""
169
if line.__class__ is not str:
170
raise errors.BzrBadParameterUnicode("lines")
172
def _check_lines_are_lines(self, lines):
173
"""Check that the lines really are full lines without inline EOL."""
175
if '\n' in line[:-1]:
176
raise errors.BzrBadParameterContainsNewline("lines")
157
178
def _check_write_ok(self):
158
179
"""Is the versioned file marked as 'finished' ? Raise if it is."""
159
180
if self.finished:
161
182
if self._access_mode != 'w':
162
183
raise errors.ReadOnlyObjectDirtiedError(self)
185
def enable_cache(self):
186
"""Tell this versioned file that it should cache any data it reads.
188
This is advisory, implementations do not have to support caching.
164
192
def clear_cache(self):
165
"""Remove any data cached in the versioned file object."""
193
"""Remove any data cached in the versioned file object.
195
This only needs to be supported if caches are supported
167
199
def clone_text(self, new_version_id, old_version_id, parents):
168
200
"""Add an identical text to old_version_id as new_version_id.
267
314
raise NotImplementedError(self.get_ancestry_with_ghosts)
270
"""Return a graph for the entire versioned file.
316
def get_graph(self, version_ids=None):
317
"""Return a graph from the versioned file.
272
319
Ghosts are not listed or referenced in the graph.
320
:param version_ids: Versions to select.
321
None means retrieve all versions.
275
for version in self.versions():
276
result[version] = self.get_parents(version)
324
if version_ids is None:
325
for version in self.versions():
326
result[version] = self.get_parents(version)
328
pending = set(version_ids)
330
version = pending.pop()
331
if version in result:
333
parents = self.get_parents(version)
334
for parent in parents:
338
result[version] = parents
279
341
def get_graph_with_ghosts(self):
353
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,
354
417
"""Iterate over the lines in the versioned file from version_ids.
356
419
This may return lines from other versions, and does not return the
357
420
specific version marker at this point. The api may be changed
358
421
during development to include the version that the versioned file
359
422
thinks is relevant, but given that such hints are just guesses,
360
its better not to have it if we dont need it.
423
its better not to have it if we don't need it.
425
If a progress bar is supplied, it may be used to indicate progress.
426
The caller is responsible for cleaning up progress bars (because this
362
429
NOTES: Lines are normalised: they will all have \n terminators.
363
430
Lines are returned in arbitrary order.
402
469
Weave lines present in none of them are skipped entirely.
472
killed-base Dead in base revision
473
killed-both Killed in each revision
476
unchanged Alive in both a and b (possibly created in both)
479
ghost-a Killed in a, unborn in b
480
ghost-b Killed in b, unborn in a
481
irrelevant Not in either revision
404
inc_a = set(self.get_ancestry([ver_a]))
405
inc_b = set(self.get_ancestry([ver_b]))
406
inc_c = inc_a & inc_b
408
for lineno, insert, deleteset, line in self.walk([ver_a, ver_b]):
409
if deleteset & inc_c:
410
# killed in parent; can't be in either a or b
411
# not relevant to our work
412
yield 'killed-base', line
413
elif insert in inc_c:
414
# was inserted in base
415
killed_a = bool(deleteset & inc_a)
416
killed_b = bool(deleteset & inc_b)
417
if killed_a and killed_b:
418
yield 'killed-both', line
420
yield 'killed-a', line
422
yield 'killed-b', line
424
yield 'unchanged', line
425
elif insert in inc_a:
426
if deleteset & inc_a:
427
yield 'ghost-a', line
431
elif insert in inc_b:
432
if deleteset & inc_b:
433
yield 'ghost-b', line
437
# not in either revision
438
yield 'irrelevant', line
440
yield 'unchanged', '' # terminator
442
def weave_merge(self, plan, a_marker='<<<<<<< \n', b_marker='>>>>>>> \n'):
483
raise NotImplementedError(VersionedFile.plan_merge)
485
def weave_merge(self, plan, a_marker=TextMerge.A_MARKER,
486
b_marker=TextMerge.B_MARKER):
487
return PlanWeaveMerge(plan, a_marker, b_marker).merge_lines()[0]
490
class PlanWeaveMerge(TextMerge):
491
"""Weave merge that takes a plan as its input.
493
This exists so that VersionedFile.plan_merge is implementable.
494
Most callers will want to use WeaveMerge instead.
497
def __init__(self, plan, a_marker=TextMerge.A_MARKER,
498
b_marker=TextMerge.B_MARKER):
499
TextMerge.__init__(self, a_marker, b_marker)
502
def _merge_struct(self):
445
505
ch_a = ch_b = False
446
# TODO: Return a structured form of the conflicts (e.g. 2-tuples for
447
# conflicted regions), rather than just inserting the markers.
449
# TODO: Show some version information (e.g. author, date) on
450
# conflicted regions.
507
def outstanding_struct():
508
if not lines_a and not lines_b:
510
elif ch_a and not ch_b:
513
elif ch_b and not ch_a:
515
elif lines_a == lines_b:
518
yield (lines_a, lines_b)
452
520
# We previously considered either 'unchanged' or 'killed-both' lines
453
521
# to be possible places to resynchronize. However, assuming agreement
454
# on killed-both lines may be too agressive. -- mbp 20060324
455
for state, line in plan:
522
# on killed-both lines may be too aggressive. -- mbp 20060324
523
for state, line in self.plan:
456
524
if state == 'unchanged':
457
525
# resync and flush queued conflicts changes if any
458
if not lines_a and not lines_b:
460
elif ch_a and not ch_b:
462
for l in lines_a: yield l
463
elif ch_b and not ch_a:
464
for l in lines_b: yield l
465
elif lines_a == lines_b:
466
for l in lines_a: yield l
469
for l in lines_a: yield l
471
for l in lines_b: yield l
526
for struct in outstanding_struct():
476
530
ch_a = ch_b = False
478
532
if state == 'unchanged':
481
535
elif state == 'killed-a':
483
537
lines_b.append(line)
492
546
lines_b.append(line)
494
assert state in ('irrelevant', 'ghost-a', 'ghost-b', 'killed-base',
548
assert state in ('irrelevant', 'ghost-a', 'ghost-b',
549
'killed-base', 'killed-both'), state
550
for struct in outstanding_struct():
554
class WeaveMerge(PlanWeaveMerge):
555
"""Weave merge that takes a VersionedFile and two versions as its input"""
557
def __init__(self, versionedfile, ver_a, ver_b,
558
a_marker=PlanWeaveMerge.A_MARKER, b_marker=PlanWeaveMerge.B_MARKER):
559
plan = versionedfile.plan_merge(ver_a, ver_b)
560
PlanWeaveMerge.__init__(self, plan, a_marker, b_marker)
499
563
class InterVersionedFile(InterObject):
533
597
# Make a new target-format versioned file.
534
598
temp_source = self.target.create_empty("temp", MemoryTransport())
535
599
target = temp_source
536
graph = self.source.get_graph()
537
order = topo_sort(graph.items())
600
version_ids = self._get_source_version_ids(version_ids, ignore_missing)
601
graph = self.source.get_graph(version_ids)
602
order = tsort.topo_sort(graph.items())
538
603
pb = ui.ui_factory.nested_progress_bar()
539
604
parent_texts = {}
646
def _get_source_version_ids(self, version_ids, ignore_missing):
647
"""Determine the version ids to be used from self.source.
649
:param version_ids: The caller-supplied version ids to check. (None
650
for all). If None is in version_ids, it is stripped.
651
:param ignore_missing: if True, remove missing ids from the version
652
list. If False, raise RevisionNotPresent on
653
a missing version id.
654
:return: A set of version ids.
656
if version_ids is None:
657
# None cannot be in source.versions
658
return set(self.source.versions())
661
return set(self.source.versions()).intersection(set(version_ids))
663
new_version_ids = set()
664
for version in version_ids:
667
if not self.source.has_version(version):
668
raise errors.RevisionNotPresent(version, str(self.source))
670
new_version_ids.add(version)
671
return new_version_ids
582
674
class InterVersionedFileTestProviderAdapter(object):
583
675
"""A tool to generate a suite testing multiple inter versioned-file classes.
585
This is done by copying the test once for each interversionedfile provider
677
This is done by copying the test once for each InterVersionedFile provider
586
678
and injecting the transport_server, transport_readonly_server,
587
679
versionedfile_factory and versionedfile_factory_to classes into each copy.
588
680
Each copy is also given a new id() to make it easy to identify.
617
709
from bzrlib.weave import WeaveFile
618
710
from bzrlib.knit import KnitVersionedFile
620
# test the fallback InterVersionedFile from weave to annotated knits
712
# test the fallback InterVersionedFile from annotated knits to weave
621
713
result.append((InterVersionedFile,
624
716
for optimiser in InterVersionedFile._optimisers:
625
717
result.append((optimiser,
626
optimiser._matching_file_factory,
627
optimiser._matching_file_factory
718
optimiser._matching_file_from_factory,
719
optimiser._matching_file_to_factory
629
721
# if there are specific combinations we want to use, we can add them