/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

  • Committer: Patch Queue Manager
  • Date: 2011-09-26 10:55:49 UTC
  • mfrom: (6165.1.1 trunk)
  • Revision ID: pqm@pqm.ubuntu.com-20110926105549-5ywzuwhv6proidxc
(vila) Merge 2.4 into trunk (Vincent Ladeuil)

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')
81
161
        wt.commit(message='add hello')
82
162
 
83
163
        os.remove('hello')
84
 
        wt.commit('removed hello', rev_id='rev2')
 
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)
85
169
 
86
170
        tree = b.repository.revision_tree('rev2')
87
171
        self.assertFalse(tree.has_id('hello-id'))
88
172
 
 
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
 
89
193
    def test_pointless_commit(self):
90
194
        """Commit refuses unless there are changes or it's forced."""
91
195
        wt = self.make_branch_and_tree('.')
99
203
                          message='fails',
100
204
                          allow_pointless=False)
101
205
        self.assertEquals(b.revno(), 1)
102
 
        
 
206
 
103
207
    def test_commit_empty(self):
104
208
        """Commiting an empty tree works."""
105
209
        wt = self.make_branch_and_tree('.')
122
226
              ['hello-id', 'buongia-id'])
123
227
        wt.commit(message='add files',
124
228
                 rev_id='test@rev-1')
125
 
        
 
229
 
126
230
        os.remove('hello')
127
231
        file('buongia', 'w').write('new text')
128
232
        wt.commit(message='update text',
139
243
        eq(b.revno(), 3)
140
244
 
141
245
        tree2 = b.repository.revision_tree('test@rev-2')
 
246
        tree2.lock_read()
 
247
        self.addCleanup(tree2.unlock)
142
248
        self.assertTrue(tree2.has_filename('hello'))
143
249
        self.assertEquals(tree2.get_file_text('hello-id'), 'hello')
144
250
        self.assertEquals(tree2.get_file_text('buongia-id'), 'new text')
145
 
        
 
251
 
146
252
        tree3 = b.repository.revision_tree('test@rev-3')
 
253
        tree3.lock_read()
 
254
        self.addCleanup(tree3.unlock)
147
255
        self.assertFalse(tree3.has_filename('hello'))
148
256
        self.assertEquals(tree3.get_file_text('buongia-id'), 'new text')
149
257
 
160
268
 
161
269
        eq = self.assertEquals
162
270
        tree1 = b.repository.revision_tree('test@rev-1')
 
271
        tree1.lock_read()
 
272
        self.addCleanup(tree1.unlock)
163
273
        eq(tree1.id2path('hello-id'), 'hello')
164
274
        eq(tree1.get_file_text('hello-id'), 'contents of hello\n')
165
275
        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')
 
276
        self.check_tree_shape(tree1, ['hello'])
 
277
        eq(tree1.get_file_revision('hello-id'), 'test@rev-1')
169
278
 
170
279
        tree2 = b.repository.revision_tree('test@rev-2')
 
280
        tree2.lock_read()
 
281
        self.addCleanup(tree2.unlock)
171
282
        eq(tree2.id2path('hello-id'), 'fruity')
172
283
        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')
 
284
        self.check_tree_shape(tree2, ['fruity'])
 
285
        eq(tree2.get_file_revision('hello-id'), 'test@rev-2')
176
286
 
177
287
    def test_reused_rev_id(self):
178
288
        """Test that a revision id cannot be reused in a branch"""
197
307
        wt.move(['hello'], 'a')
198
308
        r2 = 'test@rev-2'
199
309
        wt.commit('two', rev_id=r2, allow_pointless=False)
200
 
        self.check_inventory_shape(wt.read_working_inventory(),
201
 
                                   ['a', 'a/hello', 'b'])
 
310
        wt.lock_read()
 
311
        try:
 
312
            self.check_tree_shape(wt, ['a/', 'a/hello', 'b/'])
 
313
        finally:
 
314
            wt.unlock()
202
315
 
203
316
        wt.move(['b'], 'a')
204
317
        r3 = 'test@rev-3'
205
318
        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'])
 
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()
210
327
 
211
328
        wt.move(['a/hello'], 'a/b')
212
329
        r4 = 'test@rev-4'
213
330
        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'])
 
331
        wt.lock_read()
 
332
        try:
 
333
            self.check_tree_shape(wt, ['a/', 'a/b/hello', 'a/b/'])
 
334
        finally:
 
335
            wt.unlock()
216
336
 
217
 
        inv = b.repository.get_revision_inventory(r4)
 
337
        inv = b.repository.get_inventory(r4)
218
338
        eq(inv['hello-id'].revision, r4)
219
339
        eq(inv['a-id'].revision, r1)
220
340
        eq(inv['b-id'].revision, r3)
221
 
        
 
341
 
222
342
    def test_removed_commit(self):
223
343
        """Commit with a removed file"""
224
344
        wt = self.make_branch_and_tree('.')
248
368
        eq = self.assertEquals
249
369
        eq(b.revision_history(), rev_ids)
250
370
        for i in range(4):
251
 
            anc = b.repository.get_ancestry(rev_ids[i])
252
 
            eq(anc, [None] + rev_ids[:i+1])
 
371
            self.assertThat(rev_ids[:i+1],
 
372
                MatchesAncestry(b.repository, rev_ids[i]))
253
373
 
254
374
    def test_commit_new_subdir_child_selective(self):
255
375
        wt = self.make_branch_and_tree('.')
278
398
    def test_strict_commit_without_unknowns(self):
279
399
        """Try and commit with no unknown files and strict = True,
280
400
        should work."""
281
 
        from bzrlib.errors import StrictCommitFailed
282
401
        wt = self.make_branch_and_tree('.')
283
402
        b = wt.branch
284
403
        file('hello', 'w').write('hello world')
310
429
        wt = self.make_branch_and_tree('.')
311
430
        branch = wt.branch
312
431
        wt.commit("base", allow_pointless=True, rev_id='A')
313
 
        self.failIf(branch.repository.has_signature_for_revision_id('A'))
 
432
        self.assertFalse(branch.repository.has_signature_for_revision_id('A'))
314
433
        try:
315
434
            from bzrlib.testament import Testament
316
435
            # monkey patch gpg signing mechanism
319
438
                                                      allow_pointless=True,
320
439
                                                      rev_id='B',
321
440
                                                      working_tree=wt)
322
 
            self.assertEqual(Testament.from_revision(branch.repository,
323
 
                             'B').as_short_text(),
 
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()),
324
445
                             branch.repository.get_signature_text('B'))
325
446
        finally:
326
447
            bzrlib.gpg.GPGStrategy = oldstrategy
332
453
        wt = self.make_branch_and_tree('.')
333
454
        branch = wt.branch
334
455
        wt.commit("base", allow_pointless=True, rev_id='A')
335
 
        self.failIf(branch.repository.has_signature_for_revision_id('A'))
 
456
        self.assertFalse(branch.repository.has_signature_for_revision_id('A'))
336
457
        try:
337
 
            from bzrlib.testament import Testament
338
458
            # monkey patch gpg signing mechanism
339
459
            bzrlib.gpg.GPGStrategy = bzrlib.gpg.DisabledGPGStrategy
340
460
            config = MustSignConfig(branch)
346
466
                              working_tree=wt)
347
467
            branch = Branch.open(self.get_url('.'))
348
468
            self.assertEqual(branch.revision_history(), ['A'])
349
 
            self.failIf(branch.repository.has_revision('B'))
 
469
            self.assertFalse(branch.repository.has_revision('B'))
350
470
        finally:
351
471
            bzrlib.gpg.GPGStrategy = oldstrategy
352
472
 
392
512
            self.assertRaises(LockContention, wt.commit, 'silly')
393
513
        finally:
394
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'))