~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: 2007-05-08 20:32:56 UTC
  • mfrom: (2483.1.2 jam-integration)
  • Revision ID: pqm@pqm.ubuntu.com-20070508203256-wcxwdphd1y2psezh
(John Arbash Meinel) Merge fixes from 0.16 into bzr.dev and update for 0.17 development

Show diffs side-by-side

added added

removed removed

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