~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: 2010-10-13 07:04:50 UTC
  • mfrom: (5447.2.2 work)
  • Revision ID: pqm@pqm.ubuntu.com-20101013070450-xmn9cpnli5qnmrt8
(vila) Tweak the release process based on ML discussion (Vincent Ladeuil)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005 by Canonical Ltd
2
 
 
 
1
# Copyright (C) 2005-2010 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
17
 
18
18
import os
19
19
 
20
20
import bzrlib
21
 
from bzrlib.selftest 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('.')
 
82
        wt = self.make_branch_and_tree('.')
 
83
        b = wt.branch
51
84
        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')
 
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)
61
94
        rh = b.revision_history()
62
 
        rev = b.get_revision(rh[0])
 
95
        rev = b.repository.get_revision(rh[0])
63
96
        eq(rev.message, 'add hello')
64
97
 
65
 
        tree1 = b.revision_tree(rh[0])
 
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')
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('.')
 
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_missing_commit(self):
 
111
        """Test a commit with a missing file"""
 
112
        wt = self.make_branch_and_tree('.')
 
113
        b = wt.branch
75
114
        file('hello', 'w').write('hello world')
76
 
        b.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
 
        tree = b.revision_tree('rev2')
 
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('.')
 
146
        wt = self.make_branch_and_tree('.')
 
147
        b = wt.branch
88
148
        file('hello', 'w').write('hello')
89
 
        b.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('.')
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('.')
 
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.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')
132
193
        eq = self.assertEquals
133
194
        eq(b.revno(), 3)
134
195
 
135
 
        tree2 = b.revision_tree('test@rev-2')
 
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
 
        
140
 
        tree3 = b.revision_tree('test@rev-3')
 
202
 
 
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('.')
 
211
        tree = self.make_branch_and_tree('.')
 
212
        b = tree.branch
148
213
        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)
 
214
        tree.add(['hello'], ['hello-id'])
 
215
        tree.commit(message='one', rev_id='test@rev-1', allow_pointless=False)
151
216
 
152
 
        b.rename_one('hello', 'fruity')
153
 
        b.working_tree().commit(message='renamed', rev_id='test@rev-2', allow_pointless=False)
 
217
        tree.rename_one('hello', 'fruity')
 
218
        tree.commit(message='renamed', rev_id='test@rev-2', allow_pointless=False)
154
219
 
155
220
        eq = self.assertEquals
156
 
        tree1 = b.revision_tree('test@rev-1')
 
221
        tree1 = b.repository.revision_tree('test@rev-1')
 
222
        tree1.lock_read()
 
223
        self.addCleanup(tree1.unlock)
157
224
        eq(tree1.id2path('hello-id'), 'hello')
158
225
        eq(tree1.get_file_text('hello-id'), 'contents of hello\n')
159
226
        self.assertFalse(tree1.has_filename('fruity'))
161
228
        ie = tree1.inventory['hello-id']
162
229
        eq(ie.revision, 'test@rev-1')
163
230
 
164
 
        tree2 = b.revision_tree('test@rev-2')
 
231
        tree2 = b.repository.revision_tree('test@rev-2')
 
232
        tree2.lock_read()
 
233
        self.addCleanup(tree2.unlock)
165
234
        eq(tree2.id2path('hello-id'), 'fruity')
166
235
        eq(tree2.get_file_text('hello-id'), 'contents of hello\n')
167
236
        self.check_inventory_shape(tree2.inventory, ['fruity'])
168
237
        ie = tree2.inventory['hello-id']
169
238
        eq(ie.revision, 'test@rev-2')
170
239
 
171
 
 
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('.')
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)
181
 
                          
182
 
 
183
250
 
184
251
    def test_commit_move(self):
185
252
        """Test commit of revisions with moved files and directories"""
186
253
        eq = self.assertEquals
187
 
        b = Branch.initialize('.')
 
254
        wt = self.make_branch_and_tree('.')
 
255
        b = wt.branch
188
256
        r1 = 'test@rev-1'
189
257
        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')
 
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')
194
261
        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'])
 
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()
198
269
 
199
 
        b.move(['b'], 'a')
 
270
        wt.move(['b'], 'a')
200
271
        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'])
 
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_inventory(r3),
 
278
                                       ['a/', 'a/hello', 'a/b/'])
 
279
        finally:
 
280
            wt.unlock()
206
281
 
207
 
        b.move([os.sep.join(['a', 'hello'])],
208
 
               os.sep.join(['a', 'b']))
 
282
        wt.move(['a/hello'], 'a/b')
209
283
        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'])
 
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()
213
291
 
214
 
        inv = b.get_revision_inventory(r4)
 
292
        inv = b.repository.get_inventory(r4)
215
293
        eq(inv['hello-id'].revision, r4)
216
294
        eq(inv['a-id'].revision, r1)
217
295
        eq(inv['b-id'].revision, r3)
218
296
 
219
 
        
220
297
    def test_removed_commit(self):
221
298
        """Commit with a removed file"""
222
 
        b = Branch.initialize('.')
223
 
        wt = b.working_tree()
 
299
        wt = self.make_branch_and_tree('.')
 
300
        b = wt.branch
224
301
        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
 
302
        wt.add(['hello'], ['hello-id'])
 
303
        wt.commit(message='add hello')
229
304
        wt.remove('hello')
230
 
        b.working_tree().commit('removed hello', rev_id='rev2')
 
305
        wt.commit('removed hello', rev_id='rev2')
231
306
 
232
 
        tree = b.revision_tree('rev2')
 
307
        tree = b.repository.revision_tree('rev2')
233
308
        self.assertFalse(tree.has_id('hello-id'))
234
309
 
235
 
 
236
310
    def test_committed_ancestry(self):
237
311
        """Test commit appends revisions to ancestry."""
238
 
        b = Branch.initialize('.')
 
312
        wt = self.make_branch_and_tree('.')
 
313
        b = wt.branch
239
314
        rev_ids = []
240
315
        for i in range(4):
241
316
            file('hello', 'w').write((str(i) * 4) + '\n')
242
317
            if i == 0:
243
 
                b.add(['hello'], ['hello-id'])
 
318
                wt.add(['hello'], ['hello-id'])
244
319
            rev_id = 'test@rev-%d' % (i+1)
245
320
            rev_ids.append(rev_id)
246
 
            b.working_tree().commit(message='rev %d' % (i+1),
 
321
            wt.commit(message='rev %d' % (i+1),
247
322
                     rev_id=rev_id)
248
323
        eq = self.assertEquals
249
324
        eq(b.revision_history(), rev_ids)
250
325
        for i in range(4):
251
 
            anc = b.get_ancestry(rev_ids[i])
 
326
            anc = b.repository.get_ancestry(rev_ids[i])
252
327
            eq(anc, [None] + rev_ids[:i+1])
253
328
 
254
329
    def test_commit_new_subdir_child_selective(self):
255
 
        b = Branch.initialize('.')
 
330
        wt = self.make_branch_and_tree('.')
 
331
        b = wt.branch
256
332
        self.build_tree(['dir/', 'dir/file1', 'dir/file2'])
257
 
        b.add(['dir', 'dir/file1', 'dir/file2'],
 
333
        wt.add(['dir', 'dir/file1', 'dir/file2'],
258
334
              ['dirid', 'file1id', 'file2id'])
259
 
        b.working_tree().commit('dir/file1', specific_files=['dir/file1'], rev_id='1')
260
 
        inv = b.get_inventory('1')
 
335
        wt.commit('dir/file1', specific_files=['dir/file1'], rev_id='1')
 
336
        inv = b.repository.get_inventory('1')
261
337
        self.assertEqual('1', inv['dirid'].revision)
262
338
        self.assertEqual('1', inv['file1id'].revision)
263
339
        # FIXME: This should raise a KeyError I think, rbc20051006
266
342
    def test_strict_commit(self):
267
343
        """Try and commit with unknown files and strict = True, should fail."""
268
344
        from bzrlib.errors import StrictCommitFailed
269
 
        b = Branch.initialize('.')
 
345
        wt = self.make_branch_and_tree('.')
 
346
        b = wt.branch
270
347
        file('hello', 'w').write('hello world')
271
 
        b.add('hello')
 
348
        wt.add('hello')
272
349
        file('goodbye', 'w').write('goodbye cruel world!')
273
 
        self.assertRaises(StrictCommitFailed, b.working_tree().commit,
 
350
        self.assertRaises(StrictCommitFailed, wt.commit,
274
351
            message='add hello but not goodbye', strict=True)
275
352
 
276
353
    def test_strict_commit_without_unknowns(self):
277
354
        """Try and commit with no unknown files and strict = True,
278
355
        should work."""
279
356
        from bzrlib.errors import StrictCommitFailed
280
 
        b = Branch.initialize('.')
 
357
        wt = self.make_branch_and_tree('.')
 
358
        b = wt.branch
281
359
        file('hello', 'w').write('hello world')
282
 
        b.add('hello')
283
 
        b.working_tree().commit(message='add hello', strict=True)
 
360
        wt.add('hello')
 
361
        wt.commit(message='add hello', strict=True)
284
362
 
285
363
    def test_nonstrict_commit(self):
286
364
        """Try and commit with unknown files and strict = False, should work."""
287
 
        b = Branch.initialize('.')
 
365
        wt = self.make_branch_and_tree('.')
 
366
        b = wt.branch
288
367
        file('hello', 'w').write('hello world')
289
 
        b.add('hello')
 
368
        wt.add('hello')
290
369
        file('goodbye', 'w').write('goodbye cruel world!')
291
 
        b.working_tree().commit(message='add hello but not goodbye', strict=False)
 
370
        wt.commit(message='add hello but not goodbye', strict=False)
292
371
 
293
372
    def test_nonstrict_commit_without_unknowns(self):
294
373
        """Try and commit with no unknown files and strict = False,
295
374
        should work."""
296
 
        b = Branch.initialize('.')
 
375
        wt = self.make_branch_and_tree('.')
 
376
        b = wt.branch
297
377
        file('hello', 'w').write('hello world')
298
 
        b.add('hello')
299
 
        b.working_tree().commit(message='add hello', strict=False)
 
378
        wt.add('hello')
 
379
        wt.commit(message='add hello', strict=False)
300
380
 
301
381
    def test_signed_commit(self):
302
382
        import bzrlib.gpg
303
383
        import bzrlib.commit as commit
304
384
        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'))
 
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'))
308
389
        try:
309
390
            from bzrlib.testament import Testament
310
391
            # monkey patch gpg signing mechanism
311
392
            bzrlib.gpg.GPGStrategy = bzrlib.gpg.LoopbackGPGStrategy
312
 
            commit.Commit(config=MustSignConfig(branch)).commit(branch, "base",
 
393
            commit.Commit(config=MustSignConfig(branch)).commit(message="base",
313
394
                                                      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())
 
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'))
317
402
        finally:
318
403
            bzrlib.gpg.GPGStrategy = oldstrategy
319
404
 
321
406
        import bzrlib.gpg
322
407
        import bzrlib.commit as commit
323
408
        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'))
 
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'))
327
413
        try:
328
414
            from bzrlib.testament import Testament
329
415
            # monkey patch gpg signing mechanism
331
417
            config = MustSignConfig(branch)
332
418
            self.assertRaises(SigningFailed,
333
419
                              commit.Commit(config=config).commit,
334
 
                              branch, "base",
 
420
                              message="base",
335
421
                              allow_pointless=True,
336
 
                              rev_id='B')
337
 
            branch = Branch.open('.')
 
422
                              rev_id='B',
 
423
                              working_tree=wt)
 
424
            branch = Branch.open(self.get_url('.'))
338
425
            self.assertEqual(branch.revision_history(), ['A'])
339
 
            self.failIf(branch.revision_store.has_id('B'))
 
426
            self.failIf(branch.repository.has_revision('B'))
340
427
        finally:
341
428
            bzrlib.gpg.GPGStrategy = oldstrategy
342
429
 
343
430
    def test_commit_invokes_hooks(self):
344
431
        import bzrlib.commit as commit
345
 
        branch = Branch.initialize('.')
 
432
        wt = self.make_branch_and_tree('.')
 
433
        branch = wt.branch
346
434
        calls = []
347
435
        def called(branch, rev_id):
348
436
            calls.append('called')
350
438
        try:
351
439
            config = BranchWithHooks(branch)
352
440
            commit.Commit(config=config).commit(
353
 
                            branch, "base",
 
441
                            message = "base",
354
442
                            allow_pointless=True,
355
 
                            rev_id='A')
 
443
                            rev_id='A', working_tree = wt)
356
444
            self.assertEqual(['called', 'called'], calls)
357
445
        finally:
358
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 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
        expected = set([
 
554
            ('change', 'modified', 'filetomodify'),
 
555
            ('change', 'added', 'newdir'),
 
556
            ('change', 'added', 'newfile'),
 
557
            ('renamed', 'renamed', 'dirtorename', 'renameddir'),
 
558
            ('renamed', 'renamed', 'filetorename', 'renamedfile'),
 
559
            ('renamed', 'renamed', 'dirtoreparent', 'renameddir/reparenteddir'),
 
560
            ('renamed', 'renamed', 'filetoreparent', 'renameddir/reparentedfile'),
 
561
            ('deleted', 'dirtoremove'),
 
562
            ('deleted', 'filetoremove'),
 
563
            ])
 
564
        result = set(reporter.calls)
 
565
        missing = expected - result
 
566
        new = result - expected
 
567
        self.assertEqual((set(), set()), (missing, new))
 
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, arg3=None, arg4=None):
 
712
            raise errors.NoSuchFile('foo')
 
713
        repository.add_inventory = raise_
 
714
        repository.add_inventory_by_delta = raise_
 
715
        self.assertRaises(errors.NoSuchFile, tree.commit, message_callback=cb)
 
716
        self.assertFalse(cb.called)
 
717
 
 
718
    def test_selected_file_merge_commit(self):
 
719
        """Ensure the correct error is raised"""
 
720
        tree = self.make_branch_and_tree('foo')
 
721
        # pending merge would turn into a left parent
 
722
        tree.commit('commit 1')
 
723
        tree.add_parent_tree_id('example')
 
724
        self.build_tree(['foo/bar', 'foo/baz'])
 
725
        tree.add(['bar', 'baz'])
 
726
        err = self.assertRaises(errors.CannotCommitSelectedFileMerge,
 
727
            tree.commit, 'commit 2', specific_files=['bar', 'baz'])
 
728
        self.assertEqual(['bar', 'baz'], err.files)
 
729
        self.assertEqual('Selected-file commit of merges is not supported'
 
730
                         ' yet: files bar, baz', str(err))
 
731
 
 
732
    def test_commit_ordering(self):
 
733
        """Test of corner-case commit ordering error"""
 
734
        tree = self.make_branch_and_tree('.')
 
735
        self.build_tree(['a/', 'a/z/', 'a/c/', 'a/z/x', 'a/z/y'])
 
736
        tree.add(['a/', 'a/z/', 'a/c/', 'a/z/x', 'a/z/y'])
 
737
        tree.commit('setup')
 
738
        self.build_tree(['a/c/d/'])
 
739
        tree.add('a/c/d')
 
740
        tree.rename_one('a/z/x', 'a/c/d/x')
 
741
        tree.commit('test', specific_files=['a/z/y'])
 
742
 
 
743
    def test_commit_no_author(self):
 
744
        """The default kwarg author in MutableTree.commit should not add
 
745
        the 'author' revision property.
 
746
        """
 
747
        tree = self.make_branch_and_tree('foo')
 
748
        rev_id = tree.commit('commit 1')
 
749
        rev = tree.branch.repository.get_revision(rev_id)
 
750
        self.assertFalse('author' in rev.properties)
 
751
        self.assertFalse('authors' in rev.properties)
 
752
 
 
753
    def test_commit_author(self):
 
754
        """Passing a non-empty author kwarg to MutableTree.commit should add
 
755
        the 'author' revision property.
 
756
        """
 
757
        tree = self.make_branch_and_tree('foo')
 
758
        rev_id = self.callDeprecated(['The parameter author was '
 
759
                'deprecated in version 1.13. Use authors instead'],
 
760
                tree.commit, 'commit 1', author='John Doe <jdoe@example.com>')
 
761
        rev = tree.branch.repository.get_revision(rev_id)
 
762
        self.assertEqual('John Doe <jdoe@example.com>',
 
763
                         rev.properties['authors'])
 
764
        self.assertFalse('author' in rev.properties)
 
765
 
 
766
    def test_commit_empty_authors_list(self):
 
767
        """Passing an empty list to authors shouldn't add the property."""
 
768
        tree = self.make_branch_and_tree('foo')
 
769
        rev_id = tree.commit('commit 1', authors=[])
 
770
        rev = tree.branch.repository.get_revision(rev_id)
 
771
        self.assertFalse('author' in rev.properties)
 
772
        self.assertFalse('authors' in rev.properties)
 
773
 
 
774
    def test_multiple_authors(self):
 
775
        tree = self.make_branch_and_tree('foo')
 
776
        rev_id = tree.commit('commit 1',
 
777
                authors=['John Doe <jdoe@example.com>',
 
778
                         'Jane Rey <jrey@example.com>'])
 
779
        rev = tree.branch.repository.get_revision(rev_id)
 
780
        self.assertEqual('John Doe <jdoe@example.com>\n'
 
781
                'Jane Rey <jrey@example.com>', rev.properties['authors'])
 
782
        self.assertFalse('author' in rev.properties)
 
783
 
 
784
    def test_author_and_authors_incompatible(self):
 
785
        tree = self.make_branch_and_tree('foo')
 
786
        self.assertRaises(AssertionError, tree.commit, 'commit 1',
 
787
                authors=['John Doe <jdoe@example.com>',
 
788
                         'Jane Rey <jrey@example.com>'],
 
789
                author="Jack Me <jme@example.com>")
 
790
 
 
791
    def test_author_with_newline_rejected(self):
 
792
        tree = self.make_branch_and_tree('foo')
 
793
        self.assertRaises(AssertionError, tree.commit, 'commit 1',
 
794
                authors=['John\nDoe <jdoe@example.com>'])
 
795
 
 
796
    def test_commit_with_checkout_and_branch_sharing_repo(self):
 
797
        repo = self.make_repository('repo', shared=True)
 
798
        # make_branch_and_tree ignores shared repos
 
799
        branch = bzrdir.BzrDir.create_branch_convenience('repo/branch')
 
800
        tree2 = branch.create_checkout('repo/tree2')
 
801
        tree2.commit('message', rev_id='rev1')
 
802
        self.assertTrue(tree2.branch.repository.has_revision('rev1'))