~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_commit.py

  • Committer: Patch Queue Manager
  • Date: 2014-02-12 18:22:22 UTC
  • mfrom: (6589.2.1 trunk)
  • Revision ID: pqm@pqm.ubuntu.com-20140212182222-beouo25gaf1cny76
(vila) The XDG Base Directory Specification uses the XDG_CACHE_HOME,
 not XDG_CACHE_DIR. (Andrew Starr-Bochicchio)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006 Canonical Ltd
 
1
# Copyright (C) 2005-2011 Canonical Ltd
2
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
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
21
from bzrlib import (
 
22
    config,
 
23
    controldir,
22
24
    errors,
23
 
    lockdir,
24
 
    osutils,
25
 
    tests,
26
25
    )
27
26
from bzrlib.branch import Branch
28
 
from bzrlib.bzrdir import BzrDir, BzrDirMetaFormat1
 
27
from bzrlib.bzrdir import BzrDirMetaFormat1
29
28
from bzrlib.commit import Commit, NullCommitReporter
30
 
from bzrlib.config import BranchConfig
31
 
from bzrlib.errors import (PointlessCommit, BzrError, SigningFailed, 
32
 
                           LockContention)
33
 
from bzrlib.tests import TestCaseWithTransport
34
 
from bzrlib.workingtree import WorkingTree
 
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
35
43
 
36
44
 
37
45
# TODO: Test commit with some added, and added-but-missing files
38
46
 
39
 
class MustSignConfig(BranchConfig):
40
 
 
41
 
    def signature_needed(self):
42
 
        return True
43
 
 
44
 
    def gpg_signing_command(self):
45
 
        return ['cat', '-']
46
 
 
47
 
 
48
 
class BranchWithHooks(BranchConfig):
49
 
 
50
 
    def post_commit(self):
51
 
        return "bzrlib.ahook bzrlib.ahook"
 
47
class MustSignConfig(config.MemoryStack):
 
48
 
 
49
    def __init__(self):
 
50
        super(MustSignConfig, self).__init__('''
 
51
gpg_signing_command=cat -
 
52
create_signatures=always
 
53
''')
52
54
 
53
55
 
54
56
class CapturingReporter(NullCommitReporter):
70
72
    def renamed(self, change, old_path, new_path):
71
73
        self.calls.append(('renamed', change, old_path, new_path))
72
74
 
 
75
    def is_verbose(self):
 
76
        return True
 
77
 
73
78
 
74
79
class TestCommit(TestCaseWithTransport):
75
80
 
77
82
        """Commit and check two versions of a single file."""
78
83
        wt = self.make_branch_and_tree('.')
79
84
        b = wt.branch
80
 
        file('hello', 'w').write('hello world')
 
85
        with file('hello', 'w') as f: f.write('hello world')
81
86
        wt.add('hello')
82
 
        wt.commit(message='add hello')
 
87
        rev1 = wt.commit(message='add hello')
83
88
        file_id = wt.path2id('hello')
84
89
 
85
 
        file('hello', 'w').write('version 2')
86
 
        wt.commit(message='commit 2')
 
90
        with file('hello', 'w') as f: f.write('version 2')
 
91
        rev2 = wt.commit(message='commit 2')
87
92
 
88
93
        eq = self.assertEquals
89
94
        eq(b.revno(), 2)
90
 
        rh = b.revision_history()
91
 
        rev = b.repository.get_revision(rh[0])
 
95
        rev = b.repository.get_revision(rev1)
92
96
        eq(rev.message, 'add hello')
93
97
 
94
 
        tree1 = b.repository.revision_tree(rh[0])
 
98
        tree1 = b.repository.revision_tree(rev1)
 
99
        tree1.lock_read()
95
100
        text = tree1.get_file_text(file_id)
96
 
        eq(text, 'hello world')
97
 
 
98
 
        tree2 = b.repository.revision_tree(rh[1])
99
 
        eq(tree2.get_file_text(file_id), 'version 2')
100
 
 
101
 
    def test_delete_commit(self):
102
 
        """Test a commit with a deleted file"""
103
 
        wt = self.make_branch_and_tree('.')
104
 
        b = wt.branch
105
 
        file('hello', 'w').write('hello world')
 
101
        tree1.unlock()
 
102
        self.assertEqual('hello world', text)
 
103
 
 
104
        tree2 = b.repository.revision_tree(rev2)
 
105
        tree2.lock_read()
 
106
        text = tree2.get_file_text(file_id)
 
107
        tree2.unlock()
 
108
        self.assertEqual('version 2', text)
 
109
 
 
110
    def test_commit_lossy_native(self):
 
111
        """Attempt a lossy commit to a native branch."""
 
112
        wt = self.make_branch_and_tree('.')
 
113
        b = wt.branch
 
114
        with file('hello', 'w') as f: f.write('hello world')
 
115
        wt.add('hello')
 
116
        revid = wt.commit(message='add hello', rev_id='revid', lossy=True)
 
117
        self.assertEquals('revid', revid)
 
118
 
 
119
    def test_commit_lossy_foreign(self):
 
120
        """Attempt a lossy commit to a foreign branch."""
 
121
        test_foreign.register_dummy_foreign_for_test(self)
 
122
        wt = self.make_branch_and_tree('.',
 
123
            format=test_foreign.DummyForeignVcsDirFormat())
 
124
        b = wt.branch
 
125
        with file('hello', 'w') as f: f.write('hello world')
 
126
        wt.add('hello')
 
127
        revid = wt.commit(message='add hello', lossy=True,
 
128
            timestamp=1302659388, timezone=0)
 
129
        self.assertEquals('dummy-v1:1302659388.0-0-UNKNOWN', revid)
 
130
 
 
131
    def test_commit_bound_lossy_foreign(self):
 
132
        """Attempt a lossy commit to a bzr branch bound to a foreign branch."""
 
133
        test_foreign.register_dummy_foreign_for_test(self)
 
134
        foreign_branch = self.make_branch('foreign',
 
135
            format=test_foreign.DummyForeignVcsDirFormat())
 
136
        wt = foreign_branch.create_checkout("local")
 
137
        b = wt.branch
 
138
        with file('local/hello', 'w') as f: f.write('hello world')
 
139
        wt.add('hello')
 
140
        revid = wt.commit(message='add hello', lossy=True,
 
141
            timestamp=1302659388, timezone=0)
 
142
        self.assertEquals('dummy-v1:1302659388.0-0-0', revid)
 
143
        self.assertEquals('dummy-v1:1302659388.0-0-0',
 
144
            foreign_branch.last_revision())
 
145
        self.assertEquals('dummy-v1:1302659388.0-0-0',
 
146
            wt.branch.last_revision())
 
147
 
 
148
    def test_missing_commit(self):
 
149
        """Test a commit with a missing file"""
 
150
        wt = self.make_branch_and_tree('.')
 
151
        b = wt.branch
 
152
        with file('hello', 'w') as f: f.write('hello world')
106
153
        wt.add(['hello'], ['hello-id'])
107
154
        wt.commit(message='add hello')
108
155
 
109
156
        os.remove('hello')
110
 
        wt.commit('removed hello', rev_id='rev2')
 
157
        reporter = CapturingReporter()
 
158
        wt.commit('removed hello', rev_id='rev2', reporter=reporter)
 
159
        self.assertEquals(
 
160
            [('missing', u'hello'), ('deleted', u'hello')],
 
161
            reporter.calls)
111
162
 
112
163
        tree = b.repository.revision_tree('rev2')
113
164
        self.assertFalse(tree.has_id('hello-id'))
114
165
 
 
166
    def test_partial_commit_move(self):
 
167
        """Test a partial commit where a file was renamed but not committed.
 
168
 
 
169
        https://bugs.launchpad.net/bzr/+bug/83039
 
170
 
 
171
        If not handled properly, commit will try to snapshot
 
172
        dialog.py with olive/ as a parent, while
 
173
        olive/ has not been snapshotted yet.
 
174
        """
 
175
        wt = self.make_branch_and_tree('.')
 
176
        b = wt.branch
 
177
        self.build_tree(['annotate/', 'annotate/foo.py',
 
178
                         'olive/', 'olive/dialog.py'
 
179
                        ])
 
180
        wt.add(['annotate', 'olive', 'annotate/foo.py', 'olive/dialog.py'])
 
181
        wt.commit(message='add files')
 
182
        wt.rename_one("olive/dialog.py", "aaa")
 
183
        self.build_tree_contents([('annotate/foo.py', 'modified\n')])
 
184
        wt.commit('renamed hello', specific_files=["annotate"])
 
185
 
115
186
    def test_pointless_commit(self):
116
187
        """Commit refuses unless there are changes or it's forced."""
117
188
        wt = self.make_branch_and_tree('.')
118
189
        b = wt.branch
119
 
        file('hello', 'w').write('hello')
 
190
        with file('hello', 'w') as f: f.write('hello')
120
191
        wt.add(['hello'])
121
192
        wt.commit(message='add hello')
122
193
        self.assertEquals(b.revno(), 1)
125
196
                          message='fails',
126
197
                          allow_pointless=False)
127
198
        self.assertEquals(b.revno(), 1)
128
 
        
 
199
 
129
200
    def test_commit_empty(self):
130
201
        """Commiting an empty tree works."""
131
202
        wt = self.make_branch_and_tree('.')
142
213
        """Selective commit in tree with deletions"""
143
214
        wt = self.make_branch_and_tree('.')
144
215
        b = wt.branch
145
 
        file('hello', 'w').write('hello')
146
 
        file('buongia', 'w').write('buongia')
 
216
        with file('hello', 'w') as f: f.write('hello')
 
217
        with file('buongia', 'w') as f: f.write('buongia')
147
218
        wt.add(['hello', 'buongia'],
148
219
              ['hello-id', 'buongia-id'])
149
220
        wt.commit(message='add files',
150
221
                 rev_id='test@rev-1')
151
 
        
 
222
 
152
223
        os.remove('hello')
153
 
        file('buongia', 'w').write('new text')
 
224
        with file('buongia', 'w') as f: f.write('new text')
154
225
        wt.commit(message='update text',
155
226
                 specific_files=['buongia'],
156
227
                 allow_pointless=False,
165
236
        eq(b.revno(), 3)
166
237
 
167
238
        tree2 = b.repository.revision_tree('test@rev-2')
 
239
        tree2.lock_read()
 
240
        self.addCleanup(tree2.unlock)
168
241
        self.assertTrue(tree2.has_filename('hello'))
169
242
        self.assertEquals(tree2.get_file_text('hello-id'), 'hello')
170
243
        self.assertEquals(tree2.get_file_text('buongia-id'), 'new text')
171
 
        
 
244
 
172
245
        tree3 = b.repository.revision_tree('test@rev-3')
 
246
        tree3.lock_read()
 
247
        self.addCleanup(tree3.unlock)
173
248
        self.assertFalse(tree3.has_filename('hello'))
174
249
        self.assertEquals(tree3.get_file_text('buongia-id'), 'new text')
175
250
 
186
261
 
187
262
        eq = self.assertEquals
188
263
        tree1 = b.repository.revision_tree('test@rev-1')
 
264
        tree1.lock_read()
 
265
        self.addCleanup(tree1.unlock)
189
266
        eq(tree1.id2path('hello-id'), 'hello')
190
267
        eq(tree1.get_file_text('hello-id'), 'contents of hello\n')
191
268
        self.assertFalse(tree1.has_filename('fruity'))
192
 
        self.check_inventory_shape(tree1.inventory, ['hello'])
193
 
        ie = tree1.inventory['hello-id']
194
 
        eq(ie.revision, 'test@rev-1')
 
269
        self.check_tree_shape(tree1, ['hello'])
 
270
        eq(tree1.get_file_revision('hello-id'), 'test@rev-1')
195
271
 
196
272
        tree2 = b.repository.revision_tree('test@rev-2')
 
273
        tree2.lock_read()
 
274
        self.addCleanup(tree2.unlock)
197
275
        eq(tree2.id2path('hello-id'), 'fruity')
198
276
        eq(tree2.get_file_text('hello-id'), 'contents of hello\n')
199
 
        self.check_inventory_shape(tree2.inventory, ['fruity'])
200
 
        ie = tree2.inventory['hello-id']
201
 
        eq(ie.revision, 'test@rev-2')
 
277
        self.check_tree_shape(tree2, ['fruity'])
 
278
        eq(tree2.get_file_revision('hello-id'), 'test@rev-2')
202
279
 
203
280
    def test_reused_rev_id(self):
204
281
        """Test that a revision id cannot be reused in a branch"""
225
302
        wt.commit('two', rev_id=r2, allow_pointless=False)
226
303
        wt.lock_read()
227
304
        try:
228
 
            self.check_inventory_shape(wt.read_working_inventory(),
229
 
                                       ['a', 'a/hello', 'b'])
 
305
            self.check_tree_shape(wt, ['a/', 'a/hello', 'b/'])
230
306
        finally:
231
307
            wt.unlock()
232
308
 
235
311
        wt.commit('three', rev_id=r3, allow_pointless=False)
236
312
        wt.lock_read()
237
313
        try:
238
 
            self.check_inventory_shape(wt.read_working_inventory(),
239
 
                                       ['a', 'a/hello', 'a/b'])
240
 
            self.check_inventory_shape(b.repository.get_revision_inventory(r3),
241
 
                                       ['a', 'a/hello', 'a/b'])
 
314
            self.check_tree_shape(wt,
 
315
                                       ['a/', 'a/hello', 'a/b/'])
 
316
            self.check_tree_shape(b.repository.revision_tree(r3),
 
317
                                       ['a/', 'a/hello', 'a/b/'])
242
318
        finally:
243
319
            wt.unlock()
244
320
 
247
323
        wt.commit('four', rev_id=r4, allow_pointless=False)
248
324
        wt.lock_read()
249
325
        try:
250
 
            self.check_inventory_shape(wt.read_working_inventory(),
251
 
                                       ['a', 'a/b/hello', 'a/b'])
 
326
            self.check_tree_shape(wt, ['a/', 'a/b/hello', 'a/b/'])
252
327
        finally:
253
328
            wt.unlock()
254
329
 
255
 
        inv = b.repository.get_revision_inventory(r4)
 
330
        inv = b.repository.get_inventory(r4)
256
331
        eq(inv['hello-id'].revision, r4)
257
332
        eq(inv['a-id'].revision, r1)
258
333
        eq(inv['b-id'].revision, r3)
261
336
        """Commit with a removed file"""
262
337
        wt = self.make_branch_and_tree('.')
263
338
        b = wt.branch
264
 
        file('hello', 'w').write('hello world')
 
339
        with file('hello', 'w') as f: f.write('hello world')
265
340
        wt.add(['hello'], ['hello-id'])
266
341
        wt.commit(message='add hello')
267
342
        wt.remove('hello')
276
351
        b = wt.branch
277
352
        rev_ids = []
278
353
        for i in range(4):
279
 
            file('hello', 'w').write((str(i) * 4) + '\n')
 
354
            with file('hello', 'w') as f: f.write((str(i) * 4) + '\n')
280
355
            if i == 0:
281
356
                wt.add(['hello'], ['hello-id'])
282
357
            rev_id = 'test@rev-%d' % (i+1)
283
358
            rev_ids.append(rev_id)
284
359
            wt.commit(message='rev %d' % (i+1),
285
360
                     rev_id=rev_id)
286
 
        eq = self.assertEquals
287
 
        eq(b.revision_history(), rev_ids)
288
361
        for i in range(4):
289
 
            anc = b.repository.get_ancestry(rev_ids[i])
290
 
            eq(anc, [None] + rev_ids[:i+1])
 
362
            self.assertThat(rev_ids[:i+1],
 
363
                MatchesAncestry(b.repository, rev_ids[i]))
291
364
 
292
365
    def test_commit_new_subdir_child_selective(self):
293
366
        wt = self.make_branch_and_tree('.')
307
380
        from bzrlib.errors import StrictCommitFailed
308
381
        wt = self.make_branch_and_tree('.')
309
382
        b = wt.branch
310
 
        file('hello', 'w').write('hello world')
 
383
        with file('hello', 'w') as f: f.write('hello world')
311
384
        wt.add('hello')
312
 
        file('goodbye', 'w').write('goodbye cruel world!')
 
385
        with file('goodbye', 'w') as f: f.write('goodbye cruel world!')
313
386
        self.assertRaises(StrictCommitFailed, wt.commit,
314
387
            message='add hello but not goodbye', strict=True)
315
388
 
316
389
    def test_strict_commit_without_unknowns(self):
317
390
        """Try and commit with no unknown files and strict = True,
318
391
        should work."""
319
 
        from bzrlib.errors import StrictCommitFailed
320
392
        wt = self.make_branch_and_tree('.')
321
393
        b = wt.branch
322
 
        file('hello', 'w').write('hello world')
 
394
        with file('hello', 'w') as f: f.write('hello world')
323
395
        wt.add('hello')
324
396
        wt.commit(message='add hello', strict=True)
325
397
 
327
399
        """Try and commit with unknown files and strict = False, should work."""
328
400
        wt = self.make_branch_and_tree('.')
329
401
        b = wt.branch
330
 
        file('hello', 'w').write('hello world')
 
402
        with file('hello', 'w') as f: f.write('hello world')
331
403
        wt.add('hello')
332
 
        file('goodbye', 'w').write('goodbye cruel world!')
 
404
        with file('goodbye', 'w') as f: f.write('goodbye cruel world!')
333
405
        wt.commit(message='add hello but not goodbye', strict=False)
334
406
 
335
407
    def test_nonstrict_commit_without_unknowns(self):
337
409
        should work."""
338
410
        wt = self.make_branch_and_tree('.')
339
411
        b = wt.branch
340
 
        file('hello', 'w').write('hello world')
 
412
        with file('hello', 'w') as f: f.write('hello world')
341
413
        wt.add('hello')
342
414
        wt.commit(message='add hello', strict=False)
343
415
 
348
420
        wt = self.make_branch_and_tree('.')
349
421
        branch = wt.branch
350
422
        wt.commit("base", allow_pointless=True, rev_id='A')
351
 
        self.failIf(branch.repository.has_signature_for_revision_id('A'))
 
423
        self.assertFalse(branch.repository.has_signature_for_revision_id('A'))
352
424
        try:
353
425
            from bzrlib.testament import Testament
354
426
            # monkey patch gpg signing mechanism
355
427
            bzrlib.gpg.GPGStrategy = bzrlib.gpg.LoopbackGPGStrategy
356
 
            commit.Commit(config=MustSignConfig(branch)).commit(message="base",
357
 
                                                      allow_pointless=True,
358
 
                                                      rev_id='B',
359
 
                                                      working_tree=wt)
360
 
            self.assertEqual(Testament.from_revision(branch.repository,
361
 
                             'B').as_short_text(),
 
428
            conf = config.MemoryStack('''
 
429
gpg_signing_command=cat -
 
430
create_signatures=always
 
431
''')
 
432
            commit.Commit(config_stack=conf).commit(
 
433
                message="base", allow_pointless=True, rev_id='B',
 
434
                working_tree=wt)
 
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()),
362
439
                             branch.repository.get_signature_text('B'))
363
440
        finally:
364
441
            bzrlib.gpg.GPGStrategy = oldstrategy
370
447
        wt = self.make_branch_and_tree('.')
371
448
        branch = wt.branch
372
449
        wt.commit("base", allow_pointless=True, rev_id='A')
373
 
        self.failIf(branch.repository.has_signature_for_revision_id('A'))
 
450
        self.assertFalse(branch.repository.has_signature_for_revision_id('A'))
374
451
        try:
375
 
            from bzrlib.testament import Testament
376
452
            # monkey patch gpg signing mechanism
377
453
            bzrlib.gpg.GPGStrategy = bzrlib.gpg.DisabledGPGStrategy
378
 
            config = MustSignConfig(branch)
 
454
            conf = config.MemoryStack('''
 
455
gpg_signing_command=cat -
 
456
create_signatures=always
 
457
''')
379
458
            self.assertRaises(SigningFailed,
380
 
                              commit.Commit(config=config).commit,
 
459
                              commit.Commit(config_stack=conf).commit,
381
460
                              message="base",
382
461
                              allow_pointless=True,
383
462
                              rev_id='B',
384
463
                              working_tree=wt)
385
464
            branch = Branch.open(self.get_url('.'))
386
 
            self.assertEqual(branch.revision_history(), ['A'])
387
 
            self.failIf(branch.repository.has_revision('B'))
 
465
            self.assertEqual(branch.last_revision(), 'A')
 
466
            self.assertFalse(branch.repository.has_revision('B'))
388
467
        finally:
389
468
            bzrlib.gpg.GPGStrategy = oldstrategy
390
469
 
397
476
            calls.append('called')
398
477
        bzrlib.ahook = called
399
478
        try:
400
 
            config = BranchWithHooks(branch)
401
 
            commit.Commit(config=config).commit(
402
 
                            message = "base",
403
 
                            allow_pointless=True,
404
 
                            rev_id='A', working_tree = wt)
 
479
            conf = config.MemoryStack('post_commit=bzrlib.ahook bzrlib.ahook')
 
480
            commit.Commit(config_stack=conf).commit(
 
481
                message = "base", allow_pointless=True, rev_id='A',
 
482
                working_tree = wt)
405
483
            self.assertEqual(['called', 'called'], calls)
406
484
        finally:
407
485
            del bzrlib.ahook
425
503
        bound = master.sprout('bound')
426
504
        wt = bound.open_workingtree()
427
505
        wt.branch.set_bound_location(os.path.realpath('master'))
428
 
 
429
 
        orig_default = lockdir._DEFAULT_TIMEOUT_SECONDS
430
506
        master_branch.lock_write()
431
507
        try:
432
 
            lockdir._DEFAULT_TIMEOUT_SECONDS = 1
433
508
            self.assertRaises(LockContention, wt.commit, 'silly')
434
509
        finally:
435
 
            lockdir._DEFAULT_TIMEOUT_SECONDS = orig_default
436
510
            master_branch.unlock()
437
511
 
438
512
    def test_commit_bound_merge(self):
449
523
        other_bzrdir = master_branch.bzrdir.sprout('other')
450
524
        other_tree = other_bzrdir.open_workingtree()
451
525
 
452
 
        # do a commit to the the other branch changing the content file so
 
526
        # do a commit to the other branch changing the content file so
453
527
        # that our commit after merging will have a merged revision in the
454
528
        # content file history.
455
529
        self.build_tree_contents([('other/content_file', 'change in other\n')])
466
540
        bound_tree.commit(message='commit of merge in bound tree')
467
541
 
468
542
    def test_commit_reporting_after_merge(self):
469
 
        # when doing a commit of a merge, the reporter needs to still 
 
543
        # when doing a commit of a merge, the reporter needs to still
470
544
        # be called for each item that is added/removed/deleted.
471
545
        this_tree = self.make_branch_and_tree('this')
472
546
        # we need a bunch of files and dirs, to perform one action on each.
515
589
        this_tree.merge_from_branch(other_tree.branch)
516
590
        reporter = CapturingReporter()
517
591
        this_tree.commit('do the commit', reporter=reporter)
518
 
        self.assertEqual([
519
 
            ('change', 'unchanged', ''),
520
 
            ('change', 'unchanged', 'dirtoleave'),
521
 
            ('change', 'unchanged', 'filetoleave'),
 
592
        expected = set([
522
593
            ('change', 'modified', 'filetomodify'),
523
594
            ('change', 'added', 'newdir'),
524
595
            ('change', 'added', 'newfile'),
525
596
            ('renamed', 'renamed', 'dirtorename', 'renameddir'),
 
597
            ('renamed', 'renamed', 'filetorename', 'renamedfile'),
526
598
            ('renamed', 'renamed', 'dirtoreparent', 'renameddir/reparenteddir'),
527
599
            ('renamed', 'renamed', 'filetoreparent', 'renameddir/reparentedfile'),
528
 
            ('renamed', 'renamed', 'filetorename', 'renamedfile'),
529
600
            ('deleted', 'dirtoremove'),
530
601
            ('deleted', 'filetoremove'),
531
 
            ],
532
 
            reporter.calls)
 
602
            ])
 
603
        result = set(reporter.calls)
 
604
        missing = expected - result
 
605
        new = result - expected
 
606
        self.assertEqual((set(), set()), (missing, new))
533
607
 
534
608
    def test_commit_removals_respects_filespec(self):
535
609
        """Commit respects the specified_files for removals."""
579
653
            basis.unlock()
580
654
 
581
655
    def test_commit_kind_changes(self):
582
 
        if not osutils.has_symlinks():
583
 
            raise tests.TestSkipped('Test requires symlink support')
 
656
        self.requireFeature(SymlinkFeature)
584
657
        tree = self.make_branch_and_tree('.')
585
658
        os.symlink('target', 'name')
586
659
        tree.add('name', 'a-file-id')
626
699
    def test_commit_unversioned_specified(self):
627
700
        """Commit should raise if specified files isn't in basis or worktree"""
628
701
        tree = self.make_branch_and_tree('.')
629
 
        self.assertRaises(errors.PathsNotVersionedError, tree.commit, 
 
702
        self.assertRaises(errors.PathsNotVersionedError, tree.commit,
630
703
                          'message', specific_files=['bogus'])
631
704
 
632
705
    class Callback(object):
633
 
        
 
706
 
634
707
        def __init__(self, message, testcase):
635
708
            self.called = False
636
709
            self.message = message
664
737
        """Callback should not be invoked for pointless commit"""
665
738
        tree = self.make_branch_and_tree('.')
666
739
        cb = self.Callback(u'commit 2', self)
667
 
        self.assertRaises(PointlessCommit, tree.commit, message_callback=cb, 
 
740
        self.assertRaises(PointlessCommit, tree.commit, message_callback=cb,
668
741
                          allow_pointless=False)
669
742
        self.assertFalse(cb.called)
670
743
 
674
747
        cb = self.Callback(u'commit 2', self)
675
748
        repository = tree.branch.repository
676
749
        # simulate network failure
677
 
        def raise_(self, arg, arg2):
 
750
        def raise_(self, arg, arg2, arg3=None, arg4=None):
678
751
            raise errors.NoSuchFile('foo')
679
752
        repository.add_inventory = raise_
 
753
        repository.add_inventory_by_delta = raise_
680
754
        self.assertRaises(errors.NoSuchFile, tree.commit, message_callback=cb)
681
755
        self.assertFalse(cb.called)
 
756
 
 
757
    def test_selected_file_merge_commit(self):
 
758
        """Ensure the correct error is raised"""
 
759
        tree = self.make_branch_and_tree('foo')
 
760
        # pending merge would turn into a left parent
 
761
        tree.commit('commit 1')
 
762
        tree.add_parent_tree_id('example')
 
763
        self.build_tree(['foo/bar', 'foo/baz'])
 
764
        tree.add(['bar', 'baz'])
 
765
        err = self.assertRaises(errors.CannotCommitSelectedFileMerge,
 
766
            tree.commit, 'commit 2', specific_files=['bar', 'baz'])
 
767
        self.assertEqual(['bar', 'baz'], err.files)
 
768
        self.assertEqual('Selected-file commit of merges is not supported'
 
769
                         ' yet: files bar, baz', str(err))
 
770
 
 
771
    def test_commit_ordering(self):
 
772
        """Test of corner-case commit ordering error"""
 
773
        tree = self.make_branch_and_tree('.')
 
774
        self.build_tree(['a/', 'a/z/', 'a/c/', 'a/z/x', 'a/z/y'])
 
775
        tree.add(['a/', 'a/z/', 'a/c/', 'a/z/x', 'a/z/y'])
 
776
        tree.commit('setup')
 
777
        self.build_tree(['a/c/d/'])
 
778
        tree.add('a/c/d')
 
779
        tree.rename_one('a/z/x', 'a/c/d/x')
 
780
        tree.commit('test', specific_files=['a/z/y'])
 
781
 
 
782
    def test_commit_no_author(self):
 
783
        """The default kwarg author in MutableTree.commit should not add
 
784
        the 'author' revision property.
 
785
        """
 
786
        tree = self.make_branch_and_tree('foo')
 
787
        rev_id = tree.commit('commit 1')
 
788
        rev = tree.branch.repository.get_revision(rev_id)
 
789
        self.assertFalse('author' in rev.properties)
 
790
        self.assertFalse('authors' in rev.properties)
 
791
 
 
792
    def test_commit_author(self):
 
793
        """Passing a non-empty author kwarg to MutableTree.commit should add
 
794
        the 'author' revision property.
 
795
        """
 
796
        tree = self.make_branch_and_tree('foo')
 
797
        rev_id = self.callDeprecated(['The parameter author was '
 
798
                'deprecated in version 1.13. Use authors instead'],
 
799
                tree.commit, 'commit 1', author='John Doe <jdoe@example.com>')
 
800
        rev = tree.branch.repository.get_revision(rev_id)
 
801
        self.assertEqual('John Doe <jdoe@example.com>',
 
802
                         rev.properties['authors'])
 
803
        self.assertFalse('author' in rev.properties)
 
804
 
 
805
    def test_commit_empty_authors_list(self):
 
806
        """Passing an empty list to authors shouldn't add the property."""
 
807
        tree = self.make_branch_and_tree('foo')
 
808
        rev_id = tree.commit('commit 1', authors=[])
 
809
        rev = tree.branch.repository.get_revision(rev_id)
 
810
        self.assertFalse('author' in rev.properties)
 
811
        self.assertFalse('authors' in rev.properties)
 
812
 
 
813
    def test_multiple_authors(self):
 
814
        tree = self.make_branch_and_tree('foo')
 
815
        rev_id = tree.commit('commit 1',
 
816
                authors=['John Doe <jdoe@example.com>',
 
817
                         'Jane Rey <jrey@example.com>'])
 
818
        rev = tree.branch.repository.get_revision(rev_id)
 
819
        self.assertEqual('John Doe <jdoe@example.com>\n'
 
820
                'Jane Rey <jrey@example.com>', rev.properties['authors'])
 
821
        self.assertFalse('author' in rev.properties)
 
822
 
 
823
    def test_author_and_authors_incompatible(self):
 
824
        tree = self.make_branch_and_tree('foo')
 
825
        self.assertRaises(AssertionError, tree.commit, 'commit 1',
 
826
                authors=['John Doe <jdoe@example.com>',
 
827
                         'Jane Rey <jrey@example.com>'],
 
828
                author="Jack Me <jme@example.com>")
 
829
 
 
830
    def test_author_with_newline_rejected(self):
 
831
        tree = self.make_branch_and_tree('foo')
 
832
        self.assertRaises(AssertionError, tree.commit, 'commit 1',
 
833
                authors=['John\nDoe <jdoe@example.com>'])
 
834
 
 
835
    def test_commit_with_checkout_and_branch_sharing_repo(self):
 
836
        repo = self.make_repository('repo', shared=True)
 
837
        # make_branch_and_tree ignores shared repos
 
838
        branch = controldir.ControlDir.create_branch_convenience('repo/branch')
 
839
        tree2 = branch.create_checkout('repo/tree2')
 
840
        tree2.commit('message', rev_id='rev1')
 
841
        self.assertTrue(tree2.branch.repository.has_revision('rev1'))