1
1
# Copyright (C) 2004-2006 by Canonical Ltd
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
5
5
# the Free Software Foundation; either version 2 of the License, or
6
6
# (at your option) any later version.
8
8
# This program is distributed in the hope that it will be useful,
9
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
11
# GNU General Public License for more details.
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
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
from StringIO import StringIO
17
from cStringIO import StringIO
22
from bzrlib import inventory, treebuilder
19
23
from bzrlib.builtins import merge
20
24
from bzrlib.bzrdir import BzrDir
21
25
from bzrlib.bundle.apply_bundle import install_bundle, merge_bundle
22
from bzrlib.bundle.read_bundle import BundleTree, BundleReader
23
from bzrlib.bundle.serializer import write_bundle
26
from bzrlib.bundle.bundle_data import BundleTree
27
from bzrlib.bundle.serializer import write_bundle, read_bundle
28
from bzrlib.branch import Branch
24
29
from bzrlib.diff import internal_diff
25
from bzrlib.errors import BzrError, TestamentMismatch, NotABundle
30
from bzrlib.errors import (BzrError, TestamentMismatch, NotABundle, BadBundle,
26
32
from bzrlib.merge import Merge3Merger
27
33
from bzrlib.osutils import has_symlinks, sha_file
28
from bzrlib.tests import TestCaseInTempDir, TestCase, TestSkipped
34
from bzrlib.tests import (TestCaseInTempDir, TestCaseWithTransport,
35
TestCase, TestSkipped)
29
36
from bzrlib.transform import TreeTransform
30
37
from bzrlib.workingtree import WorkingTree
33
40
class MockTree(object):
34
41
def __init__(self):
35
from bzrlib.inventory import RootEntry, ROOT_ID
42
from bzrlib.inventory import InventoryDirectory, ROOT_ID
36
43
object.__init__(self)
37
44
self.paths = {ROOT_ID: ""}
38
45
self.ids = {"": ROOT_ID}
40
self.root = RootEntry(ROOT_ID)
47
self.root = InventoryDirectory(ROOT_ID, '', None)
42
49
inventory = property(lambda x:x)
286
291
def test_iteration(self):
287
292
"""Ensure that iteration through ids works properly"""
288
293
btree = self.make_tree_1()[0]
289
self.assertEqual(self.sorted_ids(btree), ['a', 'b', 'c', 'd'])
294
self.assertEqual(self.sorted_ids(btree),
295
[inventory.ROOT_ID, 'a', 'b', 'c', 'd'])
290
296
btree.note_deletion("grandparent/parent/file")
291
297
btree.note_id("e", "grandparent/alt_parent/fool", kind="directory")
292
298
btree.note_last_changed("grandparent/alt_parent/fool",
293
299
"revisionidiguess")
294
self.assertEqual(self.sorted_ids(btree), ['a', 'b', 'd', 'e'])
297
class CSetTester(TestCaseInTempDir):
299
def get_valid_bundle(self, base_rev_id, rev_id, checkout_dir=None,
301
"""Create a bundle from base_rev_id -> rev_id in built-in branch.
302
Make sure that the text generated is valid, and that it
303
can be applied against the base, and generate the same information.
305
:return: The in-memory bundle
307
from cStringIO import StringIO
300
self.assertEqual(self.sorted_ids(btree),
301
[inventory.ROOT_ID, 'a', 'b', 'd', 'e'])
304
class BundleTester(TestCaseWithTransport):
306
def create_bundle_text(self, base_rev_id, rev_id):
309
307
bundle_txt = StringIO()
310
308
rev_ids = write_bundle(self.b1.repository, rev_id, base_rev_id,
312
310
bundle_txt.seek(0)
313
311
self.assertEqual(bundle_txt.readline(),
314
'# Bazaar revision bundle v0.7\n')
312
'# Bazaar revision bundle v0.8\n')
315
313
self.assertEqual(bundle_txt.readline(), '#\n')
317
315
rev = self.b1.repository.get_revision(rev_id)
350
359
:return: The in-memory bundle
352
from cStringIO import StringIO
354
bundle_txt = StringIO()
355
rev_ids = write_bundle(self.b1.repository, rev_id, base_rev_id,
358
open(',,bundle', 'wb').write(bundle_txt.getvalue())
361
bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
360
362
new_text = bundle_txt.getvalue().replace('executable:no',
361
363
'executable:yes')
362
364
bundle_txt = StringIO(new_text)
363
bundle = BundleReader(bundle_txt)
365
bundle = read_bundle(bundle_txt)
364
366
self.valid_apply_bundle(base_rev_id, bundle)
367
369
def test_non_bundle(self):
368
self.assertRaises(NotABundle, BundleReader, StringIO('#!/bin/sh\n'))
370
self.assertRaises(NotABundle, read_bundle, StringIO('#!/bin/sh\n'))
372
def test_malformed(self):
373
self.assertRaises(BadBundle, read_bundle,
374
StringIO('# Bazaar revision bundle v'))
376
def test_crlf_bundle(self):
378
read_bundle(StringIO('# Bazaar revision bundle v0.8\r\n'))
380
# It is currently permitted for bundles with crlf line endings to
381
# make read_bundle raise a BadBundle, but this should be fixed.
382
# Anything else, especially NotABundle, is an error.
370
385
def get_checkout(self, rev_id, checkout_dir=None):
371
386
"""Get a new tree, with the specified revision in it.
373
from bzrlib.branch import Branch
376
389
if checkout_dir is None:
377
390
checkout_dir = tempfile.mkdtemp(prefix='test-branch-', dir='.')
380
392
if not os.path.exists(checkout_dir):
381
393
os.mkdir(checkout_dir)
382
394
tree = BzrDir.create_standalone_workingtree(checkout_dir)
402
422
rh = self.b1.revision_history()
403
423
tree.branch.set_revision_history(rh[:rh.index(rev_id)+1])
425
delta = tree.changes_from(self.b1.repository.revision_tree(rev_id))
426
self.assertFalse(delta.has_changed(),
427
'Working tree has modifications')
407
def valid_apply_bundle(self, base_rev_id, reader, checkout_dir=None):
430
def valid_apply_bundle(self, base_rev_id, info, checkout_dir=None):
408
431
"""Get the base revision, apply the changes, and make
409
432
sure everything matches the builtin branch.
411
434
to_tree = self.get_checkout(base_rev_id, checkout_dir=checkout_dir)
435
original_parents = to_tree.get_parent_ids()
412
436
repository = to_tree.branch.repository
437
original_parents = to_tree.get_parent_ids()
413
438
self.assertIs(repository.has_revision(base_rev_id), True)
415
439
for rev in info.real_revisions:
416
440
self.assert_(not repository.has_revision(rev.revision_id),
417
441
'Revision {%s} present before applying bundle'
418
442
% rev.revision_id)
419
merge_bundle(reader, to_tree, True, Merge3Merger, False, False)
443
merge_bundle(info, to_tree, True, Merge3Merger, False, False)
421
445
for rev in info.real_revisions:
422
446
self.assert_(repository.has_revision(rev.revision_id),
538
559
open('b1/sub/dir/WithCaps.txt', 'ab').write('\nAdding some text\n')
539
open('b1/sub/dir/trailing space ', 'ab').write('\nAdding some\nDOS format lines\n')
560
open('b1/sub/dir/ pre space', 'ab').write('\r\nAdding some\r\nDOS format lines\r\n')
540
561
open('b1/sub/dir/nolastnewline.txt', 'ab').write('\n')
541
self.tree1.rename_one('sub/dir/trailing space ',
542
'sub/ start and end space ')
562
self.tree1.rename_one('sub/dir/ pre space',
543
564
self.tree1.commit('Modified files', rev_id='a@cset-0-5')
544
565
bundle = self.get_valid_bundle('a@cset-0-4', 'a@cset-0-5')
546
# Handle international characters
548
f = open(u'b1/with Dod\xe9', 'wb')
549
except UnicodeEncodeError:
550
raise TestSkipped("Filesystem doesn't support unicode")
552
u'With international man of mystery\n'
553
u'William Dod\xe9\n').encode('utf-8'))
554
self.tree1.add([u'with Dod\xe9'])
555
# BUG: (sort of) You must set verbose=False, so that python doesn't try
556
# and print the name of William Dode as part of the commit
557
self.tree1.commit(u'i18n commit from William Dod\xe9',
558
rev_id='a@cset-0-6', committer=u'William Dod\xe9',
560
bundle = self.get_valid_bundle('a@cset-0-5', 'a@cset-0-6')
561
567
self.tree1.rename_one('sub/dir/WithCaps.txt', 'temp')
562
568
self.tree1.rename_one('with space.txt', 'WithCaps.txt')
563
569
self.tree1.rename_one('temp', 'with space.txt')
564
self.tree1.commit(u'swap filenames', rev_id='a@cset-0-7',
570
self.tree1.commit(u'swap filenames', rev_id='a@cset-0-6',
572
bundle = self.get_valid_bundle('a@cset-0-5', 'a@cset-0-6')
573
other = self.get_checkout('a@cset-0-5')
574
other.rename_one('sub/dir/nolastnewline.txt', 'sub/nolastnewline.txt')
575
other.commit('rename file', rev_id='a@cset-0-6b')
576
merge([other.basedir, -1], [None, None], this_dir=self.tree1.basedir)
577
self.tree1.commit(u'Merge', rev_id='a@cset-0-7',
566
579
bundle = self.get_valid_bundle('a@cset-0-6', 'a@cset-0-7')
567
other = self.get_checkout('a@cset-0-6')
568
other.rename_one('sub/dir/nolastnewline.txt', 'sub/nolastnewline.txt')
569
other.commit('rename file', rev_id='a@cset-0-7b')
570
merge([other.basedir, -1], [None, None], this_dir=self.tree1.basedir)
571
self.tree1.commit(u'Merge', rev_id='a@cset-0-8',
573
bundle = self.get_valid_bundle('a@cset-0-7', 'a@cset-0-8')
575
581
def test_symlink_bundle(self):
576
582
if not has_symlinks():
608
614
self.tree1 = BzrDir.create_standalone_workingtree('b1')
609
615
self.b1 = self.tree1.branch
610
616
tt = TreeTransform(self.tree1)
611
tt.new_file('file', tt.root, '\x00\xff', 'binary-1')
612
tt.new_file('file2', tt.root, '\x00\xff', 'binary-2')
619
tt.new_file('file', tt.root, '\x00\n\x00\r\x01\n\x02\r\xff', 'binary-1')
620
tt.new_file('file2', tt.root, '\x01\n\x02\r\x03\n\x04\r\xff', 'binary-2')
614
622
self.tree1.commit('add binary', rev_id='b@cset-0-1')
615
623
self.get_valid_bundle(None, 'b@cset-0-1')
616
626
tt = TreeTransform(self.tree1)
617
627
trans_id = tt.trans_id_tree_file_id('binary-1')
618
628
tt.delete_contents(trans_id)
620
630
self.tree1.commit('delete binary', rev_id='b@cset-0-2')
621
631
self.get_valid_bundle('b@cset-0-1', 'b@cset-0-2')
622
634
tt = TreeTransform(self.tree1)
623
635
trans_id = tt.trans_id_tree_file_id('binary-2')
624
636
tt.adjust_path('file3', tt.root, trans_id)
625
637
tt.delete_contents(trans_id)
626
tt.create_file('filecontents\x00', trans_id)
638
tt.create_file('file\rcontents\x00\n\x00', trans_id)
628
640
self.tree1.commit('rename and modify binary', rev_id='b@cset-0-3')
629
641
self.get_valid_bundle('b@cset-0-2', 'b@cset-0-3')
630
644
tt = TreeTransform(self.tree1)
631
645
trans_id = tt.trans_id_tree_file_id('binary-2')
632
646
tt.delete_contents(trans_id)
633
tt.create_file('\x00filecontents', trans_id)
647
tt.create_file('\x00file\rcontents', trans_id)
635
649
self.tree1.commit('just modify binary', rev_id='b@cset-0-4')
636
650
self.get_valid_bundle('b@cset-0-3', 'b@cset-0-4')
653
self.get_valid_bundle(None, 'b@cset-0-4')
638
655
def test_last_modified(self):
639
656
self.tree1 = BzrDir.create_standalone_workingtree('b1')
640
657
self.b1 = self.tree1.branch
683
697
self.assertNotContainsRe(bundle_file.getvalue(), 'two')
684
698
self.assertContainsRe(bundle_file.getvalue(), 'one')
685
699
self.assertContainsRe(bundle_file.getvalue(), 'three')
701
def test_unicode_bundle(self):
702
# Handle international characters
705
f = open(u'b1/with Dod\xe9', 'wb')
706
except UnicodeEncodeError:
707
raise TestSkipped("Filesystem doesn't support unicode")
709
self.tree1 = self.make_branch_and_tree('b1')
710
self.b1 = self.tree1.branch
713
u'With international man of mystery\n'
714
u'William Dod\xe9\n').encode('utf-8'))
717
self.tree1.add([u'with Dod\xe9'])
718
self.tree1.commit(u'i18n commit from William Dod\xe9',
719
rev_id='i18n-1', committer=u'William Dod\xe9')
722
bundle = self.get_valid_bundle(None, 'i18n-1')
725
f = open(u'b1/with Dod\xe9', 'wb')
726
f.write(u'Modified \xb5\n'.encode('utf8'))
728
self.tree1.commit(u'modified', rev_id='i18n-2')
730
bundle = self.get_valid_bundle('i18n-1', 'i18n-2')
733
self.tree1.rename_one(u'with Dod\xe9', u'B\xe5gfors')
734
self.tree1.commit(u'renamed, the new i18n man', rev_id='i18n-3',
735
committer=u'Erik B\xe5gfors')
737
bundle = self.get_valid_bundle('i18n-2', 'i18n-3')
740
self.tree1.remove([u'B\xe5gfors'])
741
self.tree1.commit(u'removed', rev_id='i18n-4')
743
bundle = self.get_valid_bundle('i18n-3', 'i18n-4')
746
bundle = self.get_valid_bundle(None, 'i18n-4')
749
def test_whitespace_bundle(self):
750
if sys.platform in ('win32', 'cygwin'):
751
raise TestSkipped('Windows doesn\'t support filenames'
752
' with tabs or trailing spaces')
753
self.tree1 = self.make_branch_and_tree('b1')
754
self.b1 = self.tree1.branch
756
self.build_tree(['b1/trailing space '])
757
self.tree1.add(['trailing space '])
758
# TODO: jam 20060701 Check for handling files with '\t' characters
759
# once we actually support them
762
self.tree1.commit('funky whitespace', rev_id='white-1')
764
bundle = self.get_valid_bundle(None, 'white-1')
767
open('b1/trailing space ', 'ab').write('add some text\n')
768
self.tree1.commit('add text', rev_id='white-2')
770
bundle = self.get_valid_bundle('white-1', 'white-2')
773
self.tree1.rename_one('trailing space ', ' start and end space ')
774
self.tree1.commit('rename', rev_id='white-3')
776
bundle = self.get_valid_bundle('white-2', 'white-3')
779
self.tree1.remove([' start and end space '])
780
self.tree1.commit('removed', rev_id='white-4')
782
bundle = self.get_valid_bundle('white-3', 'white-4')
784
# Now test a complet roll-up
785
bundle = self.get_valid_bundle(None, 'white-4')
787
def test_alt_timezone_bundle(self):
788
self.tree1 = self.make_branch_and_memory_tree('b1')
789
self.b1 = self.tree1.branch
790
builder = treebuilder.TreeBuilder()
792
self.tree1.lock_write()
793
builder.start_tree(self.tree1)
794
builder.build(['newfile'])
795
builder.finish_tree()
797
# Asia/Colombo offset = 5 hours 30 minutes
798
self.tree1.commit('non-hour offset timezone', rev_id='tz-1',
799
timezone=19800, timestamp=1152544886.0)
801
bundle = self.get_valid_bundle(None, 'tz-1')
803
rev = bundle.revisions[0]
804
self.assertEqual('Mon 2006-07-10 20:51:26.000000000 +0530', rev.date)
805
self.assertEqual(19800, rev.timezone)
806
self.assertEqual(1152544886.0, rev.timestamp)
809
def test_bundle_root_id(self):
810
self.tree1 = self.make_branch_and_tree('b1')
811
self.b1 = self.tree1.branch
812
self.tree1.commit('message', rev_id='revid1')
813
bundle = self.get_valid_bundle(None, 'revid1')
814
tree = bundle.revision_tree(self.b1.repository, 'revid1')
815
self.assertEqual('revid1', tree.inventory.root.revision)
818
class MungedBundleTester(TestCaseWithTransport):
820
def build_test_bundle(self):
821
wt = self.make_branch_and_tree('b1')
823
self.build_tree(['b1/one'])
825
wt.commit('add one', rev_id='a@cset-0-1')
826
self.build_tree(['b1/two'])
828
wt.commit('add two', rev_id='a@cset-0-2',
829
revprops={'branch-nick':'test'})
831
bundle_txt = StringIO()
832
rev_ids = write_bundle(wt.branch.repository, 'a@cset-0-2',
833
'a@cset-0-1', bundle_txt)
834
self.assertEqual(['a@cset-0-2'], rev_ids)
835
bundle_txt.seek(0, 0)
838
def check_valid(self, bundle):
839
"""Check that after whatever munging, the final object is valid."""
840
self.assertEqual(['a@cset-0-2'],
841
[r.revision_id for r in bundle.real_revisions])
843
def test_extra_whitespace(self):
844
bundle_txt = self.build_test_bundle()
846
# Seek to the end of the file
847
# Adding one extra newline used to give us
848
# TypeError: float() argument must be a string or a number
849
bundle_txt.seek(0, 2)
850
bundle_txt.write('\n')
853
bundle = read_bundle(bundle_txt)
854
self.check_valid(bundle)
856
def test_extra_whitespace_2(self):
857
bundle_txt = self.build_test_bundle()
859
# Seek to the end of the file
860
# Adding two extra newlines used to give us
861
# MalformedPatches: The first line of all patches should be ...
862
bundle_txt.seek(0, 2)
863
bundle_txt.write('\n\n')
866
bundle = read_bundle(bundle_txt)
867
self.check_valid(bundle)
869
def test_missing_trailing_whitespace(self):
870
bundle_txt = self.build_test_bundle()
872
# Remove a trailing newline, it shouldn't kill the parser
873
raw = bundle_txt.getvalue()
874
# The contents of the bundle don't have to be this, but this
875
# test is concerned with the exact case where the serializer
876
# creates a blank line at the end, and fails if that
878
self.assertEqual('\n\n', raw[-2:])
879
bundle_txt = StringIO(raw[:-1])
881
bundle = read_bundle(bundle_txt)
882
self.check_valid(bundle)
884
def test_opening_text(self):
885
bundle_txt = self.build_test_bundle()
887
bundle_txt = StringIO("Some random\nemail comments\n"
888
+ bundle_txt.getvalue())
890
bundle = read_bundle(bundle_txt)
891
self.check_valid(bundle)
893
def test_trailing_text(self):
894
bundle_txt = self.build_test_bundle()
896
bundle_txt = StringIO(bundle_txt.getvalue() +
897
"Some trailing\nrandom\ntext\n")
899
bundle = read_bundle(bundle_txt)
900
self.check_valid(bundle)