/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: Breezy landing bot
  • Author(s): Jelmer Vernooij
  • Date: 2019-07-07 19:22:03 UTC
  • mfrom: (7358.8.5 diff-binary-weird)
  • Revision ID: breezy.the.bot@gmail.com-20190707192203-n32v0wih963qdi6i
Don't include datestamps in filenames when reporting on binary files.

Merged from https://code.launchpad.net/~jelmer/brz/diff-binary-weird/+merge/369474

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