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."""
154
154
"""Check the versioned file for integrity."""
155
155
raise NotImplementedError(self.check)
157
def _check_lines_not_unicode(self, lines):
158
"""Check that lines being added to a versioned file are not unicode."""
160
if line.__class__ is not str:
161
raise errors.BzrBadParameterUnicode("lines")
163
def _check_lines_are_lines(self, lines):
164
"""Check that the lines really are full lines without inline EOL."""
166
if '\n' in line[:-1]:
167
raise errors.BzrBadParameterContainsNewline("lines")
157
169
def _check_write_ok(self):
158
170
"""Is the versioned file marked as 'finished' ? Raise if it is."""
159
171
if self.finished:
161
173
if self._access_mode != 'w':
162
174
raise errors.ReadOnlyObjectDirtiedError(self)
176
def enable_cache(self):
177
"""Tell this versioned file that it should cache any data it reads.
179
This is advisory, implementations do not have to support caching.
164
183
def clear_cache(self):
165
"""Remove any data cached in the versioned file object."""
184
"""Remove any data cached in the versioned file object.
186
This only needs to be supported if caches are supported
167
190
def clone_text(self, new_version_id, old_version_id, parents):
168
191
"""Add an identical text to old_version_id as new_version_id.
267
305
raise NotImplementedError(self.get_ancestry_with_ghosts)
270
"""Return a graph for the entire versioned file.
307
def get_graph(self, version_ids=None):
308
"""Return a graph from the versioned file.
272
310
Ghosts are not listed or referenced in the graph.
311
:param version_ids: Versions to select.
312
None means retrieve all versions.
275
for version in self.versions():
276
result[version] = self.get_parents(version)
315
if version_ids is None:
316
for version in self.versions():
317
result[version] = self.get_parents(version)
319
pending = set(version_ids)
321
version = pending.pop()
322
if version in result:
324
parents = self.get_parents(version)
325
for parent in parents:
329
result[version] = parents
279
332
def get_graph_with_ghosts(self):
402
455
Weave lines present in none of them are skipped entirely.
458
killed-base Dead in base revision
459
killed-both Killed in each revision
462
unchanged Alive in both a and b (possibly created in both)
465
ghost-a Killed in a, unborn in b
466
ghost-b Killed in b, unborn in a
467
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'):
469
raise NotImplementedError(VersionedFile.plan_merge)
471
def weave_merge(self, plan, a_marker=TextMerge.A_MARKER,
472
b_marker=TextMerge.B_MARKER):
473
return PlanWeaveMerge(plan, a_marker, b_marker).merge_lines()[0]
476
class PlanWeaveMerge(TextMerge):
477
"""Weave merge that takes a plan as its input.
479
This exists so that VersionedFile.plan_merge is implementable.
480
Most callers will want to use WeaveMerge instead.
483
def __init__(self, plan, a_marker=TextMerge.A_MARKER,
484
b_marker=TextMerge.B_MARKER):
485
TextMerge.__init__(self, a_marker, b_marker)
488
def _merge_struct(self):
445
491
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.
493
def outstanding_struct():
494
if not lines_a and not lines_b:
496
elif ch_a and not ch_b:
499
elif ch_b and not ch_a:
501
elif lines_a == lines_b:
504
yield (lines_a, lines_b)
452
506
# We previously considered either 'unchanged' or 'killed-both' lines
453
507
# 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:
508
# on killed-both lines may be too aggressive. -- mbp 20060324
509
for state, line in self.plan:
456
510
if state == 'unchanged':
457
511
# 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
512
for struct in outstanding_struct():
476
516
ch_a = ch_b = False
478
518
if state == 'unchanged':
481
521
elif state == 'killed-a':
483
523
lines_b.append(line)
492
532
lines_b.append(line)
494
assert state in ('irrelevant', 'ghost-a', 'ghost-b', 'killed-base',
534
assert state in ('irrelevant', 'ghost-a', 'ghost-b',
535
'killed-base', 'killed-both'), state
536
for struct in outstanding_struct():
540
class WeaveMerge(PlanWeaveMerge):
541
"""Weave merge that takes a VersionedFile and two versions as its input"""
543
def __init__(self, versionedfile, ver_a, ver_b,
544
a_marker=PlanWeaveMerge.A_MARKER, b_marker=PlanWeaveMerge.B_MARKER):
545
plan = versionedfile.plan_merge(ver_a, ver_b)
546
PlanWeaveMerge.__init__(self, plan, a_marker, b_marker)
499
549
class InterVersionedFile(InterObject):
632
def _get_source_version_ids(self, version_ids, ignore_missing):
633
"""Determine the version ids to be used from self.source.
635
:param version_ids: The caller-supplied version ids to check. (None
636
for all). If None is in version_ids, it is stripped.
637
:param ignore_missing: if True, remove missing ids from the version
638
list. If False, raise RevisionNotPresent on
639
a missing version id.
640
:return: A set of version ids.
642
if version_ids is None:
643
# None cannot be in source.versions
644
return set(self.source.versions())
647
return set(self.source.versions()).intersection(set(version_ids))
649
new_version_ids = set()
650
for version in version_ids:
653
if not self.source.has_version(version):
654
raise errors.RevisionNotPresent(version, str(self.source))
656
new_version_ids.add(version)
657
return new_version_ids
582
660
class InterVersionedFileTestProviderAdapter(object):
583
661
"""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
663
This is done by copying the test once for each InterVersionedFile provider
586
664
and injecting the transport_server, transport_readonly_server,
587
665
versionedfile_factory and versionedfile_factory_to classes into each copy.
588
666
Each copy is also given a new id() to make it easy to identify.
617
695
from bzrlib.weave import WeaveFile
618
696
from bzrlib.knit import KnitVersionedFile
620
# test the fallback InterVersionedFile from weave to annotated knits
698
# test the fallback InterVersionedFile from annotated knits to weave
621
699
result.append((InterVersionedFile,
624
702
for optimiser in InterVersionedFile._optimisers:
625
703
result.append((optimiser,
626
optimiser._matching_file_factory,
627
optimiser._matching_file_factory
704
optimiser._matching_file_from_factory,
705
optimiser._matching_file_to_factory
629
707
# if there are specific combinations we want to use, we can add them