~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_commit.py

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