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):
353
def iter_lines_added_or_present_in_versions(self, version_ids=None):
406
def iter_lines_added_or_present_in_versions(self, version_ids=None,
354
408
"""Iterate over the lines in the versioned file from version_ids.
356
410
This may return lines from other versions, and does not return the
357
411
specific version marker at this point. The api may be changed
358
412
during development to include the version that the versioned file
359
413
thinks is relevant, but given that such hints are just guesses,
360
its better not to have it if we dont need it.
414
its better not to have it if we don't need it.
416
If a progress bar is supplied, it may be used to indicate progress.
417
The caller is responsible for cleaning up progress bars (because this
362
420
NOTES: Lines are normalised: they will all have \n terminators.
363
421
Lines are returned in arbitrary order.
402
460
Weave lines present in none of them are skipped entirely.
463
killed-base Dead in base revision
464
killed-both Killed in each revision
467
unchanged Alive in both a and b (possibly created in both)
470
ghost-a Killed in a, unborn in b
471
ghost-b Killed in b, unborn in a
472
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'):
474
raise NotImplementedError(VersionedFile.plan_merge)
476
def weave_merge(self, plan, a_marker=TextMerge.A_MARKER,
477
b_marker=TextMerge.B_MARKER):
478
return PlanWeaveMerge(plan, a_marker, b_marker).merge_lines()[0]
481
class PlanWeaveMerge(TextMerge):
482
"""Weave merge that takes a plan as its input.
484
This exists so that VersionedFile.plan_merge is implementable.
485
Most callers will want to use WeaveMerge instead.
488
def __init__(self, plan, a_marker=TextMerge.A_MARKER,
489
b_marker=TextMerge.B_MARKER):
490
TextMerge.__init__(self, a_marker, b_marker)
493
def _merge_struct(self):
445
496
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.
498
def outstanding_struct():
499
if not lines_a and not lines_b:
501
elif ch_a and not ch_b:
504
elif ch_b and not ch_a:
506
elif lines_a == lines_b:
509
yield (lines_a, lines_b)
452
511
# We previously considered either 'unchanged' or 'killed-both' lines
453
512
# 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:
513
# on killed-both lines may be too aggressive. -- mbp 20060324
514
for state, line in self.plan:
456
515
if state == 'unchanged':
457
516
# 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
517
for struct in outstanding_struct():
476
521
ch_a = ch_b = False
478
523
if state == 'unchanged':
481
526
elif state == 'killed-a':
483
528
lines_b.append(line)
492
537
lines_b.append(line)
494
assert state in ('irrelevant', 'ghost-a', 'ghost-b', 'killed-base',
539
assert state in ('irrelevant', 'ghost-a', 'ghost-b',
540
'killed-base', 'killed-both'), state
541
for struct in outstanding_struct():
545
class WeaveMerge(PlanWeaveMerge):
546
"""Weave merge that takes a VersionedFile and two versions as its input"""
548
def __init__(self, versionedfile, ver_a, ver_b,
549
a_marker=PlanWeaveMerge.A_MARKER, b_marker=PlanWeaveMerge.B_MARKER):
550
plan = versionedfile.plan_merge(ver_a, ver_b)
551
PlanWeaveMerge.__init__(self, plan, a_marker, b_marker)
499
554
class InterVersionedFile(InterObject):
637
def _get_source_version_ids(self, version_ids, ignore_missing):
638
"""Determine the version ids to be used from self.source.
640
:param version_ids: The caller-supplied version ids to check. (None
641
for all). If None is in version_ids, it is stripped.
642
:param ignore_missing: if True, remove missing ids from the version
643
list. If False, raise RevisionNotPresent on
644
a missing version id.
645
:return: A set of version ids.
647
if version_ids is None:
648
# None cannot be in source.versions
649
return set(self.source.versions())
652
return set(self.source.versions()).intersection(set(version_ids))
654
new_version_ids = set()
655
for version in version_ids:
658
if not self.source.has_version(version):
659
raise errors.RevisionNotPresent(version, str(self.source))
661
new_version_ids.add(version)
662
return new_version_ids
582
665
class InterVersionedFileTestProviderAdapter(object):
583
666
"""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
668
This is done by copying the test once for each InterVersionedFile provider
586
669
and injecting the transport_server, transport_readonly_server,
587
670
versionedfile_factory and versionedfile_factory_to classes into each copy.
588
671
Each copy is also given a new id() to make it easy to identify.
617
700
from bzrlib.weave import WeaveFile
618
701
from bzrlib.knit import KnitVersionedFile
620
# test the fallback InterVersionedFile from weave to annotated knits
703
# test the fallback InterVersionedFile from annotated knits to weave
621
704
result.append((InterVersionedFile,
624
707
for optimiser in InterVersionedFile._optimisers:
625
708
result.append((optimiser,
626
optimiser._matching_file_factory,
627
optimiser._matching_file_factory
709
optimiser._matching_file_from_factory,
710
optimiser._matching_file_to_factory
629
712
# if there are specific combinations we want to use, we can add them