69
71
from copy import copy
70
72
from cStringIO import StringIO
73
from difflib import SequenceMatcher
73
from bzrlib.lazy_import import lazy_import
74
lazy_import(globals(), """
75
from bzrlib import tsort
78
from bzrlib.trace import mutter
81
79
from bzrlib.errors import (WeaveError, WeaveFormatError, WeaveParentMismatch,
82
80
RevisionAlreadyPresent,
83
81
RevisionNotPresent,
84
UnavailableRepresentation,
82
WeaveRevisionAlreadyPresent,
83
WeaveRevisionNotPresent,
86
from bzrlib.osutils import dirname, sha, sha_strings, split_lines
87
import bzrlib.patiencediff
88
from bzrlib.revision import NULL_REVISION
85
import bzrlib.errors as errors
86
from bzrlib.osutils import sha_strings
87
from bzrlib.patiencediff import SequenceMatcher, unified_diff
89
88
from bzrlib.symbol_versioning import *
90
from bzrlib.trace import mutter
91
from bzrlib.versionedfile import (
89
from bzrlib.tsort import topo_sort
90
from bzrlib.versionedfile import VersionedFile, InterVersionedFile
98
91
from bzrlib.weavefile import _read_weave_v5, write_weave_v5
101
class WeaveContentFactory(ContentFactory):
102
"""Content factory for streaming from weaves.
104
:seealso ContentFactory:
107
def __init__(self, version, weave):
108
"""Create a WeaveContentFactory for version from weave."""
109
ContentFactory.__init__(self)
110
self.sha1 = weave.get_sha1s([version])[version]
111
self.key = (version,)
112
parents = weave.get_parent_map([version])[version]
113
self.parents = tuple((parent,) for parent in parents)
114
self.storage_kind = 'fulltext'
117
def get_bytes_as(self, storage_kind):
118
if storage_kind == 'fulltext':
119
return self._weave.get_text(self.key[-1])
120
elif storage_kind == 'chunked':
121
return self._weave.get_lines(self.key[-1])
123
raise UnavailableRepresentation(self.key, storage_kind, 'fulltext')
126
94
class Weave(VersionedFile):
127
95
"""weave - versioned text file storage.
129
97
A Weave manages versions of line-based text files, keeping track
130
98
of the originating version for each line.
270
218
return self._parents == other._parents \
271
219
and self._weave == other._weave \
272
and self._sha1s == other._sha1s
220
and self._sha1s == other._sha1s
274
222
def __ne__(self, other):
275
223
return not self.__eq__(other)
225
@deprecated_method(zero_eight)
226
def idx_to_name(self, index):
227
"""Old public interface, the public interface is all names now."""
277
230
def _idx_to_name(self, version):
278
231
return self._names[version]
233
@deprecated_method(zero_eight)
234
def lookup(self, name):
235
"""Backwards compatability thunk:
237
Return name, as name is valid in the api now, and spew deprecation
280
242
def _lookup(self, name):
281
243
"""Convert symbolic version name to index."""
282
if not self._allow_reserved:
283
self.check_not_reserved_id(name)
285
245
return self._name_map[name]
287
247
raise RevisionNotPresent(name, self._weave_name)
249
@deprecated_method(zero_eight)
250
def iter_names(self):
251
"""Deprecated convenience function, please see VersionedFile.names()."""
252
return iter(self.names())
254
@deprecated_method(zero_eight)
256
"""See Weave.versions for the current api."""
257
return self.versions()
289
259
def versions(self):
290
260
"""See VersionedFile.versions."""
291
261
return self._names[:]
293
263
def has_version(self, version_id):
294
264
"""See VersionedFile.has_version."""
295
return (version_id in self._name_map)
265
return self._name_map.has_key(version_id)
297
267
__contains__ = has_version
299
def get_record_stream(self, versions, ordering, include_delta_closure):
300
"""Get a stream of records for versions.
302
:param versions: The versions to include. Each version is a tuple
304
:param ordering: Either 'unordered' or 'topological'. A topologically
305
sorted stream has compression parents strictly before their
307
:param include_delta_closure: If True then the closure across any
308
compression parents will be included (in the opaque data).
309
:return: An iterator of ContentFactory objects, each of which is only
310
valid until the iterator is advanced.
312
versions = [version[-1] for version in versions]
313
if ordering == 'topological':
314
parents = self.get_parent_map(versions)
315
new_versions = tsort.topo_sort(parents)
316
new_versions.extend(set(versions).difference(set(parents)))
317
versions = new_versions
318
elif ordering == 'groupcompress':
319
parents = self.get_parent_map(versions)
320
new_versions = sort_groupcompress(parents)
321
new_versions.extend(set(versions).difference(set(parents)))
322
versions = new_versions
323
for version in versions:
325
yield WeaveContentFactory(version, self)
327
yield AbsentContentFactory((version,))
329
def get_parent_map(self, version_ids):
330
"""See VersionedFile.get_parent_map."""
269
def get_delta(self, version_id):
270
"""See VersionedFile.get_delta."""
271
return self.get_deltas([version_id])[version_id]
273
def get_deltas(self, version_ids):
274
"""See VersionedFile.get_deltas."""
275
version_ids = self.get_ancestry(version_ids)
332
276
for version_id in version_ids:
333
if version_id == NULL_REVISION:
338
map(self._idx_to_name,
339
self._parents[self._lookup(version_id)]))
340
except RevisionNotPresent:
277
if not self.has_version(version_id):
278
raise RevisionNotPresent(version_id, self)
279
# try extracting all versions; parallel extraction is used
280
nv = self.num_versions()
286
last_parent_lines = {}
288
parent_inclusions = {}
293
# its simplest to generate a full set of prepared variables.
295
name = self._names[i]
296
sha1s[name] = self.get_sha1(name)
297
parents_list = self.get_parents(name)
299
parent = parents_list[0]
300
parents[name] = parent
301
parent_inclusions[name] = inclusions[parent]
304
parent_inclusions[name] = set()
305
# we want to emit start, finish, replacement_length, replacement_lines tuples.
306
diff_hunks[name] = []
307
current_hunks[name] = [0, 0, 0, []] # #start, finish, repl_length, repl_tuples
308
parent_linenums[name] = 0
310
parent_noeols[name] = False
311
last_parent_lines[name] = None
312
new_inc = set([name])
313
for p in self._parents[i]:
314
new_inc.update(inclusions[self._idx_to_name(p)])
315
# debug only, known good so far.
316
#assert set(new_inc) == set(self.get_ancestry(name)), \
317
# 'failed %s != %s' % (set(new_inc), set(self.get_ancestry(name)))
318
inclusions[name] = new_inc
320
nlines = len(self._weave)
322
for lineno, inserted, deletes, line in self._walk_internal():
323
# a line is active in a version if:
324
# insert is in the versions inclusions
326
# deleteset & the versions inclusions is an empty set.
327
# so - if we have a included by mapping - version is included by
328
# children, we get a list of children to examine for deletes affect
329
# ing them, which is less than the entire set of children.
330
for version_id in version_ids:
331
# The active inclusion must be an ancestor,
332
# and no ancestors must have deleted this line,
333
# because we don't support resurrection.
334
parent_inclusion = parent_inclusions[version_id]
335
inclusion = inclusions[version_id]
336
parent_active = inserted in parent_inclusion and not (deletes & parent_inclusion)
337
version_active = inserted in inclusion and not (deletes & inclusion)
338
if not parent_active and not version_active:
339
# unrelated line of ancestry
342
result[version_id] = parents
341
elif parent_active and version_active:
343
parent_linenum = parent_linenums[version_id]
344
if current_hunks[version_id] != [parent_linenum, parent_linenum, 0, []]:
345
diff_hunks[version_id].append(tuple(current_hunks[version_id]))
347
current_hunks[version_id] = [parent_linenum, parent_linenum, 0, []]
348
parent_linenums[version_id] = parent_linenum
351
noeols[version_id] = True
354
elif parent_active and not version_active:
356
current_hunks[version_id][1] += 1
357
parent_linenums[version_id] += 1
358
last_parent_lines[version_id] = line
359
elif not parent_active and version_active:
361
# noeol only occurs at the end of a file because we
362
# diff linewise. We want to show noeol changes as a
363
# empty diff unless the actual eol-less content changed.
366
if last_parent_lines[version_id][-1] != '\n':
367
parent_noeols[version_id] = True
368
except (TypeError, IndexError):
371
if theline[-1] != '\n':
372
noeols[version_id] = True
376
parent_should_go = False
378
if parent_noeols[version_id] == noeols[version_id]:
379
# no noeol toggle, so trust the weaves statement
380
# that this line is changed.
382
if parent_noeols[version_id]:
383
theline = theline + '\n'
384
elif parent_noeols[version_id]:
385
# parent has no eol, we do:
386
# our line is new, report as such..
388
elif noeols[version_id]:
389
# append a eol so that it looks like
391
theline = theline + '\n'
392
if parents[version_id] is not None:
393
#if last_parent_lines[version_id] is not None:
394
parent_should_go = True
395
if last_parent_lines[version_id] != theline:
398
#parent_should_go = False
400
current_hunks[version_id][2] += 1
401
current_hunks[version_id][3].append((inserted, theline))
403
# last hunk last parent line is not eaten
404
current_hunks[version_id][1] -= 1
405
if current_hunks[version_id][1] < 0:
406
current_hunks[version_id][1] = 0
407
# import pdb;pdb.set_trace()
408
# assert current_hunks[version_id][1] >= 0
412
version = self._idx_to_name(i)
413
if current_hunks[version] != [0, 0, 0, []]:
414
diff_hunks[version].append(tuple(current_hunks[version]))
416
for version_id in version_ids:
417
result[version_id] = (
421
diff_hunks[version_id],
345
def get_parents_with_ghosts(self, version_id):
346
raise NotImplementedError(self.get_parents_with_ghosts)
348
def insert_record_stream(self, stream):
349
"""Insert a record stream into this versioned file.
351
:param stream: A stream of records to insert.
353
:seealso VersionedFile.get_record_stream:
356
for record in stream:
357
# Raise an error when a record is missing.
358
if record.storage_kind == 'absent':
359
raise RevisionNotPresent([record.key[0]], self)
360
# adapt to non-tuple interface
361
parents = [parent[0] for parent in record.parents]
362
if (record.storage_kind == 'fulltext'
363
or record.storage_kind == 'chunked'):
364
self.add_lines(record.key[0], parents,
365
osutils.chunks_to_lines(record.get_bytes_as('chunked')))
367
adapter_key = record.storage_kind, 'fulltext'
369
adapter = adapters[adapter_key]
371
adapter_factory = adapter_registry.get(adapter_key)
372
adapter = adapter_factory(self)
373
adapters[adapter_key] = adapter
374
lines = split_lines(adapter.get_bytes(record))
376
self.add_lines(record.key[0], parents, lines)
377
except RevisionAlreadyPresent:
425
def get_parents(self, version_id):
426
"""See VersionedFile.get_parent."""
427
return map(self._idx_to_name, self._parents[self._lookup(version_id)])
380
429
def _check_repeated_add(self, name, parents, text, sha1):
381
430
"""Check that a duplicated add is OK.
388
437
raise RevisionAlreadyPresent(name, self._weave_name)
391
def _add_lines(self, version_id, parents, lines, parent_texts,
392
left_matching_blocks, nostore_sha, random_id, check_content):
440
@deprecated_method(zero_eight)
441
def add_identical(self, old_rev_id, new_rev_id, parents):
442
"""Please use Weave.clone_text now."""
443
return self.clone_text(new_rev_id, old_rev_id, parents)
445
def _add_lines(self, version_id, parents, lines, parent_texts):
393
446
"""See VersionedFile.add_lines."""
394
idx = self._add(version_id, lines, map(self._lookup, parents),
395
nostore_sha=nostore_sha)
396
return sha_strings(lines), sum(map(len, lines)), idx
398
def _add(self, version_id, lines, parents, sha1=None, nostore_sha=None):
447
return self._add(version_id, lines, map(self._lookup, parents))
449
@deprecated_method(zero_eight)
450
def add(self, name, parents, text, sha1=None):
451
"""See VersionedFile.add_lines for the non deprecated api."""
452
return self._add(name, text, map(self._maybe_lookup, parents), sha1)
454
def _add(self, version_id, lines, parents, sha1=None):
399
455
"""Add a single text on top of the weave.
401
457
Returns the index number of the newly added version.
404
460
Symbolic name for this version.
405
461
(Typically the revision-id of the revision that added it.)
406
If None, a name will be allocated based on the hash. (sha1:SHAHASH)
409
464
List or set of direct parent version numbers.
412
467
Sequence of lines to be added in the new version.
414
:param nostore_sha: See VersionedFile.add_lines.
470
assert isinstance(version_id, basestring)
416
471
self._check_lines_not_unicode(lines)
417
472
self._check_lines_are_lines(lines)
419
474
sha1 = sha_strings(lines)
420
if sha1 == nostore_sha:
421
raise errors.ExistingContent
422
if version_id is None:
423
version_id = "sha1:" + sha1
424
475
if version_id in self._name_map:
425
476
return self._check_repeated_add(version_id, parents, lines, sha1)
557
629
def _compatible_parents(self, my_parents, other_parents):
558
630
"""During join check that other_parents are joinable with my_parents.
560
Joinable is defined as 'is a subset of' - supersets may require
632
Joinable is defined as 'is a subset of' - supersets may require
561
633
regeneration of diffs, but subsets do not.
563
635
return len(other_parents.difference(my_parents)) == 0
565
637
def annotate(self, version_id):
566
"""Return a list of (version-id, line) tuples for version_id.
638
if isinstance(version_id, int):
639
warn('Weave.annotate(int) is deprecated. Please use version names'
640
' in all circumstances as of 0.8',
645
for origin, lineno, text in self._extract([version_id]):
646
result.append((origin, text))
649
return super(Weave, self).annotate(version_id)
651
def annotate_iter(self, version_id):
652
"""Yield list of (version-id, line) pairs for the specified version.
568
654
The index indicates when the line originated in the weave."""
569
655
incls = [self._lookup(version_id)]
570
return [(self._idx_to_name(origin), text) for origin, lineno, text in
571
self._extract(incls)]
573
def iter_lines_added_or_present_in_versions(self, version_ids=None,
656
for origin, lineno, text in self._extract(incls):
657
yield self._idx_to_name(origin), text
659
@deprecated_method(zero_eight)
661
"""_walk has become visit, a supported api."""
662
return self._walk_internal()
664
def iter_lines_added_or_present_in_versions(self, version_ids=None):
575
665
"""See VersionedFile.iter_lines_added_or_present_in_versions()."""
576
666
if version_ids is None:
577
667
version_ids = self.versions()
578
668
version_ids = set(version_ids)
579
669
for lineno, inserted, deletes, line in self._walk_internal(version_ids):
580
if inserted not in version_ids: continue
670
# if inserted not in version_ids then it was inserted before the
671
# versions we care about, but because weaves cannot represent ghosts
672
# properly, we do not filter down to that
673
# if inserted not in version_ids: continue
581
674
if line[-1] != '\n':
582
yield line + '\n', inserted
679
#@deprecated_method(zero_eight)
680
def walk(self, version_ids=None):
681
"""See VersionedFile.walk."""
682
return self._walk_internal(version_ids)
586
684
def _walk_internal(self, version_ids=None):
587
685
"""Helper method for weave actions."""
848
977
# no lines outside of insertion blocks, that deletions are
849
978
# properly paired, etc.
980
def _join(self, other, pb, msg, version_ids, ignore_missing):
981
"""Worker routine for join()."""
982
if not other.versions():
983
return # nothing to update, easy
986
# versions is never none, InterWeave checks this.
989
# two loops so that we do not change ourselves before verifying it
991
# work through in index order to make sure we get all dependencies
994
# get the selected versions only that are in other.versions.
995
version_ids = set(other.versions()).intersection(set(version_ids))
996
# pull in the referenced graph.
997
version_ids = other.get_ancestry(version_ids)
998
pending_graph = [(version, other.get_parents(version)) for
999
version in version_ids]
1000
for name in topo_sort(pending_graph):
1001
other_idx = other._name_map[name]
1002
# returns True if we have it, False if we need it.
1003
if not self._check_version_consistent(other, other_idx, name):
1004
names_to_join.append((other_idx, name))
1013
for other_idx, name in names_to_join:
1014
# TODO: If all the parents of the other version are already
1015
# present then we can avoid some work by just taking the delta
1016
# and adjusting the offsets.
1017
new_parents = self._imported_parents(other, other_idx)
1018
sha1 = other._sha1s[other_idx]
1023
pb.update(msg, merged, len(names_to_join))
1025
lines = other.get_lines(other_idx)
1026
self._add(name, lines, new_parents, sha1)
1028
mutter("merged = %d, processed = %d, file_id=%s; deltat=%d"%(
1029
merged, processed, self._weave_name, time.time()-time0))
851
1031
def _imported_parents(self, other, other_idx):
852
1032
"""Return list of parents in self corresponding to indexes in other."""
853
1033
new_parents = []
951
1140
sio = StringIO()
952
1141
write_weave_v5(self, sio)
954
bytes = sio.getvalue()
955
path = self._weave_name + WeaveFile.WEAVE_SUFFIX
957
self._transport.put_bytes(path, bytes, self._filemode)
958
except errors.NoSuchFile:
959
self._transport.mkdir(dirname(path))
960
self._transport.put_bytes(path, bytes, self._filemode)
1143
self._transport.put(self._weave_name + WeaveFile.WEAVE_SUFFIX,
963
1148
def get_suffixes():
964
1149
"""See VersionedFile.get_suffixes()."""
965
1150
return [WeaveFile.WEAVE_SUFFIX]
967
def insert_record_stream(self, stream):
968
super(WeaveFile, self).insert_record_stream(stream)
1152
def join(self, other, pb=None, msg=None, version_ids=None,
1153
ignore_missing=False):
1154
"""Join other into self and save."""
1155
super(WeaveFile, self).join(other, pb, msg, version_ids, ignore_missing)
1159
@deprecated_function(zero_eight)
1160
def reweave(wa, wb, pb=None, msg=None):
1161
"""reweaving is deprecation, please just use weave.join()."""
1162
_reweave(wa, wb, pb, msg)
972
1164
def _reweave(wa, wb, pb=None, msg=None):
973
1165
"""Combine two weaves and return the result.
975
This works even if a revision R has different parents in
1167
This works even if a revision R has different parents in
976
1168
wa and wb. In the resulting weave all the parents are given.
978
This is done by just building up a new weave, maintaining ordering
1170
This is done by just building up a new weave, maintaining ordering
979
1171
of the versions in the two inputs. More efficient approaches
980
might be possible but it should only be necessary to do
981
this operation rarely, when a new previously ghost version is
1172
might be possible but it should only be necessary to do
1173
this operation rarely, when a new previously ghost version is
984
1176
:param pb: An optional progress bar, indicating how far done we are
1029
1220
p = combined.setdefault(name, set())
1030
1221
p.update(map(weave._idx_to_name, weave._parents[idx]))
1031
1222
return combined
1226
"""Show the weave's table-of-contents"""
1227
print '%6s %50s %10s %10s' % ('ver', 'name', 'sha1', 'parents')
1228
for i in (6, 50, 10, 10):
1231
for i in range(w.num_versions()):
1234
parent_str = ' '.join(map(str, w._parents[i]))
1235
print '%6d %-50.50s %10.10s %s' % (i, name, sha1, parent_str)
1239
def weave_stats(weave_file, pb):
1240
from bzrlib.weavefile import read_weave
1242
wf = file(weave_file, 'rb')
1243
w = read_weave(wf, WeaveVersionedFile)
1244
# FIXME: doesn't work on pipes
1245
weave_size = wf.tell()
1249
for i in range(vers):
1250
pb.update('checking sizes', i, vers)
1251
for origin, lineno, line in w._extract([i]):
1256
print 'versions %9d' % vers
1257
print 'weave file %9d bytes' % weave_size
1258
print 'total contents %9d bytes' % total
1259
print 'compression ratio %9.2fx' % (float(total) / float(weave_size))
1262
print 'average size %9d bytes' % avg
1263
print 'relative size %9.2fx' % (float(weave_size) / float(avg))
1267
print """bzr weave tool
1269
Experimental tool for weave algorithm.
1272
weave init WEAVEFILE
1273
Create an empty weave file
1274
weave get WEAVEFILE VERSION
1275
Write out specified version.
1276
weave check WEAVEFILE
1277
Check consistency of all versions.
1279
Display table of contents.
1280
weave add WEAVEFILE NAME [BASE...] < NEWTEXT
1281
Add NEWTEXT, with specified parent versions.
1282
weave annotate WEAVEFILE VERSION
1283
Display origin of each line.
1284
weave merge WEAVEFILE VERSION1 VERSION2 > OUT
1285
Auto-merge two versions and display conflicts.
1286
weave diff WEAVEFILE VERSION1 VERSION2
1287
Show differences between two versions.
1291
% weave init foo.weave
1293
% weave add foo.weave ver0 < foo.txt
1296
(create updated version)
1298
% weave get foo.weave 0 | diff -u - foo.txt
1299
% weave add foo.weave ver1 0 < foo.txt
1302
% weave get foo.weave 0 > foo.txt (create forked version)
1304
% weave add foo.weave ver2 0 < foo.txt
1307
% weave merge foo.weave 1 2 > foo.txt (merge them)
1308
% vi foo.txt (resolve conflicts)
1309
% weave add foo.weave merged 1 2 < foo.txt (commit merged version)
1321
# in case we're run directly from the subdirectory
1322
sys.path.append('..')
1324
from bzrlib.weavefile import write_weave, read_weave
1325
from bzrlib.progress import ProgressBar
1340
return read_weave(file(argv[2], 'rb'))
1346
# at the moment, based on everything in the file
1348
parents = map(int, argv[4:])
1349
lines = sys.stdin.readlines()
1350
ver = w.add(name, parents, lines)
1351
write_weave(w, file(argv[2], 'wb'))
1352
print 'added version %r %d' % (name, ver)
1355
if os.path.exists(fn):
1356
raise IOError("file exists")
1358
write_weave(w, file(fn, 'wb'))
1359
elif cmd == 'get': # get one version
1361
sys.stdout.writelines(w.get_iter(int(argv[3])))
1366
v1, v2 = map(int, argv[3:5])
1369
diff_gen = unified_diff(lines1, lines2,
1370
'%s version %d' % (fn, v1),
1371
'%s version %d' % (fn, v2))
1372
sys.stdout.writelines(diff_gen)
1374
elif cmd == 'annotate':
1376
# newline is added to all lines regardless; too hard to get
1377
# reasonable formatting otherwise
1379
for origin, text in w.annotate(int(argv[3])):
1380
text = text.rstrip('\r\n')
1382
print ' | %s' % (text)
1384
print '%5d | %s' % (origin, text)
1390
elif cmd == 'stats':
1391
weave_stats(argv[2], ProgressBar())
1393
elif cmd == 'check':
1398
print '%d versions ok' % w.num_versions()
1400
elif cmd == 'inclusions':
1402
print ' '.join(map(str, w.inclusions([int(argv[3])])))
1404
elif cmd == 'parents':
1406
print ' '.join(map(str, w._parents[int(argv[3])]))
1408
elif cmd == 'plan-merge':
1409
# replaced by 'bzr weave-plan-merge'
1411
for state, line in w.plan_merge(int(argv[3]), int(argv[4])):
1413
print '%14s | %s' % (state, line),
1414
elif cmd == 'merge':
1415
# replaced by 'bzr weave-merge-text'
1417
p = w.plan_merge(int(argv[3]), int(argv[4]))
1418
sys.stdout.writelines(w.weave_merge(p))
1420
raise ValueError('unknown command %r' % cmd)
1424
def profile_main(argv):
1425
import tempfile, hotshot, hotshot.stats
1427
prof_f = tempfile.NamedTemporaryFile()
1429
prof = hotshot.Profile(prof_f.name)
1431
ret = prof.runcall(main, argv)
1434
stats = hotshot.stats.load(prof_f.name)
1436
stats.sort_stats('cumulative')
1437
## XXX: Might like to write to stderr or the trace file instead but
1438
## print_stats seems hardcoded to stdout
1439
stats.print_stats(20)
1444
def lsprofile_main(argv):
1445
from bzrlib.lsprof import profile
1446
ret,stats = profile(main, argv)
1452
if __name__ == '__main__':
1454
if '--profile' in sys.argv:
1456
args.remove('--profile')
1457
sys.exit(profile_main(args))
1458
elif '--lsprof' in sys.argv:
1460
args.remove('--lsprof')
1461
sys.exit(lsprofile_main(args))
1463
sys.exit(main(sys.argv))
1466
class InterWeave(InterVersionedFile):
1467
"""Optimised code paths for weave to weave operations."""
1469
_matching_file_from_factory = staticmethod(WeaveFile)
1470
_matching_file_to_factory = staticmethod(WeaveFile)
1473
def is_compatible(source, target):
1474
"""Be compatible with weaves."""
1476
return (isinstance(source, Weave) and
1477
isinstance(target, Weave))
1478
except AttributeError:
1481
def join(self, pb=None, msg=None, version_ids=None, ignore_missing=False):
1482
"""See InterVersionedFile.join."""
1483
version_ids = self._get_source_version_ids(version_ids, ignore_missing)
1484
if self.target.versions() == [] and version_ids is None:
1485
self.target._copy_weave_content(self.source)
1488
self.target._join(self.source, pb, msg, version_ids, ignore_missing)
1489
except errors.WeaveParentMismatch:
1490
self.target._reweave(self.source, pb, msg)
1493
InterVersionedFile.register_optimiser(InterWeave)