1
# Copyright (C) 2005, 2006, 2008 Canonical Ltd
1
# Copyright (C) 2005, 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
# 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
21
from bzrlib.tests import TestCaseWithTransport
28
22
from bzrlib.branch import Branch
29
23
from bzrlib.bzrdir import BzrDir, BzrDirMetaFormat1
24
from bzrlib.workingtree import WorkingTree
30
25
from bzrlib.commit import Commit, NullCommitReporter
31
26
from bzrlib.config import BranchConfig
32
from bzrlib.errors import (PointlessCommit, BzrError, SigningFailed,
27
from bzrlib.errors import (PointlessCommit, BzrError, SigningFailed,
34
from bzrlib.tests import SymlinkFeature, TestCaseWithTransport
35
from bzrlib.workingtree import WorkingTree
38
31
# TODO: Test commit with some added, and added-but-missing files
547
490
other_tree.commit('modify all sample files and dirs.')
549
492
other_tree.unlock()
550
this_tree.merge_from_branch(other_tree.branch)
493
self.merge(other_tree.branch, this_tree)
551
494
reporter = CapturingReporter()
552
495
this_tree.commit('do the commit', reporter=reporter)
497
('change', 'unchanged', 'dirtoleave'),
498
('change', 'unchanged', 'filetoleave'),
554
499
('change', 'modified', 'filetomodify'),
555
500
('change', 'added', 'newdir'),
556
501
('change', 'added', 'newfile'),
557
502
('renamed', 'renamed', 'dirtorename', 'renameddir'),
558
('renamed', 'renamed', 'filetorename', 'renamedfile'),
559
503
('renamed', 'renamed', 'dirtoreparent', 'renameddir/reparenteddir'),
560
504
('renamed', 'renamed', 'filetoreparent', 'renameddir/reparentedfile'),
505
('renamed', 'renamed', 'filetorename', 'renamedfile'),
561
506
('deleted', 'dirtoremove'),
562
507
('deleted', 'filetoremove'),
564
result = set(reporter.calls)
565
missing = expected - result
566
new = result - expected
567
self.assertEqual((set(), set()), (missing, new))
569
511
def test_commit_removals_respects_filespec(self):
570
512
"""Commit respects the specified_files for removals."""
573
515
tree.add(['a', 'b'])
574
516
tree.commit('added a, b')
575
517
tree.remove(['a', 'b'])
576
tree.commit('removed a', specific_files='a')
577
basis = tree.basis_tree()
580
self.assertIs(None, basis.path2id('a'))
581
self.assertFalse(basis.path2id('b') is None)
585
def test_commit_saves_1ms_timestamp(self):
586
"""Passing in a timestamp is saved with 1ms resolution"""
587
tree = self.make_branch_and_tree('.')
588
self.build_tree(['a'])
590
tree.commit('added a', timestamp=1153248633.4186721, timezone=0,
593
rev = tree.branch.repository.get_revision('a1')
594
self.assertEqual(1153248633.419, rev.timestamp)
596
def test_commit_has_1ms_resolution(self):
597
"""Allowing commit to generate the timestamp also has 1ms resolution"""
598
tree = self.make_branch_and_tree('.')
599
self.build_tree(['a'])
601
tree.commit('added a', rev_id='a1')
603
rev = tree.branch.repository.get_revision('a1')
604
timestamp = rev.timestamp
605
timestamp_1ms = round(timestamp, 3)
606
self.assertEqual(timestamp_1ms, timestamp)
608
def assertBasisTreeKind(self, kind, tree, file_id):
609
basis = tree.basis_tree()
612
self.assertEqual(kind, basis.kind(file_id))
616
def test_commit_kind_changes(self):
617
self.requireFeature(SymlinkFeature)
618
tree = self.make_branch_and_tree('.')
619
os.symlink('target', 'name')
620
tree.add('name', 'a-file-id')
621
tree.commit('Added a symlink')
622
self.assertBasisTreeKind('symlink', tree, 'a-file-id')
625
self.build_tree(['name'])
626
tree.commit('Changed symlink to file')
627
self.assertBasisTreeKind('file', tree, 'a-file-id')
630
os.symlink('target', 'name')
631
tree.commit('file to symlink')
632
self.assertBasisTreeKind('symlink', tree, 'a-file-id')
636
tree.commit('symlink to directory')
637
self.assertBasisTreeKind('directory', tree, 'a-file-id')
640
os.symlink('target', 'name')
641
tree.commit('directory to symlink')
642
self.assertBasisTreeKind('symlink', tree, 'a-file-id')
644
# prepare for directory <-> file tests
647
tree.commit('symlink to directory')
648
self.assertBasisTreeKind('directory', tree, 'a-file-id')
651
self.build_tree(['name'])
652
tree.commit('Changed directory to file')
653
self.assertBasisTreeKind('file', tree, 'a-file-id')
657
tree.commit('file to directory')
658
self.assertBasisTreeKind('directory', tree, 'a-file-id')
660
def test_commit_unversioned_specified(self):
661
"""Commit should raise if specified files isn't in basis or worktree"""
662
tree = self.make_branch_and_tree('.')
663
self.assertRaises(errors.PathsNotVersionedError, tree.commit,
664
'message', specific_files=['bogus'])
666
class Callback(object):
668
def __init__(self, message, testcase):
670
self.message = message
671
self.testcase = testcase
673
def __call__(self, commit_obj):
675
self.testcase.assertTrue(isinstance(commit_obj, Commit))
678
def test_commit_callback(self):
679
"""Commit should invoke a callback to get the message"""
681
tree = self.make_branch_and_tree('.')
685
self.assertTrue(isinstance(e, BzrError))
686
self.assertEqual('The message or message_callback keyword'
687
' parameter is required for commit().', str(e))
689
self.fail('exception not raised')
690
cb = self.Callback(u'commit 1', self)
691
tree.commit(message_callback=cb)
692
self.assertTrue(cb.called)
693
repository = tree.branch.repository
694
message = repository.get_revision(tree.last_revision()).message
695
self.assertEqual('commit 1', message)
697
def test_no_callback_pointless(self):
698
"""Callback should not be invoked for pointless commit"""
699
tree = self.make_branch_and_tree('.')
700
cb = self.Callback(u'commit 2', self)
701
self.assertRaises(PointlessCommit, tree.commit, message_callback=cb,
702
allow_pointless=False)
703
self.assertFalse(cb.called)
705
def test_no_callback_netfailure(self):
706
"""Callback should not be invoked if connectivity fails"""
707
tree = self.make_branch_and_tree('.')
708
cb = self.Callback(u'commit 2', self)
709
repository = tree.branch.repository
710
# simulate network failure
711
def raise_(self, arg, arg2, arg3=None, arg4=None):
712
raise errors.NoSuchFile('foo')
713
repository.add_inventory = raise_
714
repository.add_inventory_by_delta = raise_
715
self.assertRaises(errors.NoSuchFile, tree.commit, message_callback=cb)
716
self.assertFalse(cb.called)
718
def test_selected_file_merge_commit(self):
719
"""Ensure the correct error is raised"""
720
tree = self.make_branch_and_tree('foo')
721
# pending merge would turn into a left parent
722
tree.commit('commit 1')
723
tree.add_parent_tree_id('example')
724
self.build_tree(['foo/bar', 'foo/baz'])
725
tree.add(['bar', 'baz'])
726
err = self.assertRaises(errors.CannotCommitSelectedFileMerge,
727
tree.commit, 'commit 2', specific_files=['bar', 'baz'])
728
self.assertEqual(['bar', 'baz'], err.files)
729
self.assertEqual('Selected-file commit of merges is not supported'
730
' yet: files bar, baz', str(err))
732
def test_commit_ordering(self):
733
"""Test of corner-case commit ordering error"""
734
tree = self.make_branch_and_tree('.')
735
self.build_tree(['a/', 'a/z/', 'a/c/', 'a/z/x', 'a/z/y'])
736
tree.add(['a/', 'a/z/', 'a/c/', 'a/z/x', 'a/z/y'])
738
self.build_tree(['a/c/d/'])
740
tree.rename_one('a/z/x', 'a/c/d/x')
741
tree.commit('test', specific_files=['a/z/y'])
743
def test_commit_no_author(self):
744
"""The default kwarg author in MutableTree.commit should not add
745
the 'author' revision property.
747
tree = self.make_branch_and_tree('foo')
748
rev_id = tree.commit('commit 1')
749
rev = tree.branch.repository.get_revision(rev_id)
750
self.assertFalse('author' in rev.properties)
751
self.assertFalse('authors' in rev.properties)
753
def test_commit_author(self):
754
"""Passing a non-empty author kwarg to MutableTree.commit should add
755
the 'author' revision property.
757
tree = self.make_branch_and_tree('foo')
758
rev_id = self.callDeprecated(['The parameter author was '
759
'deprecated in version 1.13. Use authors instead'],
760
tree.commit, 'commit 1', author='John Doe <jdoe@example.com>')
761
rev = tree.branch.repository.get_revision(rev_id)
762
self.assertEqual('John Doe <jdoe@example.com>',
763
rev.properties['authors'])
764
self.assertFalse('author' in rev.properties)
766
def test_commit_empty_authors_list(self):
767
"""Passing an empty list to authors shouldn't add the property."""
768
tree = self.make_branch_and_tree('foo')
769
rev_id = tree.commit('commit 1', authors=[])
770
rev = tree.branch.repository.get_revision(rev_id)
771
self.assertFalse('author' in rev.properties)
772
self.assertFalse('authors' in rev.properties)
774
def test_multiple_authors(self):
775
tree = self.make_branch_and_tree('foo')
776
rev_id = tree.commit('commit 1',
777
authors=['John Doe <jdoe@example.com>',
778
'Jane Rey <jrey@example.com>'])
779
rev = tree.branch.repository.get_revision(rev_id)
780
self.assertEqual('John Doe <jdoe@example.com>\n'
781
'Jane Rey <jrey@example.com>', rev.properties['authors'])
782
self.assertFalse('author' in rev.properties)
784
def test_author_and_authors_incompatible(self):
785
tree = self.make_branch_and_tree('foo')
786
self.assertRaises(AssertionError, tree.commit, 'commit 1',
787
authors=['John Doe <jdoe@example.com>',
788
'Jane Rey <jrey@example.com>'],
789
author="Jack Me <jme@example.com>")
791
def test_author_with_newline_rejected(self):
792
tree = self.make_branch_and_tree('foo')
793
self.assertRaises(AssertionError, tree.commit, 'commit 1',
794
authors=['John\nDoe <jdoe@example.com>'])
796
def test_commit_with_checkout_and_branch_sharing_repo(self):
797
repo = self.make_repository('repo', shared=True)
798
# make_branch_and_tree ignores shared repos
799
branch = bzrdir.BzrDir.create_branch_convenience('repo/branch')
800
tree2 = branch.create_checkout('repo/tree2')
801
tree2.commit('message', rev_id='rev1')
802
self.assertTrue(tree2.branch.repository.has_revision('rev1'))
518
Commit().commit(message='removed a', working_tree=tree,
520
basis = tree.basis_tree().inventory
521
self.assertIs(None, basis.path2id('a'))
522
self.assertFalse(basis.path2id('b') is None)