~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/selftest/test_commit.py

  • Committer: Robert Collins
  • Date: 2005-10-18 12:53:07 UTC
  • mfrom: (1442.1.70)
  • Revision ID: robertc@robertcollins.net-20051018125307-730e47c1a034cb6f
Merge in various improvements to pull, testing and configuration.

* 'bzr pull' now accepts '--clobber' which will discard local changes
  and make this branch identical to the source branch. (Robert Collins)

* There is a new method for TestCaseInTempDir, assertFileEqual, which
  will check that a given content is equal to the content of the named
  file. (Robert Collins)

* 'pull' has been factored out of the command as WorkingTree.pull().
  A new option to WorkingTree.pull has been added, clobber, which will
  ignore diverged history and pull anyway.
  (Robert Collins)

* config.Config has a 'get_user_option' call that accepts an option name.
  This will be looked up in branches.conf and bazaar.conf as normal.
  It is intended that this be used by plugins to support options -
  options of built in programs should have specific methods on the config.
  (Robert Collins)

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
 
import bzrlib
21
 
from bzrlib import (
22
 
    bzrdir,
23
 
    errors,
24
 
    )
 
20
from bzrlib.selftest import TestCaseInTempDir
25
21
from bzrlib.branch import Branch
26
 
from bzrlib.bzrdir import BzrDirMetaFormat1
27
 
from bzrlib.commit import Commit, NullCommitReporter
 
22
from bzrlib.commit import Commit
28
23
from bzrlib.config import BranchConfig
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
 
24
from bzrlib.errors import PointlessCommit, BzrError, SigningFailed
43
25
 
44
26
 
45
27
# TODO: Test commit with some added, and added-but-missing files
53
35
        return ['cat', '-']
54
36
 
55
37
 
56
 
class BranchWithHooks(BranchConfig):
57
 
 
58
 
    def post_commit(self):
59
 
        return "bzrlib.ahook bzrlib.ahook"
60
 
 
61
 
 
62
 
class CapturingReporter(NullCommitReporter):
63
 
    """This reporter captures the calls made to it for evaluation later."""
64
 
 
65
 
    def __init__(self):
66
 
        # a list of the calls this received
67
 
        self.calls = []
68
 
 
69
 
    def snapshot_change(self, change, path):
70
 
        self.calls.append(('change', change, path))
71
 
 
72
 
    def deleted(self, file_id):
73
 
        self.calls.append(('deleted', file_id))
74
 
 
75
 
    def missing(self, path):
76
 
        self.calls.append(('missing', path))
77
 
 
78
 
    def renamed(self, change, old_path, new_path):
79
 
        self.calls.append(('renamed', change, old_path, new_path))
80
 
 
81
 
    def is_verbose(self):
82
 
        return True
83
 
 
84
 
 
85
 
class TestCommit(TestCaseWithTransport):
 
38
class TestCommit(TestCaseInTempDir):
86
39
 
87
40
    def test_simple_commit(self):
88
41
        """Commit and check two versions of a single file."""
89
 
        wt = self.make_branch_and_tree('.')
90
 
        b = wt.branch
 
42
        b = Branch.initialize('.')
91
43
        file('hello', 'w').write('hello world')
92
 
        wt.add('hello')
93
 
        wt.commit(message='add hello')
94
 
        file_id = wt.path2id('hello')
 
44
        b.add('hello')
 
45
        b.commit(message='add hello')
 
46
        file_id = b.working_tree().path2id('hello')
95
47
 
96
48
        file('hello', 'w').write('version 2')
97
 
        wt.commit(message='commit 2')
 
49
        b.commit(message='commit 2')
98
50
 
99
51
        eq = self.assertEquals
100
52
        eq(b.revno(), 2)
101
53
        rh = b.revision_history()
102
 
        rev = b.repository.get_revision(rh[0])
 
54
        rev = b.get_revision(rh[0])
103
55
        eq(rev.message, 'add hello')
104
56
 
105
 
        tree1 = b.repository.revision_tree(rh[0])
106
 
        tree1.lock_read()
 
57
        tree1 = b.revision_tree(rh[0])
107
58
        text = tree1.get_file_text(file_id)
108
 
        tree1.unlock()
109
 
        self.assertEqual('hello world', text)
110
 
 
111
 
        tree2 = b.repository.revision_tree(rh[1])
112
 
        tree2.lock_read()
113
 
        text = tree2.get_file_text(file_id)
114
 
        tree2.unlock()
115
 
        self.assertEqual('version 2', text)
116
 
 
117
 
    def test_commit_lossy_native(self):
118
 
        """Attempt a lossy commit to a native branch."""
119
 
        wt = self.make_branch_and_tree('.')
120
 
        b = wt.branch
121
 
        file('hello', 'w').write('hello world')
122
 
        wt.add('hello')
123
 
        revid = wt.commit(message='add hello', rev_id='revid', lossy=True)
124
 
        self.assertEquals('revid', revid)
125
 
 
126
 
    def test_commit_lossy_foreign(self):
127
 
        """Attempt a lossy commit to a foreign branch."""
128
 
        test_foreign.register_dummy_foreign_for_test(self)
129
 
        wt = self.make_branch_and_tree('.',
130
 
            format=test_foreign.DummyForeignVcsDirFormat())
131
 
        b = wt.branch
132
 
        file('hello', 'w').write('hello world')
133
 
        wt.add('hello')
134
 
        revid = wt.commit(message='add hello', lossy=True,
135
 
            timestamp=1302659388, timezone=0)
136
 
        self.assertEquals('dummy-v1:1302659388.0-0-UNKNOWN', revid)
137
 
 
138
 
    def test_commit_bound_lossy_foreign(self):
139
 
        """Attempt a lossy commit to a bzr branch bound to a foreign branch."""
140
 
        test_foreign.register_dummy_foreign_for_test(self)
141
 
        foreign_branch = self.make_branch('foreign',
142
 
            format=test_foreign.DummyForeignVcsDirFormat())
143
 
        wt = foreign_branch.create_checkout("local")
144
 
        b = wt.branch
145
 
        file('local/hello', 'w').write('hello world')
146
 
        wt.add('hello')
147
 
        revid = wt.commit(message='add hello', lossy=True,
148
 
            timestamp=1302659388, timezone=0)
149
 
        self.assertEquals('dummy-v1:1302659388.0-0-0', revid)
150
 
        self.assertEquals('dummy-v1:1302659388.0-0-0',
151
 
            foreign_branch.last_revision())
152
 
        self.assertEquals('dummy-v1:1302659388.0-0-0',
153
 
            wt.branch.last_revision())
154
 
 
155
 
    def test_missing_commit(self):
156
 
        """Test a commit with a missing file"""
157
 
        wt = self.make_branch_and_tree('.')
158
 
        b = wt.branch
159
 
        file('hello', 'w').write('hello world')
160
 
        wt.add(['hello'], ['hello-id'])
161
 
        wt.commit(message='add hello')
 
59
        eq(text, 'hello world')
 
60
 
 
61
        tree2 = b.revision_tree(rh[1])
 
62
        eq(tree2.get_file_text(file_id), 'version 2')
 
63
 
 
64
    def test_delete_commit(self):
 
65
        """Test a commit with a deleted file"""
 
66
        b = Branch.initialize('.')
 
67
        file('hello', 'w').write('hello world')
 
68
        b.add(['hello'], ['hello-id'])
 
69
        b.commit(message='add hello')
162
70
 
163
71
        os.remove('hello')
164
 
        wt.commit('removed hello', rev_id='rev2')
 
72
        b.commit('removed hello', rev_id='rev2')
165
73
 
166
 
        tree = b.repository.revision_tree('rev2')
 
74
        tree = b.revision_tree('rev2')
167
75
        self.assertFalse(tree.has_id('hello-id'))
168
76
 
169
 
    def test_partial_commit_move(self):
170
 
        """Test a partial commit where a file was renamed but not committed.
171
 
 
172
 
        https://bugs.launchpad.net/bzr/+bug/83039
173
 
 
174
 
        If not handled properly, commit will try to snapshot
175
 
        dialog.py with olive/ as a parent, while
176
 
        olive/ has not been snapshotted yet.
177
 
        """
178
 
        wt = self.make_branch_and_tree('.')
179
 
        b = wt.branch
180
 
        self.build_tree(['annotate/', 'annotate/foo.py',
181
 
                         'olive/', 'olive/dialog.py'
182
 
                        ])
183
 
        wt.add(['annotate', 'olive', 'annotate/foo.py', 'olive/dialog.py'])
184
 
        wt.commit(message='add files')
185
 
        wt.rename_one("olive/dialog.py", "aaa")
186
 
        self.build_tree_contents([('annotate/foo.py', 'modified\n')])
187
 
        wt.commit('renamed hello', specific_files=["annotate"])
188
77
 
189
78
    def test_pointless_commit(self):
190
79
        """Commit refuses unless there are changes or it's forced."""
191
 
        wt = self.make_branch_and_tree('.')
192
 
        b = wt.branch
 
80
        b = Branch.initialize('.')
193
81
        file('hello', 'w').write('hello')
194
 
        wt.add(['hello'])
195
 
        wt.commit(message='add hello')
 
82
        b.add(['hello'])
 
83
        b.commit(message='add hello')
196
84
        self.assertEquals(b.revno(), 1)
197
85
        self.assertRaises(PointlessCommit,
198
 
                          wt.commit,
 
86
                          b.commit,
199
87
                          message='fails',
200
88
                          allow_pointless=False)
201
89
        self.assertEquals(b.revno(), 1)
 
90
        
 
91
 
202
92
 
203
93
    def test_commit_empty(self):
204
94
        """Commiting an empty tree works."""
205
 
        wt = self.make_branch_and_tree('.')
206
 
        b = wt.branch
207
 
        wt.commit(message='empty tree', allow_pointless=True)
 
95
        b = Branch.initialize('.')
 
96
        b.commit(message='empty tree', allow_pointless=True)
208
97
        self.assertRaises(PointlessCommit,
209
 
                          wt.commit,
 
98
                          b.commit,
210
99
                          message='empty tree',
211
100
                          allow_pointless=False)
212
 
        wt.commit(message='empty tree', allow_pointless=True)
 
101
        b.commit(message='empty tree', allow_pointless=True)
213
102
        self.assertEquals(b.revno(), 2)
214
103
 
 
104
 
215
105
    def test_selective_delete(self):
216
106
        """Selective commit in tree with deletions"""
217
 
        wt = self.make_branch_and_tree('.')
218
 
        b = wt.branch
 
107
        b = Branch.initialize('.')
219
108
        file('hello', 'w').write('hello')
220
109
        file('buongia', 'w').write('buongia')
221
 
        wt.add(['hello', 'buongia'],
 
110
        b.add(['hello', 'buongia'],
222
111
              ['hello-id', 'buongia-id'])
223
 
        wt.commit(message='add files',
 
112
        b.commit(message='add files',
224
113
                 rev_id='test@rev-1')
225
 
 
 
114
        
226
115
        os.remove('hello')
227
116
        file('buongia', 'w').write('new text')
228
 
        wt.commit(message='update text',
 
117
        b.commit(message='update text',
229
118
                 specific_files=['buongia'],
230
119
                 allow_pointless=False,
231
120
                 rev_id='test@rev-2')
232
121
 
233
 
        wt.commit(message='remove hello',
 
122
        b.commit(message='remove hello',
234
123
                 specific_files=['hello'],
235
124
                 allow_pointless=False,
236
125
                 rev_id='test@rev-3')
238
127
        eq = self.assertEquals
239
128
        eq(b.revno(), 3)
240
129
 
241
 
        tree2 = b.repository.revision_tree('test@rev-2')
242
 
        tree2.lock_read()
243
 
        self.addCleanup(tree2.unlock)
 
130
        tree2 = b.revision_tree('test@rev-2')
244
131
        self.assertTrue(tree2.has_filename('hello'))
245
132
        self.assertEquals(tree2.get_file_text('hello-id'), 'hello')
246
133
        self.assertEquals(tree2.get_file_text('buongia-id'), 'new text')
247
 
 
248
 
        tree3 = b.repository.revision_tree('test@rev-3')
249
 
        tree3.lock_read()
250
 
        self.addCleanup(tree3.unlock)
 
134
        
 
135
        tree3 = b.revision_tree('test@rev-3')
251
136
        self.assertFalse(tree3.has_filename('hello'))
252
137
        self.assertEquals(tree3.get_file_text('buongia-id'), 'new text')
253
138
 
 
139
 
254
140
    def test_commit_rename(self):
255
141
        """Test commit of a revision where a file is renamed."""
256
 
        tree = self.make_branch_and_tree('.')
257
 
        b = tree.branch
258
 
        self.build_tree(['hello'], line_endings='binary')
259
 
        tree.add(['hello'], ['hello-id'])
260
 
        tree.commit(message='one', rev_id='test@rev-1', allow_pointless=False)
 
142
        b = Branch.initialize('.')
 
143
        self.build_tree(['hello'])
 
144
        b.add(['hello'], ['hello-id'])
 
145
        b.commit(message='one', rev_id='test@rev-1', allow_pointless=False)
261
146
 
262
 
        tree.rename_one('hello', 'fruity')
263
 
        tree.commit(message='renamed', rev_id='test@rev-2', allow_pointless=False)
 
147
        b.rename_one('hello', 'fruity')
 
148
        b.commit(message='renamed', rev_id='test@rev-2', allow_pointless=False)
264
149
 
265
150
        eq = self.assertEquals
266
 
        tree1 = b.repository.revision_tree('test@rev-1')
267
 
        tree1.lock_read()
268
 
        self.addCleanup(tree1.unlock)
 
151
        tree1 = b.revision_tree('test@rev-1')
269
152
        eq(tree1.id2path('hello-id'), 'hello')
270
153
        eq(tree1.get_file_text('hello-id'), 'contents of hello\n')
271
154
        self.assertFalse(tree1.has_filename('fruity'))
272
 
        self.check_tree_shape(tree1, ['hello'])
273
 
        eq(tree1.get_file_revision('hello-id'), 'test@rev-1')
 
155
        self.check_inventory_shape(tree1.inventory, ['hello'])
 
156
        ie = tree1.inventory['hello-id']
 
157
        eq(ie.revision, 'test@rev-1')
274
158
 
275
 
        tree2 = b.repository.revision_tree('test@rev-2')
276
 
        tree2.lock_read()
277
 
        self.addCleanup(tree2.unlock)
 
159
        tree2 = b.revision_tree('test@rev-2')
278
160
        eq(tree2.id2path('hello-id'), 'fruity')
279
161
        eq(tree2.get_file_text('hello-id'), 'contents of hello\n')
280
 
        self.check_tree_shape(tree2, ['fruity'])
281
 
        eq(tree2.get_file_revision('hello-id'), 'test@rev-2')
 
162
        self.check_inventory_shape(tree2.inventory, ['fruity'])
 
163
        ie = tree2.inventory['hello-id']
 
164
        eq(ie.revision, 'test@rev-2')
 
165
 
282
166
 
283
167
    def test_reused_rev_id(self):
284
168
        """Test that a revision id cannot be reused in a branch"""
285
 
        wt = self.make_branch_and_tree('.')
286
 
        b = wt.branch
287
 
        wt.commit('initial', rev_id='test@rev-1', allow_pointless=True)
 
169
        b = Branch.initialize('.')
 
170
        b.commit('initial', rev_id='test@rev-1', allow_pointless=True)
288
171
        self.assertRaises(Exception,
289
 
                          wt.commit,
 
172
                          b.commit,
290
173
                          message='reused id',
291
174
                          rev_id='test@rev-1',
292
175
                          allow_pointless=True)
 
176
                          
 
177
 
293
178
 
294
179
    def test_commit_move(self):
295
180
        """Test commit of revisions with moved files and directories"""
296
181
        eq = self.assertEquals
297
 
        wt = self.make_branch_and_tree('.')
298
 
        b = wt.branch
 
182
        b = Branch.initialize('.')
299
183
        r1 = 'test@rev-1'
300
184
        self.build_tree(['hello', 'a/', 'b/'])
301
 
        wt.add(['hello', 'a', 'b'], ['hello-id', 'a-id', 'b-id'])
302
 
        wt.commit('initial', rev_id=r1, allow_pointless=False)
303
 
        wt.move(['hello'], 'a')
 
185
        b.add(['hello', 'a', 'b'], ['hello-id', 'a-id', 'b-id'])
 
186
        b.commit('initial', rev_id=r1, allow_pointless=False)
 
187
 
 
188
        b.move(['hello'], 'a')
304
189
        r2 = 'test@rev-2'
305
 
        wt.commit('two', rev_id=r2, allow_pointless=False)
306
 
        wt.lock_read()
307
 
        try:
308
 
            self.check_tree_shape(wt, ['a/', 'a/hello', 'b/'])
309
 
        finally:
310
 
            wt.unlock()
 
190
        b.commit('two', rev_id=r2, allow_pointless=False)
 
191
        self.check_inventory_shape(b.inventory,
 
192
                                   ['a', 'a/hello', 'b'])
311
193
 
312
 
        wt.move(['b'], 'a')
 
194
        b.move(['b'], 'a')
313
195
        r3 = 'test@rev-3'
314
 
        wt.commit('three', rev_id=r3, allow_pointless=False)
315
 
        wt.lock_read()
316
 
        try:
317
 
            self.check_tree_shape(wt,
318
 
                                       ['a/', 'a/hello', 'a/b/'])
319
 
            self.check_tree_shape(b.repository.revision_tree(r3),
320
 
                                       ['a/', 'a/hello', 'a/b/'])
321
 
        finally:
322
 
            wt.unlock()
 
196
        b.commit('three', rev_id=r3, allow_pointless=False)
 
197
        self.check_inventory_shape(b.inventory,
 
198
                                   ['a', 'a/hello', 'a/b'])
 
199
        self.check_inventory_shape(b.get_revision_inventory(r3),
 
200
                                   ['a', 'a/hello', 'a/b'])
323
201
 
324
 
        wt.move(['a/hello'], 'a/b')
 
202
        b.move([os.sep.join(['a', 'hello'])],
 
203
               os.sep.join(['a', 'b']))
325
204
        r4 = 'test@rev-4'
326
 
        wt.commit('four', rev_id=r4, allow_pointless=False)
327
 
        wt.lock_read()
328
 
        try:
329
 
            self.check_tree_shape(wt, ['a/', 'a/b/hello', 'a/b/'])
330
 
        finally:
331
 
            wt.unlock()
 
205
        b.commit('four', rev_id=r4, allow_pointless=False)
 
206
        self.check_inventory_shape(b.inventory,
 
207
                                   ['a', 'a/b/hello', 'a/b'])
332
208
 
333
 
        inv = b.repository.get_inventory(r4)
 
209
        inv = b.get_revision_inventory(r4)
334
210
        eq(inv['hello-id'].revision, r4)
335
211
        eq(inv['a-id'].revision, r1)
336
212
        eq(inv['b-id'].revision, r3)
337
213
 
 
214
        
338
215
    def test_removed_commit(self):
339
 
        """Commit with a removed file"""
340
 
        wt = self.make_branch_and_tree('.')
341
 
        b = wt.branch
 
216
        """Test a commit with a removed file"""
 
217
        b = Branch.initialize('.')
342
218
        file('hello', 'w').write('hello world')
343
 
        wt.add(['hello'], ['hello-id'])
344
 
        wt.commit(message='add hello')
345
 
        wt.remove('hello')
346
 
        wt.commit('removed hello', rev_id='rev2')
347
 
 
348
 
        tree = b.repository.revision_tree('rev2')
 
219
        b.add(['hello'], ['hello-id'])
 
220
        b.commit(message='add hello')
 
221
 
 
222
        b.working_tree().remove('hello')
 
223
        b.commit('removed hello', rev_id='rev2')
 
224
 
 
225
        tree = b.revision_tree('rev2')
349
226
        self.assertFalse(tree.has_id('hello-id'))
350
227
 
 
228
 
351
229
    def test_committed_ancestry(self):
352
230
        """Test commit appends revisions to ancestry."""
353
 
        wt = self.make_branch_and_tree('.')
354
 
        b = wt.branch
 
231
        b = Branch.initialize('.')
355
232
        rev_ids = []
356
233
        for i in range(4):
357
234
            file('hello', 'w').write((str(i) * 4) + '\n')
358
235
            if i == 0:
359
 
                wt.add(['hello'], ['hello-id'])
 
236
                b.add(['hello'], ['hello-id'])
360
237
            rev_id = 'test@rev-%d' % (i+1)
361
238
            rev_ids.append(rev_id)
362
 
            wt.commit(message='rev %d' % (i+1),
 
239
            b.commit(message='rev %d' % (i+1),
363
240
                     rev_id=rev_id)
364
241
        eq = self.assertEquals
365
242
        eq(b.revision_history(), rev_ids)
366
243
        for i in range(4):
367
 
            self.assertThat(rev_ids[:i+1],
368
 
                MatchesAncestry(b.repository, rev_ids[i]))
 
244
            anc = b.get_ancestry(rev_ids[i])
 
245
            eq(anc, [None] + rev_ids[:i+1])
369
246
 
370
247
    def test_commit_new_subdir_child_selective(self):
371
 
        wt = self.make_branch_and_tree('.')
372
 
        b = wt.branch
 
248
        b = Branch.initialize('.')
373
249
        self.build_tree(['dir/', 'dir/file1', 'dir/file2'])
374
 
        wt.add(['dir', 'dir/file1', 'dir/file2'],
 
250
        b.add(['dir', 'dir/file1', 'dir/file2'],
375
251
              ['dirid', 'file1id', 'file2id'])
376
 
        wt.commit('dir/file1', specific_files=['dir/file1'], rev_id='1')
377
 
        inv = b.repository.get_inventory('1')
 
252
        b.commit('dir/file1', specific_files=['dir/file1'], rev_id='1')
 
253
        inv = b.get_inventory('1')
378
254
        self.assertEqual('1', inv['dirid'].revision)
379
255
        self.assertEqual('1', inv['file1id'].revision)
380
256
        # FIXME: This should raise a KeyError I think, rbc20051006
381
257
        self.assertRaises(BzrError, inv.__getitem__, 'file2id')
382
258
 
383
 
    def test_strict_commit(self):
384
 
        """Try and commit with unknown files and strict = True, should fail."""
385
 
        from bzrlib.errors import StrictCommitFailed
386
 
        wt = self.make_branch_and_tree('.')
387
 
        b = wt.branch
388
 
        file('hello', 'w').write('hello world')
389
 
        wt.add('hello')
390
 
        file('goodbye', 'w').write('goodbye cruel world!')
391
 
        self.assertRaises(StrictCommitFailed, wt.commit,
392
 
            message='add hello but not goodbye', strict=True)
393
 
 
394
 
    def test_strict_commit_without_unknowns(self):
395
 
        """Try and commit with no unknown files and strict = True,
396
 
        should work."""
397
 
        wt = self.make_branch_and_tree('.')
398
 
        b = wt.branch
399
 
        file('hello', 'w').write('hello world')
400
 
        wt.add('hello')
401
 
        wt.commit(message='add hello', strict=True)
402
 
 
403
 
    def test_nonstrict_commit(self):
404
 
        """Try and commit with unknown files and strict = False, should work."""
405
 
        wt = self.make_branch_and_tree('.')
406
 
        b = wt.branch
407
 
        file('hello', 'w').write('hello world')
408
 
        wt.add('hello')
409
 
        file('goodbye', 'w').write('goodbye cruel world!')
410
 
        wt.commit(message='add hello but not goodbye', strict=False)
411
 
 
412
 
    def test_nonstrict_commit_without_unknowns(self):
413
 
        """Try and commit with no unknown files and strict = False,
414
 
        should work."""
415
 
        wt = self.make_branch_and_tree('.')
416
 
        b = wt.branch
417
 
        file('hello', 'w').write('hello world')
418
 
        wt.add('hello')
419
 
        wt.commit(message='add hello', strict=False)
420
 
 
421
259
    def test_signed_commit(self):
422
260
        import bzrlib.gpg
423
261
        import bzrlib.commit as commit
424
262
        oldstrategy = bzrlib.gpg.GPGStrategy
425
 
        wt = self.make_branch_and_tree('.')
426
 
        branch = wt.branch
427
 
        wt.commit("base", allow_pointless=True, rev_id='A')
428
 
        self.assertFalse(branch.repository.has_signature_for_revision_id('A'))
 
263
        branch = Branch.initialize('.')
 
264
        branch.commit("base", allow_pointless=True, rev_id='A')
 
265
        self.failIf(branch.revision_store.has_id('A', 'sig'))
429
266
        try:
430
267
            from bzrlib.testament import Testament
431
268
            # monkey patch gpg signing mechanism
432
269
            bzrlib.gpg.GPGStrategy = bzrlib.gpg.LoopbackGPGStrategy
433
 
            commit.Commit(config=MustSignConfig(branch)).commit(message="base",
 
270
            commit.Commit(config=MustSignConfig(branch)).commit(branch, "base",
434
271
                                                      allow_pointless=True,
435
 
                                                      rev_id='B',
436
 
                                                      working_tree=wt)
437
 
            def sign(text):
438
 
                return bzrlib.gpg.LoopbackGPGStrategy(None).sign(text)
439
 
            self.assertEqual(sign(Testament.from_revision(branch.repository,
440
 
                             'B').as_short_text()),
441
 
                             branch.repository.get_signature_text('B'))
 
272
                                                      rev_id='B')
 
273
            self.assertEqual(Testament.from_revision(branch,'B').as_short_text(),
 
274
                             branch.revision_store.get('B', 'sig').read())
442
275
        finally:
443
276
            bzrlib.gpg.GPGStrategy = oldstrategy
444
277
 
446
279
        import bzrlib.gpg
447
280
        import bzrlib.commit as commit
448
281
        oldstrategy = bzrlib.gpg.GPGStrategy
449
 
        wt = self.make_branch_and_tree('.')
450
 
        branch = wt.branch
451
 
        wt.commit("base", allow_pointless=True, rev_id='A')
452
 
        self.assertFalse(branch.repository.has_signature_for_revision_id('A'))
 
282
        branch = Branch.initialize('.')
 
283
        branch.commit("base", allow_pointless=True, rev_id='A')
 
284
        self.failIf(branch.revision_store.has_id('A', 'sig'))
453
285
        try:
 
286
            from bzrlib.testament import Testament
454
287
            # monkey patch gpg signing mechanism
455
288
            bzrlib.gpg.GPGStrategy = bzrlib.gpg.DisabledGPGStrategy
456
289
            config = MustSignConfig(branch)
457
290
            self.assertRaises(SigningFailed,
458
291
                              commit.Commit(config=config).commit,
459
 
                              message="base",
 
292
                              branch, "base",
460
293
                              allow_pointless=True,
461
 
                              rev_id='B',
462
 
                              working_tree=wt)
463
 
            branch = Branch.open(self.get_url('.'))
 
294
                              rev_id='B')
 
295
            branch = Branch.open('.')
464
296
            self.assertEqual(branch.revision_history(), ['A'])
465
 
            self.assertFalse(branch.repository.has_revision('B'))
 
297
            self.failIf(branch.revision_store.has_id('B'))
466
298
        finally:
467
299
            bzrlib.gpg.GPGStrategy = oldstrategy
468
300
 
469
 
    def test_commit_invokes_hooks(self):
470
 
        import bzrlib.commit as commit
471
 
        wt = self.make_branch_and_tree('.')
472
 
        branch = wt.branch
473
 
        calls = []
474
 
        def called(branch, rev_id):
475
 
            calls.append('called')
476
 
        bzrlib.ahook = called
477
 
        try:
478
 
            config = BranchWithHooks(branch)
479
 
            commit.Commit(config=config).commit(
480
 
                            message = "base",
481
 
                            allow_pointless=True,
482
 
                            rev_id='A', working_tree = wt)
483
 
            self.assertEqual(['called', 'called'], calls)
484
 
        finally:
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 = bzrdir.BzrDir.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'))
 
301