/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: Canonical.com Patch Queue Manager
  • Date: 2011-06-30 16:06:19 UTC
  • mfrom: (5971.1.80 bzr-gpgme)
  • Revision ID: pqm@pqm.ubuntu.com-20110630160619-3022zmfchft893nt
(jr) A new command ``bzr verify-signatures`` has been added to check that
 commits
 are correctly signed with trusted keys by GPG. This requires python-gpgme to
 be installed. ``bzr log`` has gained a ``--signatures`` option to list the
 validity of signatures for each commit. New config options
 ``acceptable_keys``
 and ``validate_signatures_in_log`` can be set to control options to these
 commands. (Jonathan Riddell)

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