/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_commit.py

(gz) Never raise KnownFailure in tests,
 use knownFailure method instead (Martin [gz])

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005 by Canonical Ltd
2
 
 
 
1
# Copyright (C) 2005-2011 Canonical Ltd
 
2
#
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.
7
 
 
 
7
#
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.
12
 
 
 
12
#
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
16
16
 
17
17
 
18
18
import os
19
19
 
20
20
import bzrlib
21
 
from bzrlib.tests import TestCaseWithTransport
 
21
from bzrlib import (
 
22
    bzrdir,
 
23
    errors,
 
24
    )
22
25
from bzrlib.branch import Branch
23
 
from bzrlib.bzrdir import BzrDir, BzrDirMetaFormat1
24
 
from bzrlib.workingtree import WorkingTree
25
 
from bzrlib.commit import Commit
 
26
from bzrlib.bzrdir import BzrDirMetaFormat1
 
27
from bzrlib.commit import Commit, NullCommitReporter
26
28
from bzrlib.config import BranchConfig
27
 
from bzrlib.errors import (PointlessCommit, BzrError, SigningFailed, 
28
 
                           LockContention)
 
29
from bzrlib.errors import (
 
30
    PointlessCommit,
 
31
    BzrError,
 
32
    SigningFailed,
 
33
    LockContention,
 
34
    )
 
35
from bzrlib.tests import (
 
36
    TestCaseWithTransport,
 
37
    test_foreign,
 
38
    )
 
39
from bzrlib.tests.features import (
 
40
    SymlinkFeature,
 
41
    )
 
42
from bzrlib.tests.matchers import MatchesAncestry
29
43
 
30
44
 
31
45
# TODO: Test commit with some added, and added-but-missing files
45
59
        return "bzrlib.ahook bzrlib.ahook"
46
60
 
47
61
 
 
62
class CapturingReporter(NullCommitReporter):
 
63
    """This reporter captures the calls made to it for evaluation later."""
 
64
 
 
65
    def __init__(self):
 
66
        # a list of the calls this received
 
67
        self.calls = []
 
68
 
 
69
    def snapshot_change(self, change, path):
 
70
        self.calls.append(('change', change, path))
 
71
 
 
72
    def deleted(self, file_id):
 
73
        self.calls.append(('deleted', file_id))
 
74
 
 
75
    def missing(self, path):
 
76
        self.calls.append(('missing', path))
 
77
 
 
78
    def renamed(self, change, old_path, new_path):
 
79
        self.calls.append(('renamed', change, old_path, new_path))
 
80
 
 
81
    def is_verbose(self):
 
82
        return True
 
83
 
 
84
 
48
85
class TestCommit(TestCaseWithTransport):
49
86
 
50
87
    def test_simple_commit(self):
66
103
        eq(rev.message, 'add hello')
67
104
 
68
105
        tree1 = b.repository.revision_tree(rh[0])
 
106
        tree1.lock_read()
69
107
        text = tree1.get_file_text(file_id)
70
 
        eq(text, 'hello world')
 
108
        tree1.unlock()
 
109
        self.assertEqual('hello world', text)
71
110
 
72
111
        tree2 = b.repository.revision_tree(rh[1])
73
 
        eq(tree2.get_file_text(file_id), 'version 2')
74
 
 
75
 
    def test_delete_commit(self):
76
 
        """Test a commit with a deleted file"""
 
112
        tree2.lock_read()
 
113
        text = tree2.get_file_text(file_id)
 
114
        tree2.unlock()
 
115
        self.assertEqual('version 2', text)
 
116
 
 
117
    def test_commit_lossy_native(self):
 
118
        """Attempt a lossy commit to a native branch."""
 
119
        wt = self.make_branch_and_tree('.')
 
120
        b = wt.branch
 
121
        file('hello', 'w').write('hello world')
 
122
        wt.add('hello')
 
123
        revid = wt.commit(message='add hello', rev_id='revid', lossy=True)
 
124
        self.assertEquals('revid', revid)
 
125
 
 
126
    def test_commit_lossy_foreign(self):
 
127
        """Attempt a lossy commit to a foreign branch."""
 
128
        test_foreign.register_dummy_foreign_for_test(self)
 
129
        wt = self.make_branch_and_tree('.',
 
130
            format=test_foreign.DummyForeignVcsDirFormat())
 
131
        b = wt.branch
 
132
        file('hello', 'w').write('hello world')
 
133
        wt.add('hello')
 
134
        revid = wt.commit(message='add hello', lossy=True,
 
135
            timestamp=1302659388, timezone=0)
 
136
        self.assertEquals('dummy-v1:1302659388.0-0-UNKNOWN', revid)
 
137
 
 
138
    def test_commit_bound_lossy_foreign(self):
 
139
        """Attempt a lossy commit to a bzr branch bound to a foreign branch."""
 
140
        test_foreign.register_dummy_foreign_for_test(self)
 
141
        foreign_branch = self.make_branch('foreign',
 
142
            format=test_foreign.DummyForeignVcsDirFormat())
 
143
        wt = foreign_branch.create_checkout("local")
 
144
        b = wt.branch
 
145
        file('local/hello', 'w').write('hello world')
 
146
        wt.add('hello')
 
147
        revid = wt.commit(message='add hello', lossy=True,
 
148
            timestamp=1302659388, timezone=0)
 
149
        self.assertEquals('dummy-v1:1302659388.0-0-0', revid)
 
150
        self.assertEquals('dummy-v1:1302659388.0-0-0',
 
151
            foreign_branch.last_revision())
 
152
        self.assertEquals('dummy-v1:1302659388.0-0-0',
 
153
            wt.branch.last_revision())
 
154
 
 
155
    def test_missing_commit(self):
 
156
        """Test a commit with a missing file"""
77
157
        wt = self.make_branch_and_tree('.')
78
158
        b = wt.branch
79
159
        file('hello', 'w').write('hello world')
86
166
        tree = b.repository.revision_tree('rev2')
87
167
        self.assertFalse(tree.has_id('hello-id'))
88
168
 
 
169
    def test_partial_commit_move(self):
 
170
        """Test a partial commit where a file was renamed but not committed.
 
171
 
 
172
        https://bugs.launchpad.net/bzr/+bug/83039
 
173
 
 
174
        If not handled properly, commit will try to snapshot
 
175
        dialog.py with olive/ as a parent, while
 
176
        olive/ has not been snapshotted yet.
 
177
        """
 
178
        wt = self.make_branch_and_tree('.')
 
179
        b = wt.branch
 
180
        self.build_tree(['annotate/', 'annotate/foo.py',
 
181
                         'olive/', 'olive/dialog.py'
 
182
                        ])
 
183
        wt.add(['annotate', 'olive', 'annotate/foo.py', 'olive/dialog.py'])
 
184
        wt.commit(message='add files')
 
185
        wt.rename_one("olive/dialog.py", "aaa")
 
186
        self.build_tree_contents([('annotate/foo.py', 'modified\n')])
 
187
        wt.commit('renamed hello', specific_files=["annotate"])
 
188
 
89
189
    def test_pointless_commit(self):
90
190
        """Commit refuses unless there are changes or it's forced."""
91
191
        wt = self.make_branch_and_tree('.')
99
199
                          message='fails',
100
200
                          allow_pointless=False)
101
201
        self.assertEquals(b.revno(), 1)
102
 
        
 
202
 
103
203
    def test_commit_empty(self):
104
204
        """Commiting an empty tree works."""
105
205
        wt = self.make_branch_and_tree('.')
122
222
              ['hello-id', 'buongia-id'])
123
223
        wt.commit(message='add files',
124
224
                 rev_id='test@rev-1')
125
 
        
 
225
 
126
226
        os.remove('hello')
127
227
        file('buongia', 'w').write('new text')
128
228
        wt.commit(message='update text',
139
239
        eq(b.revno(), 3)
140
240
 
141
241
        tree2 = b.repository.revision_tree('test@rev-2')
 
242
        tree2.lock_read()
 
243
        self.addCleanup(tree2.unlock)
142
244
        self.assertTrue(tree2.has_filename('hello'))
143
245
        self.assertEquals(tree2.get_file_text('hello-id'), 'hello')
144
246
        self.assertEquals(tree2.get_file_text('buongia-id'), 'new text')
145
 
        
 
247
 
146
248
        tree3 = b.repository.revision_tree('test@rev-3')
 
249
        tree3.lock_read()
 
250
        self.addCleanup(tree3.unlock)
147
251
        self.assertFalse(tree3.has_filename('hello'))
148
252
        self.assertEquals(tree3.get_file_text('buongia-id'), 'new text')
149
253
 
160
264
 
161
265
        eq = self.assertEquals
162
266
        tree1 = b.repository.revision_tree('test@rev-1')
 
267
        tree1.lock_read()
 
268
        self.addCleanup(tree1.unlock)
163
269
        eq(tree1.id2path('hello-id'), 'hello')
164
270
        eq(tree1.get_file_text('hello-id'), 'contents of hello\n')
165
271
        self.assertFalse(tree1.has_filename('fruity'))
166
 
        self.check_inventory_shape(tree1.inventory, ['hello'])
167
 
        ie = tree1.inventory['hello-id']
168
 
        eq(ie.revision, 'test@rev-1')
 
272
        self.check_tree_shape(tree1, ['hello'])
 
273
        eq(tree1.get_file_revision('hello-id'), 'test@rev-1')
169
274
 
170
275
        tree2 = b.repository.revision_tree('test@rev-2')
 
276
        tree2.lock_read()
 
277
        self.addCleanup(tree2.unlock)
171
278
        eq(tree2.id2path('hello-id'), 'fruity')
172
279
        eq(tree2.get_file_text('hello-id'), 'contents of hello\n')
173
 
        self.check_inventory_shape(tree2.inventory, ['fruity'])
174
 
        ie = tree2.inventory['hello-id']
175
 
        eq(ie.revision, 'test@rev-2')
 
280
        self.check_tree_shape(tree2, ['fruity'])
 
281
        eq(tree2.get_file_revision('hello-id'), 'test@rev-2')
176
282
 
177
283
    def test_reused_rev_id(self):
178
284
        """Test that a revision id cannot be reused in a branch"""
197
303
        wt.move(['hello'], 'a')
198
304
        r2 = 'test@rev-2'
199
305
        wt.commit('two', rev_id=r2, allow_pointless=False)
200
 
        self.check_inventory_shape(wt.read_working_inventory(),
201
 
                                   ['a', 'a/hello', 'b'])
 
306
        wt.lock_read()
 
307
        try:
 
308
            self.check_tree_shape(wt, ['a/', 'a/hello', 'b/'])
 
309
        finally:
 
310
            wt.unlock()
202
311
 
203
312
        wt.move(['b'], 'a')
204
313
        r3 = 'test@rev-3'
205
314
        wt.commit('three', rev_id=r3, allow_pointless=False)
206
 
        self.check_inventory_shape(wt.read_working_inventory(),
207
 
                                   ['a', 'a/hello', 'a/b'])
208
 
        self.check_inventory_shape(b.repository.get_revision_inventory(r3),
209
 
                                   ['a', 'a/hello', 'a/b'])
 
315
        wt.lock_read()
 
316
        try:
 
317
            self.check_tree_shape(wt,
 
318
                                       ['a/', 'a/hello', 'a/b/'])
 
319
            self.check_tree_shape(b.repository.revision_tree(r3),
 
320
                                       ['a/', 'a/hello', 'a/b/'])
 
321
        finally:
 
322
            wt.unlock()
210
323
 
211
324
        wt.move(['a/hello'], 'a/b')
212
325
        r4 = 'test@rev-4'
213
326
        wt.commit('four', rev_id=r4, allow_pointless=False)
214
 
        self.check_inventory_shape(wt.read_working_inventory(),
215
 
                                   ['a', 'a/b/hello', 'a/b'])
 
327
        wt.lock_read()
 
328
        try:
 
329
            self.check_tree_shape(wt, ['a/', 'a/b/hello', 'a/b/'])
 
330
        finally:
 
331
            wt.unlock()
216
332
 
217
 
        inv = b.repository.get_revision_inventory(r4)
 
333
        inv = b.repository.get_inventory(r4)
218
334
        eq(inv['hello-id'].revision, r4)
219
335
        eq(inv['a-id'].revision, r1)
220
336
        eq(inv['b-id'].revision, r3)
221
 
        
 
337
 
222
338
    def test_removed_commit(self):
223
339
        """Commit with a removed file"""
224
340
        wt = self.make_branch_and_tree('.')
248
364
        eq = self.assertEquals
249
365
        eq(b.revision_history(), rev_ids)
250
366
        for i in range(4):
251
 
            anc = b.repository.get_ancestry(rev_ids[i])
252
 
            eq(anc, [None] + rev_ids[:i+1])
 
367
            self.assertThat(rev_ids[:i+1],
 
368
                MatchesAncestry(b.repository, rev_ids[i]))
253
369
 
254
370
    def test_commit_new_subdir_child_selective(self):
255
371
        wt = self.make_branch_and_tree('.')
278
394
    def test_strict_commit_without_unknowns(self):
279
395
        """Try and commit with no unknown files and strict = True,
280
396
        should work."""
281
 
        from bzrlib.errors import StrictCommitFailed
282
397
        wt = self.make_branch_and_tree('.')
283
398
        b = wt.branch
284
399
        file('hello', 'w').write('hello world')
310
425
        wt = self.make_branch_and_tree('.')
311
426
        branch = wt.branch
312
427
        wt.commit("base", allow_pointless=True, rev_id='A')
313
 
        self.failIf(branch.repository.has_signature_for_revision_id('A'))
 
428
        self.assertFalse(branch.repository.has_signature_for_revision_id('A'))
314
429
        try:
315
430
            from bzrlib.testament import Testament
316
431
            # monkey patch gpg signing mechanism
319
434
                                                      allow_pointless=True,
320
435
                                                      rev_id='B',
321
436
                                                      working_tree=wt)
322
 
            self.assertEqual(Testament.from_revision(branch.repository,
323
 
                             'B').as_short_text(),
 
437
            def sign(text):
 
438
                return bzrlib.gpg.LoopbackGPGStrategy(None).sign(text)
 
439
            self.assertEqual(sign(Testament.from_revision(branch.repository,
 
440
                             'B').as_short_text()),
324
441
                             branch.repository.get_signature_text('B'))
325
442
        finally:
326
443
            bzrlib.gpg.GPGStrategy = oldstrategy
332
449
        wt = self.make_branch_and_tree('.')
333
450
        branch = wt.branch
334
451
        wt.commit("base", allow_pointless=True, rev_id='A')
335
 
        self.failIf(branch.repository.has_signature_for_revision_id('A'))
 
452
        self.assertFalse(branch.repository.has_signature_for_revision_id('A'))
336
453
        try:
337
 
            from bzrlib.testament import Testament
338
454
            # monkey patch gpg signing mechanism
339
455
            bzrlib.gpg.GPGStrategy = bzrlib.gpg.DisabledGPGStrategy
340
456
            config = MustSignConfig(branch)
346
462
                              working_tree=wt)
347
463
            branch = Branch.open(self.get_url('.'))
348
464
            self.assertEqual(branch.revision_history(), ['A'])
349
 
            self.failIf(branch.repository.has_revision('B'))
 
465
            self.assertFalse(branch.repository.has_revision('B'))
350
466
        finally:
351
467
            bzrlib.gpg.GPGStrategy = oldstrategy
352
468
 
392
508
            self.assertRaises(LockContention, wt.commit, 'silly')
393
509
        finally:
394
510
            master_branch.unlock()
 
511
 
 
512
    def test_commit_bound_merge(self):
 
513
        # see bug #43959; commit of a merge in a bound branch fails to push
 
514
        # the new commit into the master
 
515
        master_branch = self.make_branch('master')
 
516
        bound_tree = self.make_branch_and_tree('bound')
 
517
        bound_tree.branch.bind(master_branch)
 
518
 
 
519
        self.build_tree_contents([('bound/content_file', 'initial contents\n')])
 
520
        bound_tree.add(['content_file'])
 
521
        bound_tree.commit(message='woo!')
 
522
 
 
523
        other_bzrdir = master_branch.bzrdir.sprout('other')
 
524
        other_tree = other_bzrdir.open_workingtree()
 
525
 
 
526
        # do a commit to the other branch changing the content file so
 
527
        # that our commit after merging will have a merged revision in the
 
528
        # content file history.
 
529
        self.build_tree_contents([('other/content_file', 'change in other\n')])
 
530
        other_tree.commit('change in other')
 
531
 
 
532
        # do a merge into the bound branch from other, and then change the
 
533
        # content file locally to force a new revision (rather than using the
 
534
        # revision from other). This forces extra processing in commit.
 
535
        bound_tree.merge_from_branch(other_tree.branch)
 
536
        self.build_tree_contents([('bound/content_file', 'change in bound\n')])
 
537
 
 
538
        # before #34959 was fixed, this failed with 'revision not present in
 
539
        # weave' when trying to implicitly push from the bound branch to the master
 
540
        bound_tree.commit(message='commit of merge in bound tree')
 
541
 
 
542
    def test_commit_reporting_after_merge(self):
 
543
        # when doing a commit of a merge, the reporter needs to still
 
544
        # be called for each item that is added/removed/deleted.
 
545
        this_tree = self.make_branch_and_tree('this')
 
546
        # we need a bunch of files and dirs, to perform one action on each.
 
547
        self.build_tree([
 
548
            'this/dirtorename/',
 
549
            'this/dirtoreparent/',
 
550
            'this/dirtoleave/',
 
551
            'this/dirtoremove/',
 
552
            'this/filetoreparent',
 
553
            'this/filetorename',
 
554
            'this/filetomodify',
 
555
            'this/filetoremove',
 
556
            'this/filetoleave']
 
557
            )
 
558
        this_tree.add([
 
559
            'dirtorename',
 
560
            'dirtoreparent',
 
561
            'dirtoleave',
 
562
            'dirtoremove',
 
563
            'filetoreparent',
 
564
            'filetorename',
 
565
            'filetomodify',
 
566
            'filetoremove',
 
567
            'filetoleave']
 
568
            )
 
569
        this_tree.commit('create_files')
 
570
        other_dir = this_tree.bzrdir.sprout('other')
 
571
        other_tree = other_dir.open_workingtree()
 
572
        other_tree.lock_write()
 
573
        # perform the needed actions on the files and dirs.
 
574
        try:
 
575
            other_tree.rename_one('dirtorename', 'renameddir')
 
576
            other_tree.rename_one('dirtoreparent', 'renameddir/reparenteddir')
 
577
            other_tree.rename_one('filetorename', 'renamedfile')
 
578
            other_tree.rename_one('filetoreparent', 'renameddir/reparentedfile')
 
579
            other_tree.remove(['dirtoremove', 'filetoremove'])
 
580
            self.build_tree_contents([
 
581
                ('other/newdir/', ),
 
582
                ('other/filetomodify', 'new content'),
 
583
                ('other/newfile', 'new file content')])
 
584
            other_tree.add('newfile')
 
585
            other_tree.add('newdir/')
 
586
            other_tree.commit('modify all sample files and dirs.')
 
587
        finally:
 
588
            other_tree.unlock()
 
589
        this_tree.merge_from_branch(other_tree.branch)
 
590
        reporter = CapturingReporter()
 
591
        this_tree.commit('do the commit', reporter=reporter)
 
592
        expected = set([
 
593
            ('change', 'modified', 'filetomodify'),
 
594
            ('change', 'added', 'newdir'),
 
595
            ('change', 'added', 'newfile'),
 
596
            ('renamed', 'renamed', 'dirtorename', 'renameddir'),
 
597
            ('renamed', 'renamed', 'filetorename', 'renamedfile'),
 
598
            ('renamed', 'renamed', 'dirtoreparent', 'renameddir/reparenteddir'),
 
599
            ('renamed', 'renamed', 'filetoreparent', 'renameddir/reparentedfile'),
 
600
            ('deleted', 'dirtoremove'),
 
601
            ('deleted', 'filetoremove'),
 
602
            ])
 
603
        result = set(reporter.calls)
 
604
        missing = expected - result
 
605
        new = result - expected
 
606
        self.assertEqual((set(), set()), (missing, new))
 
607
 
 
608
    def test_commit_removals_respects_filespec(self):
 
609
        """Commit respects the specified_files for removals."""
 
610
        tree = self.make_branch_and_tree('.')
 
611
        self.build_tree(['a', 'b'])
 
612
        tree.add(['a', 'b'])
 
613
        tree.commit('added a, b')
 
614
        tree.remove(['a', 'b'])
 
615
        tree.commit('removed a', specific_files='a')
 
616
        basis = tree.basis_tree()
 
617
        tree.lock_read()
 
618
        try:
 
619
            self.assertIs(None, basis.path2id('a'))
 
620
            self.assertFalse(basis.path2id('b') is None)
 
621
        finally:
 
622
            tree.unlock()
 
623
 
 
624
    def test_commit_saves_1ms_timestamp(self):
 
625
        """Passing in a timestamp is saved with 1ms resolution"""
 
626
        tree = self.make_branch_and_tree('.')
 
627
        self.build_tree(['a'])
 
628
        tree.add('a')
 
629
        tree.commit('added a', timestamp=1153248633.4186721, timezone=0,
 
630
                    rev_id='a1')
 
631
 
 
632
        rev = tree.branch.repository.get_revision('a1')
 
633
        self.assertEqual(1153248633.419, rev.timestamp)
 
634
 
 
635
    def test_commit_has_1ms_resolution(self):
 
636
        """Allowing commit to generate the timestamp also has 1ms resolution"""
 
637
        tree = self.make_branch_and_tree('.')
 
638
        self.build_tree(['a'])
 
639
        tree.add('a')
 
640
        tree.commit('added a', rev_id='a1')
 
641
 
 
642
        rev = tree.branch.repository.get_revision('a1')
 
643
        timestamp = rev.timestamp
 
644
        timestamp_1ms = round(timestamp, 3)
 
645
        self.assertEqual(timestamp_1ms, timestamp)
 
646
 
 
647
    def assertBasisTreeKind(self, kind, tree, file_id):
 
648
        basis = tree.basis_tree()
 
649
        basis.lock_read()
 
650
        try:
 
651
            self.assertEqual(kind, basis.kind(file_id))
 
652
        finally:
 
653
            basis.unlock()
 
654
 
 
655
    def test_commit_kind_changes(self):
 
656
        self.requireFeature(SymlinkFeature)
 
657
        tree = self.make_branch_and_tree('.')
 
658
        os.symlink('target', 'name')
 
659
        tree.add('name', 'a-file-id')
 
660
        tree.commit('Added a symlink')
 
661
        self.assertBasisTreeKind('symlink', tree, 'a-file-id')
 
662
 
 
663
        os.unlink('name')
 
664
        self.build_tree(['name'])
 
665
        tree.commit('Changed symlink to file')
 
666
        self.assertBasisTreeKind('file', tree, 'a-file-id')
 
667
 
 
668
        os.unlink('name')
 
669
        os.symlink('target', 'name')
 
670
        tree.commit('file to symlink')
 
671
        self.assertBasisTreeKind('symlink', tree, 'a-file-id')
 
672
 
 
673
        os.unlink('name')
 
674
        os.mkdir('name')
 
675
        tree.commit('symlink to directory')
 
676
        self.assertBasisTreeKind('directory', tree, 'a-file-id')
 
677
 
 
678
        os.rmdir('name')
 
679
        os.symlink('target', 'name')
 
680
        tree.commit('directory to symlink')
 
681
        self.assertBasisTreeKind('symlink', tree, 'a-file-id')
 
682
 
 
683
        # prepare for directory <-> file tests
 
684
        os.unlink('name')
 
685
        os.mkdir('name')
 
686
        tree.commit('symlink to directory')
 
687
        self.assertBasisTreeKind('directory', tree, 'a-file-id')
 
688
 
 
689
        os.rmdir('name')
 
690
        self.build_tree(['name'])
 
691
        tree.commit('Changed directory to file')
 
692
        self.assertBasisTreeKind('file', tree, 'a-file-id')
 
693
 
 
694
        os.unlink('name')
 
695
        os.mkdir('name')
 
696
        tree.commit('file to directory')
 
697
        self.assertBasisTreeKind('directory', tree, 'a-file-id')
 
698
 
 
699
    def test_commit_unversioned_specified(self):
 
700
        """Commit should raise if specified files isn't in basis or worktree"""
 
701
        tree = self.make_branch_and_tree('.')
 
702
        self.assertRaises(errors.PathsNotVersionedError, tree.commit,
 
703
                          'message', specific_files=['bogus'])
 
704
 
 
705
    class Callback(object):
 
706
 
 
707
        def __init__(self, message, testcase):
 
708
            self.called = False
 
709
            self.message = message
 
710
            self.testcase = testcase
 
711
 
 
712
        def __call__(self, commit_obj):
 
713
            self.called = True
 
714
            self.testcase.assertTrue(isinstance(commit_obj, Commit))
 
715
            return self.message
 
716
 
 
717
    def test_commit_callback(self):
 
718
        """Commit should invoke a callback to get the message"""
 
719
 
 
720
        tree = self.make_branch_and_tree('.')
 
721
        try:
 
722
            tree.commit()
 
723
        except Exception, e:
 
724
            self.assertTrue(isinstance(e, BzrError))
 
725
            self.assertEqual('The message or message_callback keyword'
 
726
                             ' parameter is required for commit().', str(e))
 
727
        else:
 
728
            self.fail('exception not raised')
 
729
        cb = self.Callback(u'commit 1', self)
 
730
        tree.commit(message_callback=cb)
 
731
        self.assertTrue(cb.called)
 
732
        repository = tree.branch.repository
 
733
        message = repository.get_revision(tree.last_revision()).message
 
734
        self.assertEqual('commit 1', message)
 
735
 
 
736
    def test_no_callback_pointless(self):
 
737
        """Callback should not be invoked for pointless commit"""
 
738
        tree = self.make_branch_and_tree('.')
 
739
        cb = self.Callback(u'commit 2', self)
 
740
        self.assertRaises(PointlessCommit, tree.commit, message_callback=cb,
 
741
                          allow_pointless=False)
 
742
        self.assertFalse(cb.called)
 
743
 
 
744
    def test_no_callback_netfailure(self):
 
745
        """Callback should not be invoked if connectivity fails"""
 
746
        tree = self.make_branch_and_tree('.')
 
747
        cb = self.Callback(u'commit 2', self)
 
748
        repository = tree.branch.repository
 
749
        # simulate network failure
 
750
        def raise_(self, arg, arg2, arg3=None, arg4=None):
 
751
            raise errors.NoSuchFile('foo')
 
752
        repository.add_inventory = raise_
 
753
        repository.add_inventory_by_delta = raise_
 
754
        self.assertRaises(errors.NoSuchFile, tree.commit, message_callback=cb)
 
755
        self.assertFalse(cb.called)
 
756
 
 
757
    def test_selected_file_merge_commit(self):
 
758
        """Ensure the correct error is raised"""
 
759
        tree = self.make_branch_and_tree('foo')
 
760
        # pending merge would turn into a left parent
 
761
        tree.commit('commit 1')
 
762
        tree.add_parent_tree_id('example')
 
763
        self.build_tree(['foo/bar', 'foo/baz'])
 
764
        tree.add(['bar', 'baz'])
 
765
        err = self.assertRaises(errors.CannotCommitSelectedFileMerge,
 
766
            tree.commit, 'commit 2', specific_files=['bar', 'baz'])
 
767
        self.assertEqual(['bar', 'baz'], err.files)
 
768
        self.assertEqual('Selected-file commit of merges is not supported'
 
769
                         ' yet: files bar, baz', str(err))
 
770
 
 
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'])
 
776
        tree.commit('setup')
 
777
        self.build_tree(['a/c/d/'])
 
778
        tree.add('a/c/d')
 
779
        tree.rename_one('a/z/x', 'a/c/d/x')
 
780
        tree.commit('test', specific_files=['a/z/y'])
 
781
 
 
782
    def test_commit_no_author(self):
 
783
        """The default kwarg author in MutableTree.commit should not add
 
784
        the 'author' revision property.
 
785
        """
 
786
        tree = self.make_branch_and_tree('foo')
 
787
        rev_id = tree.commit('commit 1')
 
788
        rev = tree.branch.repository.get_revision(rev_id)
 
789
        self.assertFalse('author' in rev.properties)
 
790
        self.assertFalse('authors' in rev.properties)
 
791
 
 
792
    def test_commit_author(self):
 
793
        """Passing a non-empty author kwarg to MutableTree.commit should add
 
794
        the 'author' revision property.
 
795
        """
 
796
        tree = self.make_branch_and_tree('foo')
 
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>')
 
800
        rev = tree.branch.repository.get_revision(rev_id)
 
801
        self.assertEqual('John Doe <jdoe@example.com>',
 
802
                         rev.properties['authors'])
 
803
        self.assertFalse('author' in rev.properties)
 
804
 
 
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)
 
812
 
 
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)
 
822
 
 
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>")
 
829
 
 
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>'])
 
834
 
 
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'))