/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to breezy/tests/test_commit.py

  • Committer: Jelmer Vernooij
  • Date: 2017-09-01 07:15:43 UTC
  • mfrom: (6770.3.2 py3_test_cleanup)
  • Revision ID: jelmer@jelmer.uk-20170901071543-1t83321xkog9qrxh
Merge lp:~gz/brz/py3_test_cleanup

Show diffs side-by-side

added added

removed removed

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