~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/selftest/test_commit.py

Refactored the export code to make it easier to add new export formats.

Show diffs side-by-side

added added

removed removed

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