~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_commit.py

  • Committer: John Arbash Meinel
  • Author(s): Mark Hammond
  • Date: 2008-09-09 17:02:21 UTC
  • mto: This revision was merged to the branch mainline in revision 3697.
  • Revision ID: john@arbash-meinel.com-20080909170221-svim3jw2mrz0amp3
An updated transparent icon for bzr.

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