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
21
from bzrlib import (
26
27
from bzrlib.branch import Branch
27
from bzrlib.bzrdir import BzrDirMetaFormat1
28
from bzrlib.bzrdir import BzrDir, BzrDirMetaFormat1
28
29
from bzrlib.commit import Commit, NullCommitReporter
29
from bzrlib.errors import (
35
from bzrlib.tests import (
36
TestCaseWithTransport,
39
from bzrlib.tests.features import (
42
from bzrlib.tests.matchers import MatchesAncestry
30
from bzrlib.config import BranchConfig
31
from bzrlib.errors import (PointlessCommit, BzrError, SigningFailed,
33
from bzrlib.tests import TestCaseWithTransport
34
from bzrlib.workingtree import WorkingTree
45
37
# TODO: Test commit with some added, and added-but-missing files
47
class MustSignConfig(config.Stack):
49
def __init__(self, branch):
50
store = config.IniFileStore()
51
store._load_from_string('''
52
gpg_signing_command=cat -
53
create_signatures=always
55
super(MustSignConfig, self).__init__([store.get_sections])
56
# FIXME: Strictly speaking we should fallback to the no-name section in
57
# branch.conf but no tests need that so far -- vila 2011-12-14
60
class BranchWithHooks(config.Stack):
62
def __init__(self, branch):
63
store = config.IniFileStore()
64
store._load_from_string('post_commit=bzrlib.ahook bzrlib.ahook')
65
super(BranchWithHooks, self).__init__([store.get_sections])
66
# FIXME: Strictly speaking we should fallback to the no-name section in
67
# branch.conf but no tests need that so far -- vila 2011-12-14
39
class MustSignConfig(BranchConfig):
41
def signature_needed(self):
44
def gpg_signing_command(self):
48
class BranchWithHooks(BranchConfig):
50
def post_commit(self):
51
return "bzrlib.ahook bzrlib.ahook"
70
54
class CapturingReporter(NullCommitReporter):
99
80
file('hello', 'w').write('hello world')
101
rev1 = wt.commit(message='add hello')
82
wt.commit(message='add hello')
102
83
file_id = wt.path2id('hello')
104
85
file('hello', 'w').write('version 2')
105
rev2 = wt.commit(message='commit 2')
86
wt.commit(message='commit 2')
107
88
eq = self.assertEquals
109
rev = b.repository.get_revision(rev1)
90
rh = b.revision_history()
91
rev = b.repository.get_revision(rh[0])
110
92
eq(rev.message, 'add hello')
112
tree1 = b.repository.revision_tree(rev1)
94
tree1 = b.repository.revision_tree(rh[0])
114
95
text = tree1.get_file_text(file_id)
116
self.assertEqual('hello world', text)
118
tree2 = b.repository.revision_tree(rev2)
120
text = tree2.get_file_text(file_id)
122
self.assertEqual('version 2', text)
124
def test_commit_lossy_native(self):
125
"""Attempt a lossy commit to a native branch."""
126
wt = self.make_branch_and_tree('.')
128
file('hello', 'w').write('hello world')
130
revid = wt.commit(message='add hello', rev_id='revid', lossy=True)
131
self.assertEquals('revid', revid)
133
def test_commit_lossy_foreign(self):
134
"""Attempt a lossy commit to a foreign branch."""
135
test_foreign.register_dummy_foreign_for_test(self)
136
wt = self.make_branch_and_tree('.',
137
format=test_foreign.DummyForeignVcsDirFormat())
139
file('hello', 'w').write('hello world')
141
revid = wt.commit(message='add hello', lossy=True,
142
timestamp=1302659388, timezone=0)
143
self.assertEquals('dummy-v1:1302659388.0-0-UNKNOWN', revid)
145
def test_commit_bound_lossy_foreign(self):
146
"""Attempt a lossy commit to a bzr branch bound to a foreign branch."""
147
test_foreign.register_dummy_foreign_for_test(self)
148
foreign_branch = self.make_branch('foreign',
149
format=test_foreign.DummyForeignVcsDirFormat())
150
wt = foreign_branch.create_checkout("local")
152
file('local/hello', 'w').write('hello world')
154
revid = wt.commit(message='add hello', lossy=True,
155
timestamp=1302659388, timezone=0)
156
self.assertEquals('dummy-v1:1302659388.0-0-0', revid)
157
self.assertEquals('dummy-v1:1302659388.0-0-0',
158
foreign_branch.last_revision())
159
self.assertEquals('dummy-v1:1302659388.0-0-0',
160
wt.branch.last_revision())
162
def test_missing_commit(self):
163
"""Test a commit with a missing file"""
96
eq(text, 'hello world')
98
tree2 = b.repository.revision_tree(rh[1])
99
eq(tree2.get_file_text(file_id), 'version 2')
101
def test_delete_commit(self):
102
"""Test a commit with a deleted file"""
164
103
wt = self.make_branch_and_tree('.')
166
105
file('hello', 'w').write('hello world')
168
107
wt.commit(message='add hello')
170
109
os.remove('hello')
171
reporter = CapturingReporter()
172
wt.commit('removed hello', rev_id='rev2', reporter=reporter)
174
[('missing', u'hello'), ('deleted', u'hello')],
110
wt.commit('removed hello', rev_id='rev2')
177
112
tree = b.repository.revision_tree('rev2')
178
113
self.assertFalse(tree.has_id('hello-id'))
180
def test_partial_commit_move(self):
181
"""Test a partial commit where a file was renamed but not committed.
183
https://bugs.launchpad.net/bzr/+bug/83039
185
If not handled properly, commit will try to snapshot
186
dialog.py with olive/ as a parent, while
187
olive/ has not been snapshotted yet.
189
wt = self.make_branch_and_tree('.')
191
self.build_tree(['annotate/', 'annotate/foo.py',
192
'olive/', 'olive/dialog.py'
194
wt.add(['annotate', 'olive', 'annotate/foo.py', 'olive/dialog.py'])
195
wt.commit(message='add files')
196
wt.rename_one("olive/dialog.py", "aaa")
197
self.build_tree_contents([('annotate/foo.py', 'modified\n')])
198
wt.commit('renamed hello', specific_files=["annotate"])
200
115
def test_pointless_commit(self):
201
116
"""Commit refuses unless there are changes or it's forced."""
202
117
wt = self.make_branch_and_tree('.')
276
187
eq = self.assertEquals
277
188
tree1 = b.repository.revision_tree('test@rev-1')
279
self.addCleanup(tree1.unlock)
280
189
eq(tree1.id2path('hello-id'), 'hello')
281
190
eq(tree1.get_file_text('hello-id'), 'contents of hello\n')
282
191
self.assertFalse(tree1.has_filename('fruity'))
283
self.check_tree_shape(tree1, ['hello'])
284
eq(tree1.get_file_revision('hello-id'), 'test@rev-1')
192
self.check_inventory_shape(tree1.inventory, ['hello'])
193
ie = tree1.inventory['hello-id']
194
eq(ie.revision, 'test@rev-1')
286
196
tree2 = b.repository.revision_tree('test@rev-2')
288
self.addCleanup(tree2.unlock)
289
197
eq(tree2.id2path('hello-id'), 'fruity')
290
198
eq(tree2.get_file_text('hello-id'), 'contents of hello\n')
291
self.check_tree_shape(tree2, ['fruity'])
292
eq(tree2.get_file_revision('hello-id'), 'test@rev-2')
199
self.check_inventory_shape(tree2.inventory, ['fruity'])
200
ie = tree2.inventory['hello-id']
201
eq(ie.revision, 'test@rev-2')
294
203
def test_reused_rev_id(self):
295
204
"""Test that a revision id cannot be reused in a branch"""
337
247
wt.commit('four', rev_id=r4, allow_pointless=False)
340
self.check_tree_shape(wt, ['a/', 'a/b/hello', 'a/b/'])
250
self.check_inventory_shape(wt.read_working_inventory(),
251
['a', 'a/b/hello', 'a/b'])
344
inv = b.repository.get_inventory(r4)
255
inv = b.repository.get_revision_inventory(r4)
345
256
eq(inv['hello-id'].revision, r4)
346
257
eq(inv['a-id'].revision, r1)
347
258
eq(inv['b-id'].revision, r3)
434
348
wt = self.make_branch_and_tree('.')
435
349
branch = wt.branch
436
350
wt.commit("base", allow_pointless=True, rev_id='A')
437
self.assertFalse(branch.repository.has_signature_for_revision_id('A'))
351
self.failIf(branch.repository.has_signature_for_revision_id('A'))
439
353
from bzrlib.testament import Testament
440
354
# monkey patch gpg signing mechanism
441
355
bzrlib.gpg.GPGStrategy = bzrlib.gpg.LoopbackGPGStrategy
442
commit.Commit(config_stack=MustSignConfig(branch)
443
).commit(message="base", allow_pointless=True,
444
rev_id='B', working_tree=wt)
356
commit.Commit(config=MustSignConfig(branch)).commit(message="base",
357
allow_pointless=True,
446
361
return bzrlib.gpg.LoopbackGPGStrategy(None).sign(text)
447
362
self.assertEqual(sign(Testament.from_revision(branch.repository,
448
'B').as_short_text()),
363
'B').as_short_text()),
449
364
branch.repository.get_signature_text('B'))
451
366
bzrlib.gpg.GPGStrategy = oldstrategy
457
372
wt = self.make_branch_and_tree('.')
458
373
branch = wt.branch
459
374
wt.commit("base", allow_pointless=True, rev_id='A')
460
self.assertFalse(branch.repository.has_signature_for_revision_id('A'))
375
self.failIf(branch.repository.has_signature_for_revision_id('A'))
377
from bzrlib.testament import Testament
462
378
# monkey patch gpg signing mechanism
463
379
bzrlib.gpg.GPGStrategy = bzrlib.gpg.DisabledGPGStrategy
464
380
config = MustSignConfig(branch)
465
381
self.assertRaises(SigningFailed,
466
commit.Commit(config_stack=config).commit,
382
commit.Commit(config=config).commit,
468
384
allow_pointless=True,
471
387
branch = Branch.open(self.get_url('.'))
472
self.assertEqual(branch.last_revision(), 'A')
473
self.assertFalse(branch.repository.has_revision('B'))
388
self.assertEqual(branch.revision_history(), ['A'])
389
self.failIf(branch.repository.has_revision('B'))
475
391
bzrlib.gpg.GPGStrategy = oldstrategy
597
517
this_tree.merge_from_branch(other_tree.branch)
598
518
reporter = CapturingReporter()
599
519
this_tree.commit('do the commit', reporter=reporter)
521
('change', 'unchanged', ''),
522
('change', 'unchanged', 'dirtoleave'),
523
('change', 'unchanged', 'filetoleave'),
601
524
('change', 'modified', 'filetomodify'),
602
525
('change', 'added', 'newdir'),
603
526
('change', 'added', 'newfile'),
604
527
('renamed', 'renamed', 'dirtorename', 'renameddir'),
605
('renamed', 'renamed', 'filetorename', 'renamedfile'),
606
528
('renamed', 'renamed', 'dirtoreparent', 'renameddir/reparenteddir'),
607
529
('renamed', 'renamed', 'filetoreparent', 'renameddir/reparentedfile'),
530
('renamed', 'renamed', 'filetorename', 'renamedfile'),
608
531
('deleted', 'dirtoremove'),
609
532
('deleted', 'filetoremove'),
611
result = set(reporter.calls)
612
missing = expected - result
613
new = result - expected
614
self.assertEqual((set(), set()), (missing, new))
616
536
def test_commit_removals_respects_filespec(self):
617
537
"""Commit respects the specified_files for removals."""
755
676
cb = self.Callback(u'commit 2', self)
756
677
repository = tree.branch.repository
757
678
# simulate network failure
758
def raise_(self, arg, arg2, arg3=None, arg4=None):
679
def raise_(self, arg, arg2):
759
680
raise errors.NoSuchFile('foo')
760
681
repository.add_inventory = raise_
761
repository.add_inventory_by_delta = raise_
762
682
self.assertRaises(errors.NoSuchFile, tree.commit, message_callback=cb)
763
683
self.assertFalse(cb.called)
775
695
self.assertEqual(['bar', 'baz'], err.files)
776
696
self.assertEqual('Selected-file commit of merges is not supported'
777
697
' yet: files bar, baz', str(err))
779
def test_commit_ordering(self):
780
"""Test of corner-case commit ordering error"""
781
tree = self.make_branch_and_tree('.')
782
self.build_tree(['a/', 'a/z/', 'a/c/', 'a/z/x', 'a/z/y'])
783
tree.add(['a/', 'a/z/', 'a/c/', 'a/z/x', 'a/z/y'])
785
self.build_tree(['a/c/d/'])
787
tree.rename_one('a/z/x', 'a/c/d/x')
788
tree.commit('test', specific_files=['a/z/y'])
790
def test_commit_no_author(self):
791
"""The default kwarg author in MutableTree.commit should not add
792
the 'author' revision property.
794
tree = self.make_branch_and_tree('foo')
795
rev_id = tree.commit('commit 1')
796
rev = tree.branch.repository.get_revision(rev_id)
797
self.assertFalse('author' in rev.properties)
798
self.assertFalse('authors' in rev.properties)
800
def test_commit_author(self):
801
"""Passing a non-empty author kwarg to MutableTree.commit should add
802
the 'author' revision property.
804
tree = self.make_branch_and_tree('foo')
805
rev_id = self.callDeprecated(['The parameter author was '
806
'deprecated in version 1.13. Use authors instead'],
807
tree.commit, 'commit 1', author='John Doe <jdoe@example.com>')
808
rev = tree.branch.repository.get_revision(rev_id)
809
self.assertEqual('John Doe <jdoe@example.com>',
810
rev.properties['authors'])
811
self.assertFalse('author' in rev.properties)
813
def test_commit_empty_authors_list(self):
814
"""Passing an empty list to authors shouldn't add the property."""
815
tree = self.make_branch_and_tree('foo')
816
rev_id = tree.commit('commit 1', authors=[])
817
rev = tree.branch.repository.get_revision(rev_id)
818
self.assertFalse('author' in rev.properties)
819
self.assertFalse('authors' in rev.properties)
821
def test_multiple_authors(self):
822
tree = self.make_branch_and_tree('foo')
823
rev_id = tree.commit('commit 1',
824
authors=['John Doe <jdoe@example.com>',
825
'Jane Rey <jrey@example.com>'])
826
rev = tree.branch.repository.get_revision(rev_id)
827
self.assertEqual('John Doe <jdoe@example.com>\n'
828
'Jane Rey <jrey@example.com>', rev.properties['authors'])
829
self.assertFalse('author' in rev.properties)
831
def test_author_and_authors_incompatible(self):
832
tree = self.make_branch_and_tree('foo')
833
self.assertRaises(AssertionError, tree.commit, 'commit 1',
834
authors=['John Doe <jdoe@example.com>',
835
'Jane Rey <jrey@example.com>'],
836
author="Jack Me <jme@example.com>")
838
def test_author_with_newline_rejected(self):
839
tree = self.make_branch_and_tree('foo')
840
self.assertRaises(AssertionError, tree.commit, 'commit 1',
841
authors=['John\nDoe <jdoe@example.com>'])
843
def test_commit_with_checkout_and_branch_sharing_repo(self):
844
repo = self.make_repository('repo', shared=True)
845
# make_branch_and_tree ignores shared repos
846
branch = bzrdir.BzrDir.create_branch_convenience('repo/branch')
847
tree2 = branch.create_checkout('repo/tree2')
848
tree2.commit('message', rev_id='rev1')
849
self.assertTrue(tree2.branch.repository.has_revision('rev1'))