13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19
from io import BytesIO
28
from ..branch import Branch
29
from ..bzr.bzrdir import BzrDirMetaFormat1
30
from ..commit import (
31
CannotCommitSelectedFileMerge,
37
from ..errors import (
41
from ..bzr.inventorytree import InventoryTreeChange
44
TestCaseWithTransport,
47
from .features import (
50
from .matchers import MatchesAncestry
27
from bzrlib.branch import Branch
28
from bzrlib.bzrdir import BzrDir, BzrDirMetaFormat1
29
from bzrlib.commit import Commit, NullCommitReporter
30
from bzrlib.config import BranchConfig
31
from bzrlib.errors import (PointlessCommit, BzrError, SigningFailed,
33
from bzrlib.tests import TestCaseWithTransport
34
from bzrlib.workingtree import WorkingTree
53
37
# TODO: Test commit with some added, and added-but-missing files
55
class MustSignConfig(config.MemoryStack):
58
super(MustSignConfig, self).__init__(b'''
59
create_signatures=always
39
class MustSignConfig(BranchConfig):
41
def signature_needed(self):
44
def gpg_signing_command(self):
48
class BranchWithHooks(BranchConfig):
50
def post_commit(self):
51
return "bzrlib.ahook bzrlib.ahook"
63
54
class CapturingReporter(NullCommitReporter):
89
77
"""Commit and check two versions of a single file."""
90
78
wt = self.make_branch_and_tree('.')
92
with open('hello', 'w') as f:
93
f.write('hello world')
80
file('hello', 'w').write('hello world')
95
rev1 = wt.commit(message='add hello')
97
with open('hello', 'w') as f:
99
rev2 = wt.commit(message='commit 2')
101
eq = self.assertEqual
82
wt.commit(message='add hello')
83
file_id = wt.path2id('hello')
85
file('hello', 'w').write('version 2')
86
wt.commit(message='commit 2')
88
eq = self.assertEquals
103
rev = b.repository.get_revision(rev1)
90
rh = b.revision_history()
91
rev = b.repository.get_revision(rh[0])
104
92
eq(rev.message, 'add hello')
106
tree1 = b.repository.revision_tree(rev1)
108
text = tree1.get_file_text('hello')
110
self.assertEqual(b'hello world', text)
112
tree2 = b.repository.revision_tree(rev2)
114
text = tree2.get_file_text('hello')
116
self.assertEqual(b'version 2', text)
118
def test_commit_lossy_native(self):
119
"""Attempt a lossy commit to a native branch."""
120
wt = self.make_branch_and_tree('.')
122
with open('hello', 'w') as f:
123
f.write('hello world')
125
revid = wt.commit(message='add hello', rev_id=b'revid', lossy=True)
126
self.assertEqual(b'revid', revid)
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())
134
with open('hello', 'w') as f:
135
f.write('hello world')
137
revid = wt.commit(message='add hello', lossy=True,
138
timestamp=1302659388, timezone=0)
139
self.assertEqual(b'dummy-v1:1302659388-0-UNKNOWN', revid)
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")
148
with open('local/hello', 'w') as f:
149
f.write('hello world')
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())
159
def test_missing_commit(self):
160
"""Test a commit with a missing file"""
161
wt = self.make_branch_and_tree('.')
163
with open('hello', 'w') as f:
164
f.write('hello world')
165
wt.add(['hello'], [b'hello-id'])
94
tree1 = b.repository.revision_tree(rh[0])
95
text = tree1.get_file_text(file_id)
96
eq(text, 'hello world')
98
tree2 = b.repository.revision_tree(rh[1])
99
eq(tree2.get_file_text(file_id), 'version 2')
101
def test_delete_commit(self):
102
"""Test a commit with a deleted file"""
103
wt = self.make_branch_and_tree('.')
105
file('hello', 'w').write('hello world')
106
wt.add(['hello'], ['hello-id'])
166
107
wt.commit(message='add hello')
168
109
os.remove('hello')
169
reporter = CapturingReporter()
170
wt.commit('removed hello', rev_id=b'rev2', reporter=reporter)
172
[('missing', u'hello'), ('deleted', u'hello')],
175
tree = b.repository.revision_tree(b'rev2')
176
self.assertFalse(tree.has_filename('hello'))
178
def test_partial_commit_move(self):
179
"""Test a partial commit where a file was renamed but not committed.
181
https://bugs.launchpad.net/bzr/+bug/83039
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.
187
wt = self.make_branch_and_tree('.')
189
self.build_tree(['annotate/', 'annotate/foo.py',
190
'olive/', 'olive/dialog.py'
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"])
110
wt.commit('removed hello', rev_id='rev2')
112
tree = b.repository.revision_tree('rev2')
113
self.assertFalse(tree.has_id('hello-id'))
198
115
def test_pointless_commit(self):
199
116
"""Commit refuses unless there are changes or it's forced."""
200
117
wt = self.make_branch_and_tree('.')
202
with open('hello', 'w') as f:
119
file('hello', 'w').write('hello')
204
120
wt.add(['hello'])
205
121
wt.commit(message='add hello')
206
self.assertEqual(b.revno(), 1)
122
self.assertEquals(b.revno(), 1)
207
123
self.assertRaises(PointlessCommit,
210
126
allow_pointless=False)
211
self.assertEqual(b.revno(), 1)
127
self.assertEquals(b.revno(), 1)
213
129
def test_commit_empty(self):
214
130
"""Commiting an empty tree works."""
215
131
wt = self.make_branch_and_tree('.')
220
136
message='empty tree',
221
137
allow_pointless=False)
222
138
wt.commit(message='empty tree', allow_pointless=True)
223
self.assertEqual(b.revno(), 2)
139
self.assertEquals(b.revno(), 2)
225
141
def test_selective_delete(self):
226
142
"""Selective commit in tree with deletions"""
227
143
wt = self.make_branch_and_tree('.')
229
with open('hello', 'w') as f:
231
with open('buongia', 'w') as f:
145
file('hello', 'w').write('hello')
146
file('buongia', 'w').write('buongia')
233
147
wt.add(['hello', 'buongia'],
234
[b'hello-id', b'buongia-id'])
148
['hello-id', 'buongia-id'])
235
149
wt.commit(message='add files',
236
rev_id=b'test@rev-1')
238
152
os.remove('hello')
239
with open('buongia', 'w') as f:
153
file('buongia', 'w').write('new text')
241
154
wt.commit(message='update text',
242
specific_files=['buongia'],
243
allow_pointless=False,
244
rev_id=b'test@rev-2')
155
specific_files=['buongia'],
156
allow_pointless=False,
246
159
wt.commit(message='remove hello',
247
specific_files=['hello'],
248
allow_pointless=False,
249
rev_id=b'test@rev-3')
160
specific_files=['hello'],
161
allow_pointless=False,
251
eq = self.assertEqual
164
eq = self.assertEquals
254
tree2 = b.repository.revision_tree(b'test@rev-2')
256
self.addCleanup(tree2.unlock)
167
tree2 = b.repository.revision_tree('test@rev-2')
257
168
self.assertTrue(tree2.has_filename('hello'))
258
self.assertEqual(tree2.get_file_text('hello'), b'hello')
259
self.assertEqual(tree2.get_file_text('buongia'), b'new text')
261
tree3 = b.repository.revision_tree(b'test@rev-3')
263
self.addCleanup(tree3.unlock)
169
self.assertEquals(tree2.get_file_text('hello-id'), 'hello')
170
self.assertEquals(tree2.get_file_text('buongia-id'), 'new text')
172
tree3 = b.repository.revision_tree('test@rev-3')
264
173
self.assertFalse(tree3.has_filename('hello'))
265
self.assertEqual(tree3.get_file_text('buongia'), b'new text')
174
self.assertEquals(tree3.get_file_text('buongia-id'), 'new text')
267
176
def test_commit_rename(self):
268
177
"""Test commit of a revision where a file is renamed."""
269
178
tree = self.make_branch_and_tree('.')
271
180
self.build_tree(['hello'], line_endings='binary')
272
tree.add(['hello'], [b'hello-id'])
273
tree.commit(message='one', rev_id=b'test@rev-1', allow_pointless=False)
181
tree.add(['hello'], ['hello-id'])
182
tree.commit(message='one', rev_id='test@rev-1', allow_pointless=False)
275
184
tree.rename_one('hello', 'fruity')
276
tree.commit(message='renamed', rev_id=b'test@rev-2',
277
allow_pointless=False)
185
tree.commit(message='renamed', rev_id='test@rev-2', allow_pointless=False)
279
eq = self.assertEqual
280
tree1 = b.repository.revision_tree(b'test@rev-1')
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')
187
eq = self.assertEquals
188
tree1 = b.repository.revision_tree('test@rev-1')
189
eq(tree1.id2path('hello-id'), 'hello')
190
eq(tree1.get_file_text('hello-id'), 'contents of hello\n')
285
191
self.assertFalse(tree1.has_filename('fruity'))
286
self.check_tree_shape(tree1, ['hello'])
287
eq(tree1.get_file_revision('hello'), b'test@rev-1')
192
self.check_inventory_shape(tree1.inventory, ['hello'])
193
ie = tree1.inventory['hello-id']
194
eq(ie.revision, 'test@rev-1')
289
tree2 = b.repository.revision_tree(b'test@rev-2')
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')
196
tree2 = b.repository.revision_tree('test@rev-2')
197
eq(tree2.id2path('hello-id'), 'fruity')
198
eq(tree2.get_file_text('hello-id'), 'contents of hello\n')
199
self.check_inventory_shape(tree2.inventory, ['fruity'])
200
ie = tree2.inventory['hello-id']
201
eq(ie.revision, 'test@rev-2')
297
203
def test_reused_rev_id(self):
298
204
"""Test that a revision id cannot be reused in a branch"""
299
205
wt = self.make_branch_and_tree('.')
301
wt.commit('initial', rev_id=b'test@rev-1', allow_pointless=True)
207
wt.commit('initial', rev_id='test@rev-1', allow_pointless=True)
302
208
self.assertRaises(Exception,
304
210
message='reused id',
305
rev_id=b'test@rev-1',
306
212
allow_pointless=True)
308
214
def test_commit_move(self):
309
215
"""Test commit of revisions with moved files and directories"""
310
eq = self.assertEqual
216
eq = self.assertEquals
311
217
wt = self.make_branch_and_tree('.')
314
220
self.build_tree(['hello', 'a/', 'b/'])
315
wt.add(['hello', 'a', 'b'], [b'hello-id', b'a-id', b'b-id'])
221
wt.add(['hello', 'a', 'b'], ['hello-id', 'a-id', 'b-id'])
316
222
wt.commit('initial', rev_id=r1, allow_pointless=False)
317
223
wt.move(['hello'], 'a')
319
225
wt.commit('two', rev_id=r2, allow_pointless=False)
322
self.check_tree_shape(wt, ['a/', 'a/hello', 'b/'])
228
self.check_inventory_shape(wt.read_working_inventory(),
229
['a', 'a/hello', 'b'])
326
233
wt.move(['b'], 'a')
328
235
wt.commit('three', rev_id=r3, allow_pointless=False)
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/'])
238
self.check_inventory_shape(wt.read_working_inventory(),
239
['a', 'a/hello', 'a/b'])
240
self.check_inventory_shape(b.repository.get_revision_inventory(r3),
241
['a', 'a/hello', 'a/b'])
338
245
wt.move(['a/hello'], 'a/b')
340
247
wt.commit('four', rev_id=r4, allow_pointless=False)
343
self.check_tree_shape(wt, ['a/', 'a/b/hello', 'a/b/'])
250
self.check_inventory_shape(wt.read_working_inventory(),
251
['a', 'a/b/hello', 'a/b'])
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)
255
inv = b.repository.get_revision_inventory(r4)
256
eq(inv['hello-id'].revision, r4)
257
eq(inv['a-id'].revision, r1)
258
eq(inv['b-id'].revision, r3)
352
260
def test_removed_commit(self):
353
261
"""Commit with a removed file"""
354
262
wt = self.make_branch_and_tree('.')
356
with open('hello', 'w') as f:
357
f.write('hello world')
358
wt.add(['hello'], [b'hello-id'])
264
file('hello', 'w').write('hello world')
265
wt.add(['hello'], ['hello-id'])
359
266
wt.commit(message='add hello')
360
267
wt.remove('hello')
361
wt.commit('removed hello', rev_id=b'rev2')
268
wt.commit('removed hello', rev_id='rev2')
363
tree = b.repository.revision_tree(b'rev2')
364
self.assertFalse(tree.has_filename('hello'))
270
tree = b.repository.revision_tree('rev2')
271
self.assertFalse(tree.has_id('hello-id'))
366
273
def test_committed_ancestry(self):
367
274
"""Test commit appends revisions to ancestry."""
371
278
for i in range(4):
372
with open('hello', 'w') as f:
373
f.write((str(i) * 4) + '\n')
279
file('hello', 'w').write((str(i) * 4) + '\n')
375
wt.add(['hello'], [b'hello-id'])
376
rev_id = b'test@rev-%d' % (i + 1)
281
wt.add(['hello'], ['hello-id'])
282
rev_id = 'test@rev-%d' % (i+1)
377
283
rev_ids.append(rev_id)
378
wt.commit(message='rev %d' % (i + 1),
284
wt.commit(message='rev %d' % (i+1),
286
eq = self.assertEquals
287
eq(b.revision_history(), rev_ids)
380
288
for i in range(4):
381
self.assertThat(rev_ids[:i + 1],
382
MatchesAncestry(b.repository, rev_ids[i]))
289
anc = b.repository.get_ancestry(rev_ids[i])
290
eq(anc, [None] + rev_ids[:i+1])
384
292
def test_commit_new_subdir_child_selective(self):
385
293
wt = self.make_branch_and_tree('.')
387
295
self.build_tree(['dir/', 'dir/file1', 'dir/file2'])
388
296
wt.add(['dir', 'dir/file1', 'dir/file2'],
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)
297
['dirid', 'file1id', 'file2id'])
298
wt.commit('dir/file1', specific_files=['dir/file1'], rev_id='1')
299
inv = b.repository.get_inventory('1')
300
self.assertEqual('1', inv['dirid'].revision)
301
self.assertEqual('1', inv['file1id'].revision)
394
302
# FIXME: This should raise a KeyError I think, rbc20051006
395
self.assertRaises(BzrError, inv.get_entry, b'file2id')
303
self.assertRaises(BzrError, inv.__getitem__, 'file2id')
397
305
def test_strict_commit(self):
398
306
"""Try and commit with unknown files and strict = True, should fail."""
399
from ..errors import StrictCommitFailed
307
from bzrlib.errors import StrictCommitFailed
400
308
wt = self.make_branch_and_tree('.')
402
with open('hello', 'w') as f:
403
f.write('hello world')
310
file('hello', 'w').write('hello world')
405
with open('goodbye', 'w') as f:
406
f.write('goodbye cruel world!')
312
file('goodbye', 'w').write('goodbye cruel world!')
407
313
self.assertRaises(StrictCommitFailed, wt.commit,
408
message='add hello but not goodbye', strict=True)
314
message='add hello but not goodbye', strict=True)
410
316
def test_strict_commit_without_unknowns(self):
411
317
"""Try and commit with no unknown files and strict = True,
319
from bzrlib.errors import StrictCommitFailed
413
320
wt = self.make_branch_and_tree('.')
415
with open('hello', 'w') as f:
416
f.write('hello world')
322
file('hello', 'w').write('hello world')
418
324
wt.commit(message='add hello', strict=True)
434
338
wt = self.make_branch_and_tree('.')
436
with open('hello', 'w') as f:
437
f.write('hello world')
340
file('hello', 'w').write('hello world')
439
342
wt.commit(message='add hello', strict=False)
441
344
def test_signed_commit(self):
443
import breezy.commit as commit
444
oldstrategy = breezy.gpg.GPGStrategy
346
import bzrlib.commit as commit
347
oldstrategy = bzrlib.gpg.GPGStrategy
445
348
wt = self.make_branch_and_tree('.')
446
349
branch = wt.branch
447
wt.commit("base", allow_pointless=True, rev_id=b'A')
448
self.assertFalse(branch.repository.has_signature_for_revision_id(b'A'))
350
wt.commit("base", allow_pointless=True, rev_id='A')
351
self.failIf(branch.repository.has_signature_for_revision_id('A'))
450
from ..bzr.testament import Testament
353
from bzrlib.testament import Testament
451
354
# monkey patch gpg signing mechanism
452
breezy.gpg.GPGStrategy = breezy.gpg.LoopbackGPGStrategy
453
conf = config.MemoryStack(b'''
454
create_signatures=always
456
commit.Commit(config_stack=conf).commit(
457
message="base", allow_pointless=True, rev_id=b'B',
355
bzrlib.gpg.GPGStrategy = bzrlib.gpg.LoopbackGPGStrategy
356
commit.Commit(config=MustSignConfig(branch)).commit(message="base",
357
allow_pointless=True,
461
return breezy.gpg.LoopbackGPGStrategy(None).sign(
462
text, breezy.gpg.MODE_CLEAR)
361
return bzrlib.gpg.LoopbackGPGStrategy(None).sign(text)
463
362
self.assertEqual(sign(Testament.from_revision(branch.repository,
464
b'B').as_short_text()),
465
branch.repository.get_signature_text(b'B'))
363
'B').as_short_text()),
364
branch.repository.get_signature_text('B'))
467
breezy.gpg.GPGStrategy = oldstrategy
366
bzrlib.gpg.GPGStrategy = oldstrategy
469
368
def test_commit_failed_signature(self):
471
import breezy.commit as commit
472
oldstrategy = breezy.gpg.GPGStrategy
370
import bzrlib.commit as commit
371
oldstrategy = bzrlib.gpg.GPGStrategy
473
372
wt = self.make_branch_and_tree('.')
474
373
branch = wt.branch
475
wt.commit("base", allow_pointless=True, rev_id=b'A')
476
self.assertFalse(branch.repository.has_signature_for_revision_id(b'A'))
374
wt.commit("base", allow_pointless=True, rev_id='A')
375
self.failIf(branch.repository.has_signature_for_revision_id('A'))
377
from bzrlib.testament import Testament
478
378
# monkey patch gpg signing mechanism
479
breezy.gpg.GPGStrategy = breezy.gpg.DisabledGPGStrategy
480
conf = config.MemoryStack(b'''
481
create_signatures=always
483
self.assertRaises(breezy.gpg.SigningFailed,
484
commit.Commit(config_stack=conf).commit,
379
bzrlib.gpg.GPGStrategy = bzrlib.gpg.DisabledGPGStrategy
380
config = MustSignConfig(branch)
381
self.assertRaises(SigningFailed,
382
commit.Commit(config=config).commit,
486
384
allow_pointless=True,
489
387
branch = Branch.open(self.get_url('.'))
490
self.assertEqual(branch.last_revision(), b'A')
491
self.assertFalse(branch.repository.has_revision(b'B'))
388
self.assertEqual(branch.revision_history(), ['A'])
389
self.failIf(branch.repository.has_revision('B'))
493
breezy.gpg.GPGStrategy = oldstrategy
391
bzrlib.gpg.GPGStrategy = oldstrategy
495
393
def test_commit_invokes_hooks(self):
496
import breezy.commit as commit
394
import bzrlib.commit as commit
497
395
wt = self.make_branch_and_tree('.')
498
396
branch = wt.branch
501
398
def called(branch, rev_id):
502
399
calls.append('called')
503
breezy.ahook = called
400
bzrlib.ahook = called
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',
402
config = BranchWithHooks(branch)
403
commit.Commit(config=config).commit(
405
allow_pointless=True,
406
rev_id='A', working_tree = wt)
509
407
self.assertEqual(['called', 'called'], calls)
513
411
def test_commit_object_doesnt_set_nick(self):
514
412
# using the Commit object directly does not set the branch nick.
515
413
wt = self.make_branch_and_tree('.')
517
415
c.commit(working_tree=wt, message='empty tree', allow_pointless=True)
518
self.assertEqual(wt.branch.revno(), 1)
416
self.assertEquals(wt.branch.revno(), 1)
519
417
self.assertEqual({},
520
418
wt.branch.repository.get_revision(
521
wt.branch.last_revision()).properties)
419
wt.branch.last_revision()).properties)
523
421
def test_safe_master_lock(self):
524
422
os.mkdir('master')
539
444
bound_tree = self.make_branch_and_tree('bound')
540
445
bound_tree.branch.bind(master_branch)
542
self.build_tree_contents(
543
[('bound/content_file', b'initial contents\n')])
447
self.build_tree_contents([('bound/content_file', 'initial contents\n')])
544
448
bound_tree.add(['content_file'])
545
449
bound_tree.commit(message='woo!')
547
other_bzrdir = master_branch.controldir.sprout('other')
451
other_bzrdir = master_branch.bzrdir.sprout('other')
548
452
other_tree = other_bzrdir.open_workingtree()
550
# do a commit to the other branch changing the content file so
454
# do a commit to the the other branch changing the content file so
551
455
# that our commit after merging will have a merged revision in the
552
456
# content file history.
553
self.build_tree_contents(
554
[('other/content_file', b'change in other\n')])
457
self.build_tree_contents([('other/content_file', 'change in other\n')])
555
458
other_tree.commit('change in other')
557
460
# do a merge into the bound branch from other, and then change the
558
461
# content file locally to force a new revision (rather than using the
559
462
# revision from other). This forces extra processing in commit.
560
463
bound_tree.merge_from_branch(other_tree.branch)
561
self.build_tree_contents(
562
[('bound/content_file', b'change in bound\n')])
464
self.build_tree_contents([('bound/content_file', 'change in bound\n')])
564
466
# before #34959 was fixed, this failed with 'revision not present in
565
467
# weave' when trying to implicitly push from the bound branch to the master
566
468
bound_tree.commit(message='commit of merge in bound tree')
568
470
def test_commit_reporting_after_merge(self):
569
# when doing a commit of a merge, the reporter needs to still
471
# when doing a commit of a merge, the reporter needs to still
570
472
# be called for each item that is added/removed/deleted.
571
473
this_tree = self.make_branch_and_tree('this')
572
474
# we need a bunch of files and dirs, to perform one action on each.
616
517
this_tree.merge_from_branch(other_tree.branch)
617
518
reporter = CapturingReporter()
618
519
this_tree.commit('do the commit', reporter=reporter)
521
('change', 'unchanged', ''),
522
('change', 'unchanged', 'dirtoleave'),
523
('change', 'unchanged', 'filetoleave'),
620
524
('change', 'modified', 'filetomodify'),
621
525
('change', 'added', 'newdir'),
622
526
('change', 'added', 'newfile'),
623
527
('renamed', 'renamed', 'dirtorename', 'renameddir'),
624
('renamed', 'renamed', 'filetorename', 'renamedfile'),
625
528
('renamed', 'renamed', 'dirtoreparent', 'renameddir/reparenteddir'),
626
529
('renamed', 'renamed', 'filetoreparent', 'renameddir/reparentedfile'),
530
('renamed', 'renamed', 'filetorename', 'renamedfile'),
627
531
('deleted', 'dirtoremove'),
628
532
('deleted', 'filetoremove'),
630
result = set(reporter.calls)
631
missing = expected - result
632
new = result - expected
633
self.assertEqual((set(), set()), (missing, new))
635
536
def test_commit_removals_respects_filespec(self):
636
537
"""Commit respects the specified_files for removals."""
661
565
tree = self.make_branch_and_tree('.')
662
566
self.build_tree(['a'])
664
tree.commit('added a', rev_id=b'a1')
568
tree.commit('added a', rev_id='a1')
666
rev = tree.branch.repository.get_revision(b'a1')
570
rev = tree.branch.repository.get_revision('a1')
667
571
timestamp = rev.timestamp
668
572
timestamp_1ms = round(timestamp, 3)
669
573
self.assertEqual(timestamp_1ms, timestamp)
671
def assertBasisTreeKind(self, kind, tree, path):
575
def assertBasisTreeKind(self, kind, tree, file_id):
672
576
basis = tree.basis_tree()
673
577
basis.lock_read()
675
self.assertEqual(kind, basis.kind(path))
579
self.assertEqual(kind, basis.kind(file_id))
679
def test_unsupported_symlink_commit(self):
680
self.requireFeature(SymlinkFeature)
681
tree = self.make_branch_and_tree('.')
682
self.build_tree(['hello'])
684
tree.commit('added hello', rev_id=b'hello_id')
685
os.symlink('hello', 'foo')
687
tree.commit('added foo', rev_id=b'foo_id')
689
trace.push_log_file(log)
690
os_symlink = getattr(os, 'symlink', None)
693
# At this point as bzr thinks symlinks are not supported
694
# we should get a warning about symlink foo and bzr should
695
# not think its removed.
697
self.build_tree(['world'])
699
tree.commit('added world', rev_id=b'world_id')
702
os.symlink = os_symlink
703
self.assertContainsRe(
705
b'Ignoring "foo" as symlinks are not '
706
b'supported on this filesystem\\.')
708
583
def test_commit_kind_changes(self):
709
self.requireFeature(SymlinkFeature)
584
if not osutils.has_symlinks():
585
raise tests.TestSkipped('Test requires symlink support')
710
586
tree = self.make_branch_and_tree('.')
711
587
os.symlink('target', 'name')
712
tree.add('name', b'a-file-id')
588
tree.add('name', 'a-file-id')
713
589
tree.commit('Added a symlink')
714
self.assertBasisTreeKind('symlink', tree, 'name')
590
self.assertBasisTreeKind('symlink', tree, 'a-file-id')
716
592
os.unlink('name')
717
593
self.build_tree(['name'])
718
594
tree.commit('Changed symlink to file')
719
self.assertBasisTreeKind('file', tree, 'name')
595
self.assertBasisTreeKind('file', tree, 'a-file-id')
721
597
os.unlink('name')
722
598
os.symlink('target', 'name')
723
599
tree.commit('file to symlink')
724
self.assertBasisTreeKind('symlink', tree, 'name')
600
self.assertBasisTreeKind('symlink', tree, 'a-file-id')
726
602
os.unlink('name')
728
604
tree.commit('symlink to directory')
729
self.assertBasisTreeKind('directory', tree, 'name')
605
self.assertBasisTreeKind('directory', tree, 'a-file-id')
732
608
os.symlink('target', 'name')
733
609
tree.commit('directory to symlink')
734
self.assertBasisTreeKind('symlink', tree, 'name')
610
self.assertBasisTreeKind('symlink', tree, 'a-file-id')
736
612
# prepare for directory <-> file tests
737
613
os.unlink('name')
739
615
tree.commit('symlink to directory')
740
self.assertBasisTreeKind('directory', tree, 'name')
616
self.assertBasisTreeKind('directory', tree, 'a-file-id')
743
619
self.build_tree(['name'])
744
620
tree.commit('Changed directory to file')
745
self.assertBasisTreeKind('file', tree, 'name')
621
self.assertBasisTreeKind('file', tree, 'a-file-id')
747
623
os.unlink('name')
749
625
tree.commit('file to directory')
750
self.assertBasisTreeKind('directory', tree, 'name')
626
self.assertBasisTreeKind('directory', tree, 'a-file-id')
752
628
def test_commit_unversioned_specified(self):
753
629
"""Commit should raise if specified files isn't in basis or worktree"""
754
630
tree = self.make_branch_and_tree('.')
755
self.assertRaises(errors.PathsNotVersionedError, tree.commit,
631
self.assertRaises(errors.PathsNotVersionedError, tree.commit,
756
632
'message', specific_files=['bogus'])
758
634
class Callback(object):
760
636
def __init__(self, message, testcase):
761
637
self.called = False
762
638
self.message = message
800
676
cb = self.Callback(u'commit 2', self)
801
677
repository = tree.branch.repository
802
678
# simulate network failure
804
def raise_(self, arg, arg2, arg3=None, arg4=None):
679
def raise_(self, arg, arg2):
805
680
raise errors.NoSuchFile('foo')
806
681
repository.add_inventory = raise_
807
repository.add_inventory_by_delta = raise_
808
682
self.assertRaises(errors.NoSuchFile, tree.commit, message_callback=cb)
809
683
self.assertFalse(cb.called)
811
def test_selected_file_merge_commit(self):
812
"""Ensure the correct error is raised"""
813
tree = self.make_branch_and_tree('foo')
814
# pending merge would turn into a left parent
815
tree.commit('commit 1')
816
tree.add_parent_tree_id(b'example')
817
self.build_tree(['foo/bar', 'foo/baz'])
818
tree.add(['bar', 'baz'])
819
err = self.assertRaises(CannotCommitSelectedFileMerge,
820
tree.commit, 'commit 2', specific_files=['bar', 'baz'])
821
self.assertEqual(['bar', 'baz'], err.files)
822
self.assertEqual('Selected-file commit of merges is not supported'
823
' yet: files bar, baz', str(err))
825
def test_commit_ordering(self):
826
"""Test of corner-case commit ordering error"""
827
tree = self.make_branch_and_tree('.')
828
self.build_tree(['a/', 'a/z/', 'a/c/', 'a/z/x', 'a/z/y'])
829
tree.add(['a/', 'a/z/', 'a/c/', 'a/z/x', 'a/z/y'])
831
self.build_tree(['a/c/d/'])
833
tree.rename_one('a/z/x', 'a/c/d/x')
834
tree.commit('test', specific_files=['a/z/y'])
836
def test_commit_no_author(self):
837
"""The default kwarg author in MutableTree.commit should not add
838
the 'author' revision property.
840
tree = self.make_branch_and_tree('foo')
841
rev_id = tree.commit('commit 1')
842
rev = tree.branch.repository.get_revision(rev_id)
843
self.assertFalse('author' in rev.properties)
844
self.assertFalse('authors' in rev.properties)
846
def test_commit_author(self):
847
"""Passing a non-empty authors kwarg to MutableTree.commit should add
848
the 'author' revision property.
850
tree = self.make_branch_and_tree('foo')
851
rev_id = tree.commit(
853
authors=['John Doe <jdoe@example.com>'])
854
rev = tree.branch.repository.get_revision(rev_id)
855
self.assertEqual('John Doe <jdoe@example.com>',
856
rev.properties['authors'])
857
self.assertFalse('author' in rev.properties)
859
def test_commit_empty_authors_list(self):
860
"""Passing an empty list to authors shouldn't add the property."""
861
tree = self.make_branch_and_tree('foo')
862
rev_id = tree.commit('commit 1', authors=[])
863
rev = tree.branch.repository.get_revision(rev_id)
864
self.assertFalse('author' in rev.properties)
865
self.assertFalse('authors' in rev.properties)
867
def test_multiple_authors(self):
868
tree = self.make_branch_and_tree('foo')
869
rev_id = tree.commit('commit 1',
870
authors=['John Doe <jdoe@example.com>',
871
'Jane Rey <jrey@example.com>'])
872
rev = tree.branch.repository.get_revision(rev_id)
873
self.assertEqual('John Doe <jdoe@example.com>\n'
874
'Jane Rey <jrey@example.com>', rev.properties['authors'])
875
self.assertFalse('author' in rev.properties)
877
def test_author_with_newline_rejected(self):
878
tree = self.make_branch_and_tree('foo')
879
self.assertRaises(AssertionError, tree.commit, 'commit 1',
880
authors=['John\nDoe <jdoe@example.com>'])
882
def test_commit_with_checkout_and_branch_sharing_repo(self):
883
repo = self.make_repository('repo', shared=True)
884
# make_branch_and_tree ignores shared repos
885
branch = controldir.ControlDir.create_branch_convenience('repo/branch')
886
tree2 = branch.create_checkout('repo/tree2')
887
tree2.commit('message', rev_id=b'rev1')
888
self.assertTrue(tree2.branch.repository.has_revision(b'rev1'))
891
class FilterExcludedTests(TestCase):
893
def test_add_file_not_excluded(self):
896
'fid', (None, 'newpath'),
897
0, (False, False), ('pid', 'pid'), ('newpath', 'newpath'),
898
('file', 'file'), (True, True))]
899
self.assertEqual(changes, list(
900
filter_excluded(changes, ['otherpath'])))
902
def test_add_file_excluded(self):
905
'fid', (None, 'newpath'),
906
0, (False, False), ('pid', 'pid'), ('newpath', 'newpath'),
907
('file', 'file'), (True, True))]
908
self.assertEqual([], list(filter_excluded(changes, ['newpath'])))
910
def test_delete_file_excluded(self):
913
'fid', ('somepath', None),
914
0, (False, None), ('pid', None), ('newpath', None),
915
('file', None), (True, None))]
916
self.assertEqual([], list(filter_excluded(changes, ['somepath'])))
918
def test_move_from_or_to_excluded(self):
921
'fid', ('oldpath', 'newpath'),
922
0, (False, False), ('pid', 'pid'), ('oldpath', 'newpath'),
923
('file', 'file'), (True, True))]
924
self.assertEqual([], list(filter_excluded(changes, ['oldpath'])))
925
self.assertEqual([], list(filter_excluded(changes, ['newpath'])))