~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/selftest/test_commit.py

Exclude more files from dumb-rsync upload

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