~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_commit.py

  • Committer: John Arbash Meinel
  • Date: 2007-08-14 19:29:56 UTC
  • mto: This revision was merged to the branch mainline in revision 2698.
  • Revision ID: john@arbash-meinel.com-20070814192956-34h336i5q3m34ods
Switch bzr.dev to 0.91 development

Show diffs side-by-side

added added

removed removed

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