13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
17
from cStringIO import StringIO
23
22
from bzrlib import (
31
revision as _mod_revision,
35
from bzrlib.bundle import read_mergeable_from_url
29
from bzrlib.builtins import _merge_helper
30
from bzrlib.bzrdir import BzrDir
36
31
from bzrlib.bundle.apply_bundle import install_bundle, merge_bundle
37
32
from bzrlib.bundle.bundle_data import BundleTree
38
from bzrlib.bzrdir import BzrDir
39
from bzrlib.directory_service import directories
40
from bzrlib.bundle.serializer import write_bundle, read_bundle, v09, v4
33
from bzrlib.bundle.serializer import write_bundle, read_bundle
41
34
from bzrlib.bundle.serializer.v08 import BundleSerializerV08
42
35
from bzrlib.bundle.serializer.v09 import BundleSerializerV09
43
from bzrlib.bundle.serializer.v4 import BundleSerializerV4
36
from bzrlib.bundle.serializer.v10 import BundleSerializerV10
44
37
from bzrlib.branch import Branch
38
from bzrlib.diff import internal_diff
39
from bzrlib.errors import (BzrError, TestamentMismatch, NotABundle, BadBundle,
41
from bzrlib.merge import Merge3Merger
45
42
from bzrlib.repofmt import knitrepo
46
from bzrlib.tests import (
43
from bzrlib.osutils import has_symlinks, sha_file
44
from bzrlib.tests import (TestCaseInTempDir, TestCaseWithTransport,
45
TestCase, TestSkipped)
50
46
from bzrlib.transform import TreeTransform
53
def get_text(vf, key):
54
"""Get the fulltext for a given revision id that is present in the vf"""
55
stream = vf.get_record_stream([key], 'unordered', True)
56
record = stream.next()
57
return record.get_bytes_as('fulltext')
60
def get_inventory_text(repo, revision_id):
61
"""Get the fulltext for the inventory at revision id"""
64
return get_text(repo.inventories, (revision_id,))
47
from bzrlib.workingtree import WorkingTree
69
50
class MockTree(object):
665
637
bundle = self.get_valid_bundle('a@cset-0-5', 'a@cset-0-6')
666
638
other = self.get_checkout('a@cset-0-5')
667
tree1_inv = get_inventory_text(self.tree1.branch.repository,
669
tree2_inv = get_inventory_text(other.branch.repository,
639
tree1_inv = self.tree1.branch.repository.get_inventory_xml(
641
tree2_inv = other.branch.repository.get_inventory_xml('a@cset-0-5')
671
642
self.assertEqualDiff(tree1_inv, tree2_inv)
672
643
other.rename_one('sub/dir/nolastnewline.txt', 'sub/nolastnewline.txt')
673
644
other.commit('rename file', rev_id='a@cset-0-6b')
674
self.tree1.merge_from_branch(other.branch)
645
_merge_helper([other.basedir, -1], [None, None],
646
this_dir=self.tree1.basedir)
675
647
self.tree1.commit(u'Merge', rev_id='a@cset-0-7',
677
649
bundle = self.get_valid_bundle('a@cset-0-6', 'a@cset-0-7')
679
def _test_symlink_bundle(self, link_name, link_target, new_link_target):
682
self.requireFeature(tests.SymlinkFeature)
651
def test_symlink_bundle(self):
652
if not has_symlinks():
653
raise TestSkipped("No symlink support")
683
654
self.tree1 = self.make_branch_and_tree('b1')
684
655
self.b1 = self.tree1.branch
686
656
tt = TreeTransform(self.tree1)
687
tt.new_symlink(link_name, tt.root, link_target, link_id)
657
tt.new_symlink('link', tt.root, 'bar/foo', 'link-1')
689
659
self.tree1.commit('add symlink', rev_id='l@cset-0-1')
690
bundle = self.get_valid_bundle('null:', 'l@cset-0-1')
691
if getattr(bundle ,'revision_tree', None) is not None:
692
# Not all bundle formats supports revision_tree
693
bund_tree = bundle.revision_tree(self.b1.repository, 'l@cset-0-1')
694
self.assertEqual(link_target, bund_tree.get_symlink_target(link_id))
660
self.get_valid_bundle(None, 'l@cset-0-1')
696
661
tt = TreeTransform(self.tree1)
697
trans_id = tt.trans_id_tree_file_id(link_id)
662
trans_id = tt.trans_id_tree_file_id('link-1')
698
663
tt.adjust_path('link2', tt.root, trans_id)
699
664
tt.delete_contents(trans_id)
700
tt.create_symlink(new_link_target, trans_id)
665
tt.create_symlink('mars', trans_id)
702
667
self.tree1.commit('rename and change symlink', rev_id='l@cset-0-2')
703
bundle = self.get_valid_bundle('l@cset-0-1', 'l@cset-0-2')
704
if getattr(bundle ,'revision_tree', None) is not None:
705
# Not all bundle formats supports revision_tree
706
bund_tree = bundle.revision_tree(self.b1.repository, 'l@cset-0-2')
707
self.assertEqual(new_link_target,
708
bund_tree.get_symlink_target(link_id))
668
self.get_valid_bundle('l@cset-0-1', 'l@cset-0-2')
710
669
tt = TreeTransform(self.tree1)
711
trans_id = tt.trans_id_tree_file_id(link_id)
670
trans_id = tt.trans_id_tree_file_id('link-1')
712
671
tt.delete_contents(trans_id)
713
672
tt.create_symlink('jupiter', trans_id)
715
674
self.tree1.commit('just change symlink target', rev_id='l@cset-0-3')
716
bundle = self.get_valid_bundle('l@cset-0-2', 'l@cset-0-3')
675
self.get_valid_bundle('l@cset-0-2', 'l@cset-0-3')
718
676
tt = TreeTransform(self.tree1)
719
trans_id = tt.trans_id_tree_file_id(link_id)
677
trans_id = tt.trans_id_tree_file_id('link-1')
720
678
tt.delete_contents(trans_id)
722
680
self.tree1.commit('Delete symlink', rev_id='l@cset-0-4')
723
bundle = self.get_valid_bundle('l@cset-0-3', 'l@cset-0-4')
725
def test_symlink_bundle(self):
726
self._test_symlink_bundle('link', 'bar/foo', 'mars')
728
def test_unicode_symlink_bundle(self):
729
self.requireFeature(tests.UnicodeFilenameFeature)
730
self._test_symlink_bundle(u'\N{Euro Sign}link',
731
u'bar/\N{Euro Sign}foo',
732
u'mars\N{Euro Sign}')
681
self.get_valid_bundle('l@cset-0-3', 'l@cset-0-4')
734
683
def test_binary_bundle(self):
735
684
self.tree1 = self.make_branch_and_tree('b1')
736
685
self.b1 = self.tree1.branch
737
686
tt = TreeTransform(self.tree1)
740
689
tt.new_file('file', tt.root, '\x00\n\x00\r\x01\n\x02\r\xff', 'binary-1')
741
tt.new_file('file2', tt.root, '\x01\n\x02\r\x03\n\x04\r\xff',
690
tt.new_file('file2', tt.root, '\x01\n\x02\r\x03\n\x04\r\xff', 'binary-2')
744
692
self.tree1.commit('add binary', rev_id='b@cset-0-1')
745
self.get_valid_bundle('null:', 'b@cset-0-1')
693
self.get_valid_bundle(None, 'b@cset-0-1')
748
696
tt = TreeTransform(self.tree1)
846
785
u'William Dod\xe9\n').encode('utf-8'))
849
self.tree1.add([u'with Dod\N{Euro Sign}'], ['withdod-id'])
788
self.tree1.add([u'with Dod\xe9'], ['withdod-id'])
850
789
self.tree1.commit(u'i18n commit from William Dod\xe9',
851
790
rev_id='i18n-1', committer=u'William Dod\xe9')
792
if sys.platform == 'darwin':
793
# On Mac the '\xe9' gets changed to 'e\u0301'
794
self.assertEqual([u'.bzr', u'with Dode\u0301'],
795
sorted(os.listdir(u'b1')))
796
delta = self.tree1.changes_from(self.tree1.basis_tree())
797
self.assertEqual([(u'with Dod\xe9', 'withdod-id', 'file')],
799
self.knownFailure("Mac OSX doesn't preserve unicode"
800
" combining characters.")
854
bundle = self.get_valid_bundle('null:', 'i18n-1')
803
bundle = self.get_valid_bundle(None, 'i18n-1')
857
f = open(u'b1/with Dod\N{Euro Sign}', 'wb')
806
f = open(u'b1/with Dod\xe9', 'wb')
858
807
f.write(u'Modified \xb5\n'.encode('utf8'))
860
809
self.tree1.commit(u'modified', rev_id='i18n-2')
862
811
bundle = self.get_valid_bundle('i18n-1', 'i18n-2')
865
self.tree1.rename_one(u'with Dod\N{Euro Sign}', u'B\N{Euro Sign}gfors')
814
self.tree1.rename_one(u'with Dod\xe9', u'B\xe5gfors')
866
815
self.tree1.commit(u'renamed, the new i18n man', rev_id='i18n-3',
867
816
committer=u'Erik B\xe5gfors')
869
818
bundle = self.get_valid_bundle('i18n-2', 'i18n-3')
872
self.tree1.remove([u'B\N{Euro Sign}gfors'])
821
self.tree1.remove([u'B\xe5gfors'])
873
822
self.tree1.commit(u'removed', rev_id='i18n-4')
875
824
bundle = self.get_valid_bundle('i18n-3', 'i18n-4')
878
bundle = self.get_valid_bundle('null:', 'i18n-4')
827
bundle = self.get_valid_bundle(None, 'i18n-4')
881
830
def test_whitespace_bundle(self):
882
831
if sys.platform in ('win32', 'cygwin'):
883
raise tests.TestSkipped('Windows doesn\'t support filenames'
884
' with tabs or trailing spaces')
832
raise TestSkipped('Windows doesn\'t support filenames'
833
' with tabs or trailing spaces')
885
834
self.tree1 = self.make_branch_and_tree('b1')
886
835
self.b1 = self.tree1.branch
965
914
tree.add([''], ['TREE_ROOT'])
966
915
tree.commit('One', revprops={'one':'two', 'empty':''}, rev_id='rev1')
967
916
self.b1 = tree.branch
968
bundle_sio, revision_ids = self.create_bundle_text('null:', 'rev1')
969
bundle = read_bundle(bundle_sio)
970
revision_info = bundle.revisions[0]
971
self.assertEqual('rev1', revision_info.revision_id)
972
rev = revision_info.as_revision()
973
self.assertEqual({'branch-nick':'tree', 'empty':'', 'one':'two'},
976
def test_bundle_sorted_properties(self):
977
"""For stability the writer should write properties in sorted order."""
978
tree = self.make_branch_and_memory_tree('tree')
980
self.addCleanup(tree.unlock)
982
tree.add([''], ['TREE_ROOT'])
983
tree.commit('One', rev_id='rev1',
984
revprops={'a':'4', 'b':'3', 'c':'2', 'd':'1'})
985
self.b1 = tree.branch
986
bundle_sio, revision_ids = self.create_bundle_text('null:', 'rev1')
987
bundle = read_bundle(bundle_sio)
988
revision_info = bundle.revisions[0]
989
self.assertEqual('rev1', revision_info.revision_id)
990
rev = revision_info.as_revision()
991
self.assertEqual({'branch-nick':'tree', 'a':'4', 'b':'3', 'c':'2',
992
'd':'1'}, rev.properties)
994
def test_bundle_unicode_properties(self):
995
"""We should be able to round trip a non-ascii property."""
996
tree = self.make_branch_and_memory_tree('tree')
998
self.addCleanup(tree.unlock)
1000
tree.add([''], ['TREE_ROOT'])
1001
# Revisions themselves do not require anything about revision property
1002
# keys, other than that they are a basestring, and do not contain
1004
# However, Testaments assert than they are str(), and thus should not
1006
tree.commit('One', rev_id='rev1',
1007
revprops={'omega':u'\u03a9', 'alpha':u'\u03b1'})
1008
self.b1 = tree.branch
1009
bundle_sio, revision_ids = self.create_bundle_text('null:', 'rev1')
1010
bundle = read_bundle(bundle_sio)
1011
revision_info = bundle.revisions[0]
1012
self.assertEqual('rev1', revision_info.revision_id)
1013
rev = revision_info.as_revision()
1014
self.assertEqual({'branch-nick':'tree', 'omega':u'\u03a9',
1015
'alpha':u'\u03b1'}, rev.properties)
1017
def test_bundle_with_ghosts(self):
1018
tree = self.make_branch_and_tree('tree')
1019
self.b1 = tree.branch
1020
self.build_tree_contents([('tree/file', 'content1')])
1023
self.build_tree_contents([('tree/file', 'content2')])
1024
tree.add_parent_tree_id('ghost')
1025
tree.commit('rev2', rev_id='rev2')
1026
bundle = self.get_valid_bundle('null:', 'rev2')
1028
def make_simple_tree(self, format=None):
1029
tree = self.make_branch_and_tree('b1', format=format)
1030
self.b1 = tree.branch
1031
self.build_tree(['b1/file'])
1035
def test_across_serializers(self):
1036
tree = self.make_simple_tree('knit')
1037
tree.commit('hello', rev_id='rev1')
1038
tree.commit('hello', rev_id='rev2')
1039
bundle = read_bundle(self.create_bundle_text('null:', 'rev2')[0])
1040
repo = self.make_repository('repo', format='dirstate-with-subtree')
1041
bundle.install_revisions(repo)
1042
inv_text = repo._get_inventory_xml('rev2')
1043
self.assertNotContainsRe(inv_text, 'format="5"')
1044
self.assertContainsRe(inv_text, 'format="7"')
1046
def make_repo_with_installed_revisions(self):
1047
tree = self.make_simple_tree('knit')
1048
tree.commit('hello', rev_id='rev1')
1049
tree.commit('hello', rev_id='rev2')
1050
bundle = read_bundle(self.create_bundle_text('null:', 'rev2')[0])
1051
repo = self.make_repository('repo', format='dirstate-with-subtree')
1052
bundle.install_revisions(repo)
1055
def test_across_models(self):
1056
repo = self.make_repo_with_installed_revisions()
1057
inv = repo.get_inventory('rev2')
1058
self.assertEqual('rev2', inv.root.revision)
1059
root_id = inv.root.file_id
1061
self.addCleanup(repo.unlock)
1062
self.assertEqual({(root_id, 'rev1'):(),
1063
(root_id, 'rev2'):((root_id, 'rev1'),)},
1064
repo.texts.get_parent_map([(root_id, 'rev1'), (root_id, 'rev2')]))
1066
def test_inv_hash_across_serializers(self):
1067
repo = self.make_repo_with_installed_revisions()
1068
recorded_inv_sha1 = repo.get_revision('rev2').inventory_sha1
1069
xml = repo._get_inventory_xml('rev2')
1070
self.assertEqual(osutils.sha_string(xml), recorded_inv_sha1)
1072
def test_across_models_incompatible(self):
1073
tree = self.make_simple_tree('dirstate-with-subtree')
1074
tree.commit('hello', rev_id='rev1')
1075
tree.commit('hello', rev_id='rev2')
1077
bundle = read_bundle(self.create_bundle_text('null:', 'rev1')[0])
1078
except errors.IncompatibleBundleFormat:
1079
raise tests.TestSkipped("Format 0.8 doesn't work with knit3")
1080
repo = self.make_repository('repo', format='knit')
1081
bundle.install_revisions(repo)
1083
bundle = read_bundle(self.create_bundle_text('null:', 'rev2')[0])
1084
self.assertRaises(errors.IncompatibleRevision,
1085
bundle.install_revisions, repo)
1087
def test_get_merge_request(self):
1088
tree = self.make_simple_tree()
1089
tree.commit('hello', rev_id='rev1')
1090
tree.commit('hello', rev_id='rev2')
1091
bundle = read_bundle(self.create_bundle_text('null:', 'rev1')[0])
1092
result = bundle.get_merge_request(tree.branch.repository)
1093
self.assertEqual((None, 'rev1', 'inapplicable'), result)
1095
def test_with_subtree(self):
1096
tree = self.make_branch_and_tree('tree',
1097
format='dirstate-with-subtree')
1098
self.b1 = tree.branch
1099
subtree = self.make_branch_and_tree('tree/subtree',
1100
format='dirstate-with-subtree')
1102
tree.commit('hello', rev_id='rev1')
1104
bundle = read_bundle(self.create_bundle_text('null:', 'rev1')[0])
1105
except errors.IncompatibleBundleFormat:
1106
raise tests.TestSkipped("Format 0.8 doesn't work with knit3")
1107
if isinstance(bundle, v09.BundleInfo09):
1108
raise tests.TestSkipped("Format 0.9 doesn't work with subtrees")
1109
repo = self.make_repository('repo', format='knit')
1110
self.assertRaises(errors.IncompatibleRevision,
1111
bundle.install_revisions, repo)
1112
repo2 = self.make_repository('repo2', format='dirstate-with-subtree')
1113
bundle.install_revisions(repo2)
1115
def test_revision_id_with_slash(self):
1116
self.tree1 = self.make_branch_and_tree('tree')
1117
self.b1 = self.tree1.branch
1119
self.tree1.commit('Revision/id/with/slashes', rev_id='rev/id')
1121
raise tests.TestSkipped(
1122
"Repository doesn't support revision ids with slashes")
1123
bundle = self.get_valid_bundle('null:', 'rev/id')
1125
def test_skip_file(self):
1126
"""Make sure we don't accidentally write to the wrong versionedfile"""
1127
self.tree1 = self.make_branch_and_tree('tree')
1128
self.b1 = self.tree1.branch
1129
# rev1 is not present in bundle, done by fetch
1130
self.build_tree_contents([('tree/file2', 'contents1')])
1131
self.tree1.add('file2', 'file2-id')
1132
self.tree1.commit('rev1', rev_id='reva')
1133
self.build_tree_contents([('tree/file3', 'contents2')])
1134
# rev2 is present in bundle, and done by fetch
1135
# having file1 in the bunle causes file1's versionedfile to be opened.
1136
self.tree1.add('file3', 'file3-id')
1137
self.tree1.commit('rev2')
1138
# Updating file2 should not cause an attempt to add to file1's vf
1139
target = self.tree1.bzrdir.sprout('target').open_workingtree()
1140
self.build_tree_contents([('tree/file2', 'contents3')])
1141
self.tree1.commit('rev3', rev_id='rev3')
1142
bundle = self.get_valid_bundle('reva', 'rev3')
1143
if getattr(bundle, 'get_bundle_reader', None) is None:
1144
raise tests.TestSkipped('Bundle format cannot provide reader')
1145
# be sure that file1 comes before file2
1146
for b, m, k, r, f in bundle.get_bundle_reader().iter_records():
1149
self.assertNotEqual(f, 'file2-id')
1150
bundle.install_revisions(target.branch.repository)
1153
class V08BundleTester(BundleTester, tests.TestCaseWithTransport):
1157
def test_bundle_empty_property(self):
1158
"""Test serializing revision properties with an empty value."""
1159
tree = self.make_branch_and_memory_tree('tree')
1161
self.addCleanup(tree.unlock)
1162
tree.add([''], ['TREE_ROOT'])
1163
tree.commit('One', revprops={'one':'two', 'empty':''}, rev_id='rev1')
1164
self.b1 = tree.branch
1165
bundle_sio, revision_ids = self.create_bundle_text('null:', 'rev1')
917
bundle_sio, revision_ids = self.create_bundle_text(None, 'rev1')
1166
918
self.assertContainsRe(bundle_sio.getvalue(),
1167
919
'# properties:\n'
1168
920
'# branch-nick: tree\n'
1313
1062
branch_rev = repository.get_revision(bundle_rev.revision_id)
1314
1063
for a in ('inventory_sha1', 'revision_id', 'parent_ids',
1315
'timestamp', 'timezone', 'message', 'committer',
1064
'timestamp', 'timezone', 'message', 'committer',
1316
1065
'parent_ids', 'properties'):
1317
self.assertEqual(getattr(branch_rev, a),
1066
self.assertEqual(getattr(branch_rev, a),
1318
1067
getattr(bundle_rev, a))
1319
self.assertEqual(len(branch_rev.parent_ids),
1068
self.assertEqual(len(branch_rev.parent_ids),
1320
1069
len(bundle_rev.parent_ids))
1321
self.assertEqual(set(rev_ids),
1322
set([r.revision_id for r in bundle.real_revisions]))
1070
self.assertEqual(rev_ids,
1071
[r.revision_id for r in bundle.real_revisions])
1323
1072
self.valid_apply_bundle(base_rev_id, bundle,
1324
1073
checkout_dir=checkout_dir)
1328
def get_invalid_bundle(self, base_rev_id, rev_id):
1329
"""Create a bundle from base_rev_id -> rev_id in built-in branch.
1330
Munge the text so that it's invalid.
1332
:return: The in-memory bundle
1334
from bzrlib.bundle import serializer
1335
bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
1336
new_text = self.get_raw(StringIO(''.join(bundle_txt)))
1337
new_text = new_text.replace('<file file_id="exe-1"',
1338
'<file executable="y" file_id="exe-1"')
1339
new_text = new_text.replace('B260', 'B275')
1340
bundle_txt = StringIO()
1341
bundle_txt.write(serializer._get_bundle_header('4'))
1342
bundle_txt.write('\n')
1343
bundle_txt.write(new_text.encode('bz2'))
1345
bundle = read_bundle(bundle_txt)
1346
self.valid_apply_bundle(base_rev_id, bundle)
1349
1077
def create_bundle_text(self, base_rev_id, rev_id):
1350
1078
bundle_txt = StringIO()
1351
rev_ids = write_bundle(self.b1.repository, rev_id, base_rev_id,
1079
rev_ids = write_bundle(self.b1.repository, rev_id, base_rev_id,
1352
1080
bundle_txt, format=self.format)
1353
1081
bundle_txt.seek(0)
1354
self.assertEqual(bundle_txt.readline(),
1082
self.assertEqual(bundle_txt.readline(),
1355
1083
'# Bazaar revision bundle v%s\n' % self.format)
1356
1084
self.assertEqual(bundle_txt.readline(), '#\n')
1357
1086
rev = self.b1.repository.get_revision(rev_id)
1087
self.assertEqual(bundle_txt.readline().decode('utf-8'),
1088
u'bzr pack format 1\n')
1090
open(',,bundle', 'wb').write(bundle_txt.getvalue())
1358
1091
bundle_txt.seek(0)
1359
1092
return bundle_txt, rev_ids
1361
def get_bundle_tree(self, bundle, revision_id):
1362
repository = self.make_repository('repo')
1363
bundle.install_revisions(repository)
1364
return repository.revision_tree(revision_id)
1366
1094
def test_creation(self):
1367
1095
tree = self.make_branch_and_tree('tree')
1368
1096
self.build_tree_contents([('tree/file', 'contents1\nstatic\n')])
1371
1099
self.build_tree_contents([('tree/file', 'contents2\nstatic\n')])
1372
1100
tree.commit('changed file', rev_id='rev2')
1374
serializer = BundleSerializerV4('1.0')
1102
serializer = BundleSerializerV10('1.0')
1375
1103
serializer.write(tree.branch.repository, ['rev1', 'rev2'], {}, s)
1377
1105
tree2 = self.make_branch_and_tree('target')
1378
1106
target_repo = tree2.branch.repository
1379
1107
install_bundle(target_repo, serializer.read(s))
1380
target_repo.lock_read()
1381
self.addCleanup(target_repo.unlock)
1382
# Turn the 'iterators_of_bytes' back into simple strings for comparison
1383
repo_texts = dict((i, ''.join(content)) for i, content
1384
in target_repo.iter_files_bytes(
1385
[('fileid-2', 'rev1', '1'),
1386
('fileid-2', 'rev2', '2')]))
1387
self.assertEqual({'1':'contents1\nstatic\n',
1388
'2':'contents2\nstatic\n'},
1108
vf = target_repo.weave_store.get_weave('fileid-2',
1109
target_repo.get_transaction())
1110
self.assertEqual('contents1\nstatic\n', vf.get_text('rev1'))
1111
self.assertEqual('contents2\nstatic\n', vf.get_text('rev2'))
1390
1112
rtree = target_repo.revision_tree('rev2')
1391
inventory_vf = target_repo.inventories
1392
# If the inventory store has a graph, it must match the revision graph.
1394
[inventory_vf.get_parent_map([('rev2',)])[('rev2',)]],
1395
[None, (('rev1',),)])
1113
inventory_vf = target_repo.get_inventory_weave()
1114
self.assertEqual(['rev1'], inventory_vf.get_parents('rev2'))
1396
1115
self.assertEqual('changed file',
1397
1116
target_repo.get_revision('rev2').message)
1400
def get_raw(bundle_file):
1402
line = bundle_file.readline()
1403
line = bundle_file.readline()
1404
lines = bundle_file.readlines()
1405
return ''.join(lines).decode('bz2')
1407
def test_copy_signatures(self):
1408
tree_a = self.make_branch_and_tree('tree_a')
1410
import bzrlib.commit as commit
1411
oldstrategy = bzrlib.gpg.GPGStrategy
1412
branch = tree_a.branch
1413
repo_a = branch.repository
1414
tree_a.commit("base", allow_pointless=True, rev_id='A')
1415
self.failIf(branch.repository.has_signature_for_revision_id('A'))
1417
from bzrlib.testament import Testament
1418
# monkey patch gpg signing mechanism
1419
bzrlib.gpg.GPGStrategy = bzrlib.gpg.LoopbackGPGStrategy
1420
new_config = test_commit.MustSignConfig(branch)
1421
commit.Commit(config=new_config).commit(message="base",
1422
allow_pointless=True,
1424
working_tree=tree_a)
1426
return bzrlib.gpg.LoopbackGPGStrategy(None).sign(text)
1427
self.assertTrue(repo_a.has_signature_for_revision_id('B'))
1429
bzrlib.gpg.GPGStrategy = oldstrategy
1430
tree_b = self.make_branch_and_tree('tree_b')
1431
repo_b = tree_b.branch.repository
1433
serializer = BundleSerializerV4('4')
1434
serializer.write(tree_a.branch.repository, ['A', 'B'], {}, s)
1436
install_bundle(repo_b, serializer.read(s))
1437
self.assertTrue(repo_b.has_signature_for_revision_id('B'))
1438
self.assertEqual(repo_b.get_signature_text('B'),
1439
repo_a.get_signature_text('B'))
1441
# ensure repeat installs are harmless
1442
install_bundle(repo_b, serializer.read(s))
1445
class V4WeaveBundleTester(V4BundleTester):
1447
def bzrdir_format(self):
1451
class V4_2aBundleTester(V4BundleTester):
1453
def bzrdir_format(self):
1456
def get_invalid_bundle(self, base_rev_id, rev_id):
1457
"""Create a bundle from base_rev_id -> rev_id in built-in branch.
1458
Munge the text so that it's invalid.
1460
:return: The in-memory bundle
1462
from bzrlib.bundle import serializer
1463
bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
1464
new_text = self.get_raw(StringIO(''.join(bundle_txt)))
1465
# We are going to be replacing some text to set the executable bit on a
1466
# file. Make sure the text replacement actually works correctly.
1467
self.assertContainsRe(new_text, '(?m)B244\n\ni 1\n<inventory')
1468
new_text = new_text.replace('<file file_id="exe-1"',
1469
'<file executable="y" file_id="exe-1"')
1470
new_text = new_text.replace('B244', 'B259')
1471
bundle_txt = StringIO()
1472
bundle_txt.write(serializer._get_bundle_header('4'))
1473
bundle_txt.write('\n')
1474
bundle_txt.write(new_text.encode('bz2'))
1476
bundle = read_bundle(bundle_txt)
1477
self.valid_apply_bundle(base_rev_id, bundle)
1480
def make_merged_branch(self):
1481
builder = self.make_branch_builder('source')
1482
builder.start_series()
1483
builder.build_snapshot('a@cset-0-1', None, [
1484
('add', ('', 'root-id', 'directory', None)),
1485
('add', ('file', 'file-id', 'file', 'original content\n')),
1487
builder.build_snapshot('a@cset-0-2a', ['a@cset-0-1'], [
1488
('modify', ('file-id', 'new-content\n')),
1490
builder.build_snapshot('a@cset-0-2b', ['a@cset-0-1'], [
1491
('add', ('other-file', 'file2-id', 'file', 'file2-content\n')),
1493
builder.build_snapshot('a@cset-0-3', ['a@cset-0-2a', 'a@cset-0-2b'], [
1494
('add', ('other-file', 'file2-id', 'file', 'file2-content\n')),
1496
builder.finish_series()
1497
self.b1 = builder.get_branch()
1499
self.addCleanup(self.b1.unlock)
1501
def make_bundle_just_inventories(self, base_revision_id,
1505
writer = v4.BundleWriteOperation(base_revision_id, target_revision_id,
1506
self.b1.repository, sio)
1507
writer.bundle.begin()
1508
writer._add_inventory_mpdiffs_from_serializer(revision_ids)
1513
def test_single_inventory_multiple_parents_as_xml(self):
1514
self.make_merged_branch()
1515
sio = self.make_bundle_just_inventories('a@cset-0-1', 'a@cset-0-3',
1517
reader = v4.BundleReader(sio, stream_input=False)
1518
records = list(reader.iter_records())
1519
self.assertEqual(1, len(records))
1520
(bytes, metadata, repo_kind, revision_id,
1521
file_id) = records[0]
1522
self.assertIs(None, file_id)
1523
self.assertEqual('a@cset-0-3', revision_id)
1524
self.assertEqual('inventory', repo_kind)
1525
self.assertEqual({'parents': ['a@cset-0-2a', 'a@cset-0-2b'],
1526
'sha1': '09c53b0c4de0895e11a2aacc34fef60a6e70865c',
1527
'storage_kind': 'mpdiff',
1529
# We should have an mpdiff that takes some lines from both parents.
1530
self.assertEqualDiff(
1532
'<inventory format="10" revision_id="a@cset-0-3">\n'
1535
'c 1 3 3 2\n', bytes)
1537
def test_single_inv_no_parents_as_xml(self):
1538
self.make_merged_branch()
1539
sio = self.make_bundle_just_inventories('null:', 'a@cset-0-1',
1541
reader = v4.BundleReader(sio, stream_input=False)
1542
records = list(reader.iter_records())
1543
self.assertEqual(1, len(records))
1544
(bytes, metadata, repo_kind, revision_id,
1545
file_id) = records[0]
1546
self.assertIs(None, file_id)
1547
self.assertEqual('a@cset-0-1', revision_id)
1548
self.assertEqual('inventory', repo_kind)
1549
self.assertEqual({'parents': [],
1550
'sha1': 'a13f42b142d544aac9b085c42595d304150e31a2',
1551
'storage_kind': 'mpdiff',
1553
# We should have an mpdiff that takes some lines from both parents.
1554
self.assertEqualDiff(
1556
'<inventory format="10" revision_id="a@cset-0-1">\n'
1557
'<directory file_id="root-id" name=""'
1558
' revision="a@cset-0-1" />\n'
1559
'<file file_id="file-id" name="file" parent_id="root-id"'
1560
' revision="a@cset-0-1"'
1561
' text_sha1="09c2f8647e14e49e922b955c194102070597c2d1"'
1562
' text_size="17" />\n'
1566
def test_multiple_inventories_as_xml(self):
1567
self.make_merged_branch()
1568
sio = self.make_bundle_just_inventories('a@cset-0-1', 'a@cset-0-3',
1569
['a@cset-0-2a', 'a@cset-0-2b', 'a@cset-0-3'])
1570
reader = v4.BundleReader(sio, stream_input=False)
1571
records = list(reader.iter_records())
1572
self.assertEqual(3, len(records))
1573
revision_ids = [rev_id for b, m, k, rev_id, f in records]
1574
self.assertEqual(['a@cset-0-2a', 'a@cset-0-2b', 'a@cset-0-3'],
1576
metadata_2a = records[0][1]
1577
self.assertEqual({'parents': ['a@cset-0-1'],
1578
'sha1': '1e105886d62d510763e22885eec733b66f5f09bf',
1579
'storage_kind': 'mpdiff',
1581
metadata_2b = records[1][1]
1582
self.assertEqual({'parents': ['a@cset-0-1'],
1583
'sha1': 'f03f12574bdb5ed2204c28636c98a8547544ccd8',
1584
'storage_kind': 'mpdiff',
1586
metadata_3 = records[2][1]
1587
self.assertEqual({'parents': ['a@cset-0-2a', 'a@cset-0-2b'],
1588
'sha1': '09c53b0c4de0895e11a2aacc34fef60a6e70865c',
1589
'storage_kind': 'mpdiff',
1591
bytes_2a = records[0][0]
1592
self.assertEqualDiff(
1594
'<inventory format="10" revision_id="a@cset-0-2a">\n'
1598
'<file file_id="file-id" name="file" parent_id="root-id"'
1599
' revision="a@cset-0-2a"'
1600
' text_sha1="50f545ff40e57b6924b1f3174b267ffc4576e9a9"'
1601
' text_size="12" />\n'
1603
'c 0 3 3 1\n', bytes_2a)
1604
bytes_2b = records[1][0]
1605
self.assertEqualDiff(
1607
'<inventory format="10" revision_id="a@cset-0-2b">\n'
1611
'<file file_id="file2-id" name="other-file" parent_id="root-id"'
1612
' revision="a@cset-0-2b"'
1613
' text_sha1="b46c0c8ea1e5ef8e46fc8894bfd4752a88ec939e"'
1614
' text_size="14" />\n'
1616
'c 0 3 4 1\n', bytes_2b)
1617
bytes_3 = records[2][0]
1618
self.assertEqualDiff(
1620
'<inventory format="10" revision_id="a@cset-0-3">\n'
1623
'c 1 3 3 2\n', bytes_3)
1625
def test_creating_bundle_preserves_chk_pages(self):
1626
self.make_merged_branch()
1627
target = self.b1.bzrdir.sprout('target',
1628
revision_id='a@cset-0-2a').open_branch()
1629
bundle_txt, rev_ids = self.create_bundle_text('a@cset-0-2a',
1631
self.assertEqual(['a@cset-0-2b', 'a@cset-0-3'], rev_ids)
1632
bundle = read_bundle(bundle_txt)
1634
self.addCleanup(target.unlock)
1635
install_bundle(target.repository, bundle)
1636
inv1 = self.b1.repository.inventories.get_record_stream([
1637
('a@cset-0-3',)], 'unordered',
1638
True).next().get_bytes_as('fulltext')
1639
inv2 = target.repository.inventories.get_record_stream([
1640
('a@cset-0-3',)], 'unordered',
1641
True).next().get_bytes_as('fulltext')
1642
self.assertEqualDiff(inv1, inv2)
1645
class MungedBundleTester(object):
1118
def test_name_encode(self):
1119
self.assertEqual('revision:rev1',
1120
BundleSerializerV10.encode_name('revision', 'rev1'))
1121
self.assertEqual('file:rev1/file-id-1',
1122
BundleSerializerV10.encode_name('file', 'rev1', 'file-id-1'))
1124
def test_name_decode(self):
1125
self.assertEqual(('revision', 'rev1', None),
1126
BundleSerializerV10.decode_name('revision:rev1'))
1127
self.assertEqual(('file', 'rev1', 'file-id-1'),
1128
BundleSerializerV10.decode_name('file:rev1/file-id-1'))
1131
class MungedBundleTester(TestCaseWithTransport):
1647
1133
def build_test_bundle(self):
1648
1134
wt = self.make_branch_and_tree('b1')
1731
1212
bundle = read_bundle(bundle_txt)
1732
1213
self.check_valid(bundle)
1735
class MungedBundleTesterV4(tests.TestCaseWithTransport, MungedBundleTester):
1740
class TestBundleWriterReader(tests.TestCase):
1742
def test_roundtrip_record(self):
1743
fileobj = StringIO()
1744
writer = v4.BundleWriter(fileobj)
1746
writer.add_info_record(foo='bar')
1747
writer._add_record("Record body", {'parents': ['1', '3'],
1748
'storage_kind':'fulltext'}, 'file', 'revid', 'fileid')
1751
reader = v4.BundleReader(fileobj, stream_input=True)
1752
record_iter = reader.iter_records()
1753
record = record_iter.next()
1754
self.assertEqual((None, {'foo': 'bar', 'storage_kind': 'header'},
1755
'info', None, None), record)
1756
record = record_iter.next()
1757
self.assertEqual(("Record body", {'storage_kind': 'fulltext',
1758
'parents': ['1', '3']}, 'file', 'revid', 'fileid'),
1761
def test_roundtrip_record_memory_hungry(self):
1762
fileobj = StringIO()
1763
writer = v4.BundleWriter(fileobj)
1765
writer.add_info_record(foo='bar')
1766
writer._add_record("Record body", {'parents': ['1', '3'],
1767
'storage_kind':'fulltext'}, 'file', 'revid', 'fileid')
1770
reader = v4.BundleReader(fileobj, stream_input=False)
1771
record_iter = reader.iter_records()
1772
record = record_iter.next()
1773
self.assertEqual((None, {'foo': 'bar', 'storage_kind': 'header'},
1774
'info', None, None), record)
1775
record = record_iter.next()
1776
self.assertEqual(("Record body", {'storage_kind': 'fulltext',
1777
'parents': ['1', '3']}, 'file', 'revid', 'fileid'),
1780
def test_encode_name(self):
1781
self.assertEqual('revision/rev1',
1782
v4.BundleWriter.encode_name('revision', 'rev1'))
1783
self.assertEqual('file/rev//1/file-id-1',
1784
v4.BundleWriter.encode_name('file', 'rev/1', 'file-id-1'))
1785
self.assertEqual('info',
1786
v4.BundleWriter.encode_name('info', None, None))
1788
def test_decode_name(self):
1789
self.assertEqual(('revision', 'rev1', None),
1790
v4.BundleReader.decode_name('revision/rev1'))
1791
self.assertEqual(('file', 'rev/1', 'file-id-1'),
1792
v4.BundleReader.decode_name('file/rev//1/file-id-1'))
1793
self.assertEqual(('info', None, None),
1794
v4.BundleReader.decode_name('info'))
1796
def test_too_many_names(self):
1797
fileobj = StringIO()
1798
writer = v4.BundleWriter(fileobj)
1800
writer.add_info_record(foo='bar')
1801
writer._container.add_bytes_record('blah', ['two', 'names'])
1804
record_iter = v4.BundleReader(fileobj).iter_records()
1805
record = record_iter.next()
1806
self.assertEqual((None, {'foo': 'bar', 'storage_kind': 'header'},
1807
'info', None, None), record)
1808
self.assertRaises(errors.BadBundle, record_iter.next)
1811
class TestReadMergeableFromUrl(tests.TestCaseWithTransport):
1813
def test_read_mergeable_skips_local(self):
1814
"""A local bundle named like the URL should not be read.
1816
out, wt = test_read_bundle.create_bundle_file(self)
1817
class FooService(object):
1818
"""A directory service that always returns source"""
1820
def look_up(self, name, url):
1822
directories.register('foo:', FooService, 'Testing directory service')
1823
self.addCleanup(directories.remove, 'foo:')
1824
self.build_tree_contents([('./foo:bar', out.getvalue())])
1825
self.assertRaises(errors.NotABundle, read_mergeable_from_url,
1828
def test_infinite_redirects_are_not_a_bundle(self):
1829
"""If a URL causes TooManyRedirections then NotABundle is raised.
1831
from bzrlib.tests.blackbox.test_push import RedirectingMemoryServer
1832
server = RedirectingMemoryServer()
1833
self.start_server(server)
1834
url = server.get_url() + 'infinite-loop'
1835
self.assertRaises(errors.NotABundle, read_mergeable_from_url, url)
1837
def test_smart_server_connection_reset(self):
1838
"""If a smart server connection fails during the attempt to read a
1839
bundle, then the ConnectionReset error should be propagated.
1841
# Instantiate a server that will provoke a ConnectionReset
1842
sock_server = _DisconnectingTCPServer()
1843
self.start_server(sock_server)
1844
# We don't really care what the url is since the server will close the
1845
# connection without interpreting it
1846
url = sock_server.get_url()
1847
self.assertRaises(errors.ConnectionReset, read_mergeable_from_url, url)
1850
class _DisconnectingTCPServer(object):
1851
"""A TCP server that immediately closes any connection made to it."""
1853
def start_server(self):
1854
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1855
self.sock.bind(('127.0.0.1', 0))
1857
self.port = self.sock.getsockname()[1]
1858
self.thread = threading.Thread(
1859
name='%s (port %d)' % (self.__class__.__name__, self.port),
1860
target=self.accept_and_close)
1863
def accept_and_close(self):
1864
conn, addr = self.sock.accept()
1865
conn.shutdown(socket.SHUT_RDWR)
1869
return 'bzr://127.0.0.1:%d/' % (self.port,)
1871
def stop_server(self):
1873
# make sure the thread dies by connecting to the listening socket,
1874
# just in case the test failed to do so.
1875
conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1876
conn.connect(self.sock.getsockname())
1878
except socket.error: