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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21
21
from bzrlib import (
27
26
from bzrlib.branch import Branch
28
from bzrlib.bzrdir import BzrDir, BzrDirMetaFormat1
27
from bzrlib.bzrdir import BzrDirMetaFormat1
29
28
from bzrlib.commit import Commit, NullCommitReporter
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
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
37
45
# TODO: Test commit with some added, and added-but-missing files
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"
47
class MustSignConfig(config.MemoryStack):
50
super(MustSignConfig, self).__init__('''
51
gpg_signing_command=cat -
52
create_signatures=always
54
56
class CapturingReporter(NullCommitReporter):
80
85
file('hello', 'w').write('hello world')
82
wt.commit(message='add hello')
87
rev1 = wt.commit(message='add hello')
83
88
file_id = wt.path2id('hello')
85
90
file('hello', 'w').write('version 2')
86
wt.commit(message='commit 2')
91
rev2 = wt.commit(message='commit 2')
88
93
eq = self.assertEquals
90
rh = b.revision_history()
91
rev = b.repository.get_revision(rh[0])
95
rev = b.repository.get_revision(rev1)
92
96
eq(rev.message, 'add hello')
94
tree1 = b.repository.revision_tree(rh[0])
98
tree1 = b.repository.revision_tree(rev1)
95
100
text = tree1.get_file_text(file_id)
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"""
102
self.assertEqual('hello world', text)
104
tree2 = b.repository.revision_tree(rev2)
106
text = tree2.get_file_text(file_id)
108
self.assertEqual('version 2', text)
110
def test_commit_lossy_native(self):
111
"""Attempt a lossy commit to a native branch."""
112
wt = self.make_branch_and_tree('.')
114
file('hello', 'w').write('hello world')
116
revid = wt.commit(message='add hello', rev_id='revid', lossy=True)
117
self.assertEquals('revid', revid)
119
def test_commit_lossy_foreign(self):
120
"""Attempt a lossy commit to a foreign branch."""
121
test_foreign.register_dummy_foreign_for_test(self)
122
wt = self.make_branch_and_tree('.',
123
format=test_foreign.DummyForeignVcsDirFormat())
125
file('hello', 'w').write('hello world')
127
revid = wt.commit(message='add hello', lossy=True,
128
timestamp=1302659388, timezone=0)
129
self.assertEquals('dummy-v1:1302659388.0-0-UNKNOWN', revid)
131
def test_commit_bound_lossy_foreign(self):
132
"""Attempt a lossy commit to a bzr branch bound to a foreign branch."""
133
test_foreign.register_dummy_foreign_for_test(self)
134
foreign_branch = self.make_branch('foreign',
135
format=test_foreign.DummyForeignVcsDirFormat())
136
wt = foreign_branch.create_checkout("local")
138
file('local/hello', 'w').write('hello world')
140
revid = wt.commit(message='add hello', lossy=True,
141
timestamp=1302659388, timezone=0)
142
self.assertEquals('dummy-v1:1302659388.0-0-0', revid)
143
self.assertEquals('dummy-v1:1302659388.0-0-0',
144
foreign_branch.last_revision())
145
self.assertEquals('dummy-v1:1302659388.0-0-0',
146
wt.branch.last_revision())
148
def test_missing_commit(self):
149
"""Test a commit with a missing file"""
103
150
wt = self.make_branch_and_tree('.')
105
152
file('hello', 'w').write('hello world')
107
154
wt.commit(message='add hello')
109
156
os.remove('hello')
110
wt.commit('removed hello', rev_id='rev2')
157
reporter = CapturingReporter()
158
wt.commit('removed hello', rev_id='rev2', reporter=reporter)
160
[('missing', u'hello'), ('deleted', u'hello')],
112
163
tree = b.repository.revision_tree('rev2')
113
164
self.assertFalse(tree.has_id('hello-id'))
166
def test_partial_commit_move(self):
167
"""Test a partial commit where a file was renamed but not committed.
169
https://bugs.launchpad.net/bzr/+bug/83039
171
If not handled properly, commit will try to snapshot
172
dialog.py with olive/ as a parent, while
173
olive/ has not been snapshotted yet.
175
wt = self.make_branch_and_tree('.')
177
self.build_tree(['annotate/', 'annotate/foo.py',
178
'olive/', 'olive/dialog.py'
180
wt.add(['annotate', 'olive', 'annotate/foo.py', 'olive/dialog.py'])
181
wt.commit(message='add files')
182
wt.rename_one("olive/dialog.py", "aaa")
183
self.build_tree_contents([('annotate/foo.py', 'modified\n')])
184
wt.commit('renamed hello', specific_files=["annotate"])
115
186
def test_pointless_commit(self):
116
187
"""Commit refuses unless there are changes or it's forced."""
117
188
wt = self.make_branch_and_tree('.')
167
238
tree2 = b.repository.revision_tree('test@rev-2')
240
self.addCleanup(tree2.unlock)
168
241
self.assertTrue(tree2.has_filename('hello'))
169
242
self.assertEquals(tree2.get_file_text('hello-id'), 'hello')
170
243
self.assertEquals(tree2.get_file_text('buongia-id'), 'new text')
172
245
tree3 = b.repository.revision_tree('test@rev-3')
247
self.addCleanup(tree3.unlock)
173
248
self.assertFalse(tree3.has_filename('hello'))
174
249
self.assertEquals(tree3.get_file_text('buongia-id'), 'new text')
187
262
eq = self.assertEquals
188
263
tree1 = b.repository.revision_tree('test@rev-1')
265
self.addCleanup(tree1.unlock)
189
266
eq(tree1.id2path('hello-id'), 'hello')
190
267
eq(tree1.get_file_text('hello-id'), 'contents of hello\n')
191
268
self.assertFalse(tree1.has_filename('fruity'))
192
self.check_inventory_shape(tree1.inventory, ['hello'])
193
ie = tree1.inventory['hello-id']
194
eq(ie.revision, 'test@rev-1')
269
self.check_tree_shape(tree1, ['hello'])
270
eq(tree1.get_file_revision('hello-id'), 'test@rev-1')
196
272
tree2 = b.repository.revision_tree('test@rev-2')
274
self.addCleanup(tree2.unlock)
197
275
eq(tree2.id2path('hello-id'), 'fruity')
198
276
eq(tree2.get_file_text('hello-id'), 'contents of hello\n')
199
self.check_inventory_shape(tree2.inventory, ['fruity'])
200
ie = tree2.inventory['hello-id']
201
eq(ie.revision, 'test@rev-2')
277
self.check_tree_shape(tree2, ['fruity'])
278
eq(tree2.get_file_revision('hello-id'), 'test@rev-2')
203
280
def test_reused_rev_id(self):
204
281
"""Test that a revision id cannot be reused in a branch"""
235
311
wt.commit('three', rev_id=r3, allow_pointless=False)
238
self.check_inventory_shape(wt.read_working_inventory(),
314
self.check_tree_shape(wt,
239
315
['a/', 'a/hello', 'a/b/'])
240
self.check_inventory_shape(b.repository.get_revision_inventory(r3),
316
self.check_tree_shape(b.repository.revision_tree(r3),
241
317
['a/', 'a/hello', 'a/b/'])
247
323
wt.commit('four', rev_id=r4, allow_pointless=False)
250
self.check_inventory_shape(wt.read_working_inventory(),
251
['a/', 'a/b/hello', 'a/b/'])
326
self.check_tree_shape(wt, ['a/', 'a/b/hello', 'a/b/'])
255
inv = b.repository.get_revision_inventory(r4)
330
inv = b.repository.get_inventory(r4)
256
331
eq(inv['hello-id'].revision, r4)
257
332
eq(inv['a-id'].revision, r1)
258
333
eq(inv['b-id'].revision, r3)
283
358
rev_ids.append(rev_id)
284
359
wt.commit(message='rev %d' % (i+1),
286
eq = self.assertEquals
287
eq(b.revision_history(), rev_ids)
288
361
for i in range(4):
289
anc = b.repository.get_ancestry(rev_ids[i])
290
eq(anc, [None] + rev_ids[:i+1])
362
self.assertThat(rev_ids[:i+1],
363
MatchesAncestry(b.repository, rev_ids[i]))
292
365
def test_commit_new_subdir_child_selective(self):
293
366
wt = self.make_branch_and_tree('.')
348
420
wt = self.make_branch_and_tree('.')
349
421
branch = wt.branch
350
422
wt.commit("base", allow_pointless=True, rev_id='A')
351
self.failIf(branch.repository.has_signature_for_revision_id('A'))
423
self.assertFalse(branch.repository.has_signature_for_revision_id('A'))
353
425
from bzrlib.testament import Testament
354
426
# monkey patch gpg signing mechanism
355
427
bzrlib.gpg.GPGStrategy = bzrlib.gpg.LoopbackGPGStrategy
356
commit.Commit(config=MustSignConfig(branch)).commit(message="base",
357
allow_pointless=True,
428
conf = config.MemoryStack('''
429
gpg_signing_command=cat -
430
create_signatures=always
432
commit.Commit(config_stack=conf).commit(
433
message="base", allow_pointless=True, rev_id='B',
361
436
return bzrlib.gpg.LoopbackGPGStrategy(None).sign(text)
362
437
self.assertEqual(sign(Testament.from_revision(branch.repository,
363
'B').as_short_text()),
438
'B').as_short_text()),
364
439
branch.repository.get_signature_text('B'))
366
441
bzrlib.gpg.GPGStrategy = oldstrategy
372
447
wt = self.make_branch_and_tree('.')
373
448
branch = wt.branch
374
449
wt.commit("base", allow_pointless=True, rev_id='A')
375
self.failIf(branch.repository.has_signature_for_revision_id('A'))
450
self.assertFalse(branch.repository.has_signature_for_revision_id('A'))
377
from bzrlib.testament import Testament
378
452
# monkey patch gpg signing mechanism
379
453
bzrlib.gpg.GPGStrategy = bzrlib.gpg.DisabledGPGStrategy
380
config = MustSignConfig(branch)
454
conf = config.MemoryStack('''
455
gpg_signing_command=cat -
456
create_signatures=always
381
458
self.assertRaises(SigningFailed,
382
commit.Commit(config=config).commit,
459
commit.Commit(config_stack=conf).commit,
384
461
allow_pointless=True,
387
464
branch = Branch.open(self.get_url('.'))
388
self.assertEqual(branch.revision_history(), ['A'])
389
self.failIf(branch.repository.has_revision('B'))
465
self.assertEqual(branch.last_revision(), 'A')
466
self.assertFalse(branch.repository.has_revision('B'))
391
468
bzrlib.gpg.GPGStrategy = oldstrategy
427
503
bound = master.sprout('bound')
428
504
wt = bound.open_workingtree()
429
505
wt.branch.set_bound_location(os.path.realpath('master'))
431
orig_default = lockdir._DEFAULT_TIMEOUT_SECONDS
432
506
master_branch.lock_write()
434
lockdir._DEFAULT_TIMEOUT_SECONDS = 1
435
508
self.assertRaises(LockContention, wt.commit, 'silly')
437
lockdir._DEFAULT_TIMEOUT_SECONDS = orig_default
438
510
master_branch.unlock()
440
512
def test_commit_bound_merge(self):
517
589
this_tree.merge_from_branch(other_tree.branch)
518
590
reporter = CapturingReporter()
519
591
this_tree.commit('do the commit', reporter=reporter)
521
('change', 'unchanged', ''),
522
('change', 'unchanged', 'dirtoleave'),
523
('change', 'unchanged', 'filetoleave'),
524
593
('change', 'modified', 'filetomodify'),
525
594
('change', 'added', 'newdir'),
526
595
('change', 'added', 'newfile'),
527
596
('renamed', 'renamed', 'dirtorename', 'renameddir'),
597
('renamed', 'renamed', 'filetorename', 'renamedfile'),
528
598
('renamed', 'renamed', 'dirtoreparent', 'renameddir/reparenteddir'),
529
599
('renamed', 'renamed', 'filetoreparent', 'renameddir/reparentedfile'),
530
('renamed', 'renamed', 'filetorename', 'renamedfile'),
531
600
('deleted', 'dirtoremove'),
532
601
('deleted', 'filetoremove'),
603
result = set(reporter.calls)
604
missing = expected - result
605
new = result - expected
606
self.assertEqual((set(), set()), (missing, new))
536
608
def test_commit_removals_respects_filespec(self):
537
609
"""Commit respects the specified_files for removals."""
628
699
def test_commit_unversioned_specified(self):
629
700
"""Commit should raise if specified files isn't in basis or worktree"""
630
701
tree = self.make_branch_and_tree('.')
631
self.assertRaises(errors.PathsNotVersionedError, tree.commit,
702
self.assertRaises(errors.PathsNotVersionedError, tree.commit,
632
703
'message', specific_files=['bogus'])
634
705
class Callback(object):
636
707
def __init__(self, message, testcase):
637
708
self.called = False
638
709
self.message = message
676
747
cb = self.Callback(u'commit 2', self)
677
748
repository = tree.branch.repository
678
749
# simulate network failure
679
def raise_(self, arg, arg2):
750
def raise_(self, arg, arg2, arg3=None, arg4=None):
680
751
raise errors.NoSuchFile('foo')
681
752
repository.add_inventory = raise_
753
repository.add_inventory_by_delta = raise_
682
754
self.assertRaises(errors.NoSuchFile, tree.commit, message_callback=cb)
683
755
self.assertFalse(cb.called)
696
768
self.assertEqual('Selected-file commit of merges is not supported'
697
769
' yet: files bar, baz', str(err))
771
def test_commit_ordering(self):
772
"""Test of corner-case commit ordering error"""
773
tree = self.make_branch_and_tree('.')
774
self.build_tree(['a/', 'a/z/', 'a/c/', 'a/z/x', 'a/z/y'])
775
tree.add(['a/', 'a/z/', 'a/c/', 'a/z/x', 'a/z/y'])
777
self.build_tree(['a/c/d/'])
779
tree.rename_one('a/z/x', 'a/c/d/x')
780
tree.commit('test', specific_files=['a/z/y'])
699
782
def test_commit_no_author(self):
700
783
"""The default kwarg author in MutableTree.commit should not add
701
784
the 'author' revision property.
704
787
rev_id = tree.commit('commit 1')
705
788
rev = tree.branch.repository.get_revision(rev_id)
706
789
self.assertFalse('author' in rev.properties)
790
self.assertFalse('authors' in rev.properties)
708
792
def test_commit_author(self):
709
793
"""Passing a non-empty author kwarg to MutableTree.commit should add
710
794
the 'author' revision property.
712
796
tree = self.make_branch_and_tree('foo')
713
rev_id = tree.commit('commit 1', author='John Doe <jdoe@example.com>')
797
rev_id = self.callDeprecated(['The parameter author was '
798
'deprecated in version 1.13. Use authors instead'],
799
tree.commit, 'commit 1', author='John Doe <jdoe@example.com>')
714
800
rev = tree.branch.repository.get_revision(rev_id)
715
801
self.assertEqual('John Doe <jdoe@example.com>',
716
rev.properties['author'])
802
rev.properties['authors'])
803
self.assertFalse('author' in rev.properties)
805
def test_commit_empty_authors_list(self):
806
"""Passing an empty list to authors shouldn't add the property."""
807
tree = self.make_branch_and_tree('foo')
808
rev_id = tree.commit('commit 1', authors=[])
809
rev = tree.branch.repository.get_revision(rev_id)
810
self.assertFalse('author' in rev.properties)
811
self.assertFalse('authors' in rev.properties)
813
def test_multiple_authors(self):
814
tree = self.make_branch_and_tree('foo')
815
rev_id = tree.commit('commit 1',
816
authors=['John Doe <jdoe@example.com>',
817
'Jane Rey <jrey@example.com>'])
818
rev = tree.branch.repository.get_revision(rev_id)
819
self.assertEqual('John Doe <jdoe@example.com>\n'
820
'Jane Rey <jrey@example.com>', rev.properties['authors'])
821
self.assertFalse('author' in rev.properties)
823
def test_author_and_authors_incompatible(self):
824
tree = self.make_branch_and_tree('foo')
825
self.assertRaises(AssertionError, tree.commit, 'commit 1',
826
authors=['John Doe <jdoe@example.com>',
827
'Jane Rey <jrey@example.com>'],
828
author="Jack Me <jme@example.com>")
830
def test_author_with_newline_rejected(self):
831
tree = self.make_branch_and_tree('foo')
832
self.assertRaises(AssertionError, tree.commit, 'commit 1',
833
authors=['John\nDoe <jdoe@example.com>'])
835
def test_commit_with_checkout_and_branch_sharing_repo(self):
836
repo = self.make_repository('repo', shared=True)
837
# make_branch_and_tree ignores shared repos
838
branch = bzrdir.BzrDir.create_branch_convenience('repo/branch')
839
tree2 = branch.create_checkout('repo/tree2')
840
tree2.commit('message', rev_id='rev1')
841
self.assertTrue(tree2.branch.repository.has_revision('rev1'))