28
from bzrlib.branch import Branch
29
from bzrlib.bzrdir import BzrDir, BzrDirMetaFormat1
30
from bzrlib.commit import Commit, NullCommitReporter
31
from bzrlib.config import BranchConfig
32
from bzrlib.errors import (PointlessCommit, BzrError, SigningFailed,
34
from bzrlib.tests import SymlinkFeature, TestCaseWithTransport
35
from bzrlib.workingtree import WorkingTree
26
from ..branch import Branch
27
from ..bzr.bzrdir import BzrDirMetaFormat1
28
from ..commit import (
33
from ..errors import (
41
TestCaseWithTransport,
44
from .features import (
47
from .matchers import MatchesAncestry
38
50
# TODO: Test commit with some added, and added-but-missing files
40
class MustSignConfig(BranchConfig):
42
def signature_needed(self):
45
def gpg_signing_command(self):
49
class BranchWithHooks(BranchConfig):
51
def post_commit(self):
52
return "bzrlib.ahook bzrlib.ahook"
52
class MustSignConfig(config.MemoryStack):
55
super(MustSignConfig, self).__init__('''
56
gpg_signing_command=cat -
57
create_signatures=always
55
61
class CapturingReporter(NullCommitReporter):
81
87
"""Commit and check two versions of a single file."""
82
88
wt = self.make_branch_and_tree('.')
84
file('hello', 'w').write('hello world')
90
with file('hello', 'w') as f: f.write('hello world')
86
wt.commit(message='add hello')
92
rev1 = wt.commit(message='add hello')
87
93
file_id = wt.path2id('hello')
89
file('hello', 'w').write('version 2')
90
wt.commit(message='commit 2')
95
with file('hello', 'w') as f: f.write('version 2')
96
rev2 = wt.commit(message='commit 2')
92
eq = self.assertEquals
94
rh = b.revision_history()
95
rev = b.repository.get_revision(rh[0])
100
rev = b.repository.get_revision(rev1)
96
101
eq(rev.message, 'add hello')
98
tree1 = b.repository.revision_tree(rh[0])
103
tree1 = b.repository.revision_tree(rev1)
100
105
text = tree1.get_file_text(file_id)
102
107
self.assertEqual('hello world', text)
104
tree2 = b.repository.revision_tree(rh[1])
109
tree2 = b.repository.revision_tree(rev2)
105
110
tree2.lock_read()
106
111
text = tree2.get_file_text(file_id)
108
113
self.assertEqual('version 2', text)
115
def test_commit_lossy_native(self):
116
"""Attempt a lossy commit to a native branch."""
117
wt = self.make_branch_and_tree('.')
119
with file('hello', 'w') as f: f.write('hello world')
121
revid = wt.commit(message='add hello', rev_id='revid', lossy=True)
122
self.assertEqual('revid', revid)
124
def test_commit_lossy_foreign(self):
125
"""Attempt a lossy commit to a foreign branch."""
126
test_foreign.register_dummy_foreign_for_test(self)
127
wt = self.make_branch_and_tree('.',
128
format=test_foreign.DummyForeignVcsDirFormat())
130
with file('hello', 'w') as f: f.write('hello world')
132
revid = wt.commit(message='add hello', lossy=True,
133
timestamp=1302659388, timezone=0)
134
self.assertEqual('dummy-v1:1302659388.0-0-UNKNOWN', revid)
136
def test_commit_bound_lossy_foreign(self):
137
"""Attempt a lossy commit to a bzr branch bound to a foreign branch."""
138
test_foreign.register_dummy_foreign_for_test(self)
139
foreign_branch = self.make_branch('foreign',
140
format=test_foreign.DummyForeignVcsDirFormat())
141
wt = foreign_branch.create_checkout("local")
143
with file('local/hello', 'w') as f: f.write('hello world')
145
revid = wt.commit(message='add hello', lossy=True,
146
timestamp=1302659388, timezone=0)
147
self.assertEqual('dummy-v1:1302659388.0-0-0', revid)
148
self.assertEqual('dummy-v1:1302659388.0-0-0',
149
foreign_branch.last_revision())
150
self.assertEqual('dummy-v1:1302659388.0-0-0',
151
wt.branch.last_revision())
110
153
def test_missing_commit(self):
111
154
"""Test a commit with a missing file"""
112
155
wt = self.make_branch_and_tree('.')
114
file('hello', 'w').write('hello world')
157
with file('hello', 'w') as f: f.write('hello world')
115
158
wt.add(['hello'], ['hello-id'])
116
159
wt.commit(message='add hello')
118
161
os.remove('hello')
119
wt.commit('removed hello', rev_id='rev2')
162
reporter = CapturingReporter()
163
wt.commit('removed hello', rev_id='rev2', reporter=reporter)
165
[('missing', u'hello'), ('deleted', u'hello')],
121
168
tree = b.repository.revision_tree('rev2')
122
169
self.assertFalse(tree.has_id('hello-id'))
145
192
"""Commit refuses unless there are changes or it's forced."""
146
193
wt = self.make_branch_and_tree('.')
148
file('hello', 'w').write('hello')
195
with file('hello', 'w') as f: f.write('hello')
149
196
wt.add(['hello'])
150
197
wt.commit(message='add hello')
151
self.assertEquals(b.revno(), 1)
198
self.assertEqual(b.revno(), 1)
152
199
self.assertRaises(PointlessCommit,
155
202
allow_pointless=False)
156
self.assertEquals(b.revno(), 1)
203
self.assertEqual(b.revno(), 1)
158
205
def test_commit_empty(self):
159
206
"""Commiting an empty tree works."""
165
212
message='empty tree',
166
213
allow_pointless=False)
167
214
wt.commit(message='empty tree', allow_pointless=True)
168
self.assertEquals(b.revno(), 2)
215
self.assertEqual(b.revno(), 2)
170
217
def test_selective_delete(self):
171
218
"""Selective commit in tree with deletions"""
172
219
wt = self.make_branch_and_tree('.')
174
file('hello', 'w').write('hello')
175
file('buongia', 'w').write('buongia')
221
with file('hello', 'w') as f: f.write('hello')
222
with file('buongia', 'w') as f: f.write('buongia')
176
223
wt.add(['hello', 'buongia'],
177
224
['hello-id', 'buongia-id'])
178
225
wt.commit(message='add files',
179
226
rev_id='test@rev-1')
181
228
os.remove('hello')
182
file('buongia', 'w').write('new text')
229
with file('buongia', 'w') as f: f.write('new text')
183
230
wt.commit(message='update text',
184
231
specific_files=['buongia'],
185
232
allow_pointless=False,
190
237
allow_pointless=False,
191
238
rev_id='test@rev-3')
193
eq = self.assertEquals
240
eq = self.assertEqual
196
243
tree2 = b.repository.revision_tree('test@rev-2')
197
244
tree2.lock_read()
198
245
self.addCleanup(tree2.unlock)
199
246
self.assertTrue(tree2.has_filename('hello'))
200
self.assertEquals(tree2.get_file_text('hello-id'), 'hello')
201
self.assertEquals(tree2.get_file_text('buongia-id'), 'new text')
247
self.assertEqual(tree2.get_file_text('hello-id'), 'hello')
248
self.assertEqual(tree2.get_file_text('buongia-id'), 'new text')
203
250
tree3 = b.repository.revision_tree('test@rev-3')
204
251
tree3.lock_read()
205
252
self.addCleanup(tree3.unlock)
206
253
self.assertFalse(tree3.has_filename('hello'))
207
self.assertEquals(tree3.get_file_text('buongia-id'), 'new text')
254
self.assertEqual(tree3.get_file_text('buongia-id'), 'new text')
209
256
def test_commit_rename(self):
210
257
"""Test commit of a revision where a file is renamed."""
217
264
tree.rename_one('hello', 'fruity')
218
265
tree.commit(message='renamed', rev_id='test@rev-2', allow_pointless=False)
220
eq = self.assertEquals
267
eq = self.assertEqual
221
268
tree1 = b.repository.revision_tree('test@rev-1')
222
269
tree1.lock_read()
223
270
self.addCleanup(tree1.unlock)
224
271
eq(tree1.id2path('hello-id'), 'hello')
225
272
eq(tree1.get_file_text('hello-id'), 'contents of hello\n')
226
273
self.assertFalse(tree1.has_filename('fruity'))
227
self.check_inventory_shape(tree1.inventory, ['hello'])
228
ie = tree1.inventory['hello-id']
229
eq(ie.revision, 'test@rev-1')
274
self.check_tree_shape(tree1, ['hello'])
275
eq(tree1.get_file_revision('hello-id'), 'test@rev-1')
231
277
tree2 = b.repository.revision_tree('test@rev-2')
232
278
tree2.lock_read()
233
279
self.addCleanup(tree2.unlock)
234
280
eq(tree2.id2path('hello-id'), 'fruity')
235
281
eq(tree2.get_file_text('hello-id'), 'contents of hello\n')
236
self.check_inventory_shape(tree2.inventory, ['fruity'])
237
ie = tree2.inventory['hello-id']
238
eq(ie.revision, 'test@rev-2')
282
self.check_tree_shape(tree2, ['fruity'])
283
eq(tree2.get_file_revision('hello-id'), 'test@rev-2')
240
285
def test_reused_rev_id(self):
241
286
"""Test that a revision id cannot be reused in a branch"""
315
358
for i in range(4):
316
file('hello', 'w').write((str(i) * 4) + '\n')
359
with file('hello', 'w') as f: f.write((str(i) * 4) + '\n')
318
361
wt.add(['hello'], ['hello-id'])
319
362
rev_id = 'test@rev-%d' % (i+1)
320
363
rev_ids.append(rev_id)
321
364
wt.commit(message='rev %d' % (i+1),
323
eq = self.assertEquals
324
eq(b.revision_history(), rev_ids)
325
366
for i in range(4):
326
anc = b.repository.get_ancestry(rev_ids[i])
327
eq(anc, [None] + rev_ids[:i+1])
367
self.assertThat(rev_ids[:i+1],
368
MatchesAncestry(b.repository, rev_ids[i]))
329
370
def test_commit_new_subdir_child_selective(self):
330
371
wt = self.make_branch_and_tree('.')
342
383
def test_strict_commit(self):
343
384
"""Try and commit with unknown files and strict = True, should fail."""
344
from bzrlib.errors import StrictCommitFailed
385
from ..errors import StrictCommitFailed
345
386
wt = self.make_branch_and_tree('.')
347
file('hello', 'w').write('hello world')
388
with file('hello', 'w') as f: f.write('hello world')
349
file('goodbye', 'w').write('goodbye cruel world!')
390
with file('goodbye', 'w') as f: f.write('goodbye cruel world!')
350
391
self.assertRaises(StrictCommitFailed, wt.commit,
351
392
message='add hello but not goodbye', strict=True)
353
394
def test_strict_commit_without_unknowns(self):
354
395
"""Try and commit with no unknown files and strict = True,
356
from bzrlib.errors import StrictCommitFailed
357
397
wt = self.make_branch_and_tree('.')
359
file('hello', 'w').write('hello world')
399
with file('hello', 'w') as f: f.write('hello world')
361
401
wt.commit(message='add hello', strict=True)
375
415
wt = self.make_branch_and_tree('.')
377
file('hello', 'w').write('hello world')
417
with file('hello', 'w') as f: f.write('hello world')
379
419
wt.commit(message='add hello', strict=False)
381
421
def test_signed_commit(self):
383
import bzrlib.commit as commit
384
oldstrategy = bzrlib.gpg.GPGStrategy
423
import breezy.commit as commit
424
oldstrategy = breezy.gpg.GPGStrategy
385
425
wt = self.make_branch_and_tree('.')
386
426
branch = wt.branch
387
427
wt.commit("base", allow_pointless=True, rev_id='A')
388
self.failIf(branch.repository.has_signature_for_revision_id('A'))
428
self.assertFalse(branch.repository.has_signature_for_revision_id('A'))
390
from bzrlib.testament import Testament
430
from ..testament import Testament
391
431
# monkey patch gpg signing mechanism
392
bzrlib.gpg.GPGStrategy = bzrlib.gpg.LoopbackGPGStrategy
393
commit.Commit(config=MustSignConfig(branch)).commit(message="base",
394
allow_pointless=True,
432
breezy.gpg.GPGStrategy = breezy.gpg.LoopbackGPGStrategy
433
conf = config.MemoryStack('''
434
gpg_signing_command=cat -
435
create_signatures=always
437
commit.Commit(config_stack=conf).commit(
438
message="base", allow_pointless=True, rev_id='B',
398
return bzrlib.gpg.LoopbackGPGStrategy(None).sign(text)
441
return breezy.gpg.LoopbackGPGStrategy(None).sign(text)
399
442
self.assertEqual(sign(Testament.from_revision(branch.repository,
400
'B').as_short_text()),
443
'B').as_short_text()),
401
444
branch.repository.get_signature_text('B'))
403
bzrlib.gpg.GPGStrategy = oldstrategy
446
breezy.gpg.GPGStrategy = oldstrategy
405
448
def test_commit_failed_signature(self):
407
import bzrlib.commit as commit
408
oldstrategy = bzrlib.gpg.GPGStrategy
450
import breezy.commit as commit
451
oldstrategy = breezy.gpg.GPGStrategy
409
452
wt = self.make_branch_and_tree('.')
410
453
branch = wt.branch
411
454
wt.commit("base", allow_pointless=True, rev_id='A')
412
self.failIf(branch.repository.has_signature_for_revision_id('A'))
455
self.assertFalse(branch.repository.has_signature_for_revision_id('A'))
414
from bzrlib.testament import Testament
415
457
# monkey patch gpg signing mechanism
416
bzrlib.gpg.GPGStrategy = bzrlib.gpg.DisabledGPGStrategy
417
config = MustSignConfig(branch)
458
breezy.gpg.GPGStrategy = breezy.gpg.DisabledGPGStrategy
459
conf = config.MemoryStack('''
460
gpg_signing_command=cat -
461
create_signatures=always
418
463
self.assertRaises(SigningFailed,
419
commit.Commit(config=config).commit,
464
commit.Commit(config_stack=conf).commit,
421
466
allow_pointless=True,
424
469
branch = Branch.open(self.get_url('.'))
425
self.assertEqual(branch.revision_history(), ['A'])
426
self.failIf(branch.repository.has_revision('B'))
470
self.assertEqual(branch.last_revision(), 'A')
471
self.assertFalse(branch.repository.has_revision('B'))
428
bzrlib.gpg.GPGStrategy = oldstrategy
473
breezy.gpg.GPGStrategy = oldstrategy
430
475
def test_commit_invokes_hooks(self):
431
import bzrlib.commit as commit
476
import breezy.commit as commit
432
477
wt = self.make_branch_and_tree('.')
433
478
branch = wt.branch
435
480
def called(branch, rev_id):
436
481
calls.append('called')
437
bzrlib.ahook = called
482
breezy.ahook = called
439
config = BranchWithHooks(branch)
440
commit.Commit(config=config).commit(
442
allow_pointless=True,
443
rev_id='A', working_tree = wt)
484
conf = config.MemoryStack('post_commit=breezy.ahook breezy.ahook')
485
commit.Commit(config_stack=conf).commit(
486
message = "base", allow_pointless=True, rev_id='A',
444
488
self.assertEqual(['called', 'called'], calls)
448
492
def test_commit_object_doesnt_set_nick(self):
449
493
# using the Commit object directly does not set the branch nick.
450
494
wt = self.make_branch_and_tree('.')
452
496
c.commit(working_tree=wt, message='empty tree', allow_pointless=True)
453
self.assertEquals(wt.branch.revno(), 1)
497
self.assertEqual(wt.branch.revno(), 1)
454
498
self.assertEqual({},
455
499
wt.branch.repository.get_revision(
456
500
wt.branch.last_revision()).properties)
751
795
self.assertFalse('authors' in rev.properties)
753
797
def test_commit_author(self):
754
"""Passing a non-empty author kwarg to MutableTree.commit should add
798
"""Passing a non-empty authors kwarg to MutableTree.commit should add
755
799
the 'author' revision property.
757
801
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>')
802
rev_id = tree.commit(
804
authors=['John Doe <jdoe@example.com>'])
761
805
rev = tree.branch.repository.get_revision(rev_id)
762
806
self.assertEqual('John Doe <jdoe@example.com>',
763
807
rev.properties['authors'])
781
825
'Jane Rey <jrey@example.com>', rev.properties['authors'])
782
826
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
828
def test_author_with_newline_rejected(self):
792
829
tree = self.make_branch_and_tree('foo')
793
830
self.assertRaises(AssertionError, tree.commit, 'commit 1',
796
833
def test_commit_with_checkout_and_branch_sharing_repo(self):
797
834
repo = self.make_repository('repo', shared=True)
798
835
# make_branch_and_tree ignores shared repos
799
branch = bzrdir.BzrDir.create_branch_convenience('repo/branch')
836
branch = controldir.ControlDir.create_branch_convenience('repo/branch')
800
837
tree2 = branch.create_checkout('repo/tree2')
801
838
tree2.commit('message', rev_id='rev1')
802
839
self.assertTrue(tree2.branch.repository.has_revision('rev1'))
842
class FilterExcludedTests(TestCase):
844
def test_add_file_not_excluded(self):
846
('fid', (None, 'newpath'),
847
0, (False, False), ('pid', 'pid'), ('newpath', 'newpath'),
848
('file', 'file'), (True, True))]
849
self.assertEqual(changes, list(filter_excluded(changes, ['otherpath'])))
851
def test_add_file_excluded(self):
853
('fid', (None, 'newpath'),
854
0, (False, False), ('pid', 'pid'), ('newpath', 'newpath'),
855
('file', 'file'), (True, True))]
856
self.assertEqual([], list(filter_excluded(changes, ['newpath'])))
858
def test_delete_file_excluded(self):
860
('fid', ('somepath', None),
861
0, (False, None), ('pid', None), ('newpath', None),
862
('file', None), (True, None))]
863
self.assertEqual([], list(filter_excluded(changes, ['somepath'])))
865
def test_move_from_or_to_excluded(self):
867
('fid', ('oldpath', 'newpath'),
868
0, (False, False), ('pid', 'pid'), ('oldpath', 'newpath'),
869
('file', 'file'), (True, True))]
870
self.assertEqual([], list(filter_excluded(changes, ['oldpath'])))
871
self.assertEqual([], list(filter_excluded(changes, ['newpath'])))