/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-04-18 23:19:16 UTC
  • mfrom: (5784.3.1 cleanup)
  • Revision ID: pqm@pqm.ubuntu.com-20110418231916-8x8pvhwr2q8l6gq1
(mbp) remove unnecessary TestShowDiffTreesHelper from tests (Martin Pool)

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