~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_commit.py

  • Committer: Andrew Bennetts
  • Date: 2008-02-12 01:56:09 UTC
  • mto: (3245.4.1 Version 3 implementation.)
  • mto: This revision was merged to the branch mainline in revision 3428.
  • Revision ID: andrew.bennetts@canonical.com-20080212015609-loaxv5te0eb6uot8
Make the general request handler dispatch version 3 events to the specific request handler (i.e. to the SmartServerRequest instance).

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