~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/selftest/test_commit.py

Exclude more files from dumb-rsync upload

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006 Canonical Ltd
2
 
#
 
1
# Copyright (C) 2005 by 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 import (
22
 
    errors,
23
 
    lockdir,
24
 
    osutils,
25
 
    tests,
26
 
    )
 
21
from bzrlib.selftest import TestCaseInTempDir
27
22
from bzrlib.branch import Branch
28
 
from bzrlib.bzrdir import BzrDir, BzrDirMetaFormat1
29
 
from bzrlib.commit import Commit, NullCommitReporter
 
23
from bzrlib.workingtree import WorkingTree
 
24
from bzrlib.commit import Commit
30
25
from bzrlib.config import BranchConfig
31
 
from bzrlib.errors import (PointlessCommit, BzrError, SigningFailed, 
32
 
                           LockContention)
33
 
from bzrlib.tests import TestCaseWithTransport
34
 
from bzrlib.workingtree import WorkingTree
 
26
from bzrlib.errors import PointlessCommit, BzrError, SigningFailed
35
27
 
36
28
 
37
29
# TODO: Test commit with some added, and added-but-missing files
51
43
        return "bzrlib.ahook bzrlib.ahook"
52
44
 
53
45
 
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):
 
46
class TestCommit(TestCaseInTempDir):
75
47
 
76
48
    def test_simple_commit(self):
77
49
        """Commit and check two versions of a single file."""
78
 
        wt = self.make_branch_and_tree('.')
79
 
        b = wt.branch
 
50
        b = Branch.initialize('.')
80
51
        file('hello', 'w').write('hello world')
81
 
        wt.add('hello')
82
 
        wt.commit(message='add hello')
83
 
        file_id = wt.path2id('hello')
 
52
        b.add('hello')
 
53
        b.working_tree().commit(message='add hello')
 
54
        file_id = b.working_tree().path2id('hello')
84
55
 
85
56
        file('hello', 'w').write('version 2')
86
 
        wt.commit(message='commit 2')
 
57
        b.working_tree().commit(message='commit 2')
87
58
 
88
59
        eq = self.assertEquals
89
60
        eq(b.revno(), 2)
90
61
        rh = b.revision_history()
91
 
        rev = b.repository.get_revision(rh[0])
 
62
        rev = b.get_revision(rh[0])
92
63
        eq(rev.message, 'add hello')
93
64
 
94
 
        tree1 = b.repository.revision_tree(rh[0])
 
65
        tree1 = b.revision_tree(rh[0])
95
66
        text = tree1.get_file_text(file_id)
96
67
        eq(text, 'hello world')
97
68
 
98
 
        tree2 = b.repository.revision_tree(rh[1])
 
69
        tree2 = b.revision_tree(rh[1])
99
70
        eq(tree2.get_file_text(file_id), 'version 2')
100
71
 
101
72
    def test_delete_commit(self):
102
73
        """Test a commit with a deleted file"""
103
 
        wt = self.make_branch_and_tree('.')
104
 
        b = wt.branch
 
74
        b = Branch.initialize('.')
105
75
        file('hello', 'w').write('hello world')
106
 
        wt.add(['hello'], ['hello-id'])
107
 
        wt.commit(message='add hello')
 
76
        b.add(['hello'], ['hello-id'])
 
77
        b.working_tree().commit(message='add hello')
108
78
 
109
79
        os.remove('hello')
110
 
        wt.commit('removed hello', rev_id='rev2')
 
80
        b.working_tree().commit('removed hello', rev_id='rev2')
111
81
 
112
 
        tree = b.repository.revision_tree('rev2')
 
82
        tree = b.revision_tree('rev2')
113
83
        self.assertFalse(tree.has_id('hello-id'))
114
84
 
115
85
    def test_pointless_commit(self):
116
86
        """Commit refuses unless there are changes or it's forced."""
117
 
        wt = self.make_branch_and_tree('.')
118
 
        b = wt.branch
 
87
        b = Branch.initialize('.')
119
88
        file('hello', 'w').write('hello')
120
 
        wt.add(['hello'])
121
 
        wt.commit(message='add hello')
 
89
        b.add(['hello'])
 
90
        b.working_tree().commit(message='add hello')
122
91
        self.assertEquals(b.revno(), 1)
123
92
        self.assertRaises(PointlessCommit,
124
 
                          wt.commit,
 
93
                          b.working_tree().commit,
125
94
                          message='fails',
126
95
                          allow_pointless=False)
127
96
        self.assertEquals(b.revno(), 1)
128
97
        
129
98
    def test_commit_empty(self):
130
99
        """Commiting an empty tree works."""
131
 
        wt = self.make_branch_and_tree('.')
132
 
        b = wt.branch
133
 
        wt.commit(message='empty tree', allow_pointless=True)
 
100
        b = Branch.initialize('.')
 
101
        b.working_tree().commit(message='empty tree', allow_pointless=True)
134
102
        self.assertRaises(PointlessCommit,
135
 
                          wt.commit,
 
103
                          b.working_tree().commit,
136
104
                          message='empty tree',
137
105
                          allow_pointless=False)
138
 
        wt.commit(message='empty tree', allow_pointless=True)
 
106
        b.working_tree().commit(message='empty tree', allow_pointless=True)
139
107
        self.assertEquals(b.revno(), 2)
140
108
 
 
109
 
141
110
    def test_selective_delete(self):
142
111
        """Selective commit in tree with deletions"""
143
 
        wt = self.make_branch_and_tree('.')
144
 
        b = wt.branch
 
112
        b = Branch.initialize('.')
145
113
        file('hello', 'w').write('hello')
146
114
        file('buongia', 'w').write('buongia')
147
 
        wt.add(['hello', 'buongia'],
 
115
        b.add(['hello', 'buongia'],
148
116
              ['hello-id', 'buongia-id'])
149
 
        wt.commit(message='add files',
 
117
        b.working_tree().commit(message='add files',
150
118
                 rev_id='test@rev-1')
151
119
        
152
120
        os.remove('hello')
153
121
        file('buongia', 'w').write('new text')
154
 
        wt.commit(message='update text',
 
122
        b.working_tree().commit(message='update text',
155
123
                 specific_files=['buongia'],
156
124
                 allow_pointless=False,
157
125
                 rev_id='test@rev-2')
158
126
 
159
 
        wt.commit(message='remove hello',
 
127
        b.working_tree().commit(message='remove hello',
160
128
                 specific_files=['hello'],
161
129
                 allow_pointless=False,
162
130
                 rev_id='test@rev-3')
164
132
        eq = self.assertEquals
165
133
        eq(b.revno(), 3)
166
134
 
167
 
        tree2 = b.repository.revision_tree('test@rev-2')
 
135
        tree2 = b.revision_tree('test@rev-2')
168
136
        self.assertTrue(tree2.has_filename('hello'))
169
137
        self.assertEquals(tree2.get_file_text('hello-id'), 'hello')
170
138
        self.assertEquals(tree2.get_file_text('buongia-id'), 'new text')
171
139
        
172
 
        tree3 = b.repository.revision_tree('test@rev-3')
 
140
        tree3 = b.revision_tree('test@rev-3')
173
141
        self.assertFalse(tree3.has_filename('hello'))
174
142
        self.assertEquals(tree3.get_file_text('buongia-id'), 'new text')
175
143
 
 
144
 
176
145
    def test_commit_rename(self):
177
146
        """Test commit of a revision where a file is renamed."""
178
 
        tree = self.make_branch_and_tree('.')
179
 
        b = tree.branch
 
147
        b = Branch.initialize('.')
180
148
        self.build_tree(['hello'], line_endings='binary')
181
 
        tree.add(['hello'], ['hello-id'])
182
 
        tree.commit(message='one', rev_id='test@rev-1', allow_pointless=False)
 
149
        b.add(['hello'], ['hello-id'])
 
150
        b.working_tree().commit(message='one', rev_id='test@rev-1', allow_pointless=False)
183
151
 
184
 
        tree.rename_one('hello', 'fruity')
185
 
        tree.commit(message='renamed', rev_id='test@rev-2', allow_pointless=False)
 
152
        b.rename_one('hello', 'fruity')
 
153
        b.working_tree().commit(message='renamed', rev_id='test@rev-2', allow_pointless=False)
186
154
 
187
155
        eq = self.assertEquals
188
 
        tree1 = b.repository.revision_tree('test@rev-1')
 
156
        tree1 = b.revision_tree('test@rev-1')
189
157
        eq(tree1.id2path('hello-id'), 'hello')
190
158
        eq(tree1.get_file_text('hello-id'), 'contents of hello\n')
191
159
        self.assertFalse(tree1.has_filename('fruity'))
193
161
        ie = tree1.inventory['hello-id']
194
162
        eq(ie.revision, 'test@rev-1')
195
163
 
196
 
        tree2 = b.repository.revision_tree('test@rev-2')
 
164
        tree2 = b.revision_tree('test@rev-2')
197
165
        eq(tree2.id2path('hello-id'), 'fruity')
198
166
        eq(tree2.get_file_text('hello-id'), 'contents of hello\n')
199
167
        self.check_inventory_shape(tree2.inventory, ['fruity'])
200
168
        ie = tree2.inventory['hello-id']
201
169
        eq(ie.revision, 'test@rev-2')
202
170
 
 
171
 
203
172
    def test_reused_rev_id(self):
204
173
        """Test that a revision id cannot be reused in a branch"""
205
 
        wt = self.make_branch_and_tree('.')
206
 
        b = wt.branch
207
 
        wt.commit('initial', rev_id='test@rev-1', allow_pointless=True)
 
174
        b = Branch.initialize('.')
 
175
        b.working_tree().commit('initial', rev_id='test@rev-1', allow_pointless=True)
208
176
        self.assertRaises(Exception,
209
 
                          wt.commit,
 
177
                          b.working_tree().commit,
210
178
                          message='reused id',
211
179
                          rev_id='test@rev-1',
212
180
                          allow_pointless=True)
 
181
                          
 
182
 
213
183
 
214
184
    def test_commit_move(self):
215
185
        """Test commit of revisions with moved files and directories"""
216
186
        eq = self.assertEquals
217
 
        wt = self.make_branch_and_tree('.')
218
 
        b = wt.branch
 
187
        b = Branch.initialize('.')
219
188
        r1 = 'test@rev-1'
220
189
        self.build_tree(['hello', 'a/', 'b/'])
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')
 
190
        b.add(['hello', 'a', 'b'], ['hello-id', 'a-id', 'b-id'])
 
191
        b.working_tree().commit('initial', rev_id=r1, allow_pointless=False)
 
192
 
 
193
        b.move(['hello'], 'a')
224
194
        r2 = 'test@rev-2'
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
        b.working_tree().commit('two', rev_id=r2, allow_pointless=False)
 
196
        self.check_inventory_shape(b.working_tree().read_working_inventory(),
 
197
                                   ['a', 'a/hello', 'b'])
232
198
 
233
 
        wt.move(['b'], 'a')
 
199
        b.move(['b'], 'a')
234
200
        r3 = 'test@rev-3'
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()
 
201
        b.working_tree().commit('three', rev_id=r3, allow_pointless=False)
 
202
        self.check_inventory_shape(b.working_tree().read_working_inventory(),
 
203
                                   ['a', 'a/hello', 'a/b'])
 
204
        self.check_inventory_shape(b.get_revision_inventory(r3),
 
205
                                   ['a', 'a/hello', 'a/b'])
244
206
 
245
 
        wt.move(['a/hello'], 'a/b')
 
207
        b.move([os.sep.join(['a', 'hello'])],
 
208
               os.sep.join(['a', 'b']))
246
209
        r4 = 'test@rev-4'
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()
 
210
        b.working_tree().commit('four', rev_id=r4, allow_pointless=False)
 
211
        self.check_inventory_shape(b.working_tree().read_working_inventory(),
 
212
                                   ['a', 'a/b/hello', 'a/b'])
254
213
 
255
 
        inv = b.repository.get_revision_inventory(r4)
 
214
        inv = b.get_revision_inventory(r4)
256
215
        eq(inv['hello-id'].revision, r4)
257
216
        eq(inv['a-id'].revision, r1)
258
217
        eq(inv['b-id'].revision, r3)
259
218
 
 
219
        
260
220
    def test_removed_commit(self):
261
221
        """Commit with a removed file"""
262
 
        wt = self.make_branch_and_tree('.')
263
 
        b = wt.branch
 
222
        b = Branch.initialize('.')
 
223
        wt = b.working_tree()
264
224
        file('hello', 'w').write('hello world')
265
 
        wt.add(['hello'], ['hello-id'])
266
 
        wt.commit(message='add hello')
 
225
        b.add(['hello'], ['hello-id'])
 
226
        b.working_tree().commit(message='add hello')
 
227
 
 
228
        wt = b.working_tree()  # FIXME: kludge for aliasing of working inventory
267
229
        wt.remove('hello')
268
 
        wt.commit('removed hello', rev_id='rev2')
 
230
        b.working_tree().commit('removed hello', rev_id='rev2')
269
231
 
270
 
        tree = b.repository.revision_tree('rev2')
 
232
        tree = b.revision_tree('rev2')
271
233
        self.assertFalse(tree.has_id('hello-id'))
272
234
 
 
235
 
273
236
    def test_committed_ancestry(self):
274
237
        """Test commit appends revisions to ancestry."""
275
 
        wt = self.make_branch_and_tree('.')
276
 
        b = wt.branch
 
238
        b = Branch.initialize('.')
277
239
        rev_ids = []
278
240
        for i in range(4):
279
241
            file('hello', 'w').write((str(i) * 4) + '\n')
280
242
            if i == 0:
281
 
                wt.add(['hello'], ['hello-id'])
 
243
                b.add(['hello'], ['hello-id'])
282
244
            rev_id = 'test@rev-%d' % (i+1)
283
245
            rev_ids.append(rev_id)
284
 
            wt.commit(message='rev %d' % (i+1),
 
246
            b.working_tree().commit(message='rev %d' % (i+1),
285
247
                     rev_id=rev_id)
286
248
        eq = self.assertEquals
287
249
        eq(b.revision_history(), rev_ids)
288
250
        for i in range(4):
289
 
            anc = b.repository.get_ancestry(rev_ids[i])
 
251
            anc = b.get_ancestry(rev_ids[i])
290
252
            eq(anc, [None] + rev_ids[:i+1])
291
253
 
292
254
    def test_commit_new_subdir_child_selective(self):
293
 
        wt = self.make_branch_and_tree('.')
294
 
        b = wt.branch
 
255
        b = Branch.initialize('.')
295
256
        self.build_tree(['dir/', 'dir/file1', 'dir/file2'])
296
 
        wt.add(['dir', 'dir/file1', 'dir/file2'],
 
257
        b.add(['dir', 'dir/file1', 'dir/file2'],
297
258
              ['dirid', 'file1id', 'file2id'])
298
 
        wt.commit('dir/file1', specific_files=['dir/file1'], rev_id='1')
299
 
        inv = b.repository.get_inventory('1')
 
259
        b.working_tree().commit('dir/file1', specific_files=['dir/file1'], rev_id='1')
 
260
        inv = b.get_inventory('1')
300
261
        self.assertEqual('1', inv['dirid'].revision)
301
262
        self.assertEqual('1', inv['file1id'].revision)
302
263
        # FIXME: This should raise a KeyError I think, rbc20051006
305
266
    def test_strict_commit(self):
306
267
        """Try and commit with unknown files and strict = True, should fail."""
307
268
        from bzrlib.errors import StrictCommitFailed
308
 
        wt = self.make_branch_and_tree('.')
309
 
        b = wt.branch
 
269
        b = Branch.initialize('.')
310
270
        file('hello', 'w').write('hello world')
311
 
        wt.add('hello')
 
271
        b.add('hello')
312
272
        file('goodbye', 'w').write('goodbye cruel world!')
313
 
        self.assertRaises(StrictCommitFailed, wt.commit,
 
273
        self.assertRaises(StrictCommitFailed, b.working_tree().commit,
314
274
            message='add hello but not goodbye', strict=True)
315
275
 
316
276
    def test_strict_commit_without_unknowns(self):
317
277
        """Try and commit with no unknown files and strict = True,
318
278
        should work."""
319
279
        from bzrlib.errors import StrictCommitFailed
320
 
        wt = self.make_branch_and_tree('.')
321
 
        b = wt.branch
 
280
        b = Branch.initialize('.')
322
281
        file('hello', 'w').write('hello world')
323
 
        wt.add('hello')
324
 
        wt.commit(message='add hello', strict=True)
 
282
        b.add('hello')
 
283
        b.working_tree().commit(message='add hello', strict=True)
325
284
 
326
285
    def test_nonstrict_commit(self):
327
286
        """Try and commit with unknown files and strict = False, should work."""
328
 
        wt = self.make_branch_and_tree('.')
329
 
        b = wt.branch
 
287
        b = Branch.initialize('.')
330
288
        file('hello', 'w').write('hello world')
331
 
        wt.add('hello')
 
289
        b.add('hello')
332
290
        file('goodbye', 'w').write('goodbye cruel world!')
333
 
        wt.commit(message='add hello but not goodbye', strict=False)
 
291
        b.working_tree().commit(message='add hello but not goodbye', strict=False)
334
292
 
335
293
    def test_nonstrict_commit_without_unknowns(self):
336
294
        """Try and commit with no unknown files and strict = False,
337
295
        should work."""
338
 
        wt = self.make_branch_and_tree('.')
339
 
        b = wt.branch
 
296
        b = Branch.initialize('.')
340
297
        file('hello', 'w').write('hello world')
341
 
        wt.add('hello')
342
 
        wt.commit(message='add hello', strict=False)
 
298
        b.add('hello')
 
299
        b.working_tree().commit(message='add hello', strict=False)
343
300
 
344
301
    def test_signed_commit(self):
345
302
        import bzrlib.gpg
346
303
        import bzrlib.commit as commit
347
304
        oldstrategy = bzrlib.gpg.GPGStrategy
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'))
 
305
        branch = Branch.initialize('.')
 
306
        branch.working_tree().commit("base", allow_pointless=True, rev_id='A')
 
307
        self.failIf(branch.revision_store.has_id('A', 'sig'))
352
308
        try:
353
309
            from bzrlib.testament import Testament
354
310
            # monkey patch gpg signing mechanism
355
311
            bzrlib.gpg.GPGStrategy = bzrlib.gpg.LoopbackGPGStrategy
356
 
            commit.Commit(config=MustSignConfig(branch)).commit(message="base",
 
312
            commit.Commit(config=MustSignConfig(branch)).commit(branch, "base",
357
313
                                                      allow_pointless=True,
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
                                                      rev_id='B')
 
315
            self.assertEqual(Testament.from_revision(branch,'B').as_short_text(),
 
316
                             branch.revision_store.get('B', 'sig').read())
365
317
        finally:
366
318
            bzrlib.gpg.GPGStrategy = oldstrategy
367
319
 
369
321
        import bzrlib.gpg
370
322
        import bzrlib.commit as commit
371
323
        oldstrategy = bzrlib.gpg.GPGStrategy
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
        branch = Branch.initialize('.')
 
325
        branch.working_tree().commit("base", allow_pointless=True, rev_id='A')
 
326
        self.failIf(branch.revision_store.has_id('A', 'sig'))
376
327
        try:
377
328
            from bzrlib.testament import Testament
378
329
            # monkey patch gpg signing mechanism
380
331
            config = MustSignConfig(branch)
381
332
            self.assertRaises(SigningFailed,
382
333
                              commit.Commit(config=config).commit,
383
 
                              message="base",
 
334
                              branch, "base",
384
335
                              allow_pointless=True,
385
 
                              rev_id='B',
386
 
                              working_tree=wt)
387
 
            branch = Branch.open(self.get_url('.'))
 
336
                              rev_id='B')
 
337
            branch = Branch.open('.')
388
338
            self.assertEqual(branch.revision_history(), ['A'])
389
 
            self.failIf(branch.repository.has_revision('B'))
 
339
            self.failIf(branch.revision_store.has_id('B'))
390
340
        finally:
391
341
            bzrlib.gpg.GPGStrategy = oldstrategy
392
342
 
393
343
    def test_commit_invokes_hooks(self):
394
344
        import bzrlib.commit as commit
395
 
        wt = self.make_branch_and_tree('.')
396
 
        branch = wt.branch
 
345
        branch = Branch.initialize('.')
397
346
        calls = []
398
347
        def called(branch, rev_id):
399
348
            calls.append('called')
401
350
        try:
402
351
            config = BranchWithHooks(branch)
403
352
            commit.Commit(config=config).commit(
404
 
                            message = "base",
 
353
                            branch, "base",
405
354
                            allow_pointless=True,
406
 
                            rev_id='A', working_tree = wt)
 
355
                            rev_id='A')
407
356
            self.assertEqual(['called', 'called'], calls)
408
357
        finally:
409
358
            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))