78
79
"""Returns whether version is present."""
79
80
raise NotImplementedError(self.has_version)
81
def add_lines(self, version_id, parents, lines):
82
def add_delta(self, version_id, parents, delta_parent, sha1, noeol, delta):
83
"""Add a text to the versioned file via a pregenerated delta.
85
:param version_id: The version id being added.
86
:param parents: The parents of the version_id.
87
:param delta_parent: The parent this delta was created against.
88
:param sha1: The sha1 of the full text.
89
:param delta: The delta instructions. See get_delta for details.
91
self._check_write_ok()
92
if self.has_version(version_id):
93
raise errors.RevisionAlreadyPresent(version_id, self)
94
return self._add_delta(version_id, parents, delta_parent, sha1, noeol, delta)
96
def _add_delta(self, version_id, parents, delta_parent, sha1, noeol, delta):
97
"""Class specific routine to add a delta.
99
This generic version simply applies the delta to the delta_parent and
102
# strip annotation from delta
104
for start, stop, delta_len, delta_lines in delta:
105
new_delta.append((start, stop, delta_len, [text for origin, text in delta_lines]))
106
if delta_parent is not None:
107
parent_full = self.get_lines(delta_parent)
110
new_full = self._apply_delta(parent_full, new_delta)
111
# its impossible to have noeol on an empty file
112
if noeol and new_full[-1][-1] == '\n':
113
new_full[-1] = new_full[-1][:-1]
114
self.add_lines(version_id, parents, new_full)
116
def add_lines(self, version_id, parents, lines, parent_texts=None):
82
117
"""Add a single text on top of the versioned file.
84
119
Must raise RevisionAlreadyPresent if the new version is
87
122
Must raise RevisionNotPresent if any of the given parents are
88
123
not present in file history.
124
:param parent_texts: An optional dictionary containing the opaque
125
representations of some or all of the parents of
126
version_id to allow delta optimisations.
127
VERY IMPORTANT: the texts must be those returned
128
by add_lines or data corruption can be caused.
129
:return: An opaque representation of the inserted version which can be
130
provided back to future add_lines calls in the parent_texts
90
133
self._check_write_ok()
91
return self._add_lines(version_id, parents, lines)
134
return self._add_lines(version_id, parents, lines, parent_texts)
93
def _add_lines(self, version_id, parents, lines):
136
def _add_lines(self, version_id, parents, lines, parent_texts):
94
137
"""Helper to do the class specific add_lines."""
95
138
raise NotImplementedError(self.add_lines)
97
def add_lines_with_ghosts(self, version_id, parents, lines):
98
"""Add lines to the versioned file, allowing ghosts to be present."""
140
def add_lines_with_ghosts(self, version_id, parents, lines,
142
"""Add lines to the versioned file, allowing ghosts to be present.
144
This takes the same parameters as add_lines.
99
146
self._check_write_ok()
100
return self._add_lines_with_ghosts(version_id, parents, lines)
147
return self._add_lines_with_ghosts(version_id, parents, lines,
102
def _add_lines_with_ghosts(self, version_id, parents, lines):
150
def _add_lines_with_ghosts(self, version_id, parents, lines, parent_texts):
103
151
"""Helper to do class specific add_lines_with_ghosts."""
104
152
raise NotImplementedError(self.add_lines_with_ghosts)
107
155
"""Check the versioned file for integrity."""
108
156
raise NotImplementedError(self.check)
158
def _check_lines_not_unicode(self, lines):
159
"""Check that lines being added to a versioned file are not unicode."""
161
if line.__class__ is not str:
162
raise errors.BzrBadParameterUnicode("lines")
164
def _check_lines_are_lines(self, lines):
165
"""Check that the lines really are full lines without inline EOL."""
167
if '\n' in line[:-1]:
168
raise errors.BzrBadParameterContainsNewline("lines")
110
170
def _check_write_ok(self):
111
171
"""Is the versioned file marked as 'finished' ? Raise if it is."""
112
172
if self.finished:
156
216
"""Helper for fix_parents."""
157
217
raise NotImplementedError(self.fix_parents)
219
def get_delta(self, version):
220
"""Get a delta for constructing version from some other version.
222
:return: (delta_parent, sha1, noeol, delta)
223
Where delta_parent is a version id or None to indicate no parent.
225
raise NotImplementedError(self.get_delta)
227
def get_deltas(self, versions):
228
"""Get multiple deltas at once for constructing versions.
230
:return: dict(version_id:(delta_parent, sha1, noeol, delta))
231
Where delta_parent is a version id or None to indicate no parent, and
232
version_id is the version_id created by that delta.
235
for version in versions:
236
result[version] = self.get_delta(version)
239
def get_sha1(self, version_id):
240
"""Get the stored sha1 sum for the given revision.
242
:param name: The name of the version to lookup
244
raise NotImplementedError(self.get_sha1)
159
246
def get_suffixes(self):
160
247
"""Return the file suffixes associated with this versioned file."""
161
248
raise NotImplementedError(self.get_suffixes)
256
343
def annotate(self, version_id):
257
344
return list(self.annotate_iter(version_id))
346
def _apply_delta(self, lines, delta):
347
"""Apply delta to lines."""
350
for start, end, count, delta_lines in delta:
351
lines[offset+start:offset+end] = delta_lines
352
offset = offset + (start - end) + count
259
355
def join(self, other, pb=None, msg=None, version_ids=None,
260
356
ignore_missing=False):
261
357
"""Integrate versions from other into this versioned file.
326
422
Weave lines present in none of them are skipped entirely.
425
killed-base Dead in base revision
426
killed-both Killed in each revision
429
unchanged Alive in both a and b (possibly created in both)
432
ghost-a Killed in a, unborn in b
433
ghost-b Killed in b, unborn in a
434
irrelevant Not in either revision
328
inc_a = set(self.get_ancestry([ver_a]))
329
inc_b = set(self.get_ancestry([ver_b]))
330
inc_c = inc_a & inc_b
332
for lineno, insert, deleteset, line in self.walk([ver_a, ver_b]):
333
if deleteset & inc_c:
334
# killed in parent; can't be in either a or b
335
# not relevant to our work
336
yield 'killed-base', line
337
elif insert in inc_c:
338
# was inserted in base
339
killed_a = bool(deleteset & inc_a)
340
killed_b = bool(deleteset & inc_b)
341
if killed_a and killed_b:
342
yield 'killed-both', line
344
yield 'killed-a', line
346
yield 'killed-b', line
348
yield 'unchanged', line
349
elif insert in inc_a:
350
if deleteset & inc_a:
351
yield 'ghost-a', line
355
elif insert in inc_b:
356
if deleteset & inc_b:
357
yield 'ghost-b', line
361
# not in either revision
362
yield 'irrelevant', line
364
yield 'unchanged', '' # terminator
366
def weave_merge(self, plan, a_marker='<<<<<<< \n', b_marker='>>>>>>> \n'):
436
raise NotImplementedError(VersionedFile.plan_merge)
438
def weave_merge(self, plan, a_marker=TextMerge.A_MARKER,
439
b_marker=TextMerge.B_MARKER):
440
return PlanWeaveMerge(plan, a_marker, b_marker).merge_lines()[0]
443
class PlanWeaveMerge(TextMerge):
444
"""Weave merge that takes a plan as its input.
446
This exists so that VersionedFile.plan_merge is implementable.
447
Most callers will want to use WeaveMerge instead.
450
def __init__(self, plan, a_marker=TextMerge.A_MARKER,
451
b_marker=TextMerge.B_MARKER):
452
TextMerge.__init__(self, a_marker, b_marker)
455
def _merge_struct(self):
369
458
ch_a = ch_b = False
370
# TODO: Return a structured form of the conflicts (e.g. 2-tuples for
371
# conflicted regions), rather than just inserting the markers.
373
# TODO: Show some version information (e.g. author, date) on
374
# conflicted regions.
375
for state, line in plan:
376
if state == 'unchanged' or state == 'killed-both':
460
def outstanding_struct():
461
if not lines_a and not lines_b:
463
elif ch_a and not ch_b:
466
elif ch_b and not ch_a:
468
elif lines_a == lines_b:
471
yield (lines_a, lines_b)
473
# We previously considered either 'unchanged' or 'killed-both' lines
474
# to be possible places to resynchronize. However, assuming agreement
475
# on killed-both lines may be too agressive. -- mbp 20060324
476
for state, line in self.plan:
477
if state == 'unchanged':
377
478
# resync and flush queued conflicts changes if any
378
if not lines_a and not lines_b:
380
elif ch_a and not ch_b:
382
for l in lines_a: yield l
383
elif ch_b and not ch_a:
384
for l in lines_b: yield l
385
elif lines_a == lines_b:
386
for l in lines_a: yield l
389
for l in lines_a: yield l
391
for l in lines_b: yield l
479
for struct in outstanding_struct():
396
483
ch_a = ch_b = False
398
485
if state == 'unchanged':
401
488
elif state == 'killed-a':
403
490
lines_b.append(line)
412
499
lines_b.append(line)
414
assert state in ('irrelevant', 'ghost-a', 'ghost-b', 'killed-base',
501
assert state in ('irrelevant', 'ghost-a', 'ghost-b',
502
'killed-base', 'killed-both'), state
503
for struct in outstanding_struct():
507
class WeaveMerge(PlanWeaveMerge):
508
"""Weave merge that takes a VersionedFile and two versions as its input"""
510
def __init__(self, versionedfile, ver_a, ver_b,
511
a_marker=PlanWeaveMerge.A_MARKER, b_marker=PlanWeaveMerge.B_MARKER):
512
plan = versionedfile.plan_merge(ver_a, ver_b)
513
PlanWeaveMerge.__init__(self, plan, a_marker, b_marker)
419
516
class InterVersionedFile(InterObject):
456
553
graph = self.source.get_graph()
457
554
order = topo_sort(graph.items())
458
555
pb = ui.ui_factory.nested_progress_bar()
558
# TODO for incremental cross-format work:
559
# make a versioned file with the following content:
560
# all revisions we have been asked to join
561
# all their ancestors that are *not* in target already.
562
# the immediate parents of the above two sets, with
563
# empty parent lists - these versions are in target already
564
# and the incorrect version data will be ignored.
565
# TODO: for all ancestors that are present in target already,
566
# check them for consistent data, this requires moving sha1 from
568
# TODO: remove parent texts when they are not relevant any more for
569
# memory pressure reduction. RBC 20060313
570
# pb.update('Converting versioned data', 0, len(order))
571
# deltas = self.source.get_deltas(order)
460
572
for index, version in enumerate(order):
461
573
pb.update('Converting versioned data', index, len(order))
462
target.add_lines(version,
463
self.source.get_parents(version),
464
self.source.get_lines(version))
574
parent_text = target.add_lines(version,
575
self.source.get_parents(version),
576
self.source.get_lines(version),
577
parent_texts=parent_texts)
578
parent_texts[version] = parent_text
579
#delta_parent, sha1, noeol, delta = deltas[version]
580
#target.add_delta(version,
581
# self.source.get_parents(version),
586
#target.get_lines(version)
466
588
# this should hit the native code path for target
467
589
if target is not self.target: