/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: Martin
  • Date: 2011-04-15 21:22:52 UTC
  • mto: This revision was merged to the branch mainline in revision 5797.
  • Revision ID: gzlist@googlemail.com-20110415212252-lhqulomwg2y538xj
Add user encoding name to argv decoding error message per poolie in review

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005-2011 Canonical Ltd
 
2
#
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
#
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
#
 
13
# You should have received a copy of the GNU General Public License
 
14
# along with this program; if not, write to the Free Software
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
16
 
 
17
 
 
18
import os
 
19
 
 
20
import bzrlib
 
21
from bzrlib import (
 
22
    bzrdir,
 
23
    errors,
 
24
    )
 
25
from bzrlib.branch import Branch
 
26
from bzrlib.bzrdir import BzrDirMetaFormat1
 
27
from bzrlib.commit import Commit, NullCommitReporter
 
28
from bzrlib.config import BranchConfig
 
29
from bzrlib.errors import (PointlessCommit, BzrError, SigningFailed,
 
30
                           LockContention)
 
31
from bzrlib.tests import SymlinkFeature, TestCaseWithTransport
 
32
 
 
33
 
 
34
# TODO: Test commit with some added, and added-but-missing files
 
35
 
 
36
class MustSignConfig(BranchConfig):
 
37
 
 
38
    def signature_needed(self):
 
39
        return True
 
40
 
 
41
    def gpg_signing_command(self):
 
42
        return ['cat', '-']
 
43
 
 
44
 
 
45
class BranchWithHooks(BranchConfig):
 
46
 
 
47
    def post_commit(self):
 
48
        return "bzrlib.ahook bzrlib.ahook"
 
49
 
 
50
 
 
51
class CapturingReporter(NullCommitReporter):
 
52
    """This reporter captures the calls made to it for evaluation later."""
 
53
 
 
54
    def __init__(self):
 
55
        # a list of the calls this received
 
56
        self.calls = []
 
57
 
 
58
    def snapshot_change(self, change, path):
 
59
        self.calls.append(('change', change, path))
 
60
 
 
61
    def deleted(self, file_id):
 
62
        self.calls.append(('deleted', file_id))
 
63
 
 
64
    def missing(self, path):
 
65
        self.calls.append(('missing', path))
 
66
 
 
67
    def renamed(self, change, old_path, new_path):
 
68
        self.calls.append(('renamed', change, old_path, new_path))
 
69
 
 
70
    def is_verbose(self):
 
71
        return True
 
72
 
 
73
 
 
74
class TestCommit(TestCaseWithTransport):
 
75
 
 
76
    def test_simple_commit(self):
 
77
        """Commit and check two versions of a single file."""
 
78
        wt = self.make_branch_and_tree('.')
 
79
        b = wt.branch
 
80
        file('hello', 'w').write('hello world')
 
81
        wt.add('hello')
 
82
        wt.commit(message='add hello')
 
83
        file_id = wt.path2id('hello')
 
84
 
 
85
        file('hello', 'w').write('version 2')
 
86
        wt.commit(message='commit 2')
 
87
 
 
88
        eq = self.assertEquals
 
89
        eq(b.revno(), 2)
 
90
        rh = b.revision_history()
 
91
        rev = b.repository.get_revision(rh[0])
 
92
        eq(rev.message, 'add hello')
 
93
 
 
94
        tree1 = b.repository.revision_tree(rh[0])
 
95
        tree1.lock_read()
 
96
        text = tree1.get_file_text(file_id)
 
97
        tree1.unlock()
 
98
        self.assertEqual('hello world', text)
 
99
 
 
100
        tree2 = b.repository.revision_tree(rh[1])
 
101
        tree2.lock_read()
 
102
        text = tree2.get_file_text(file_id)
 
103
        tree2.unlock()
 
104
        self.assertEqual('version 2', text)
 
105
 
 
106
    def test_missing_commit(self):
 
107
        """Test a commit with a missing file"""
 
108
        wt = self.make_branch_and_tree('.')
 
109
        b = wt.branch
 
110
        file('hello', 'w').write('hello world')
 
111
        wt.add(['hello'], ['hello-id'])
 
112
        wt.commit(message='add hello')
 
113
 
 
114
        os.remove('hello')
 
115
        wt.commit('removed hello', rev_id='rev2')
 
116
 
 
117
        tree = b.repository.revision_tree('rev2')
 
118
        self.assertFalse(tree.has_id('hello-id'))
 
119
 
 
120
    def test_partial_commit_move(self):
 
121
        """Test a partial commit where a file was renamed but not committed.
 
122
 
 
123
        https://bugs.launchpad.net/bzr/+bug/83039
 
124
 
 
125
        If not handled properly, commit will try to snapshot
 
126
        dialog.py with olive/ as a parent, while
 
127
        olive/ has not been snapshotted yet.
 
128
        """
 
129
        wt = self.make_branch_and_tree('.')
 
130
        b = wt.branch
 
131
        self.build_tree(['annotate/', 'annotate/foo.py',
 
132
                         'olive/', 'olive/dialog.py'
 
133
                        ])
 
134
        wt.add(['annotate', 'olive', 'annotate/foo.py', 'olive/dialog.py'])
 
135
        wt.commit(message='add files')
 
136
        wt.rename_one("olive/dialog.py", "aaa")
 
137
        self.build_tree_contents([('annotate/foo.py', 'modified\n')])
 
138
        wt.commit('renamed hello', specific_files=["annotate"])
 
139
 
 
140
    def test_pointless_commit(self):
 
141
        """Commit refuses unless there are changes or it's forced."""
 
142
        wt = self.make_branch_and_tree('.')
 
143
        b = wt.branch
 
144
        file('hello', 'w').write('hello')
 
145
        wt.add(['hello'])
 
146
        wt.commit(message='add hello')
 
147
        self.assertEquals(b.revno(), 1)
 
148
        self.assertRaises(PointlessCommit,
 
149
                          wt.commit,
 
150
                          message='fails',
 
151
                          allow_pointless=False)
 
152
        self.assertEquals(b.revno(), 1)
 
153
 
 
154
    def test_commit_empty(self):
 
155
        """Commiting an empty tree works."""
 
156
        wt = self.make_branch_and_tree('.')
 
157
        b = wt.branch
 
158
        wt.commit(message='empty tree', allow_pointless=True)
 
159
        self.assertRaises(PointlessCommit,
 
160
                          wt.commit,
 
161
                          message='empty tree',
 
162
                          allow_pointless=False)
 
163
        wt.commit(message='empty tree', allow_pointless=True)
 
164
        self.assertEquals(b.revno(), 2)
 
165
 
 
166
    def test_selective_delete(self):
 
167
        """Selective commit in tree with deletions"""
 
168
        wt = self.make_branch_and_tree('.')
 
169
        b = wt.branch
 
170
        file('hello', 'w').write('hello')
 
171
        file('buongia', 'w').write('buongia')
 
172
        wt.add(['hello', 'buongia'],
 
173
              ['hello-id', 'buongia-id'])
 
174
        wt.commit(message='add files',
 
175
                 rev_id='test@rev-1')
 
176
 
 
177
        os.remove('hello')
 
178
        file('buongia', 'w').write('new text')
 
179
        wt.commit(message='update text',
 
180
                 specific_files=['buongia'],
 
181
                 allow_pointless=False,
 
182
                 rev_id='test@rev-2')
 
183
 
 
184
        wt.commit(message='remove hello',
 
185
                 specific_files=['hello'],
 
186
                 allow_pointless=False,
 
187
                 rev_id='test@rev-3')
 
188
 
 
189
        eq = self.assertEquals
 
190
        eq(b.revno(), 3)
 
191
 
 
192
        tree2 = b.repository.revision_tree('test@rev-2')
 
193
        tree2.lock_read()
 
194
        self.addCleanup(tree2.unlock)
 
195
        self.assertTrue(tree2.has_filename('hello'))
 
196
        self.assertEquals(tree2.get_file_text('hello-id'), 'hello')
 
197
        self.assertEquals(tree2.get_file_text('buongia-id'), 'new text')
 
198
 
 
199
        tree3 = b.repository.revision_tree('test@rev-3')
 
200
        tree3.lock_read()
 
201
        self.addCleanup(tree3.unlock)
 
202
        self.assertFalse(tree3.has_filename('hello'))
 
203
        self.assertEquals(tree3.get_file_text('buongia-id'), 'new text')
 
204
 
 
205
    def test_commit_rename(self):
 
206
        """Test commit of a revision where a file is renamed."""
 
207
        tree = self.make_branch_and_tree('.')
 
208
        b = tree.branch
 
209
        self.build_tree(['hello'], line_endings='binary')
 
210
        tree.add(['hello'], ['hello-id'])
 
211
        tree.commit(message='one', rev_id='test@rev-1', allow_pointless=False)
 
212
 
 
213
        tree.rename_one('hello', 'fruity')
 
214
        tree.commit(message='renamed', rev_id='test@rev-2', allow_pointless=False)
 
215
 
 
216
        eq = self.assertEquals
 
217
        tree1 = b.repository.revision_tree('test@rev-1')
 
218
        tree1.lock_read()
 
219
        self.addCleanup(tree1.unlock)
 
220
        eq(tree1.id2path('hello-id'), 'hello')
 
221
        eq(tree1.get_file_text('hello-id'), 'contents of hello\n')
 
222
        self.assertFalse(tree1.has_filename('fruity'))
 
223
        self.check_inventory_shape(tree1.inventory, ['hello'])
 
224
        ie = tree1.inventory['hello-id']
 
225
        eq(ie.revision, 'test@rev-1')
 
226
 
 
227
        tree2 = b.repository.revision_tree('test@rev-2')
 
228
        tree2.lock_read()
 
229
        self.addCleanup(tree2.unlock)
 
230
        eq(tree2.id2path('hello-id'), 'fruity')
 
231
        eq(tree2.get_file_text('hello-id'), 'contents of hello\n')
 
232
        self.check_inventory_shape(tree2.inventory, ['fruity'])
 
233
        ie = tree2.inventory['hello-id']
 
234
        eq(ie.revision, 'test@rev-2')
 
235
 
 
236
    def test_reused_rev_id(self):
 
237
        """Test that a revision id cannot be reused in a branch"""
 
238
        wt = self.make_branch_and_tree('.')
 
239
        b = wt.branch
 
240
        wt.commit('initial', rev_id='test@rev-1', allow_pointless=True)
 
241
        self.assertRaises(Exception,
 
242
                          wt.commit,
 
243
                          message='reused id',
 
244
                          rev_id='test@rev-1',
 
245
                          allow_pointless=True)
 
246
 
 
247
    def test_commit_move(self):
 
248
        """Test commit of revisions with moved files and directories"""
 
249
        eq = self.assertEquals
 
250
        wt = self.make_branch_and_tree('.')
 
251
        b = wt.branch
 
252
        r1 = 'test@rev-1'
 
253
        self.build_tree(['hello', 'a/', 'b/'])
 
254
        wt.add(['hello', 'a', 'b'], ['hello-id', 'a-id', 'b-id'])
 
255
        wt.commit('initial', rev_id=r1, allow_pointless=False)
 
256
        wt.move(['hello'], 'a')
 
257
        r2 = 'test@rev-2'
 
258
        wt.commit('two', rev_id=r2, allow_pointless=False)
 
259
        wt.lock_read()
 
260
        try:
 
261
            self.check_inventory_shape(wt.read_working_inventory(),
 
262
                                       ['a/', 'a/hello', 'b/'])
 
263
        finally:
 
264
            wt.unlock()
 
265
 
 
266
        wt.move(['b'], 'a')
 
267
        r3 = 'test@rev-3'
 
268
        wt.commit('three', rev_id=r3, allow_pointless=False)
 
269
        wt.lock_read()
 
270
        try:
 
271
            self.check_inventory_shape(wt.read_working_inventory(),
 
272
                                       ['a/', 'a/hello', 'a/b/'])
 
273
            self.check_inventory_shape(b.repository.get_inventory(r3),
 
274
                                       ['a/', 'a/hello', 'a/b/'])
 
275
        finally:
 
276
            wt.unlock()
 
277
 
 
278
        wt.move(['a/hello'], 'a/b')
 
279
        r4 = 'test@rev-4'
 
280
        wt.commit('four', rev_id=r4, allow_pointless=False)
 
281
        wt.lock_read()
 
282
        try:
 
283
            self.check_inventory_shape(wt.read_working_inventory(),
 
284
                                       ['a/', 'a/b/hello', 'a/b/'])
 
285
        finally:
 
286
            wt.unlock()
 
287
 
 
288
        inv = b.repository.get_inventory(r4)
 
289
        eq(inv['hello-id'].revision, r4)
 
290
        eq(inv['a-id'].revision, r1)
 
291
        eq(inv['b-id'].revision, r3)
 
292
 
 
293
    def test_removed_commit(self):
 
294
        """Commit with a removed file"""
 
295
        wt = self.make_branch_and_tree('.')
 
296
        b = wt.branch
 
297
        file('hello', 'w').write('hello world')
 
298
        wt.add(['hello'], ['hello-id'])
 
299
        wt.commit(message='add hello')
 
300
        wt.remove('hello')
 
301
        wt.commit('removed hello', rev_id='rev2')
 
302
 
 
303
        tree = b.repository.revision_tree('rev2')
 
304
        self.assertFalse(tree.has_id('hello-id'))
 
305
 
 
306
    def test_committed_ancestry(self):
 
307
        """Test commit appends revisions to ancestry."""
 
308
        wt = self.make_branch_and_tree('.')
 
309
        b = wt.branch
 
310
        rev_ids = []
 
311
        for i in range(4):
 
312
            file('hello', 'w').write((str(i) * 4) + '\n')
 
313
            if i == 0:
 
314
                wt.add(['hello'], ['hello-id'])
 
315
            rev_id = 'test@rev-%d' % (i+1)
 
316
            rev_ids.append(rev_id)
 
317
            wt.commit(message='rev %d' % (i+1),
 
318
                     rev_id=rev_id)
 
319
        eq = self.assertEquals
 
320
        eq(b.revision_history(), rev_ids)
 
321
        for i in range(4):
 
322
            anc = b.repository.get_ancestry(rev_ids[i])
 
323
            eq(anc, [None] + rev_ids[:i+1])
 
324
 
 
325
    def test_commit_new_subdir_child_selective(self):
 
326
        wt = self.make_branch_and_tree('.')
 
327
        b = wt.branch
 
328
        self.build_tree(['dir/', 'dir/file1', 'dir/file2'])
 
329
        wt.add(['dir', 'dir/file1', 'dir/file2'],
 
330
              ['dirid', 'file1id', 'file2id'])
 
331
        wt.commit('dir/file1', specific_files=['dir/file1'], rev_id='1')
 
332
        inv = b.repository.get_inventory('1')
 
333
        self.assertEqual('1', inv['dirid'].revision)
 
334
        self.assertEqual('1', inv['file1id'].revision)
 
335
        # FIXME: This should raise a KeyError I think, rbc20051006
 
336
        self.assertRaises(BzrError, inv.__getitem__, 'file2id')
 
337
 
 
338
    def test_strict_commit(self):
 
339
        """Try and commit with unknown files and strict = True, should fail."""
 
340
        from bzrlib.errors import StrictCommitFailed
 
341
        wt = self.make_branch_and_tree('.')
 
342
        b = wt.branch
 
343
        file('hello', 'w').write('hello world')
 
344
        wt.add('hello')
 
345
        file('goodbye', 'w').write('goodbye cruel world!')
 
346
        self.assertRaises(StrictCommitFailed, wt.commit,
 
347
            message='add hello but not goodbye', strict=True)
 
348
 
 
349
    def test_strict_commit_without_unknowns(self):
 
350
        """Try and commit with no unknown files and strict = True,
 
351
        should work."""
 
352
        from bzrlib.errors import StrictCommitFailed
 
353
        wt = self.make_branch_and_tree('.')
 
354
        b = wt.branch
 
355
        file('hello', 'w').write('hello world')
 
356
        wt.add('hello')
 
357
        wt.commit(message='add hello', strict=True)
 
358
 
 
359
    def test_nonstrict_commit(self):
 
360
        """Try and commit with unknown files and strict = False, should work."""
 
361
        wt = self.make_branch_and_tree('.')
 
362
        b = wt.branch
 
363
        file('hello', 'w').write('hello world')
 
364
        wt.add('hello')
 
365
        file('goodbye', 'w').write('goodbye cruel world!')
 
366
        wt.commit(message='add hello but not goodbye', strict=False)
 
367
 
 
368
    def test_nonstrict_commit_without_unknowns(self):
 
369
        """Try and commit with no unknown files and strict = False,
 
370
        should work."""
 
371
        wt = self.make_branch_and_tree('.')
 
372
        b = wt.branch
 
373
        file('hello', 'w').write('hello world')
 
374
        wt.add('hello')
 
375
        wt.commit(message='add hello', strict=False)
 
376
 
 
377
    def test_signed_commit(self):
 
378
        import bzrlib.gpg
 
379
        import bzrlib.commit as commit
 
380
        oldstrategy = bzrlib.gpg.GPGStrategy
 
381
        wt = self.make_branch_and_tree('.')
 
382
        branch = wt.branch
 
383
        wt.commit("base", allow_pointless=True, rev_id='A')
 
384
        self.failIf(branch.repository.has_signature_for_revision_id('A'))
 
385
        try:
 
386
            from bzrlib.testament import Testament
 
387
            # monkey patch gpg signing mechanism
 
388
            bzrlib.gpg.GPGStrategy = bzrlib.gpg.LoopbackGPGStrategy
 
389
            commit.Commit(config=MustSignConfig(branch)).commit(message="base",
 
390
                                                      allow_pointless=True,
 
391
                                                      rev_id='B',
 
392
                                                      working_tree=wt)
 
393
            def sign(text):
 
394
                return bzrlib.gpg.LoopbackGPGStrategy(None).sign(text)
 
395
            self.assertEqual(sign(Testament.from_revision(branch.repository,
 
396
                             'B').as_short_text()),
 
397
                             branch.repository.get_signature_text('B'))
 
398
        finally:
 
399
            bzrlib.gpg.GPGStrategy = oldstrategy
 
400
 
 
401
    def test_commit_failed_signature(self):
 
402
        import bzrlib.gpg
 
403
        import bzrlib.commit as commit
 
404
        oldstrategy = bzrlib.gpg.GPGStrategy
 
405
        wt = self.make_branch_and_tree('.')
 
406
        branch = wt.branch
 
407
        wt.commit("base", allow_pointless=True, rev_id='A')
 
408
        self.failIf(branch.repository.has_signature_for_revision_id('A'))
 
409
        try:
 
410
            from bzrlib.testament import Testament
 
411
            # monkey patch gpg signing mechanism
 
412
            bzrlib.gpg.GPGStrategy = bzrlib.gpg.DisabledGPGStrategy
 
413
            config = MustSignConfig(branch)
 
414
            self.assertRaises(SigningFailed,
 
415
                              commit.Commit(config=config).commit,
 
416
                              message="base",
 
417
                              allow_pointless=True,
 
418
                              rev_id='B',
 
419
                              working_tree=wt)
 
420
            branch = Branch.open(self.get_url('.'))
 
421
            self.assertEqual(branch.revision_history(), ['A'])
 
422
            self.failIf(branch.repository.has_revision('B'))
 
423
        finally:
 
424
            bzrlib.gpg.GPGStrategy = oldstrategy
 
425
 
 
426
    def test_commit_invokes_hooks(self):
 
427
        import bzrlib.commit as commit
 
428
        wt = self.make_branch_and_tree('.')
 
429
        branch = wt.branch
 
430
        calls = []
 
431
        def called(branch, rev_id):
 
432
            calls.append('called')
 
433
        bzrlib.ahook = called
 
434
        try:
 
435
            config = BranchWithHooks(branch)
 
436
            commit.Commit(config=config).commit(
 
437
                            message = "base",
 
438
                            allow_pointless=True,
 
439
                            rev_id='A', working_tree = wt)
 
440
            self.assertEqual(['called', 'called'], calls)
 
441
        finally:
 
442
            del bzrlib.ahook
 
443
 
 
444
    def test_commit_object_doesnt_set_nick(self):
 
445
        # using the Commit object directly does not set the branch nick.
 
446
        wt = self.make_branch_and_tree('.')
 
447
        c = Commit()
 
448
        c.commit(working_tree=wt, message='empty tree', allow_pointless=True)
 
449
        self.assertEquals(wt.branch.revno(), 1)
 
450
        self.assertEqual({},
 
451
                         wt.branch.repository.get_revision(
 
452
                            wt.branch.last_revision()).properties)
 
453
 
 
454
    def test_safe_master_lock(self):
 
455
        os.mkdir('master')
 
456
        master = BzrDirMetaFormat1().initialize('master')
 
457
        master.create_repository()
 
458
        master_branch = master.create_branch()
 
459
        master.create_workingtree()
 
460
        bound = master.sprout('bound')
 
461
        wt = bound.open_workingtree()
 
462
        wt.branch.set_bound_location(os.path.realpath('master'))
 
463
        master_branch.lock_write()
 
464
        try:
 
465
            self.assertRaises(LockContention, wt.commit, 'silly')
 
466
        finally:
 
467
            master_branch.unlock()
 
468
 
 
469
    def test_commit_bound_merge(self):
 
470
        # see bug #43959; commit of a merge in a bound branch fails to push
 
471
        # the new commit into the master
 
472
        master_branch = self.make_branch('master')
 
473
        bound_tree = self.make_branch_and_tree('bound')
 
474
        bound_tree.branch.bind(master_branch)
 
475
 
 
476
        self.build_tree_contents([('bound/content_file', 'initial contents\n')])
 
477
        bound_tree.add(['content_file'])
 
478
        bound_tree.commit(message='woo!')
 
479
 
 
480
        other_bzrdir = master_branch.bzrdir.sprout('other')
 
481
        other_tree = other_bzrdir.open_workingtree()
 
482
 
 
483
        # do a commit to the other branch changing the content file so
 
484
        # that our commit after merging will have a merged revision in the
 
485
        # content file history.
 
486
        self.build_tree_contents([('other/content_file', 'change in other\n')])
 
487
        other_tree.commit('change in other')
 
488
 
 
489
        # do a merge into the bound branch from other, and then change the
 
490
        # content file locally to force a new revision (rather than using the
 
491
        # revision from other). This forces extra processing in commit.
 
492
        bound_tree.merge_from_branch(other_tree.branch)
 
493
        self.build_tree_contents([('bound/content_file', 'change in bound\n')])
 
494
 
 
495
        # before #34959 was fixed, this failed with 'revision not present in
 
496
        # weave' when trying to implicitly push from the bound branch to the master
 
497
        bound_tree.commit(message='commit of merge in bound tree')
 
498
 
 
499
    def test_commit_reporting_after_merge(self):
 
500
        # when doing a commit of a merge, the reporter needs to still
 
501
        # be called for each item that is added/removed/deleted.
 
502
        this_tree = self.make_branch_and_tree('this')
 
503
        # we need a bunch of files and dirs, to perform one action on each.
 
504
        self.build_tree([
 
505
            'this/dirtorename/',
 
506
            'this/dirtoreparent/',
 
507
            'this/dirtoleave/',
 
508
            'this/dirtoremove/',
 
509
            'this/filetoreparent',
 
510
            'this/filetorename',
 
511
            'this/filetomodify',
 
512
            'this/filetoremove',
 
513
            'this/filetoleave']
 
514
            )
 
515
        this_tree.add([
 
516
            'dirtorename',
 
517
            'dirtoreparent',
 
518
            'dirtoleave',
 
519
            'dirtoremove',
 
520
            'filetoreparent',
 
521
            'filetorename',
 
522
            'filetomodify',
 
523
            'filetoremove',
 
524
            'filetoleave']
 
525
            )
 
526
        this_tree.commit('create_files')
 
527
        other_dir = this_tree.bzrdir.sprout('other')
 
528
        other_tree = other_dir.open_workingtree()
 
529
        other_tree.lock_write()
 
530
        # perform the needed actions on the files and dirs.
 
531
        try:
 
532
            other_tree.rename_one('dirtorename', 'renameddir')
 
533
            other_tree.rename_one('dirtoreparent', 'renameddir/reparenteddir')
 
534
            other_tree.rename_one('filetorename', 'renamedfile')
 
535
            other_tree.rename_one('filetoreparent', 'renameddir/reparentedfile')
 
536
            other_tree.remove(['dirtoremove', 'filetoremove'])
 
537
            self.build_tree_contents([
 
538
                ('other/newdir/', ),
 
539
                ('other/filetomodify', 'new content'),
 
540
                ('other/newfile', 'new file content')])
 
541
            other_tree.add('newfile')
 
542
            other_tree.add('newdir/')
 
543
            other_tree.commit('modify all sample files and dirs.')
 
544
        finally:
 
545
            other_tree.unlock()
 
546
        this_tree.merge_from_branch(other_tree.branch)
 
547
        reporter = CapturingReporter()
 
548
        this_tree.commit('do the commit', reporter=reporter)
 
549
        expected = set([
 
550
            ('change', 'modified', 'filetomodify'),
 
551
            ('change', 'added', 'newdir'),
 
552
            ('change', 'added', 'newfile'),
 
553
            ('renamed', 'renamed', 'dirtorename', 'renameddir'),
 
554
            ('renamed', 'renamed', 'filetorename', 'renamedfile'),
 
555
            ('renamed', 'renamed', 'dirtoreparent', 'renameddir/reparenteddir'),
 
556
            ('renamed', 'renamed', 'filetoreparent', 'renameddir/reparentedfile'),
 
557
            ('deleted', 'dirtoremove'),
 
558
            ('deleted', 'filetoremove'),
 
559
            ])
 
560
        result = set(reporter.calls)
 
561
        missing = expected - result
 
562
        new = result - expected
 
563
        self.assertEqual((set(), set()), (missing, new))
 
564
 
 
565
    def test_commit_removals_respects_filespec(self):
 
566
        """Commit respects the specified_files for removals."""
 
567
        tree = self.make_branch_and_tree('.')
 
568
        self.build_tree(['a', 'b'])
 
569
        tree.add(['a', 'b'])
 
570
        tree.commit('added a, b')
 
571
        tree.remove(['a', 'b'])
 
572
        tree.commit('removed a', specific_files='a')
 
573
        basis = tree.basis_tree()
 
574
        tree.lock_read()
 
575
        try:
 
576
            self.assertIs(None, basis.path2id('a'))
 
577
            self.assertFalse(basis.path2id('b') is None)
 
578
        finally:
 
579
            tree.unlock()
 
580
 
 
581
    def test_commit_saves_1ms_timestamp(self):
 
582
        """Passing in a timestamp is saved with 1ms resolution"""
 
583
        tree = self.make_branch_and_tree('.')
 
584
        self.build_tree(['a'])
 
585
        tree.add('a')
 
586
        tree.commit('added a', timestamp=1153248633.4186721, timezone=0,
 
587
                    rev_id='a1')
 
588
 
 
589
        rev = tree.branch.repository.get_revision('a1')
 
590
        self.assertEqual(1153248633.419, rev.timestamp)
 
591
 
 
592
    def test_commit_has_1ms_resolution(self):
 
593
        """Allowing commit to generate the timestamp also has 1ms resolution"""
 
594
        tree = self.make_branch_and_tree('.')
 
595
        self.build_tree(['a'])
 
596
        tree.add('a')
 
597
        tree.commit('added a', rev_id='a1')
 
598
 
 
599
        rev = tree.branch.repository.get_revision('a1')
 
600
        timestamp = rev.timestamp
 
601
        timestamp_1ms = round(timestamp, 3)
 
602
        self.assertEqual(timestamp_1ms, timestamp)
 
603
 
 
604
    def assertBasisTreeKind(self, kind, tree, file_id):
 
605
        basis = tree.basis_tree()
 
606
        basis.lock_read()
 
607
        try:
 
608
            self.assertEqual(kind, basis.kind(file_id))
 
609
        finally:
 
610
            basis.unlock()
 
611
 
 
612
    def test_commit_kind_changes(self):
 
613
        self.requireFeature(SymlinkFeature)
 
614
        tree = self.make_branch_and_tree('.')
 
615
        os.symlink('target', 'name')
 
616
        tree.add('name', 'a-file-id')
 
617
        tree.commit('Added a symlink')
 
618
        self.assertBasisTreeKind('symlink', tree, 'a-file-id')
 
619
 
 
620
        os.unlink('name')
 
621
        self.build_tree(['name'])
 
622
        tree.commit('Changed symlink to file')
 
623
        self.assertBasisTreeKind('file', tree, 'a-file-id')
 
624
 
 
625
        os.unlink('name')
 
626
        os.symlink('target', 'name')
 
627
        tree.commit('file to symlink')
 
628
        self.assertBasisTreeKind('symlink', tree, 'a-file-id')
 
629
 
 
630
        os.unlink('name')
 
631
        os.mkdir('name')
 
632
        tree.commit('symlink to directory')
 
633
        self.assertBasisTreeKind('directory', tree, 'a-file-id')
 
634
 
 
635
        os.rmdir('name')
 
636
        os.symlink('target', 'name')
 
637
        tree.commit('directory to symlink')
 
638
        self.assertBasisTreeKind('symlink', tree, 'a-file-id')
 
639
 
 
640
        # prepare for directory <-> file tests
 
641
        os.unlink('name')
 
642
        os.mkdir('name')
 
643
        tree.commit('symlink to directory')
 
644
        self.assertBasisTreeKind('directory', tree, 'a-file-id')
 
645
 
 
646
        os.rmdir('name')
 
647
        self.build_tree(['name'])
 
648
        tree.commit('Changed directory to file')
 
649
        self.assertBasisTreeKind('file', tree, 'a-file-id')
 
650
 
 
651
        os.unlink('name')
 
652
        os.mkdir('name')
 
653
        tree.commit('file to directory')
 
654
        self.assertBasisTreeKind('directory', tree, 'a-file-id')
 
655
 
 
656
    def test_commit_unversioned_specified(self):
 
657
        """Commit should raise if specified files isn't in basis or worktree"""
 
658
        tree = self.make_branch_and_tree('.')
 
659
        self.assertRaises(errors.PathsNotVersionedError, tree.commit,
 
660
                          'message', specific_files=['bogus'])
 
661
 
 
662
    class Callback(object):
 
663
 
 
664
        def __init__(self, message, testcase):
 
665
            self.called = False
 
666
            self.message = message
 
667
            self.testcase = testcase
 
668
 
 
669
        def __call__(self, commit_obj):
 
670
            self.called = True
 
671
            self.testcase.assertTrue(isinstance(commit_obj, Commit))
 
672
            return self.message
 
673
 
 
674
    def test_commit_callback(self):
 
675
        """Commit should invoke a callback to get the message"""
 
676
 
 
677
        tree = self.make_branch_and_tree('.')
 
678
        try:
 
679
            tree.commit()
 
680
        except Exception, e:
 
681
            self.assertTrue(isinstance(e, BzrError))
 
682
            self.assertEqual('The message or message_callback keyword'
 
683
                             ' parameter is required for commit().', str(e))
 
684
        else:
 
685
            self.fail('exception not raised')
 
686
        cb = self.Callback(u'commit 1', self)
 
687
        tree.commit(message_callback=cb)
 
688
        self.assertTrue(cb.called)
 
689
        repository = tree.branch.repository
 
690
        message = repository.get_revision(tree.last_revision()).message
 
691
        self.assertEqual('commit 1', message)
 
692
 
 
693
    def test_no_callback_pointless(self):
 
694
        """Callback should not be invoked for pointless commit"""
 
695
        tree = self.make_branch_and_tree('.')
 
696
        cb = self.Callback(u'commit 2', self)
 
697
        self.assertRaises(PointlessCommit, tree.commit, message_callback=cb,
 
698
                          allow_pointless=False)
 
699
        self.assertFalse(cb.called)
 
700
 
 
701
    def test_no_callback_netfailure(self):
 
702
        """Callback should not be invoked if connectivity fails"""
 
703
        tree = self.make_branch_and_tree('.')
 
704
        cb = self.Callback(u'commit 2', self)
 
705
        repository = tree.branch.repository
 
706
        # simulate network failure
 
707
        def raise_(self, arg, arg2, arg3=None, arg4=None):
 
708
            raise errors.NoSuchFile('foo')
 
709
        repository.add_inventory = raise_
 
710
        repository.add_inventory_by_delta = raise_
 
711
        self.assertRaises(errors.NoSuchFile, tree.commit, message_callback=cb)
 
712
        self.assertFalse(cb.called)
 
713
 
 
714
    def test_selected_file_merge_commit(self):
 
715
        """Ensure the correct error is raised"""
 
716
        tree = self.make_branch_and_tree('foo')
 
717
        # pending merge would turn into a left parent
 
718
        tree.commit('commit 1')
 
719
        tree.add_parent_tree_id('example')
 
720
        self.build_tree(['foo/bar', 'foo/baz'])
 
721
        tree.add(['bar', 'baz'])
 
722
        err = self.assertRaises(errors.CannotCommitSelectedFileMerge,
 
723
            tree.commit, 'commit 2', specific_files=['bar', 'baz'])
 
724
        self.assertEqual(['bar', 'baz'], err.files)
 
725
        self.assertEqual('Selected-file commit of merges is not supported'
 
726
                         ' yet: files bar, baz', str(err))
 
727
 
 
728
    def test_commit_ordering(self):
 
729
        """Test of corner-case commit ordering error"""
 
730
        tree = self.make_branch_and_tree('.')
 
731
        self.build_tree(['a/', 'a/z/', 'a/c/', 'a/z/x', 'a/z/y'])
 
732
        tree.add(['a/', 'a/z/', 'a/c/', 'a/z/x', 'a/z/y'])
 
733
        tree.commit('setup')
 
734
        self.build_tree(['a/c/d/'])
 
735
        tree.add('a/c/d')
 
736
        tree.rename_one('a/z/x', 'a/c/d/x')
 
737
        tree.commit('test', specific_files=['a/z/y'])
 
738
 
 
739
    def test_commit_no_author(self):
 
740
        """The default kwarg author in MutableTree.commit should not add
 
741
        the 'author' revision property.
 
742
        """
 
743
        tree = self.make_branch_and_tree('foo')
 
744
        rev_id = tree.commit('commit 1')
 
745
        rev = tree.branch.repository.get_revision(rev_id)
 
746
        self.assertFalse('author' in rev.properties)
 
747
        self.assertFalse('authors' in rev.properties)
 
748
 
 
749
    def test_commit_author(self):
 
750
        """Passing a non-empty author kwarg to MutableTree.commit should add
 
751
        the 'author' revision property.
 
752
        """
 
753
        tree = self.make_branch_and_tree('foo')
 
754
        rev_id = self.callDeprecated(['The parameter author was '
 
755
                'deprecated in version 1.13. Use authors instead'],
 
756
                tree.commit, 'commit 1', author='John Doe <jdoe@example.com>')
 
757
        rev = tree.branch.repository.get_revision(rev_id)
 
758
        self.assertEqual('John Doe <jdoe@example.com>',
 
759
                         rev.properties['authors'])
 
760
        self.assertFalse('author' in rev.properties)
 
761
 
 
762
    def test_commit_empty_authors_list(self):
 
763
        """Passing an empty list to authors shouldn't add the property."""
 
764
        tree = self.make_branch_and_tree('foo')
 
765
        rev_id = tree.commit('commit 1', authors=[])
 
766
        rev = tree.branch.repository.get_revision(rev_id)
 
767
        self.assertFalse('author' in rev.properties)
 
768
        self.assertFalse('authors' in rev.properties)
 
769
 
 
770
    def test_multiple_authors(self):
 
771
        tree = self.make_branch_and_tree('foo')
 
772
        rev_id = tree.commit('commit 1',
 
773
                authors=['John Doe <jdoe@example.com>',
 
774
                         'Jane Rey <jrey@example.com>'])
 
775
        rev = tree.branch.repository.get_revision(rev_id)
 
776
        self.assertEqual('John Doe <jdoe@example.com>\n'
 
777
                'Jane Rey <jrey@example.com>', rev.properties['authors'])
 
778
        self.assertFalse('author' in rev.properties)
 
779
 
 
780
    def test_author_and_authors_incompatible(self):
 
781
        tree = self.make_branch_and_tree('foo')
 
782
        self.assertRaises(AssertionError, tree.commit, 'commit 1',
 
783
                authors=['John Doe <jdoe@example.com>',
 
784
                         'Jane Rey <jrey@example.com>'],
 
785
                author="Jack Me <jme@example.com>")
 
786
 
 
787
    def test_author_with_newline_rejected(self):
 
788
        tree = self.make_branch_and_tree('foo')
 
789
        self.assertRaises(AssertionError, tree.commit, 'commit 1',
 
790
                authors=['John\nDoe <jdoe@example.com>'])
 
791
 
 
792
    def test_commit_with_checkout_and_branch_sharing_repo(self):
 
793
        repo = self.make_repository('repo', shared=True)
 
794
        # make_branch_and_tree ignores shared repos
 
795
        branch = bzrdir.BzrDir.create_branch_convenience('repo/branch')
 
796
        tree2 = branch.create_checkout('repo/tree2')
 
797
        tree2.commit('message', rev_id='rev1')
 
798
        self.assertTrue(tree2.branch.repository.has_revision('rev1'))