3
3
# Copyright (C) 2005 Canonical Ltd
5
5
# This program is free software; you can redistribute it and/or modify
6
6
# it under the terms of the GNU General Public License as published by
7
7
# the Free Software Foundation; either version 2 of the License, or
8
8
# (at your option) any later version.
10
10
# This program is distributed in the hope that it will be useful,
11
11
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
13
# GNU General Public License for more details.
15
15
# You should have received a copy of the GNU General Public License
16
16
# along with this program; if not, write to the Free Software
17
17
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
29
29
# TODO: Nothing here so far assumes the lines are really \n newlines,
30
# rather than being split up in some other way. We could accomodate
30
# rather than being split up in some other way. We could accommodate
31
31
# binaries, perhaps by naively splitting on \n or perhaps using
32
32
# something like a rolling checksum.
66
66
# be done fairly efficiently because the sequence numbers constrain
67
67
# the possible relationships.
69
# FIXME: the conflict markers should be *7* characters
70
71
from copy import copy
71
72
from cStringIO import StringIO
72
from difflib import SequenceMatcher
77
81
from bzrlib.trace import mutter
78
82
from bzrlib.errors import (WeaveError, WeaveFormatError, WeaveParentMismatch,
79
83
RevisionAlreadyPresent,
84
88
import bzrlib.errors as errors
85
89
from bzrlib.osutils import sha_strings
86
from bzrlib.symbol_versioning import *
90
import bzrlib.patiencediff
91
from bzrlib.symbol_versioning import (deprecated_method,
87
95
from bzrlib.tsort import topo_sort
88
96
from bzrlib.versionedfile import VersionedFile, InterVersionedFile
89
97
from bzrlib.weavefile import _read_weave_v5, write_weave_v5
181
189
__slots__ = ['_weave', '_parents', '_sha1s', '_names', '_name_map',
190
'_weave_name', '_matcher']
184
def __init__(self, weave_name=None, access_mode='w'):
192
def __init__(self, weave_name=None, access_mode='w', matcher=None):
185
193
super(Weave, self).__init__(access_mode)
187
195
self._parents = []
227
239
@deprecated_method(zero_eight)
228
240
def lookup(self, name):
229
"""Backwards compatability thunk:
241
"""Backwards compatibility thunk:
231
243
Return name, as name is valid in the api now, and spew deprecation
232
244
warnings everywhere.
257
270
def has_version(self, version_id):
258
271
"""See VersionedFile.has_version."""
259
return self._name_map.has_key(version_id)
272
return (version_id in self._name_map)
261
274
__contains__ = has_version
464
477
assert isinstance(version_id, basestring)
478
self._check_lines_not_unicode(lines)
479
self._check_lines_are_lines(lines)
466
481
sha1 = sha_strings(lines)
467
482
if version_id in self._name_map:
515
530
if lines == basis_lines:
516
531
return new_version
518
# add a sentinal, because we can also match against the final line
533
# add a sentinel, because we can also match against the final line
519
534
basis_lineno.append(len(self._weave))
521
536
# XXX: which line of the weave should we really consider
525
540
#print 'basis_lines:', basis_lines
526
541
#print 'new_lines: ', lines
528
s = SequenceMatcher(None, basis_lines, lines)
543
s = self._matcher(None, basis_lines, lines)
530
545
# offset gives the number of lines that have been inserted
531
546
# into the weave up to the current point; if the original edit instruction
593
608
return self.get_ancestry(version_ids)
595
def get_ancestry(self, version_ids):
610
def get_ancestry(self, version_ids, topo_sorted=True):
596
611
"""See VersionedFile.get_ancestry."""
597
612
if isinstance(version_ids, basestring):
598
613
version_ids = [version_ids]
627
642
return len(other_parents.difference(my_parents)) == 0
629
def annotate(self, version_id):
630
if isinstance(version_id, int):
631
warn('Weave.annotate(int) is deprecated. Please use version names'
632
' in all circumstances as of 0.8',
637
for origin, lineno, text in self._extract([version_id]):
638
result.append((origin, text))
641
return super(Weave, self).annotate(version_id)
643
644
def annotate_iter(self, version_id):
644
645
"""Yield list of (version-id, line) pairs for the specified version.
653
654
"""_walk has become visit, a supported api."""
654
655
return self._walk_internal()
656
def iter_lines_added_or_present_in_versions(self, version_ids=None):
657
def iter_lines_added_or_present_in_versions(self, version_ids=None,
657
659
"""See VersionedFile.iter_lines_added_or_present_in_versions()."""
658
660
if version_ids is None:
659
661
version_ids = self.versions()
709
711
raise WeaveFormatError("unclosed deletion blocks at end of weave: %s"
714
def plan_merge(self, ver_a, ver_b):
715
"""Return pseudo-annotation indicating how the two versions merge.
717
This is computed between versions a and b and their common
720
Weave lines present in none of them are skipped entirely.
722
inc_a = set(self.get_ancestry([ver_a]))
723
inc_b = set(self.get_ancestry([ver_b]))
724
inc_c = inc_a & inc_b
726
for lineno, insert, deleteset, line in\
727
self.walk([ver_a, ver_b]):
728
if deleteset & inc_c:
729
# killed in parent; can't be in either a or b
730
# not relevant to our work
731
yield 'killed-base', line
732
elif insert in inc_c:
733
# was inserted in base
734
killed_a = bool(deleteset & inc_a)
735
killed_b = bool(deleteset & inc_b)
736
if killed_a and killed_b:
737
yield 'killed-both', line
739
yield 'killed-a', line
741
yield 'killed-b', line
743
yield 'unchanged', line
744
elif insert in inc_a:
745
if deleteset & inc_a:
746
yield 'ghost-a', line
750
elif insert in inc_b:
751
if deleteset & inc_b:
752
yield 'ghost-b', line
756
# not in either revision
757
yield 'irrelevant', line
759
yield 'unchanged', '' # terminator
712
761
def _extract(self, versions):
713
762
"""Yield annotation of lines in included set.
838
887
expected_sha1, measured_sha1))
841
def get_sha1(self, name):
842
"""Get the stored sha1 sum for the given revision.
844
:param name: The name of the version to lookup
846
return self._sha1s[self._lookup(name)]
890
def get_sha1(self, version_id):
891
"""See VersionedFile.get_sha1()."""
892
return self._sha1s[self._lookup(version_id)]
848
894
@deprecated_method(zero_eight)
849
895
def numversions(self):
930
976
if not other.versions():
931
977
return # nothing to update, easy
934
for version_id in version_ids:
935
if not other.has_version(version_id) and not ignore_missing:
936
raise RevisionNotPresent(version_id, self._weave_name)
938
version_ids = other.versions()
980
# versions is never none, InterWeave checks this.
940
983
# two loops so that we do not change ourselves before verifying it
1022
1065
@deprecated_method(zero_eight)
1023
1066
def reweave(self, other, pb=None, msg=None):
1024
"""reweave has been superceded by plain use of join."""
1067
"""reweave has been superseded by plain use of join."""
1025
1068
return self.join(other, pb, msg)
1027
1070
def _reweave(self, other, pb, msg):
1065
1108
def _add_lines(self, version_id, parents, lines, parent_texts):
1066
1109
"""Add a version and save the weave."""
1110
self.check_not_reserved_id(version_id)
1067
1111
result = super(WeaveFile, self)._add_lines(version_id, parents, lines,
1080
1124
sio = StringIO()
1081
1125
write_weave_v5(self, sio)
1083
transport.put(name + WeaveFile.WEAVE_SUFFIX, sio, self._filemode)
1127
transport.put_file(name + WeaveFile.WEAVE_SUFFIX, sio, self._filemode)
1085
1129
def create_empty(self, name, transport, filemode=None):
1086
1130
return WeaveFile(name, transport, filemode, create=True)
1091
1135
sio = StringIO()
1092
1136
write_weave_v5(self, sio)
1094
self._transport.put(self._weave_name + WeaveFile.WEAVE_SUFFIX,
1138
self._transport.put_file(self._weave_name + WeaveFile.WEAVE_SUFFIX,
1099
1143
def get_suffixes():
1191
1235
from bzrlib.weavefile import read_weave
1193
1237
wf = file(weave_file, 'rb')
1194
w = read_weave(wf, WeaveVersionedFile)
1195
1239
# FIXME: doesn't work on pipes
1196
1240
weave_size = wf.tell()
1312
1356
sys.stdout.writelines(w.get_iter(int(argv[3])))
1314
1358
elif cmd == 'diff':
1315
from difflib import unified_diff
1318
1361
v1, v2 = map(int, argv[3:5])
1319
1362
lines1 = w.get(v1)
1320
1363
lines2 = w.get(v2)
1321
diff_gen = unified_diff(lines1, lines2,
1364
diff_gen = bzrlib.patiencediff.unified_diff(lines1, lines2,
1322
1365
'%s version %d' % (fn, v1),
1323
1366
'%s version %d' % (fn, v2))
1324
1367
sys.stdout.writelines(diff_gen)
1358
1401
print ' '.join(map(str, w._parents[int(argv[3])]))
1360
1403
elif cmd == 'plan-merge':
1404
# replaced by 'bzr weave-plan-merge'
1362
1406
for state, line in w.plan_merge(int(argv[3]), int(argv[4])):
1364
1408
print '%14s | %s' % (state, line),
1366
1409
elif cmd == 'merge':
1410
# replaced by 'bzr weave-merge-text'
1368
1412
p = w.plan_merge(int(argv[3]), int(argv[4]))
1369
1413
sys.stdout.writelines(w.weave_merge(p))
1372
1415
raise ValueError('unknown command %r' % cmd)
1376
def profile_main(argv):
1377
import tempfile, hotshot, hotshot.stats
1379
prof_f = tempfile.NamedTemporaryFile()
1381
prof = hotshot.Profile(prof_f.name)
1383
ret = prof.runcall(main, argv)
1386
stats = hotshot.stats.load(prof_f.name)
1388
stats.sort_stats('cumulative')
1389
## XXX: Might like to write to stderr or the trace file instead but
1390
## print_stats seems hardcoded to stdout
1391
stats.print_stats(20)
1396
def lsprofile_main(argv):
1397
from bzrlib.lsprof import profile
1398
ret,stats = profile(main, argv)
1404
1418
if __name__ == '__main__':
1406
if '--profile' in sys.argv:
1408
args.remove('--profile')
1409
sys.exit(profile_main(args))
1410
elif '--lsprof' in sys.argv:
1412
args.remove('--lsprof')
1413
sys.exit(lsprofile_main(args))
1415
sys.exit(main(sys.argv))
1420
sys.exit(main(sys.argv))
1418
1423
class InterWeave(InterVersionedFile):
1419
1424
"""Optimised code paths for weave to weave operations."""
1421
_matching_file_factory = staticmethod(WeaveFile)
1426
_matching_file_from_factory = staticmethod(WeaveFile)
1427
_matching_file_to_factory = staticmethod(WeaveFile)
1424
1430
def is_compatible(source, target):
1432
1438
def join(self, pb=None, msg=None, version_ids=None, ignore_missing=False):
1433
1439
"""See InterVersionedFile.join."""
1434
if self.target.versions() == []:
1440
version_ids = self._get_source_version_ids(version_ids, ignore_missing)
1441
if self.target.versions() == [] and version_ids is None:
1436
1442
self.target._copy_weave_content(self.source)