~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_commit.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2006-08-28 19:11:35 UTC
  • mfrom: (1711.9.6 jam-integration)
  • Revision ID: pqm@pqm.ubuntu.com-20060828191135-f5feb1287d0711c8
(jam) add deprecation symbol for bzr-0.11

Show diffs side-by-side

added added

removed removed

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