~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_commit.py

[merge] robert's knit-performance work

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2011 Canonical Ltd
2
 
#
 
1
# Copyright (C) 2005 by 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
17
 
18
18
import os
19
19
 
20
20
import bzrlib
21
 
from bzrlib import (
22
 
    bzrdir,
23
 
    errors,
24
 
    )
 
21
from bzrlib.tests import TestCaseWithTransport
25
22
from bzrlib.branch import Branch
26
 
from bzrlib.bzrdir import BzrDirMetaFormat1
27
 
from bzrlib.commit import Commit, NullCommitReporter
 
23
from bzrlib.workingtree import WorkingTree
 
24
from bzrlib.commit import Commit
28
25
from bzrlib.config import BranchConfig
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
 
26
from bzrlib.errors import PointlessCommit, BzrError, SigningFailed
43
27
 
44
28
 
45
29
# TODO: Test commit with some added, and added-but-missing files
59
43
        return "bzrlib.ahook bzrlib.ahook"
60
44
 
61
45
 
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
 
 
85
46
class TestCommit(TestCaseWithTransport):
86
47
 
87
48
    def test_simple_commit(self):
103
64
        eq(rev.message, 'add hello')
104
65
 
105
66
        tree1 = b.repository.revision_tree(rh[0])
106
 
        tree1.lock_read()
107
67
        text = tree1.get_file_text(file_id)
108
 
        tree1.unlock()
109
 
        self.assertEqual('hello world', text)
 
68
        eq(text, 'hello world')
110
69
 
111
70
        tree2 = b.repository.revision_tree(rh[1])
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"""
 
71
        eq(tree2.get_file_text(file_id), 'version 2')
 
72
 
 
73
    def test_delete_commit(self):
 
74
        """Test a commit with a deleted file"""
157
75
        wt = self.make_branch_and_tree('.')
158
76
        b = wt.branch
159
77
        file('hello', 'w').write('hello world')
161
79
        wt.commit(message='add hello')
162
80
 
163
81
        os.remove('hello')
164
 
        reporter = CapturingReporter()
165
 
        wt.commit('removed hello', rev_id='rev2', reporter=reporter)
166
 
        self.assertEquals(
167
 
            [('missing', u'hello'), ('deleted', u'hello')],
168
 
            reporter.calls)
 
82
        wt.commit('removed hello', rev_id='rev2')
169
83
 
170
84
        tree = b.repository.revision_tree('rev2')
171
85
        self.assertFalse(tree.has_id('hello-id'))
172
86
 
173
 
    def test_partial_commit_move(self):
174
 
        """Test a partial commit where a file was renamed but not committed.
175
 
 
176
 
        https://bugs.launchpad.net/bzr/+bug/83039
177
 
 
178
 
        If not handled properly, commit will try to snapshot
179
 
        dialog.py with olive/ as a parent, while
180
 
        olive/ has not been snapshotted yet.
181
 
        """
182
 
        wt = self.make_branch_and_tree('.')
183
 
        b = wt.branch
184
 
        self.build_tree(['annotate/', 'annotate/foo.py',
185
 
                         'olive/', 'olive/dialog.py'
186
 
                        ])
187
 
        wt.add(['annotate', 'olive', 'annotate/foo.py', 'olive/dialog.py'])
188
 
        wt.commit(message='add files')
189
 
        wt.rename_one("olive/dialog.py", "aaa")
190
 
        self.build_tree_contents([('annotate/foo.py', 'modified\n')])
191
 
        wt.commit('renamed hello', specific_files=["annotate"])
192
 
 
193
87
    def test_pointless_commit(self):
194
88
        """Commit refuses unless there are changes or it's forced."""
195
89
        wt = self.make_branch_and_tree('.')
203
97
                          message='fails',
204
98
                          allow_pointless=False)
205
99
        self.assertEquals(b.revno(), 1)
206
 
 
 
100
        
207
101
    def test_commit_empty(self):
208
102
        """Commiting an empty tree works."""
209
103
        wt = self.make_branch_and_tree('.')
226
120
              ['hello-id', 'buongia-id'])
227
121
        wt.commit(message='add files',
228
122
                 rev_id='test@rev-1')
229
 
 
 
123
        
230
124
        os.remove('hello')
231
125
        file('buongia', 'w').write('new text')
232
126
        wt.commit(message='update text',
243
137
        eq(b.revno(), 3)
244
138
 
245
139
        tree2 = b.repository.revision_tree('test@rev-2')
246
 
        tree2.lock_read()
247
 
        self.addCleanup(tree2.unlock)
248
140
        self.assertTrue(tree2.has_filename('hello'))
249
141
        self.assertEquals(tree2.get_file_text('hello-id'), 'hello')
250
142
        self.assertEquals(tree2.get_file_text('buongia-id'), 'new text')
251
 
 
 
143
        
252
144
        tree3 = b.repository.revision_tree('test@rev-3')
253
 
        tree3.lock_read()
254
 
        self.addCleanup(tree3.unlock)
255
145
        self.assertFalse(tree3.has_filename('hello'))
256
146
        self.assertEquals(tree3.get_file_text('buongia-id'), 'new text')
257
147
 
268
158
 
269
159
        eq = self.assertEquals
270
160
        tree1 = b.repository.revision_tree('test@rev-1')
271
 
        tree1.lock_read()
272
 
        self.addCleanup(tree1.unlock)
273
161
        eq(tree1.id2path('hello-id'), 'hello')
274
162
        eq(tree1.get_file_text('hello-id'), 'contents of hello\n')
275
163
        self.assertFalse(tree1.has_filename('fruity'))
276
 
        self.check_tree_shape(tree1, ['hello'])
277
 
        eq(tree1.get_file_revision('hello-id'), 'test@rev-1')
 
164
        self.check_inventory_shape(tree1.inventory, ['hello'])
 
165
        ie = tree1.inventory['hello-id']
 
166
        eq(ie.revision, 'test@rev-1')
278
167
 
279
168
        tree2 = b.repository.revision_tree('test@rev-2')
280
 
        tree2.lock_read()
281
 
        self.addCleanup(tree2.unlock)
282
169
        eq(tree2.id2path('hello-id'), 'fruity')
283
170
        eq(tree2.get_file_text('hello-id'), 'contents of hello\n')
284
 
        self.check_tree_shape(tree2, ['fruity'])
285
 
        eq(tree2.get_file_revision('hello-id'), 'test@rev-2')
 
171
        self.check_inventory_shape(tree2.inventory, ['fruity'])
 
172
        ie = tree2.inventory['hello-id']
 
173
        eq(ie.revision, 'test@rev-2')
286
174
 
287
175
    def test_reused_rev_id(self):
288
176
        """Test that a revision id cannot be reused in a branch"""
307
195
        wt.move(['hello'], 'a')
308
196
        r2 = 'test@rev-2'
309
197
        wt.commit('two', rev_id=r2, allow_pointless=False)
310
 
        wt.lock_read()
311
 
        try:
312
 
            self.check_tree_shape(wt, ['a/', 'a/hello', 'b/'])
313
 
        finally:
314
 
            wt.unlock()
 
198
        self.check_inventory_shape(wt.read_working_inventory(),
 
199
                                   ['a', 'a/hello', 'b'])
315
200
 
316
201
        wt.move(['b'], 'a')
317
202
        r3 = 'test@rev-3'
318
203
        wt.commit('three', rev_id=r3, allow_pointless=False)
319
 
        wt.lock_read()
320
 
        try:
321
 
            self.check_tree_shape(wt,
322
 
                                       ['a/', 'a/hello', 'a/b/'])
323
 
            self.check_tree_shape(b.repository.revision_tree(r3),
324
 
                                       ['a/', 'a/hello', 'a/b/'])
325
 
        finally:
326
 
            wt.unlock()
 
204
        self.check_inventory_shape(wt.read_working_inventory(),
 
205
                                   ['a', 'a/hello', 'a/b'])
 
206
        self.check_inventory_shape(b.repository.get_revision_inventory(r3),
 
207
                                   ['a', 'a/hello', 'a/b'])
327
208
 
328
209
        wt.move(['a/hello'], 'a/b')
329
210
        r4 = 'test@rev-4'
330
211
        wt.commit('four', rev_id=r4, allow_pointless=False)
331
 
        wt.lock_read()
332
 
        try:
333
 
            self.check_tree_shape(wt, ['a/', 'a/b/hello', 'a/b/'])
334
 
        finally:
335
 
            wt.unlock()
 
212
        self.check_inventory_shape(wt.read_working_inventory(),
 
213
                                   ['a', 'a/b/hello', 'a/b'])
336
214
 
337
 
        inv = b.repository.get_inventory(r4)
 
215
        inv = b.repository.get_revision_inventory(r4)
338
216
        eq(inv['hello-id'].revision, r4)
339
217
        eq(inv['a-id'].revision, r1)
340
218
        eq(inv['b-id'].revision, r3)
341
 
 
 
219
        
342
220
    def test_removed_commit(self):
343
221
        """Commit with a removed file"""
344
222
        wt = self.make_branch_and_tree('.')
368
246
        eq = self.assertEquals
369
247
        eq(b.revision_history(), rev_ids)
370
248
        for i in range(4):
371
 
            self.assertThat(rev_ids[:i+1],
372
 
                MatchesAncestry(b.repository, rev_ids[i]))
 
249
            anc = b.repository.get_ancestry(rev_ids[i])
 
250
            eq(anc, [None] + rev_ids[:i+1])
373
251
 
374
252
    def test_commit_new_subdir_child_selective(self):
375
253
        wt = self.make_branch_and_tree('.')
398
276
    def test_strict_commit_without_unknowns(self):
399
277
        """Try and commit with no unknown files and strict = True,
400
278
        should work."""
 
279
        from bzrlib.errors import StrictCommitFailed
401
280
        wt = self.make_branch_and_tree('.')
402
281
        b = wt.branch
403
282
        file('hello', 'w').write('hello world')
429
308
        wt = self.make_branch_and_tree('.')
430
309
        branch = wt.branch
431
310
        wt.commit("base", allow_pointless=True, rev_id='A')
432
 
        self.assertFalse(branch.repository.has_signature_for_revision_id('A'))
 
311
        self.failIf(branch.repository.has_signature_for_revision_id('A'))
433
312
        try:
434
313
            from bzrlib.testament import Testament
435
314
            # monkey patch gpg signing mechanism
438
317
                                                      allow_pointless=True,
439
318
                                                      rev_id='B',
440
319
                                                      working_tree=wt)
441
 
            def sign(text):
442
 
                return bzrlib.gpg.LoopbackGPGStrategy(None).sign(text)
443
 
            self.assertEqual(sign(Testament.from_revision(branch.repository,
444
 
                             'B').as_short_text()),
 
320
            self.assertEqual(Testament.from_revision(branch.repository,
 
321
                             'B').as_short_text(),
445
322
                             branch.repository.get_signature_text('B'))
446
323
        finally:
447
324
            bzrlib.gpg.GPGStrategy = oldstrategy
453
330
        wt = self.make_branch_and_tree('.')
454
331
        branch = wt.branch
455
332
        wt.commit("base", allow_pointless=True, rev_id='A')
456
 
        self.assertFalse(branch.repository.has_signature_for_revision_id('A'))
 
333
        self.failIf(branch.repository.has_signature_for_revision_id('A'))
457
334
        try:
 
335
            from bzrlib.testament import Testament
458
336
            # monkey patch gpg signing mechanism
459
337
            bzrlib.gpg.GPGStrategy = bzrlib.gpg.DisabledGPGStrategy
460
338
            config = MustSignConfig(branch)
466
344
                              working_tree=wt)
467
345
            branch = Branch.open(self.get_url('.'))
468
346
            self.assertEqual(branch.revision_history(), ['A'])
469
 
            self.assertFalse(branch.repository.has_revision('B'))
 
347
            self.failIf(branch.repository.has_revision('B'))
470
348
        finally:
471
349
            bzrlib.gpg.GPGStrategy = oldstrategy
472
350
 
498
376
                         wt.branch.repository.get_revision(
499
377
                            wt.branch.last_revision()).properties)
500
378
 
501
 
    def test_safe_master_lock(self):
502
 
        os.mkdir('master')
503
 
        master = BzrDirMetaFormat1().initialize('master')
504
 
        master.create_repository()
505
 
        master_branch = master.create_branch()
506
 
        master.create_workingtree()
507
 
        bound = master.sprout('bound')
508
 
        wt = bound.open_workingtree()
509
 
        wt.branch.set_bound_location(os.path.realpath('master'))
510
 
        master_branch.lock_write()
511
 
        try:
512
 
            self.assertRaises(LockContention, wt.commit, 'silly')
513
 
        finally:
514
 
            master_branch.unlock()
515
 
 
516
 
    def test_commit_bound_merge(self):
517
 
        # see bug #43959; commit of a merge in a bound branch fails to push
518
 
        # the new commit into the master
519
 
        master_branch = self.make_branch('master')
520
 
        bound_tree = self.make_branch_and_tree('bound')
521
 
        bound_tree.branch.bind(master_branch)
522
 
 
523
 
        self.build_tree_contents([('bound/content_file', 'initial contents\n')])
524
 
        bound_tree.add(['content_file'])
525
 
        bound_tree.commit(message='woo!')
526
 
 
527
 
        other_bzrdir = master_branch.bzrdir.sprout('other')
528
 
        other_tree = other_bzrdir.open_workingtree()
529
 
 
530
 
        # do a commit to the other branch changing the content file so
531
 
        # that our commit after merging will have a merged revision in the
532
 
        # content file history.
533
 
        self.build_tree_contents([('other/content_file', 'change in other\n')])
534
 
        other_tree.commit('change in other')
535
 
 
536
 
        # do a merge into the bound branch from other, and then change the
537
 
        # content file locally to force a new revision (rather than using the
538
 
        # revision from other). This forces extra processing in commit.
539
 
        bound_tree.merge_from_branch(other_tree.branch)
540
 
        self.build_tree_contents([('bound/content_file', 'change in bound\n')])
541
 
 
542
 
        # before #34959 was fixed, this failed with 'revision not present in
543
 
        # weave' when trying to implicitly push from the bound branch to the master
544
 
        bound_tree.commit(message='commit of merge in bound tree')
545
 
 
546
 
    def test_commit_reporting_after_merge(self):
547
 
        # when doing a commit of a merge, the reporter needs to still
548
 
        # be called for each item that is added/removed/deleted.
549
 
        this_tree = self.make_branch_and_tree('this')
550
 
        # we need a bunch of files and dirs, to perform one action on each.
551
 
        self.build_tree([
552
 
            'this/dirtorename/',
553
 
            'this/dirtoreparent/',
554
 
            'this/dirtoleave/',
555
 
            'this/dirtoremove/',
556
 
            'this/filetoreparent',
557
 
            'this/filetorename',
558
 
            'this/filetomodify',
559
 
            'this/filetoremove',
560
 
            'this/filetoleave']
561
 
            )
562
 
        this_tree.add([
563
 
            'dirtorename',
564
 
            'dirtoreparent',
565
 
            'dirtoleave',
566
 
            'dirtoremove',
567
 
            'filetoreparent',
568
 
            'filetorename',
569
 
            'filetomodify',
570
 
            'filetoremove',
571
 
            'filetoleave']
572
 
            )
573
 
        this_tree.commit('create_files')
574
 
        other_dir = this_tree.bzrdir.sprout('other')
575
 
        other_tree = other_dir.open_workingtree()
576
 
        other_tree.lock_write()
577
 
        # perform the needed actions on the files and dirs.
578
 
        try:
579
 
            other_tree.rename_one('dirtorename', 'renameddir')
580
 
            other_tree.rename_one('dirtoreparent', 'renameddir/reparenteddir')
581
 
            other_tree.rename_one('filetorename', 'renamedfile')
582
 
            other_tree.rename_one('filetoreparent', 'renameddir/reparentedfile')
583
 
            other_tree.remove(['dirtoremove', 'filetoremove'])
584
 
            self.build_tree_contents([
585
 
                ('other/newdir/', ),
586
 
                ('other/filetomodify', 'new content'),
587
 
                ('other/newfile', 'new file content')])
588
 
            other_tree.add('newfile')
589
 
            other_tree.add('newdir/')
590
 
            other_tree.commit('modify all sample files and dirs.')
591
 
        finally:
592
 
            other_tree.unlock()
593
 
        this_tree.merge_from_branch(other_tree.branch)
594
 
        reporter = CapturingReporter()
595
 
        this_tree.commit('do the commit', reporter=reporter)
596
 
        expected = set([
597
 
            ('change', 'modified', 'filetomodify'),
598
 
            ('change', 'added', 'newdir'),
599
 
            ('change', 'added', 'newfile'),
600
 
            ('renamed', 'renamed', 'dirtorename', 'renameddir'),
601
 
            ('renamed', 'renamed', 'filetorename', 'renamedfile'),
602
 
            ('renamed', 'renamed', 'dirtoreparent', 'renameddir/reparenteddir'),
603
 
            ('renamed', 'renamed', 'filetoreparent', 'renameddir/reparentedfile'),
604
 
            ('deleted', 'dirtoremove'),
605
 
            ('deleted', 'filetoremove'),
606
 
            ])
607
 
        result = set(reporter.calls)
608
 
        missing = expected - result
609
 
        new = result - expected
610
 
        self.assertEqual((set(), set()), (missing, new))
611
 
 
612
 
    def test_commit_removals_respects_filespec(self):
613
 
        """Commit respects the specified_files for removals."""
614
 
        tree = self.make_branch_and_tree('.')
615
 
        self.build_tree(['a', 'b'])
616
 
        tree.add(['a', 'b'])
617
 
        tree.commit('added a, b')
618
 
        tree.remove(['a', 'b'])
619
 
        tree.commit('removed a', specific_files='a')
620
 
        basis = tree.basis_tree()
621
 
        tree.lock_read()
622
 
        try:
623
 
            self.assertIs(None, basis.path2id('a'))
624
 
            self.assertFalse(basis.path2id('b') is None)
625
 
        finally:
626
 
            tree.unlock()
627
 
 
628
 
    def test_commit_saves_1ms_timestamp(self):
629
 
        """Passing in a timestamp is saved with 1ms resolution"""
630
 
        tree = self.make_branch_and_tree('.')
631
 
        self.build_tree(['a'])
632
 
        tree.add('a')
633
 
        tree.commit('added a', timestamp=1153248633.4186721, timezone=0,
634
 
                    rev_id='a1')
635
 
 
636
 
        rev = tree.branch.repository.get_revision('a1')
637
 
        self.assertEqual(1153248633.419, rev.timestamp)
638
 
 
639
 
    def test_commit_has_1ms_resolution(self):
640
 
        """Allowing commit to generate the timestamp also has 1ms resolution"""
641
 
        tree = self.make_branch_and_tree('.')
642
 
        self.build_tree(['a'])
643
 
        tree.add('a')
644
 
        tree.commit('added a', rev_id='a1')
645
 
 
646
 
        rev = tree.branch.repository.get_revision('a1')
647
 
        timestamp = rev.timestamp
648
 
        timestamp_1ms = round(timestamp, 3)
649
 
        self.assertEqual(timestamp_1ms, timestamp)
650
 
 
651
 
    def assertBasisTreeKind(self, kind, tree, file_id):
652
 
        basis = tree.basis_tree()
653
 
        basis.lock_read()
654
 
        try:
655
 
            self.assertEqual(kind, basis.kind(file_id))
656
 
        finally:
657
 
            basis.unlock()
658
 
 
659
 
    def test_commit_kind_changes(self):
660
 
        self.requireFeature(SymlinkFeature)
661
 
        tree = self.make_branch_and_tree('.')
662
 
        os.symlink('target', 'name')
663
 
        tree.add('name', 'a-file-id')
664
 
        tree.commit('Added a symlink')
665
 
        self.assertBasisTreeKind('symlink', tree, 'a-file-id')
666
 
 
667
 
        os.unlink('name')
668
 
        self.build_tree(['name'])
669
 
        tree.commit('Changed symlink to file')
670
 
        self.assertBasisTreeKind('file', tree, 'a-file-id')
671
 
 
672
 
        os.unlink('name')
673
 
        os.symlink('target', 'name')
674
 
        tree.commit('file to symlink')
675
 
        self.assertBasisTreeKind('symlink', tree, 'a-file-id')
676
 
 
677
 
        os.unlink('name')
678
 
        os.mkdir('name')
679
 
        tree.commit('symlink to directory')
680
 
        self.assertBasisTreeKind('directory', tree, 'a-file-id')
681
 
 
682
 
        os.rmdir('name')
683
 
        os.symlink('target', 'name')
684
 
        tree.commit('directory to symlink')
685
 
        self.assertBasisTreeKind('symlink', tree, 'a-file-id')
686
 
 
687
 
        # prepare for directory <-> file tests
688
 
        os.unlink('name')
689
 
        os.mkdir('name')
690
 
        tree.commit('symlink to directory')
691
 
        self.assertBasisTreeKind('directory', tree, 'a-file-id')
692
 
 
693
 
        os.rmdir('name')
694
 
        self.build_tree(['name'])
695
 
        tree.commit('Changed directory to file')
696
 
        self.assertBasisTreeKind('file', tree, 'a-file-id')
697
 
 
698
 
        os.unlink('name')
699
 
        os.mkdir('name')
700
 
        tree.commit('file to directory')
701
 
        self.assertBasisTreeKind('directory', tree, 'a-file-id')
702
 
 
703
 
    def test_commit_unversioned_specified(self):
704
 
        """Commit should raise if specified files isn't in basis or worktree"""
705
 
        tree = self.make_branch_and_tree('.')
706
 
        self.assertRaises(errors.PathsNotVersionedError, tree.commit,
707
 
                          'message', specific_files=['bogus'])
708
 
 
709
 
    class Callback(object):
710
 
 
711
 
        def __init__(self, message, testcase):
712
 
            self.called = False
713
 
            self.message = message
714
 
            self.testcase = testcase
715
 
 
716
 
        def __call__(self, commit_obj):
717
 
            self.called = True
718
 
            self.testcase.assertTrue(isinstance(commit_obj, Commit))
719
 
            return self.message
720
 
 
721
 
    def test_commit_callback(self):
722
 
        """Commit should invoke a callback to get the message"""
723
 
 
724
 
        tree = self.make_branch_and_tree('.')
725
 
        try:
726
 
            tree.commit()
727
 
        except Exception, e:
728
 
            self.assertTrue(isinstance(e, BzrError))
729
 
            self.assertEqual('The message or message_callback keyword'
730
 
                             ' parameter is required for commit().', str(e))
731
 
        else:
732
 
            self.fail('exception not raised')
733
 
        cb = self.Callback(u'commit 1', self)
734
 
        tree.commit(message_callback=cb)
735
 
        self.assertTrue(cb.called)
736
 
        repository = tree.branch.repository
737
 
        message = repository.get_revision(tree.last_revision()).message
738
 
        self.assertEqual('commit 1', message)
739
 
 
740
 
    def test_no_callback_pointless(self):
741
 
        """Callback should not be invoked for pointless commit"""
742
 
        tree = self.make_branch_and_tree('.')
743
 
        cb = self.Callback(u'commit 2', self)
744
 
        self.assertRaises(PointlessCommit, tree.commit, message_callback=cb,
745
 
                          allow_pointless=False)
746
 
        self.assertFalse(cb.called)
747
 
 
748
 
    def test_no_callback_netfailure(self):
749
 
        """Callback should not be invoked if connectivity fails"""
750
 
        tree = self.make_branch_and_tree('.')
751
 
        cb = self.Callback(u'commit 2', self)
752
 
        repository = tree.branch.repository
753
 
        # simulate network failure
754
 
        def raise_(self, arg, arg2, arg3=None, arg4=None):
755
 
            raise errors.NoSuchFile('foo')
756
 
        repository.add_inventory = raise_
757
 
        repository.add_inventory_by_delta = raise_
758
 
        self.assertRaises(errors.NoSuchFile, tree.commit, message_callback=cb)
759
 
        self.assertFalse(cb.called)
760
 
 
761
 
    def test_selected_file_merge_commit(self):
762
 
        """Ensure the correct error is raised"""
763
 
        tree = self.make_branch_and_tree('foo')
764
 
        # pending merge would turn into a left parent
765
 
        tree.commit('commit 1')
766
 
        tree.add_parent_tree_id('example')
767
 
        self.build_tree(['foo/bar', 'foo/baz'])
768
 
        tree.add(['bar', 'baz'])
769
 
        err = self.assertRaises(errors.CannotCommitSelectedFileMerge,
770
 
            tree.commit, 'commit 2', specific_files=['bar', 'baz'])
771
 
        self.assertEqual(['bar', 'baz'], err.files)
772
 
        self.assertEqual('Selected-file commit of merges is not supported'
773
 
                         ' yet: files bar, baz', str(err))
774
 
 
775
 
    def test_commit_ordering(self):
776
 
        """Test of corner-case commit ordering error"""
777
 
        tree = self.make_branch_and_tree('.')
778
 
        self.build_tree(['a/', 'a/z/', 'a/c/', 'a/z/x', 'a/z/y'])
779
 
        tree.add(['a/', 'a/z/', 'a/c/', 'a/z/x', 'a/z/y'])
780
 
        tree.commit('setup')
781
 
        self.build_tree(['a/c/d/'])
782
 
        tree.add('a/c/d')
783
 
        tree.rename_one('a/z/x', 'a/c/d/x')
784
 
        tree.commit('test', specific_files=['a/z/y'])
785
 
 
786
 
    def test_commit_no_author(self):
787
 
        """The default kwarg author in MutableTree.commit should not add
788
 
        the 'author' revision property.
789
 
        """
790
 
        tree = self.make_branch_and_tree('foo')
791
 
        rev_id = tree.commit('commit 1')
792
 
        rev = tree.branch.repository.get_revision(rev_id)
793
 
        self.assertFalse('author' in rev.properties)
794
 
        self.assertFalse('authors' in rev.properties)
795
 
 
796
 
    def test_commit_author(self):
797
 
        """Passing a non-empty author kwarg to MutableTree.commit should add
798
 
        the 'author' revision property.
799
 
        """
800
 
        tree = self.make_branch_and_tree('foo')
801
 
        rev_id = self.callDeprecated(['The parameter author was '
802
 
                'deprecated in version 1.13. Use authors instead'],
803
 
                tree.commit, 'commit 1', author='John Doe <jdoe@example.com>')
804
 
        rev = tree.branch.repository.get_revision(rev_id)
805
 
        self.assertEqual('John Doe <jdoe@example.com>',
806
 
                         rev.properties['authors'])
807
 
        self.assertFalse('author' in rev.properties)
808
 
 
809
 
    def test_commit_empty_authors_list(self):
810
 
        """Passing an empty list to authors shouldn't add the property."""
811
 
        tree = self.make_branch_and_tree('foo')
812
 
        rev_id = tree.commit('commit 1', authors=[])
813
 
        rev = tree.branch.repository.get_revision(rev_id)
814
 
        self.assertFalse('author' in rev.properties)
815
 
        self.assertFalse('authors' in rev.properties)
816
 
 
817
 
    def test_multiple_authors(self):
818
 
        tree = self.make_branch_and_tree('foo')
819
 
        rev_id = tree.commit('commit 1',
820
 
                authors=['John Doe <jdoe@example.com>',
821
 
                         'Jane Rey <jrey@example.com>'])
822
 
        rev = tree.branch.repository.get_revision(rev_id)
823
 
        self.assertEqual('John Doe <jdoe@example.com>\n'
824
 
                'Jane Rey <jrey@example.com>', rev.properties['authors'])
825
 
        self.assertFalse('author' in rev.properties)
826
 
 
827
 
    def test_author_and_authors_incompatible(self):
828
 
        tree = self.make_branch_and_tree('foo')
829
 
        self.assertRaises(AssertionError, tree.commit, 'commit 1',
830
 
                authors=['John Doe <jdoe@example.com>',
831
 
                         'Jane Rey <jrey@example.com>'],
832
 
                author="Jack Me <jme@example.com>")
833
 
 
834
 
    def test_author_with_newline_rejected(self):
835
 
        tree = self.make_branch_and_tree('foo')
836
 
        self.assertRaises(AssertionError, tree.commit, 'commit 1',
837
 
                authors=['John\nDoe <jdoe@example.com>'])
838
 
 
839
 
    def test_commit_with_checkout_and_branch_sharing_repo(self):
840
 
        repo = self.make_repository('repo', shared=True)
841
 
        # make_branch_and_tree ignores shared repos
842
 
        branch = bzrdir.BzrDir.create_branch_convenience('repo/branch')
843
 
        tree2 = branch.create_checkout('repo/tree2')
844
 
        tree2.commit('message', rev_id='rev1')
845
 
        self.assertTrue(tree2.branch.repository.has_revision('rev1'))
 
379