~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_commit.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2008-04-07 07:52:50 UTC
  • mfrom: (3340.1.1 208418-1.4)
  • Revision ID: pqm@pqm.ubuntu.com-20080407075250-phs53xnslo8boaeo
Return the correct knit serialisation method in _StreamAccess.
        (Andrew Bennetts, Martin Pool, Robert Collins)

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