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
19
22
from bzrlib.builtins import merge
20
23
from bzrlib.bzrdir import BzrDir
21
24
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
25
from bzrlib.bundle.bundle_data import BundleTree
26
from bzrlib.bundle.serializer import write_bundle, read_bundle
27
from bzrlib.branch import Branch
24
28
from bzrlib.diff import internal_diff
25
from bzrlib.errors import BzrError, TestamentMismatch, NotABundle
29
from bzrlib.delta import compare_trees
30
from bzrlib.errors import BzrError, TestamentMismatch, NotABundle, BadBundle
26
31
from bzrlib.merge import Merge3Merger
27
32
from bzrlib.osutils import has_symlinks, sha_file
28
from bzrlib.tests import TestCaseInTempDir, TestCase, TestSkipped
33
from bzrlib.tests import (TestCaseInTempDir, TestCaseWithTransport,
34
TestCase, TestSkipped)
29
35
from bzrlib.transform import TreeTransform
30
36
from bzrlib.workingtree import WorkingTree
350
356
: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())
358
bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
360
359
new_text = bundle_txt.getvalue().replace('executable:no',
361
360
'executable:yes')
362
361
bundle_txt = StringIO(new_text)
363
bundle = BundleReader(bundle_txt)
362
bundle = read_bundle(bundle_txt)
364
363
self.valid_apply_bundle(base_rev_id, bundle)
367
366
def test_non_bundle(self):
368
self.assertRaises(NotABundle, BundleReader, StringIO('#!/bin/sh\n'))
367
self.assertRaises(NotABundle, read_bundle, StringIO('#!/bin/sh\n'))
369
def test_malformed(self):
370
self.assertRaises(BadBundle, read_bundle,
371
StringIO('# Bazaar revision bundle v'))
373
def test_crlf_bundle(self):
375
read_bundle(StringIO('# Bazaar revision bundle v0.8\r\n'))
377
# It is currently permitted for bundles with crlf line endings to
378
# make read_bundle raise a BadBundle, but this should be fixed.
379
# Anything else, especially NotABundle, is an error.
370
382
def get_checkout(self, rev_id, checkout_dir=None):
371
383
"""Get a new tree, with the specified revision in it.
373
from bzrlib.branch import Branch
376
386
if checkout_dir is None:
377
387
checkout_dir = tempfile.mkdtemp(prefix='test-branch-', dir='.')
380
389
if not os.path.exists(checkout_dir):
381
390
os.mkdir(checkout_dir)
382
391
tree = BzrDir.create_standalone_workingtree(checkout_dir)
402
419
rh = self.b1.revision_history()
403
420
tree.branch.set_revision_history(rh[:rh.index(rev_id)+1])
422
delta = compare_trees(self.b1.repository.revision_tree(rev_id),
424
self.assertFalse(delta.has_changed(),
425
'Working tree has modifications')
407
def valid_apply_bundle(self, base_rev_id, reader, checkout_dir=None):
428
def valid_apply_bundle(self, base_rev_id, info, checkout_dir=None):
408
429
"""Get the base revision, apply the changes, and make
409
430
sure everything matches the builtin branch.
411
432
to_tree = self.get_checkout(base_rev_id, checkout_dir=checkout_dir)
412
433
repository = to_tree.branch.repository
413
434
self.assertIs(repository.has_revision(base_rev_id), True)
415
435
for rev in info.real_revisions:
416
436
self.assert_(not repository.has_revision(rev.revision_id),
417
437
'Revision {%s} present before applying bundle'
418
438
% rev.revision_id)
419
merge_bundle(reader, to_tree, True, Merge3Merger, False, False)
439
merge_bundle(info, to_tree, True, Merge3Merger, False, False)
421
441
for rev in info.real_revisions:
422
442
self.assert_(repository.has_revision(rev.revision_id),
538
555
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')
556
open('b1/sub/dir/ pre space', 'ab').write('\r\nAdding some\r\nDOS format lines\r\n')
540
557
open('b1/sub/dir/nolastnewline.txt', 'ab').write('\n')
541
self.tree1.rename_one('sub/dir/trailing space ',
542
'sub/ start and end space ')
558
self.tree1.rename_one('sub/dir/ pre space',
543
560
self.tree1.commit('Modified files', rev_id='a@cset-0-5')
544
561
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
563
self.tree1.rename_one('sub/dir/WithCaps.txt', 'temp')
562
564
self.tree1.rename_one('with space.txt', 'WithCaps.txt')
563
565
self.tree1.rename_one('temp', 'with space.txt')
564
self.tree1.commit(u'swap filenames', rev_id='a@cset-0-7',
566
self.tree1.commit(u'swap filenames', rev_id='a@cset-0-6',
568
bundle = self.get_valid_bundle('a@cset-0-5', 'a@cset-0-6')
569
other = self.get_checkout('a@cset-0-5')
570
other.rename_one('sub/dir/nolastnewline.txt', 'sub/nolastnewline.txt')
571
other.commit('rename file', rev_id='a@cset-0-6b')
572
merge([other.basedir, -1], [None, None], this_dir=self.tree1.basedir)
573
self.tree1.commit(u'Merge', rev_id='a@cset-0-7',
566
575
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
577
def test_symlink_bundle(self):
576
578
if not has_symlinks():
608
610
self.tree1 = BzrDir.create_standalone_workingtree('b1')
609
611
self.b1 = self.tree1.branch
610
612
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')
615
tt.new_file('file', tt.root, '\x00\n\x00\r\x01\n\x02\r\xff', 'binary-1')
616
tt.new_file('file2', tt.root, '\x01\n\x02\r\x03\n\x04\r\xff', 'binary-2')
614
618
self.tree1.commit('add binary', rev_id='b@cset-0-1')
615
619
self.get_valid_bundle(None, 'b@cset-0-1')
616
622
tt = TreeTransform(self.tree1)
617
623
trans_id = tt.trans_id_tree_file_id('binary-1')
618
624
tt.delete_contents(trans_id)
620
626
self.tree1.commit('delete binary', rev_id='b@cset-0-2')
621
627
self.get_valid_bundle('b@cset-0-1', 'b@cset-0-2')
622
630
tt = TreeTransform(self.tree1)
623
631
trans_id = tt.trans_id_tree_file_id('binary-2')
624
632
tt.adjust_path('file3', tt.root, trans_id)
625
633
tt.delete_contents(trans_id)
626
tt.create_file('filecontents\x00', trans_id)
634
tt.create_file('file\rcontents\x00\n\x00', trans_id)
628
636
self.tree1.commit('rename and modify binary', rev_id='b@cset-0-3')
629
637
self.get_valid_bundle('b@cset-0-2', 'b@cset-0-3')
630
640
tt = TreeTransform(self.tree1)
631
641
trans_id = tt.trans_id_tree_file_id('binary-2')
632
642
tt.delete_contents(trans_id)
633
tt.create_file('\x00filecontents', trans_id)
643
tt.create_file('\x00file\rcontents', trans_id)
635
645
self.tree1.commit('just modify binary', rev_id='b@cset-0-4')
636
646
self.get_valid_bundle('b@cset-0-3', 'b@cset-0-4')
649
self.get_valid_bundle(None, 'b@cset-0-4')
638
651
def test_last_modified(self):
639
652
self.tree1 = BzrDir.create_standalone_workingtree('b1')
640
653
self.b1 = self.tree1.branch
663
676
self.tree1.commit(u'Merge', rev_id='a@lmod-0-4')
664
677
bundle = self.get_valid_bundle('a@lmod-0-2a', 'a@lmod-0-4')
679
def test_hide_history(self):
680
self.tree1 = BzrDir.create_standalone_workingtree('b1')
681
self.b1 = self.tree1.branch
683
open('b1/one', 'wb').write('one\n')
684
self.tree1.add('one')
685
self.tree1.commit('add file', rev_id='a@cset-0-1')
686
open('b1/one', 'wb').write('two\n')
687
self.tree1.commit('modify', rev_id='a@cset-0-2')
688
open('b1/one', 'wb').write('three\n')
689
self.tree1.commit('modify', rev_id='a@cset-0-3')
690
bundle_file = StringIO()
691
rev_ids = write_bundle(self.tree1.branch.repository, 'a@cset-0-3',
692
'a@cset-0-1', bundle_file)
693
self.assertNotContainsRe(bundle_file.getvalue(), 'two')
694
self.assertContainsRe(bundle_file.getvalue(), 'one')
695
self.assertContainsRe(bundle_file.getvalue(), 'three')
697
def test_unicode_bundle(self):
698
# Handle international characters
701
f = open(u'b1/with Dod\xe9', 'wb')
702
except UnicodeEncodeError:
703
raise TestSkipped("Filesystem doesn't support unicode")
705
self.tree1 = self.make_branch_and_tree('b1')
706
self.b1 = self.tree1.branch
709
u'With international man of mystery\n'
710
u'William Dod\xe9\n').encode('utf-8'))
713
self.tree1.add([u'with Dod\xe9'])
714
self.tree1.commit(u'i18n commit from William Dod\xe9',
715
rev_id='i18n-1', committer=u'William Dod\xe9')
718
bundle = self.get_valid_bundle(None, 'i18n-1')
721
f = open(u'b1/with Dod\xe9', 'wb')
722
f.write(u'Modified \xb5\n'.encode('utf8'))
724
self.tree1.commit(u'modified', rev_id='i18n-2')
726
bundle = self.get_valid_bundle('i18n-1', 'i18n-2')
729
self.tree1.rename_one(u'with Dod\xe9', u'B\xe5gfors')
730
self.tree1.commit(u'renamed, the new i18n man', rev_id='i18n-3',
731
committer=u'Erik B\xe5gfors')
733
bundle = self.get_valid_bundle('i18n-2', 'i18n-3')
736
self.tree1.remove([u'B\xe5gfors'])
737
self.tree1.commit(u'removed', rev_id='i18n-4')
739
bundle = self.get_valid_bundle('i18n-3', 'i18n-4')
742
bundle = self.get_valid_bundle(None, 'i18n-4')
745
def test_whitespace_bundle(self):
746
if sys.platform in ('win32', 'cygwin'):
747
raise TestSkipped('Windows doesn\'t support filenames'
748
' with tabs or trailing spaces')
749
self.tree1 = self.make_branch_and_tree('b1')
750
self.b1 = self.tree1.branch
752
self.build_tree(['b1/trailing space '])
753
self.tree1.add(['trailing space '])
754
# TODO: jam 20060701 Check for handling files with '\t' characters
755
# once we actually support them
758
self.tree1.commit('funky whitespace', rev_id='white-1')
760
bundle = self.get_valid_bundle(None, 'white-1')
763
open('b1/trailing space ', 'ab').write('add some text\n')
764
self.tree1.commit('add text', rev_id='white-2')
766
bundle = self.get_valid_bundle('white-1', 'white-2')
769
self.tree1.rename_one('trailing space ', ' start and end space ')
770
self.tree1.commit('rename', rev_id='white-3')
772
bundle = self.get_valid_bundle('white-2', 'white-3')
775
self.tree1.remove([' start and end space '])
776
self.tree1.commit('removed', rev_id='white-4')
778
bundle = self.get_valid_bundle('white-3', 'white-4')
780
# Now test a complet roll-up
781
bundle = self.get_valid_bundle(None, 'white-4')
783
def test_alt_timezone_bundle(self):
784
self.tree1 = self.make_branch_and_tree('b1')
785
self.b1 = self.tree1.branch
787
self.build_tree(['b1/newfile'])
788
self.tree1.add(['newfile'])
790
# Asia/Colombo offset = 5 hours 30 minutes
791
self.tree1.commit('non-hour offset timezone', rev_id='tz-1',
792
timezone=19800, timestamp=1152544886.0)
794
bundle = self.get_valid_bundle(None, 'tz-1')
796
rev = bundle.revisions[0]
797
self.assertEqual('Mon 2006-07-10 20:51:26.000000000 +0530', rev.date)
798
self.assertEqual(19800, rev.timezone)
799
self.assertEqual(1152544886.0, rev.timestamp)
802
class MungedBundleTester(TestCaseWithTransport):
804
def build_test_bundle(self):
805
wt = self.make_branch_and_tree('b1')
807
self.build_tree(['b1/one'])
809
wt.commit('add one', rev_id='a@cset-0-1')
810
self.build_tree(['b1/two'])
812
wt.commit('add two', rev_id='a@cset-0-2',
813
revprops={'branch-nick':'test'})
815
bundle_txt = StringIO()
816
rev_ids = write_bundle(wt.branch.repository, 'a@cset-0-2',
817
'a@cset-0-1', bundle_txt)
818
self.assertEqual(['a@cset-0-2'], rev_ids)
819
bundle_txt.seek(0, 0)
822
def check_valid(self, bundle):
823
"""Check that after whatever munging, the final object is valid."""
824
self.assertEqual(['a@cset-0-2'],
825
[r.revision_id for r in bundle.real_revisions])
827
def test_extra_whitespace(self):
828
bundle_txt = self.build_test_bundle()
830
# Seek to the end of the file
831
# Adding one extra newline used to give us
832
# TypeError: float() argument must be a string or a number
833
bundle_txt.seek(0, 2)
834
bundle_txt.write('\n')
837
bundle = read_bundle(bundle_txt)
838
self.check_valid(bundle)
840
def test_extra_whitespace_2(self):
841
bundle_txt = self.build_test_bundle()
843
# Seek to the end of the file
844
# Adding two extra newlines used to give us
845
# MalformedPatches: The first line of all patches should be ...
846
bundle_txt.seek(0, 2)
847
bundle_txt.write('\n\n')
850
bundle = read_bundle(bundle_txt)
851
self.check_valid(bundle)
853
def test_missing_trailing_whitespace(self):
854
bundle_txt = self.build_test_bundle()
856
# Remove a trailing newline, it shouldn't kill the parser
857
raw = bundle_txt.getvalue()
858
# The contents of the bundle don't have to be this, but this
859
# test is concerned with the exact case where the serializer
860
# creates a blank line at the end, and fails if that
862
self.assertEqual('\n\n', raw[-2:])
863
bundle_txt = StringIO(raw[:-1])
865
bundle = read_bundle(bundle_txt)
866
self.check_valid(bundle)
868
def test_opening_text(self):
869
bundle_txt = self.build_test_bundle()
871
bundle_txt = StringIO("Some random\nemail comments\n"
872
+ bundle_txt.getvalue())
874
bundle = read_bundle(bundle_txt)
875
self.check_valid(bundle)
877
def test_trailing_text(self):
878
bundle_txt = self.build_test_bundle()
880
bundle_txt = StringIO(bundle_txt.getvalue() +
881
"Some trailing\nrandom\ntext\n")
883
bundle = read_bundle(bundle_txt)
884
self.check_valid(bundle)