50
124
self.assertRaises(LockError, self.wt.unlock)
51
125
os.rmdir(pathjoin(limbo_name, 'hehe'))
52
126
os.rmdir(limbo_name)
127
os.rmdir(deletion_path)
53
128
transform, root = self.get_transform()
131
def test_existing_pending_deletion(self):
132
transform, root = self.get_transform()
133
deletion_path = self._limbodir = urlutils.local_path_from_url(
134
transform._tree._transport.abspath('pending-deletion'))
135
os.mkdir(pathjoin(deletion_path, 'blocking-directory'))
136
self.assertRaises(ImmortalPendingDeletion, transform.apply)
137
self.assertRaises(LockError, self.wt.unlock)
138
self.assertRaises(ExistingPendingDeletion, self.get_transform)
56
140
def test_build(self):
57
transform, root = self.get_transform()
141
transform, root = self.get_transform()
142
self.wt.lock_tree_write()
143
self.addCleanup(self.wt.unlock)
58
144
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
59
145
imaginary_id = transform.trans_id_tree_path('imaginary')
60
self.assertEqual(transform.get_tree_parent(imaginary_id), root)
61
self.assertEqual(transform.final_kind(root), 'directory')
62
self.assertEqual(transform.final_file_id(root), self.wt.get_root_id())
146
imaginary_id2 = transform.trans_id_tree_path('imaginary/')
147
self.assertEqual(imaginary_id, imaginary_id2)
148
self.assertEqual(root, transform.get_tree_parent(imaginary_id))
149
self.assertEqual('directory', transform.final_kind(root))
150
self.assertEqual(self.wt.get_root_id(), transform.final_file_id(root))
63
151
trans_id = transform.create_path('name', root)
64
152
self.assertIs(transform.final_file_id(trans_id), None)
65
self.assertRaises(NoSuchFile, transform.final_kind, trans_id)
66
transform.create_file('contents', trans_id)
153
self.assertIs(None, transform.final_kind(trans_id))
154
transform.create_file([b'contents'], trans_id)
67
155
transform.set_executability(True, trans_id)
68
transform.version_file('my_pretties', trans_id)
156
transform.version_file(b'my_pretties', trans_id)
69
157
self.assertRaises(DuplicateKey, transform.version_file,
70
'my_pretties', trans_id)
71
self.assertEqual(transform.final_file_id(trans_id), 'my_pretties')
158
b'my_pretties', trans_id)
159
self.assertEqual(transform.final_file_id(trans_id), b'my_pretties')
72
160
self.assertEqual(transform.final_parent(trans_id), root)
73
161
self.assertIs(transform.final_parent(root), ROOT_PARENT)
74
162
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
75
163
oz_id = transform.create_path('oz', root)
76
164
transform.create_directory(oz_id)
77
transform.version_file('ozzie', oz_id)
165
transform.version_file(b'ozzie', oz_id)
78
166
trans_id2 = transform.create_path('name2', root)
79
transform.create_file('contents', trans_id2)
167
transform.create_file([b'contents'], trans_id2)
80
168
transform.set_executability(False, trans_id2)
81
transform.version_file('my_pretties2', trans_id2)
169
transform.version_file(b'my_pretties2', trans_id2)
82
170
modified_paths = transform.apply().modified_paths
83
self.assertEqual('contents', self.wt.get_file_byname('name').read())
84
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
85
self.assertIs(self.wt.is_executable('my_pretties'), True)
86
self.assertIs(self.wt.is_executable('my_pretties2'), False)
171
with self.wt.get_file('name') as f:
172
self.assertEqual(b'contents', f.read())
173
self.assertEqual(self.wt.path2id('name'), b'my_pretties')
174
self.assertIs(self.wt.is_executable('name'), True)
175
self.assertIs(self.wt.is_executable('name2'), False)
87
176
self.assertEqual('directory', file_kind(self.wt.abspath('oz')))
88
177
self.assertEqual(len(modified_paths), 3)
89
tree_mod_paths = [self.wt.id2abspath(f) for f in
90
('ozzie', 'my_pretties', 'my_pretties2')]
178
tree_mod_paths = [self.wt.abspath(self.wt.id2path(f)) for f in
179
(b'ozzie', b'my_pretties', b'my_pretties2')]
91
180
self.assertSubset(tree_mod_paths, modified_paths)
92
181
# is it safe to finalize repeatedly?
93
182
transform.finalize()
94
183
transform.finalize()
185
def test_apply_informs_tree_of_observed_sha1(self):
186
trans, root, contents, sha1 = self.get_transform_for_sha1_test()
187
trans_id = trans.new_file('file1', root, contents, file_id=b'file1-id',
190
orig = self.wt._observed_sha1
192
def _observed_sha1(*args):
195
self.wt._observed_sha1 = _observed_sha1
197
self.assertEqual([('file1', trans._observed_sha1s[trans_id])],
200
def test_create_file_caches_sha1(self):
201
trans, root, contents, sha1 = self.get_transform_for_sha1_test()
202
trans_id = trans.create_path('file1', root)
203
trans.create_file(contents, trans_id, sha1=sha1)
204
st_val = osutils.lstat(trans._limbo_name(trans_id))
205
o_sha1, o_st_val = trans._observed_sha1s[trans_id]
206
self.assertEqual(o_sha1, sha1)
207
self.assertEqualStat(o_st_val, st_val)
209
def test__apply_insertions_updates_sha1(self):
210
trans, root, contents, sha1 = self.get_transform_for_sha1_test()
211
trans_id = trans.create_path('file1', root)
212
trans.create_file(contents, trans_id, sha1=sha1)
213
st_val = osutils.lstat(trans._limbo_name(trans_id))
214
o_sha1, o_st_val = trans._observed_sha1s[trans_id]
215
self.assertEqual(o_sha1, sha1)
216
self.assertEqualStat(o_st_val, st_val)
217
creation_mtime = trans._creation_mtime + 10.0
218
# We fake a time difference from when the file was created until now it
219
# is being renamed by using os.utime. Note that the change we actually
220
# want to see is the real ctime change from 'os.rename()', but as long
221
# as we observe a new stat value, we should be fine.
222
os.utime(trans._limbo_name(trans_id), (creation_mtime, creation_mtime))
224
new_st_val = osutils.lstat(self.wt.abspath('file1'))
225
o_sha1, o_st_val = trans._observed_sha1s[trans_id]
226
self.assertEqual(o_sha1, sha1)
227
self.assertEqualStat(o_st_val, new_st_val)
228
self.assertNotEqual(st_val.st_mtime, new_st_val.st_mtime)
230
def test_new_file_caches_sha1(self):
231
trans, root, contents, sha1 = self.get_transform_for_sha1_test()
232
trans_id = trans.new_file('file1', root, contents, file_id=b'file1-id',
234
st_val = osutils.lstat(trans._limbo_name(trans_id))
235
o_sha1, o_st_val = trans._observed_sha1s[trans_id]
236
self.assertEqual(o_sha1, sha1)
237
self.assertEqualStat(o_st_val, st_val)
239
def test_cancel_creation_removes_observed_sha1(self):
240
trans, root, contents, sha1 = self.get_transform_for_sha1_test()
241
trans_id = trans.new_file('file1', root, contents, file_id=b'file1-id',
243
self.assertTrue(trans_id in trans._observed_sha1s)
244
trans.cancel_creation(trans_id)
245
self.assertFalse(trans_id in trans._observed_sha1s)
247
def test_create_files_same_timestamp(self):
248
transform, root = self.get_transform()
249
self.wt.lock_tree_write()
250
self.addCleanup(self.wt.unlock)
251
# Roll back the clock, so that we know everything is being set to the
253
transform._creation_mtime = creation_mtime = time.time() - 20.0
254
transform.create_file([b'content-one'],
255
transform.create_path('one', root))
256
time.sleep(1) # *ugly*
257
transform.create_file([b'content-two'],
258
transform.create_path('two', root))
260
fo, st1 = self.wt.get_file_with_stat('one', filtered=False)
262
fo, st2 = self.wt.get_file_with_stat('two', filtered=False)
264
# We only guarantee 2s resolution
266
abs(creation_mtime - st1.st_mtime) < 2.0,
267
"%s != %s within 2 seconds" % (creation_mtime, st1.st_mtime))
268
# But if we have more than that, all files should get the same result
269
self.assertEqual(st1.st_mtime, st2.st_mtime)
271
def test_change_root_id(self):
272
transform, root = self.get_transform()
273
self.assertNotEqual(b'new-root-id', self.wt.get_root_id())
274
transform.new_directory('', ROOT_PARENT, b'new-root-id')
275
transform.delete_contents(root)
276
transform.unversion_file(root)
277
transform.fixup_new_roots()
279
self.assertEqual(b'new-root-id', self.wt.get_root_id())
281
def test_change_root_id_add_files(self):
282
transform, root = self.get_transform()
283
self.assertNotEqual(b'new-root-id', self.wt.get_root_id())
284
new_trans_id = transform.new_directory('', ROOT_PARENT, b'new-root-id')
285
transform.new_file('file', new_trans_id, [b'new-contents\n'],
287
transform.delete_contents(root)
288
transform.unversion_file(root)
289
transform.fixup_new_roots()
291
self.assertEqual(b'new-root-id', self.wt.get_root_id())
292
self.assertEqual(b'new-file-id', self.wt.path2id('file'))
293
self.assertFileEqual(b'new-contents\n', self.wt.abspath('file'))
295
def test_add_two_roots(self):
296
transform, root = self.get_transform()
297
transform.new_directory('', ROOT_PARENT, b'new-root-id')
298
transform.new_directory('', ROOT_PARENT, b'alt-root-id')
299
self.assertRaises(ValueError, transform.fixup_new_roots)
301
def test_retain_existing_root(self):
302
tt, root = self.get_transform()
304
tt.new_directory('', ROOT_PARENT, b'new-root-id')
306
self.assertNotEqual(b'new-root-id', tt.final_file_id(tt.root))
308
def test_retain_existing_root_added_file(self):
309
tt, root = self.get_transform()
310
new_trans_id = tt.new_directory('', ROOT_PARENT, b'new-root-id')
311
child = tt.new_directory('child', new_trans_id, b'child-id')
313
self.assertEqual(tt.root, tt.final_parent(child))
315
def test_add_unversioned_root(self):
316
transform, root = self.get_transform()
317
transform.new_directory('', ROOT_PARENT, None)
318
transform.delete_contents(transform.root)
319
transform.fixup_new_roots()
320
self.assertNotIn(transform.root, transform._new_id)
322
def test_remove_root_fixup(self):
323
transform, root = self.get_transform()
324
old_root_id = self.wt.get_root_id()
325
self.assertNotEqual(b'new-root-id', old_root_id)
326
transform.delete_contents(root)
327
transform.unversion_file(root)
328
transform.fixup_new_roots()
330
self.assertEqual(old_root_id, self.wt.get_root_id())
332
transform, root = self.get_transform()
333
transform.new_directory('', ROOT_PARENT, b'new-root-id')
334
transform.new_directory('', ROOT_PARENT, b'alt-root-id')
335
self.assertRaises(ValueError, transform.fixup_new_roots)
337
def test_fixup_new_roots_permits_empty_tree(self):
338
transform, root = self.get_transform()
339
transform.delete_contents(root)
340
transform.unversion_file(root)
341
transform.fixup_new_roots()
342
self.assertIs(None, transform.final_kind(root))
343
self.assertIs(None, transform.final_file_id(root))
345
def test_apply_retains_root_directory(self):
346
# Do not attempt to delete the physical root directory, because that
348
transform, root = self.get_transform()
350
transform.delete_contents(root)
351
e = self.assertRaises(AssertionError, self.assertRaises,
352
errors.TransformRenameFailed,
354
self.assertContainsRe('TransformRenameFailed not raised', str(e))
356
def test_apply_retains_file_id(self):
357
transform, root = self.get_transform()
358
old_root_id = transform.tree_file_id(root)
359
transform.unversion_file(root)
361
self.assertEqual(old_root_id, self.wt.get_root_id())
363
def test_hardlink(self):
364
self.requireFeature(HardlinkFeature)
365
transform, root = self.get_transform()
366
transform.new_file('file1', root, [b'contents'])
368
target = self.make_branch_and_tree('target')
369
target_transform = TreeTransform(target)
370
trans_id = target_transform.create_path('file1', target_transform.root)
371
target_transform.create_hardlink(self.wt.abspath('file1'), trans_id)
372
target_transform.apply()
373
self.assertPathExists('target/file1')
374
source_stat = os.stat(self.wt.abspath('file1'))
375
target_stat = os.stat('target/file1')
376
self.assertEqual(source_stat, target_stat)
96
378
def test_convenience(self):
97
379
transform, root = self.get_transform()
98
trans_id = transform.new_file('name', root, 'contents',
100
oz = transform.new_directory('oz', root, 'oz-id')
101
dorothy = transform.new_directory('dorothy', oz, 'dorothy-id')
102
toto = transform.new_file('toto', dorothy, 'toto-contents',
380
self.wt.lock_tree_write()
381
self.addCleanup(self.wt.unlock)
382
transform.new_file('name', root, [b'contents'], b'my_pretties', True)
383
oz = transform.new_directory('oz', root, b'oz-id')
384
dorothy = transform.new_directory('dorothy', oz, b'dorothy-id')
385
transform.new_file('toto', dorothy, [b'toto-contents'], b'toto-id',
105
388
self.assertEqual(len(transform.find_conflicts()), 0)
106
389
transform.apply()
107
390
self.assertRaises(ReusingTransform, transform.find_conflicts)
108
self.assertEqual('contents', file(self.wt.abspath('name')).read())
109
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
110
self.assertIs(self.wt.is_executable('my_pretties'), True)
111
self.assertEqual(self.wt.path2id('oz'), 'oz-id')
112
self.assertEqual(self.wt.path2id('oz/dorothy'), 'dorothy-id')
113
self.assertEqual(self.wt.path2id('oz/dorothy/toto'), 'toto-id')
115
self.assertEqual('toto-contents',
116
self.wt.get_file_byname('oz/dorothy/toto').read())
117
self.assertIs(self.wt.is_executable('toto-id'), False)
391
with open(self.wt.abspath('name'), 'r') as f:
392
self.assertEqual('contents', f.read())
393
self.assertEqual(self.wt.path2id('name'), b'my_pretties')
394
self.assertIs(self.wt.is_executable('name'), True)
395
self.assertEqual(self.wt.path2id('oz'), b'oz-id')
396
self.assertEqual(self.wt.path2id('oz/dorothy'), b'dorothy-id')
397
self.assertEqual(self.wt.path2id('oz/dorothy/toto'), b'toto-id')
399
self.assertEqual(b'toto-contents',
400
self.wt.get_file('oz/dorothy/toto').read())
401
self.assertIs(self.wt.is_executable('oz/dorothy/toto'), False)
403
def test_tree_reference(self):
404
transform, root = self.get_transform()
405
tree = transform._tree
406
trans_id = transform.new_directory('reference', root, b'subtree-id')
407
transform.set_tree_reference(b'subtree-revision', trans_id)
410
self.addCleanup(tree.unlock)
413
tree.root_inventory.get_entry(b'subtree-id').reference_revision)
119
415
def test_conflicts(self):
120
416
transform, root = self.get_transform()
121
trans_id = transform.new_file('name', root, 'contents',
417
trans_id = transform.new_file('name', root, [b'contents'],
123
419
self.assertEqual(len(transform.find_conflicts()), 0)
124
trans_id2 = transform.new_file('name', root, 'Crontents', 'toto')
125
self.assertEqual(transform.find_conflicts(),
420
trans_id2 = transform.new_file('name', root, [b'Crontents'], b'toto')
421
self.assertEqual(transform.find_conflicts(),
126
422
[('duplicate', trans_id, trans_id2, 'name')])
127
423
self.assertRaises(MalformedTransform, transform.apply)
128
424
transform.adjust_path('name', trans_id, trans_id2)
129
self.assertEqual(transform.find_conflicts(),
425
self.assertEqual(transform.find_conflicts(),
130
426
[('non-directory parent', trans_id)])
131
427
tinman_id = transform.trans_id_tree_path('tinman')
132
428
transform.adjust_path('name', tinman_id, trans_id2)
133
self.assertEqual(transform.find_conflicts(),
134
[('unversioned parent', tinman_id),
429
self.assertEqual(transform.find_conflicts(),
430
[('unversioned parent', tinman_id),
135
431
('missing parent', tinman_id)])
136
432
lion_id = transform.create_path('lion', root)
137
self.assertEqual(transform.find_conflicts(),
138
[('unversioned parent', tinman_id),
433
self.assertEqual(transform.find_conflicts(),
434
[('unversioned parent', tinman_id),
139
435
('missing parent', tinman_id)])
140
436
transform.adjust_path('name', lion_id, trans_id2)
141
self.assertEqual(transform.find_conflicts(),
437
self.assertEqual(transform.find_conflicts(),
142
438
[('unversioned parent', lion_id),
143
439
('missing parent', lion_id)])
144
transform.version_file("Courage", lion_id)
145
self.assertEqual(transform.find_conflicts(),
146
[('missing parent', lion_id),
440
transform.version_file(b"Courage", lion_id)
441
self.assertEqual(transform.find_conflicts(),
442
[('missing parent', lion_id),
147
443
('versioning no contents', lion_id)])
148
444
transform.adjust_path('name2', root, trans_id2)
149
self.assertEqual(transform.find_conflicts(),
445
self.assertEqual(transform.find_conflicts(),
150
446
[('versioning no contents', lion_id)])
151
transform.create_file('Contents, okay?', lion_id)
447
transform.create_file([b'Contents, okay?'], lion_id)
152
448
transform.adjust_path('name2', trans_id2, trans_id2)
153
self.assertEqual(transform.find_conflicts(),
154
[('parent loop', trans_id2),
449
self.assertEqual(transform.find_conflicts(),
450
[('parent loop', trans_id2),
155
451
('non-directory parent', trans_id2)])
156
452
transform.adjust_path('name2', root, trans_id2)
157
453
oz_id = transform.new_directory('oz', root)
158
454
transform.set_executability(True, oz_id)
159
self.assertEqual(transform.find_conflicts(),
455
self.assertEqual(transform.find_conflicts(),
160
456
[('unversioned executability', oz_id)])
161
transform.version_file('oz-id', oz_id)
162
self.assertEqual(transform.find_conflicts(),
457
transform.version_file(b'oz-id', oz_id)
458
self.assertEqual(transform.find_conflicts(),
163
459
[('non-file executability', oz_id)])
164
460
transform.set_executability(None, oz_id)
165
tip_id = transform.new_file('tip', oz_id, 'ozma', 'tip-id')
461
tip_id = transform.new_file('tip', oz_id, [b'ozma'], b'tip-id')
166
462
transform.apply()
167
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
168
self.assertEqual('contents', file(self.wt.abspath('name')).read())
463
self.assertEqual(self.wt.path2id('name'), b'my_pretties')
464
with open(self.wt.abspath('name'), 'rb') as f:
465
self.assertEqual(b'contents', f.read())
169
466
transform2, root = self.get_transform()
170
oz_id = transform2.trans_id_tree_file_id('oz-id')
171
newtip = transform2.new_file('tip', oz_id, 'other', 'tip-id')
467
oz_id = transform2.trans_id_tree_path('oz')
468
newtip = transform2.new_file('tip', oz_id, [b'other'], b'tip-id')
172
469
result = transform2.find_conflicts()
173
470
fp = FinalPaths(transform2)
174
self.assert_('oz/tip' in transform2._tree_path_ids)
471
self.assertTrue('oz/tip' in transform2._tree_path_ids)
175
472
self.assertEqual(fp.get_path(newtip), pathjoin('oz', 'tip'))
176
473
self.assertEqual(len(result), 2)
177
self.assertEqual((result[0][0], result[0][1]),
474
self.assertEqual((result[0][0], result[0][1]),
178
475
('duplicate', newtip))
179
self.assertEqual((result[1][0], result[1][2]),
476
self.assertEqual((result[1][0], result[1][2]),
180
477
('duplicate id', newtip))
181
478
transform2.finalize()
182
479
transform3 = TreeTransform(self.wt)
183
480
self.addCleanup(transform3.finalize)
184
oz_id = transform3.trans_id_tree_file_id('oz-id')
481
oz_id = transform3.trans_id_tree_path('oz')
185
482
transform3.delete_contents(oz_id)
186
self.assertEqual(transform3.find_conflicts(),
483
self.assertEqual(transform3.find_conflicts(),
187
484
[('missing parent', oz_id)])
188
root_id = transform3.trans_id_tree_file_id('TREE_ROOT')
189
tip_id = transform3.trans_id_tree_file_id('tip-id')
485
root_id = transform3.root
486
tip_id = transform3.trans_id_tree_path('oz/tip')
190
487
transform3.adjust_path('tip', root_id, tip_id)
191
488
transform3.apply()
490
def test_conflict_on_case_insensitive(self):
491
tree = self.make_branch_and_tree('tree')
492
# Don't try this at home, kids!
493
# Force the tree to report that it is case sensitive, for conflict
495
tree.case_sensitive = True
496
transform = TreeTransform(tree)
497
self.addCleanup(transform.finalize)
498
transform.new_file('file', transform.root, [b'content'])
499
transform.new_file('FiLe', transform.root, [b'content'])
500
result = transform.find_conflicts()
501
self.assertEqual([], result)
503
# Force the tree to report that it is case insensitive, for conflict
505
tree.case_sensitive = False
506
transform = TreeTransform(tree)
507
self.addCleanup(transform.finalize)
508
transform.new_file('file', transform.root, [b'content'])
509
transform.new_file('FiLe', transform.root, [b'content'])
510
result = transform.find_conflicts()
511
self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
513
def test_conflict_on_case_insensitive_existing(self):
514
tree = self.make_branch_and_tree('tree')
515
self.build_tree(['tree/FiLe'])
516
# Don't try this at home, kids!
517
# Force the tree to report that it is case sensitive, for conflict
519
tree.case_sensitive = True
520
transform = TreeTransform(tree)
521
self.addCleanup(transform.finalize)
522
transform.new_file('file', transform.root, [b'content'])
523
result = transform.find_conflicts()
524
self.assertEqual([], result)
526
# Force the tree to report that it is case insensitive, for conflict
528
tree.case_sensitive = False
529
transform = TreeTransform(tree)
530
self.addCleanup(transform.finalize)
531
transform.new_file('file', transform.root, [b'content'])
532
result = transform.find_conflicts()
533
self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
535
def test_resolve_case_insensitive_conflict(self):
536
tree = self.make_branch_and_tree('tree')
537
# Don't try this at home, kids!
538
# Force the tree to report that it is case insensitive, for conflict
540
tree.case_sensitive = False
541
transform = TreeTransform(tree)
542
self.addCleanup(transform.finalize)
543
transform.new_file('file', transform.root, [b'content'])
544
transform.new_file('FiLe', transform.root, [b'content'])
545
resolve_conflicts(transform)
547
self.assertPathExists('tree/file')
548
self.assertPathExists('tree/FiLe.moved')
550
def test_resolve_checkout_case_conflict(self):
551
tree = self.make_branch_and_tree('tree')
552
# Don't try this at home, kids!
553
# Force the tree to report that it is case insensitive, for conflict
555
tree.case_sensitive = False
556
transform = TreeTransform(tree)
557
self.addCleanup(transform.finalize)
558
transform.new_file('file', transform.root, [b'content'])
559
transform.new_file('FiLe', transform.root, [b'content'])
560
resolve_conflicts(transform,
561
pass_func=lambda t, c: resolve_checkout(t, c, []))
563
self.assertPathExists('tree/file')
564
self.assertPathExists('tree/FiLe.moved')
566
def test_apply_case_conflict(self):
567
"""Ensure that a transform with case conflicts can always be applied"""
568
tree = self.make_branch_and_tree('tree')
569
transform = TreeTransform(tree)
570
self.addCleanup(transform.finalize)
571
transform.new_file('file', transform.root, [b'content'])
572
transform.new_file('FiLe', transform.root, [b'content'])
573
dir = transform.new_directory('dir', transform.root)
574
transform.new_file('dirfile', dir, [b'content'])
575
transform.new_file('dirFiLe', dir, [b'content'])
576
resolve_conflicts(transform)
578
self.assertPathExists('tree/file')
579
if not os.path.exists('tree/FiLe.moved'):
580
self.assertPathExists('tree/FiLe')
581
self.assertPathExists('tree/dir/dirfile')
582
if not os.path.exists('tree/dir/dirFiLe.moved'):
583
self.assertPathExists('tree/dir/dirFiLe')
585
def test_case_insensitive_limbo(self):
586
tree = self.make_branch_and_tree('tree')
587
# Don't try this at home, kids!
588
# Force the tree to report that it is case insensitive
589
tree.case_sensitive = False
590
transform = TreeTransform(tree)
591
self.addCleanup(transform.finalize)
592
dir = transform.new_directory('dir', transform.root)
593
first = transform.new_file('file', dir, [b'content'])
594
second = transform.new_file('FiLe', dir, [b'content'])
595
self.assertContainsRe(transform._limbo_name(first), 'new-1/file')
596
self.assertNotContainsRe(transform._limbo_name(second), 'new-1/FiLe')
598
def test_adjust_path_updates_child_limbo_names(self):
599
tree = self.make_branch_and_tree('tree')
600
transform = TreeTransform(tree)
601
self.addCleanup(transform.finalize)
602
foo_id = transform.new_directory('foo', transform.root)
603
bar_id = transform.new_directory('bar', foo_id)
604
baz_id = transform.new_directory('baz', bar_id)
605
qux_id = transform.new_directory('qux', baz_id)
606
transform.adjust_path('quxx', foo_id, bar_id)
607
self.assertStartsWith(transform._limbo_name(qux_id),
608
transform._limbo_name(bar_id))
193
610
def test_add_del(self):
194
611
start, root = self.get_transform()
195
start.new_directory('a', root, 'a')
612
start.new_directory('a', root, b'a')
197
614
transform, root = self.get_transform()
198
transform.delete_versioned(transform.trans_id_tree_file_id('a'))
199
transform.new_directory('a', root, 'a')
615
transform.delete_versioned(transform.trans_id_tree_path('a'))
616
transform.new_directory('a', root, b'a')
200
617
transform.apply()
202
619
def test_unversioning(self):
203
620
create_tree, root = self.get_transform()
204
parent_id = create_tree.new_directory('parent', root, 'parent-id')
205
create_tree.new_file('child', parent_id, 'child', 'child-id')
621
parent_id = create_tree.new_directory('parent', root, b'parent-id')
622
create_tree.new_file('child', parent_id, [b'child'], b'child-id')
206
623
create_tree.apply()
207
624
unversion = TreeTransform(self.wt)
208
625
self.addCleanup(unversion.finalize)
209
626
parent = unversion.trans_id_tree_path('parent')
210
627
unversion.unversion_file(parent)
211
self.assertEqual(unversion.find_conflicts(),
628
self.assertEqual(unversion.find_conflicts(),
212
629
[('unversioned parent', parent_id)])
213
file_id = unversion.trans_id_tree_file_id('child-id')
630
file_id = unversion.trans_id_tree_path('parent/child')
214
631
unversion.unversion_file(file_id)
215
632
unversion.apply()
217
634
def test_name_invariants(self):
218
635
create_tree, root = self.get_transform()
220
root = create_tree.trans_id_tree_file_id('TREE_ROOT')
221
create_tree.new_file('name1', root, 'hello1', 'name1')
222
create_tree.new_file('name2', root, 'hello2', 'name2')
223
ddir = create_tree.new_directory('dying_directory', root, 'ddir')
224
create_tree.new_file('dying_file', ddir, 'goodbye1', 'dfile')
225
create_tree.new_file('moving_file', ddir, 'later1', 'mfile')
226
create_tree.new_file('moving_file2', root, 'later2', 'mfile2')
637
root = create_tree.root
638
create_tree.new_file('name1', root, [b'hello1'], b'name1')
639
create_tree.new_file('name2', root, [b'hello2'], b'name2')
640
ddir = create_tree.new_directory('dying_directory', root, b'ddir')
641
create_tree.new_file('dying_file', ddir, [b'goodbye1'], b'dfile')
642
create_tree.new_file('moving_file', ddir, [b'later1'], b'mfile')
643
create_tree.new_file('moving_file2', root, [b'later2'], b'mfile2')
227
644
create_tree.apply()
229
mangle_tree,root = self.get_transform()
230
root = mangle_tree.trans_id_tree_file_id('TREE_ROOT')
232
name1 = mangle_tree.trans_id_tree_file_id('name1')
233
name2 = mangle_tree.trans_id_tree_file_id('name2')
646
mangle_tree, root = self.get_transform()
647
root = mangle_tree.root
649
name1 = mangle_tree.trans_id_tree_path('name1')
650
name2 = mangle_tree.trans_id_tree_path('name2')
234
651
mangle_tree.adjust_path('name2', root, name1)
235
652
mangle_tree.adjust_path('name1', root, name2)
237
#tests for deleting parent directories
238
ddir = mangle_tree.trans_id_tree_file_id('ddir')
654
# tests for deleting parent directories
655
ddir = mangle_tree.trans_id_tree_path('dying_directory')
239
656
mangle_tree.delete_contents(ddir)
240
dfile = mangle_tree.trans_id_tree_file_id('dfile')
657
dfile = mangle_tree.trans_id_tree_path('dying_directory/dying_file')
241
658
mangle_tree.delete_versioned(dfile)
242
659
mangle_tree.unversion_file(dfile)
243
mfile = mangle_tree.trans_id_tree_file_id('mfile')
660
mfile = mangle_tree.trans_id_tree_path('dying_directory/moving_file')
244
661
mangle_tree.adjust_path('mfile', root, mfile)
246
#tests for adding parent directories
247
newdir = mangle_tree.new_directory('new_directory', root, 'newdir')
248
mfile2 = mangle_tree.trans_id_tree_file_id('mfile2')
663
# tests for adding parent directories
664
newdir = mangle_tree.new_directory('new_directory', root, b'newdir')
665
mfile2 = mangle_tree.trans_id_tree_path('moving_file2')
249
666
mangle_tree.adjust_path('mfile2', newdir, mfile2)
250
mangle_tree.new_file('newfile', newdir, 'hello3', 'dfile')
251
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
667
mangle_tree.new_file('newfile', newdir, [b'hello3'], b'dfile')
668
self.assertEqual(mangle_tree.final_file_id(mfile2), b'mfile2')
252
669
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
253
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
670
self.assertEqual(mangle_tree.final_file_id(mfile2), b'mfile2')
254
671
mangle_tree.apply()
255
self.assertEqual(file(self.wt.abspath('name1')).read(), 'hello2')
256
self.assertEqual(file(self.wt.abspath('name2')).read(), 'hello1')
257
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
672
with open(self.wt.abspath('name1'), 'r') as f:
673
self.assertEqual(f.read(), 'hello2')
674
with open(self.wt.abspath('name2'), 'r') as f:
675
self.assertEqual(f.read(), 'hello1')
676
mfile2_path = self.wt.abspath(pathjoin('new_directory', 'mfile2'))
258
677
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
259
self.assertEqual(file(mfile2_path).read(), 'later2')
260
self.assertEqual(self.wt.id2path('mfile2'), 'new_directory/mfile2')
261
self.assertEqual(self.wt.path2id('new_directory/mfile2'), 'mfile2')
262
newfile_path = self.wt.abspath(pathjoin('new_directory','newfile'))
263
self.assertEqual(file(newfile_path).read(), 'hello3')
264
self.assertEqual(self.wt.path2id('dying_directory'), 'ddir')
678
with open(mfile2_path, 'r') as f:
679
self.assertEqual(f.read(), 'later2')
680
self.assertEqual(self.wt.id2path(b'mfile2'), 'new_directory/mfile2')
681
self.assertEqual(self.wt.path2id('new_directory/mfile2'), b'mfile2')
682
newfile_path = self.wt.abspath(pathjoin('new_directory', 'newfile'))
683
with open(newfile_path, 'r') as f:
684
self.assertEqual(f.read(), 'hello3')
685
self.assertEqual(self.wt.path2id('dying_directory'), b'ddir')
265
686
self.assertIs(self.wt.path2id('dying_directory/dying_file'), None)
266
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
687
mfile2_path = self.wt.abspath(pathjoin('new_directory', 'mfile2'))
268
689
def test_both_rename(self):
269
create_tree,root = self.get_transform()
270
newdir = create_tree.new_directory('selftest', root, 'selftest-id')
271
create_tree.new_file('blackbox.py', newdir, 'hello1', 'blackbox-id')
273
mangle_tree,root = self.get_transform()
274
selftest = mangle_tree.trans_id_tree_file_id('selftest-id')
275
blackbox = mangle_tree.trans_id_tree_file_id('blackbox-id')
690
create_tree, root = self.get_transform()
691
newdir = create_tree.new_directory('selftest', root, b'selftest-id')
692
create_tree.new_file('blackbox.py', newdir, [
693
b'hello1'], b'blackbox-id')
695
mangle_tree, root = self.get_transform()
696
selftest = mangle_tree.trans_id_tree_path('selftest')
697
blackbox = mangle_tree.trans_id_tree_path('selftest/blackbox.py')
276
698
mangle_tree.adjust_path('test', root, selftest)
277
699
mangle_tree.adjust_path('test_too_much', root, selftest)
278
700
mangle_tree.set_executability(True, blackbox)
279
701
mangle_tree.apply()
281
703
def test_both_rename2(self):
282
create_tree,root = self.get_transform()
283
bzrlib = create_tree.new_directory('bzrlib', root, 'bzrlib-id')
284
tests = create_tree.new_directory('tests', bzrlib, 'tests-id')
285
blackbox = create_tree.new_directory('blackbox', tests, 'blackbox-id')
286
create_tree.new_file('test_too_much.py', blackbox, 'hello1',
289
mangle_tree,root = self.get_transform()
290
bzrlib = mangle_tree.trans_id_tree_file_id('bzrlib-id')
291
tests = mangle_tree.trans_id_tree_file_id('tests-id')
292
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
293
mangle_tree.adjust_path('selftest', bzrlib, tests)
294
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
704
create_tree, root = self.get_transform()
705
breezy = create_tree.new_directory('breezy', root, b'breezy-id')
706
tests = create_tree.new_directory('tests', breezy, b'tests-id')
707
blackbox = create_tree.new_directory('blackbox', tests, b'blackbox-id')
708
create_tree.new_file('test_too_much.py', blackbox, [b'hello1'],
711
mangle_tree, root = self.get_transform()
712
breezy = mangle_tree.trans_id_tree_path('breezy')
713
tests = mangle_tree.trans_id_tree_path('breezy/tests')
714
test_too_much = mangle_tree.trans_id_tree_path(
715
'breezy/tests/blackbox/test_too_much.py')
716
mangle_tree.adjust_path('selftest', breezy, tests)
717
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
295
718
mangle_tree.set_executability(True, test_too_much)
296
719
mangle_tree.apply()
298
721
def test_both_rename3(self):
299
create_tree,root = self.get_transform()
300
tests = create_tree.new_directory('tests', root, 'tests-id')
301
create_tree.new_file('test_too_much.py', tests, 'hello1',
304
mangle_tree,root = self.get_transform()
305
tests = mangle_tree.trans_id_tree_file_id('tests-id')
306
test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
722
create_tree, root = self.get_transform()
723
tests = create_tree.new_directory('tests', root, b'tests-id')
724
create_tree.new_file('test_too_much.py', tests, [b'hello1'],
727
mangle_tree, root = self.get_transform()
728
tests = mangle_tree.trans_id_tree_path('tests')
729
test_too_much = mangle_tree.trans_id_tree_path(
730
'tests/test_too_much.py')
307
731
mangle_tree.adjust_path('selftest', root, tests)
308
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
732
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
309
733
mangle_tree.set_executability(True, test_too_much)
310
734
mangle_tree.apply()
312
736
def test_move_dangling_ie(self):
313
737
create_tree, root = self.get_transform()
315
root = create_tree.trans_id_tree_file_id('TREE_ROOT')
316
create_tree.new_file('name1', root, 'hello1', 'name1')
739
root = create_tree.root
740
create_tree.new_file('name1', root, [b'hello1'], b'name1')
317
741
create_tree.apply()
318
742
delete_contents, root = self.get_transform()
319
file = delete_contents.trans_id_tree_file_id('name1')
743
file = delete_contents.trans_id_tree_path('name1')
320
744
delete_contents.delete_contents(file)
321
745
delete_contents.apply()
322
746
move_id, root = self.get_transform()
323
name1 = move_id.trans_id_tree_file_id('name1')
324
newdir = move_id.new_directory('dir', root, 'newdir')
747
name1 = move_id.trans_id_tree_path('name1')
748
newdir = move_id.new_directory('dir', root, b'newdir')
325
749
move_id.adjust_path('name2', newdir, name1)
328
752
def test_replace_dangling_ie(self):
329
753
create_tree, root = self.get_transform()
331
root = create_tree.trans_id_tree_file_id('TREE_ROOT')
332
create_tree.new_file('name1', root, 'hello1', 'name1')
755
root = create_tree.root
756
create_tree.new_file('name1', root, [b'hello1'], b'name1')
333
757
create_tree.apply()
334
758
delete_contents = TreeTransform(self.wt)
335
759
self.addCleanup(delete_contents.finalize)
336
file = delete_contents.trans_id_tree_file_id('name1')
760
file = delete_contents.trans_id_tree_path('name1')
337
761
delete_contents.delete_contents(file)
338
762
delete_contents.apply()
339
763
delete_contents.finalize()
340
764
replace = TreeTransform(self.wt)
341
765
self.addCleanup(replace.finalize)
342
name2 = replace.new_file('name2', root, 'hello2', 'name1')
766
name2 = replace.new_file('name2', root, [b'hello2'], b'name1')
343
767
conflicts = replace.find_conflicts()
344
name1 = replace.trans_id_tree_file_id('name1')
768
name1 = replace.trans_id_tree_path('name1')
345
769
self.assertEqual(conflicts, [('duplicate id', name1, name2)])
346
770
resolve_conflicts(replace)
349
def test_symlinks(self):
350
if not has_symlinks():
351
raise TestSkipped('Symlinks are not supported on this platform')
352
transform,root = self.get_transform()
353
oz_id = transform.new_directory('oz', root, 'oz-id')
354
wizard = transform.new_symlink('wizard', oz_id, 'wizard-target',
356
wiz_id = transform.create_path('wizard2', oz_id)
357
transform.create_symlink('behind_curtain', wiz_id)
358
transform.version_file('wiz-id2', wiz_id)
773
def _test_symlinks(self, link_name1, link_target1,
774
link_name2, link_target2):
779
self.requireFeature(SymlinkFeature)
780
transform, root = self.get_transform()
781
oz_id = transform.new_directory('oz', root, b'oz-id')
782
transform.new_symlink(link_name1, oz_id, link_target1, b'wizard-id')
783
wiz_id = transform.create_path(link_name2, oz_id)
784
transform.create_symlink(link_target2, wiz_id)
785
transform.version_file(b'wiz-id2', wiz_id)
359
786
transform.set_executability(True, wiz_id)
360
self.assertEqual(transform.find_conflicts(),
787
self.assertEqual(transform.find_conflicts(),
361
788
[('non-file executability', wiz_id)])
362
789
transform.set_executability(None, wiz_id)
363
790
transform.apply()
364
self.assertEqual(self.wt.path2id('oz/wizard'), 'wizard-id')
365
self.assertEqual(file_kind(self.wt.abspath('oz/wizard')), 'symlink')
366
self.assertEqual(os.readlink(self.wt.abspath('oz/wizard2')),
368
self.assertEqual(os.readlink(self.wt.abspath('oz/wizard')),
791
self.assertEqual(self.wt.path2id(ozpath(link_name1)), b'wizard-id')
792
self.assertEqual('symlink',
793
file_kind(self.wt.abspath(ozpath(link_name1))))
794
self.assertEqual(link_target2,
795
osutils.readlink(self.wt.abspath(ozpath(link_name2))))
796
self.assertEqual(link_target1,
797
osutils.readlink(self.wt.abspath(ozpath(link_name1))))
799
def test_symlinks(self):
800
self._test_symlinks('wizard', 'wizard-target',
801
'wizard2', 'behind_curtain')
803
def test_symlinks_unicode(self):
804
self.requireFeature(features.UnicodeFilenameFeature)
805
self._test_symlinks(u'\N{Euro Sign}wizard',
806
u'wizard-targ\N{Euro Sign}t',
807
u'\N{Euro Sign}wizard2',
808
u'b\N{Euro Sign}hind_curtain')
810
def test_unsupported_symlink_no_conflict(self):
812
wt = self.make_branch_and_tree('.')
813
tt = TreeTransform(wt)
814
self.addCleanup(tt.finalize)
815
tt.new_symlink('foo', tt.root, 'bar')
816
result = tt.find_conflicts()
817
self.assertEqual([], result)
818
os_symlink = getattr(os, 'symlink', None)
824
os.symlink = os_symlink
372
826
def get_conflicted(self):
373
create,root = self.get_transform()
374
create.new_file('dorothy', root, 'dorothy', 'dorothy-id')
375
oz = create.new_directory('oz', root, 'oz-id')
376
create.new_directory('emeraldcity', oz, 'emerald-id')
827
create, root = self.get_transform()
828
create.new_file('dorothy', root, [b'dorothy'], b'dorothy-id')
829
oz = create.new_directory('oz', root, b'oz-id')
830
create.new_directory('emeraldcity', oz, b'emerald-id')
378
conflicts,root = self.get_transform()
832
conflicts, root = self.get_transform()
379
833
# set up duplicate entry, duplicate id
380
new_dorothy = conflicts.new_file('dorothy', root, 'dorothy',
382
old_dorothy = conflicts.trans_id_tree_file_id('dorothy-id')
383
oz = conflicts.trans_id_tree_file_id('oz-id')
384
# set up missing, unversioned parent
834
new_dorothy = conflicts.new_file('dorothy', root, [b'dorothy'],
836
old_dorothy = conflicts.trans_id_tree_path('dorothy')
837
oz = conflicts.trans_id_tree_path('oz')
838
# set up DeletedParent parent conflict
385
839
conflicts.delete_versioned(oz)
386
emerald = conflicts.trans_id_tree_file_id('emerald-id')
840
emerald = conflicts.trans_id_tree_path('oz/emeraldcity')
841
# set up MissingParent conflict
842
munchkincity = conflicts.trans_id_file_id(b'munchkincity-id')
843
conflicts.adjust_path('munchkincity', root, munchkincity)
844
conflicts.new_directory('auntem', munchkincity, b'auntem-id')
387
845
# set up parent loop
388
846
conflicts.adjust_path('emeraldcity', emerald, emerald)
389
847
return conflicts, emerald, oz, old_dorothy, new_dorothy
434
900
self.assertEqual(conflicts_s[1], 'Conflict adding id to dorothy. '
435
901
'Unversioned existing file '
436
902
'dorothy.moved.')
437
self.assertEqual(conflicts_s[2], 'Conflict adding files to oz. '
439
self.assertEqual(conflicts_s[3], 'Conflict adding versioned files to '
440
'oz. Versioned directory.')
441
self.assertEqual(conflicts_s[4], 'Conflict moving oz/emeraldcity into'
442
' oz/emeraldcity. Cancelled move.')
903
self.assertEqual(conflicts_s[2], 'Conflict adding files to'
904
' munchkincity. Created directory.')
905
self.assertEqual(conflicts_s[3], 'Conflict because munchkincity is not'
906
' versioned, but has versioned'
907
' children. Versioned directory.')
908
self.assertEqualDiff(
909
conflicts_s[4], "Conflict: can't delete oz because it"
910
" is not empty. Not deleting.")
911
self.assertEqual(conflicts_s[5], 'Conflict because oz is not'
912
' versioned, but has versioned'
913
' children. Versioned directory.')
914
self.assertEqual(conflicts_s[6], 'Conflict moving oz/emeraldcity into'
915
' oz/emeraldcity. Cancelled move.')
917
def prepare_wrong_parent_kind(self):
918
tt, root = self.get_transform()
919
tt.new_file('parent', root, [b'contents'], b'parent-id')
921
tt, root = self.get_transform()
922
parent_id = tt.trans_id_file_id(b'parent-id')
923
tt.new_file('child,', parent_id, [b'contents2'], b'file-id')
926
def test_find_conflicts_wrong_parent_kind(self):
927
tt = self.prepare_wrong_parent_kind()
930
def test_resolve_conflicts_wrong_existing_parent_kind(self):
931
tt = self.prepare_wrong_parent_kind()
932
raw_conflicts = resolve_conflicts(tt)
933
self.assertEqual({('non-directory parent', 'Created directory',
934
'new-3')}, raw_conflicts)
935
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
936
self.assertEqual([NonDirectoryParent('Created directory', 'parent.new',
937
b'parent-id')], cooked_conflicts)
939
self.assertFalse(self.wt.is_versioned('parent'))
940
self.assertEqual(b'parent-id', self.wt.path2id('parent.new'))
942
def test_resolve_conflicts_wrong_new_parent_kind(self):
943
tt, root = self.get_transform()
944
parent_id = tt.new_directory('parent', root, b'parent-id')
945
tt.new_file('child,', parent_id, [b'contents2'], b'file-id')
947
tt, root = self.get_transform()
948
parent_id = tt.trans_id_file_id(b'parent-id')
949
tt.delete_contents(parent_id)
950
tt.create_file([b'contents'], parent_id)
951
raw_conflicts = resolve_conflicts(tt)
952
self.assertEqual({('non-directory parent', 'Created directory',
953
'new-3')}, raw_conflicts)
955
self.assertFalse(self.wt.is_versioned('parent'))
956
self.assertEqual(b'parent-id', self.wt.path2id('parent.new'))
958
def test_resolve_conflicts_wrong_parent_kind_unversioned(self):
959
tt, root = self.get_transform()
960
parent_id = tt.new_directory('parent', root)
961
tt.new_file('child,', parent_id, [b'contents2'])
963
tt, root = self.get_transform()
964
parent_id = tt.trans_id_tree_path('parent')
965
tt.delete_contents(parent_id)
966
tt.create_file([b'contents'], parent_id)
967
resolve_conflicts(tt)
969
self.assertFalse(self.wt.is_versioned('parent'))
970
self.assertFalse(self.wt.is_versioned('parent.new'))
972
def test_resolve_conflicts_missing_parent(self):
973
wt = self.make_branch_and_tree('.')
974
tt = TreeTransform(wt)
975
self.addCleanup(tt.finalize)
976
parent = tt.trans_id_file_id(b'parent-id')
977
tt.new_file('file', parent, [b'Contents'])
978
raw_conflicts = resolve_conflicts(tt)
979
# Since the directory doesn't exist it's seen as 'missing'. So
980
# 'resolve_conflicts' create a conflict asking for it to be created.
981
self.assertLength(1, raw_conflicts)
982
self.assertEqual(('missing parent', 'Created directory', 'new-1'),
984
# apply fail since the missing directory doesn't exist
985
self.assertRaises(errors.NoFinalPath, tt.apply)
444
987
def test_moving_versioned_directories(self):
445
988
create, root = self.get_transform()
446
kansas = create.new_directory('kansas', root, 'kansas-id')
447
create.new_directory('house', kansas, 'house-id')
448
create.new_directory('oz', root, 'oz-id')
989
kansas = create.new_directory('kansas', root, b'kansas-id')
990
create.new_directory('house', kansas, b'house-id')
991
create.new_directory('oz', root, b'oz-id')
450
993
cyclone, root = self.get_transform()
451
oz = cyclone.trans_id_tree_file_id('oz-id')
452
house = cyclone.trans_id_tree_file_id('house-id')
994
oz = cyclone.trans_id_tree_path('oz')
995
house = cyclone.trans_id_tree_path('house')
453
996
cyclone.adjust_path('house', oz, house)
456
999
def test_moving_root(self):
457
1000
create, root = self.get_transform()
458
fun = create.new_directory('fun', root, 'fun-id')
459
create.new_directory('sun', root, 'sun-id')
460
create.new_directory('moon', root, 'moon')
1001
fun = create.new_directory('fun', root, b'fun-id')
1002
create.new_directory('sun', root, b'sun-id')
1003
create.new_directory('moon', root, b'moon')
462
1005
transform, root = self.get_transform()
463
1006
transform.adjust_root_path('oldroot', fun)
464
new_root=transform.trans_id_tree_path('')
465
transform.version_file('new-root', new_root)
1007
new_root = transform.trans_id_tree_path('')
1008
transform.version_file(b'new-root', new_root)
466
1009
transform.apply()
468
1011
def test_renames(self):
469
1012
create, root = self.get_transform()
470
old = create.new_directory('old-parent', root, 'old-id')
471
intermediate = create.new_directory('intermediate', old, 'im-id')
472
myfile = create.new_file('myfile', intermediate, 'myfile-text',
1013
old = create.new_directory('old-parent', root, b'old-id')
1014
intermediate = create.new_directory('intermediate', old, b'im-id')
1015
myfile = create.new_file('myfile', intermediate, [b'myfile-text'],
475
1018
rename, root = self.get_transform()
476
old = rename.trans_id_file_id('old-id')
1019
old = rename.trans_id_file_id(b'old-id')
477
1020
rename.adjust_path('new', root, old)
478
myfile = rename.trans_id_file_id('myfile-id')
1021
myfile = rename.trans_id_file_id(b'myfile-id')
479
1022
rename.set_executability(True, myfile)
482
def test_find_interesting(self):
483
create, root = self.get_transform()
485
create.new_file('vfile', root, 'myfile-text', 'myfile-id')
486
create.new_file('uvfile', root, 'othertext')
1025
def test_rename_fails(self):
1026
self.requireFeature(features.not_running_as_root)
1027
# see https://bugs.launchpad.net/bzr/+bug/491763
1028
create, root_id = self.get_transform()
1029
create.new_directory('first-dir', root_id, b'first-id')
1030
create.new_file('myfile', root_id, [b'myfile-text'], b'myfile-id')
488
self.assertEqual(find_interesting(wt, wt, ['vfile']),
490
self.assertRaises(NotVersionedError, find_interesting, wt, wt,
1032
if os.name == "posix" and sys.platform != "cygwin":
1033
# posix filesystems fail on renaming if the readonly bit is set
1034
osutils.make_readonly(self.wt.abspath('first-dir'))
1035
elif os.name == "nt":
1036
# windows filesystems fail on renaming open files
1037
self.addCleanup(open(self.wt.abspath('myfile')).close)
1039
self.skipTest("Can't force a permissions error on rename")
1040
# now transform to rename
1041
rename_transform, root_id = self.get_transform()
1042
file_trans_id = rename_transform.trans_id_file_id(b'myfile-id')
1043
dir_id = rename_transform.trans_id_file_id(b'first-id')
1044
rename_transform.adjust_path('newname', dir_id, file_trans_id)
1045
e = self.assertRaises(errors.TransformRenameFailed,
1046
rename_transform.apply)
1047
# On nix looks like:
1048
# "Failed to rename .../work/.bzr/checkout/limbo/new-1
1049
# to .../first-dir/newname: [Errno 13] Permission denied"
1050
# On windows looks like:
1051
# "Failed to rename .../work/myfile to
1052
# .../work/.bzr/checkout/limbo/new-1: [Errno 13] Permission denied"
1053
# This test isn't concerned with exactly what the error looks like,
1054
# and the strerror will vary across OS and locales, but the assert
1055
# that the exeception attributes are what we expect
1056
self.assertEqual(e.errno, errno.EACCES)
1057
if os.name == "posix":
1058
self.assertEndsWith(e.to_path, "/first-dir/newname")
1060
self.assertEqual(os.path.basename(e.from_path), "myfile")
1062
def test_set_executability_order(self):
1063
"""Ensure that executability behaves the same, no matter what order.
1065
- create file and set executability simultaneously
1066
- create file and set executability afterward
1067
- unsetting the executability of a file whose executability has not
1069
declared should throw an exception (this may happen when a
1070
merge attempts to create a file with a duplicate ID)
1072
transform, root = self.get_transform()
1073
wt = transform._tree
1075
self.addCleanup(wt.unlock)
1076
transform.new_file('set_on_creation', root, [b'Set on creation'],
1078
sac = transform.new_file('set_after_creation', root,
1079
[b'Set after creation'], b'sac')
1080
transform.set_executability(True, sac)
1081
uws = transform.new_file('unset_without_set', root, [b'Unset badly'],
1083
self.assertRaises(KeyError, transform.set_executability, None, uws)
1085
self.assertTrue(wt.is_executable('set_on_creation'))
1086
self.assertTrue(wt.is_executable('set_after_creation'))
1088
def test_preserve_mode(self):
1089
"""File mode is preserved when replacing content"""
1090
if sys.platform == 'win32':
1091
raise TestSkipped('chmod has no effect on win32')
1092
transform, root = self.get_transform()
1093
transform.new_file('file1', root, [b'contents'], b'file1-id', True)
1095
self.wt.lock_write()
1096
self.addCleanup(self.wt.unlock)
1097
self.assertTrue(self.wt.is_executable('file1'))
1098
transform, root = self.get_transform()
1099
file1_id = transform.trans_id_tree_path('file1')
1100
transform.delete_contents(file1_id)
1101
transform.create_file([b'contents2'], file1_id)
1103
self.assertTrue(self.wt.is_executable('file1'))
1105
def test__set_mode_stats_correctly(self):
1106
"""_set_mode stats to determine file mode."""
1107
if sys.platform == 'win32':
1108
raise TestSkipped('chmod has no effect on win32')
1113
def instrumented_stat(path):
1114
stat_paths.append(path)
1115
return real_stat(path)
1117
transform, root = self.get_transform()
1119
bar1_id = transform.new_file('bar', root, [b'bar contents 1\n'],
1120
file_id=b'bar-id-1', executable=False)
1123
transform, root = self.get_transform()
1124
bar1_id = transform.trans_id_tree_path('bar')
1125
bar2_id = transform.trans_id_tree_path('bar2')
1127
os.stat = instrumented_stat
1128
transform.create_file([b'bar2 contents\n'],
1129
bar2_id, mode_id=bar1_id)
1132
transform.finalize()
1134
bar1_abspath = self.wt.abspath('bar')
1135
self.assertEqual([bar1_abspath], stat_paths)
1137
def test_iter_changes(self):
1138
self.wt.set_root_id(b'eert_toor')
1139
transform, root = self.get_transform()
1140
transform.new_file('old', root, [b'blah'], b'id-1', True)
1142
transform, root = self.get_transform()
1144
self.assertEqual([], list(transform.iter_changes()))
1145
old = transform.trans_id_tree_path('old')
1146
transform.unversion_file(old)
1147
self.assertEqual([(b'id-1', ('old', None), False, (True, False),
1148
(b'eert_toor', b'eert_toor'),
1149
('old', 'old'), ('file', 'file'),
1150
(True, True))], list(transform.iter_changes()))
1151
transform.new_directory('new', root, b'id-1')
1152
self.assertEqual([(b'id-1', ('old', 'new'), True, (True, True),
1153
(b'eert_toor', b'eert_toor'), ('old', 'new'),
1154
('file', 'directory'),
1155
(True, False))], list(transform.iter_changes()))
1157
transform.finalize()
1159
def test_iter_changes_new(self):
1160
self.wt.set_root_id(b'eert_toor')
1161
transform, root = self.get_transform()
1162
transform.new_file('old', root, [b'blah'])
1164
transform, root = self.get_transform()
1166
old = transform.trans_id_tree_path('old')
1167
transform.version_file(b'id-1', old)
1168
self.assertEqual([(b'id-1', (None, 'old'), False, (False, True),
1169
(b'eert_toor', b'eert_toor'),
1170
('old', 'old'), ('file', 'file'),
1172
list(transform.iter_changes()))
1174
transform.finalize()
1176
def test_iter_changes_modifications(self):
1177
self.wt.set_root_id(b'eert_toor')
1178
transform, root = self.get_transform()
1179
transform.new_file('old', root, [b'blah'], b'id-1')
1180
transform.new_file('new', root, [b'blah'])
1181
transform.new_directory('subdir', root, b'subdir-id')
1183
transform, root = self.get_transform()
1185
old = transform.trans_id_tree_path('old')
1186
subdir = transform.trans_id_tree_path('subdir')
1187
new = transform.trans_id_tree_path('new')
1188
self.assertEqual([], list(transform.iter_changes()))
1191
transform.delete_contents(old)
1192
self.assertEqual([(b'id-1', ('old', 'old'), True, (True, True),
1193
(b'eert_toor', b'eert_toor'),
1194
('old', 'old'), ('file', None),
1196
list(transform.iter_changes()))
1199
transform.create_file([b'blah'], old)
1200
self.assertEqual([(b'id-1', ('old', 'old'), True, (True, True),
1201
(b'eert_toor', b'eert_toor'),
1202
('old', 'old'), ('file', 'file'),
1204
list(transform.iter_changes()))
1205
transform.cancel_deletion(old)
1206
self.assertEqual([(b'id-1', ('old', 'old'), True, (True, True),
1207
(b'eert_toor', b'eert_toor'),
1208
('old', 'old'), ('file', 'file'),
1210
list(transform.iter_changes()))
1211
transform.cancel_creation(old)
1213
# move file_id to a different file
1214
self.assertEqual([], list(transform.iter_changes()))
1215
transform.unversion_file(old)
1216
transform.version_file(b'id-1', new)
1217
transform.adjust_path('old', root, new)
1218
self.assertEqual([(b'id-1', ('old', 'old'), True, (True, True),
1219
(b'eert_toor', b'eert_toor'),
1220
('old', 'old'), ('file', 'file'),
1222
list(transform.iter_changes()))
1223
transform.cancel_versioning(new)
1224
transform._removed_id = set()
1227
self.assertEqual([], list(transform.iter_changes()))
1228
transform.set_executability(True, old)
1229
self.assertEqual([(b'id-1', ('old', 'old'), False, (True, True),
1230
(b'eert_toor', b'eert_toor'),
1231
('old', 'old'), ('file', 'file'),
1233
list(transform.iter_changes()))
1234
transform.set_executability(None, old)
1237
self.assertEqual([], list(transform.iter_changes()))
1238
transform.adjust_path('new', root, old)
1239
transform._new_parent = {}
1240
self.assertEqual([(b'id-1', ('old', 'new'), False, (True, True),
1241
(b'eert_toor', b'eert_toor'),
1242
('old', 'new'), ('file', 'file'),
1244
list(transform.iter_changes()))
1245
transform._new_name = {}
1248
self.assertEqual([], list(transform.iter_changes()))
1249
transform.adjust_path('new', subdir, old)
1250
transform._new_name = {}
1251
self.assertEqual([(b'id-1', ('old', 'subdir/old'), False,
1252
(True, True), (b'eert_toor',
1253
b'subdir-id'), ('old', 'old'),
1254
('file', 'file'), (False, False))],
1255
list(transform.iter_changes()))
1256
transform._new_path = {}
1259
transform.finalize()
1261
def test_iter_changes_modified_bleed(self):
1262
self.wt.set_root_id(b'eert_toor')
1263
"""Modified flag should not bleed from one change to another"""
1264
# unfortunately, we have no guarantee that file1 (which is modified)
1265
# will be applied before file2. And if it's applied after file2, it
1266
# obviously can't bleed into file2's change output. But for now, it
1268
transform, root = self.get_transform()
1269
transform.new_file('file1', root, [b'blah'], b'id-1')
1270
transform.new_file('file2', root, [b'blah'], b'id-2')
1272
transform, root = self.get_transform()
1274
transform.delete_contents(transform.trans_id_file_id(b'id-1'))
1275
transform.set_executability(True,
1276
transform.trans_id_file_id(b'id-2'))
1278
[(b'id-1', (u'file1', u'file1'), True, (True, True),
1279
(b'eert_toor', b'eert_toor'), ('file1', u'file1'),
1280
('file', None), (False, False)),
1281
(b'id-2', (u'file2', u'file2'), False, (True, True),
1282
(b'eert_toor', b'eert_toor'), ('file2', u'file2'),
1283
('file', 'file'), (False, True))],
1284
list(transform.iter_changes()))
1286
transform.finalize()
1288
def test_iter_changes_move_missing(self):
1289
"""Test moving ids with no files around"""
1290
self.wt.set_root_id(b'toor_eert')
1291
# Need two steps because versioning a non-existant file is a conflict.
1292
transform, root = self.get_transform()
1293
transform.new_directory('floater', root, b'floater-id')
1295
transform, root = self.get_transform()
1296
transform.delete_contents(transform.trans_id_tree_path('floater'))
1298
transform, root = self.get_transform()
1299
floater = transform.trans_id_tree_path('floater')
1301
transform.adjust_path('flitter', root, floater)
1302
self.assertEqual([(b'floater-id', ('floater', 'flitter'), False,
1304
(b'toor_eert', b'toor_eert'),
1305
('floater', 'flitter'),
1306
(None, None), (False, False))],
1307
list(transform.iter_changes()))
1309
transform.finalize()
1311
def test_iter_changes_pointless(self):
1312
"""Ensure that no-ops are not treated as modifications"""
1313
self.wt.set_root_id(b'eert_toor')
1314
transform, root = self.get_transform()
1315
transform.new_file('old', root, [b'blah'], b'id-1')
1316
transform.new_directory('subdir', root, b'subdir-id')
1318
transform, root = self.get_transform()
1320
old = transform.trans_id_tree_path('old')
1321
subdir = transform.trans_id_tree_path('subdir')
1322
self.assertEqual([], list(transform.iter_changes()))
1323
transform.delete_contents(subdir)
1324
transform.create_directory(subdir)
1325
transform.set_executability(False, old)
1326
transform.unversion_file(old)
1327
transform.version_file(b'id-1', old)
1328
transform.adjust_path('old', root, old)
1329
self.assertEqual([], list(transform.iter_changes()))
1331
transform.finalize()
1333
def test_rename_count(self):
1334
transform, root = self.get_transform()
1335
transform.new_file('name1', root, [b'contents'])
1336
self.assertEqual(transform.rename_count, 0)
1338
self.assertEqual(transform.rename_count, 1)
1339
transform2, root = self.get_transform()
1340
transform2.adjust_path('name2', root,
1341
transform2.trans_id_tree_path('name1'))
1342
self.assertEqual(transform2.rename_count, 0)
1344
self.assertEqual(transform2.rename_count, 2)
1346
def test_change_parent(self):
1347
"""Ensure that after we change a parent, the results are still right.
1349
Renames and parent changes on pending transforms can happen as part
1350
of conflict resolution, and are explicitly permitted by the
1353
This test ensures they work correctly with the rename-avoidance
1356
transform, root = self.get_transform()
1357
parent1 = transform.new_directory('parent1', root)
1358
child1 = transform.new_file('child1', parent1, [b'contents'])
1359
parent2 = transform.new_directory('parent2', root)
1360
transform.adjust_path('child1', parent2, child1)
1362
self.assertPathDoesNotExist(self.wt.abspath('parent1/child1'))
1363
self.assertPathExists(self.wt.abspath('parent2/child1'))
1364
# rename limbo/new-1 => parent1, rename limbo/new-3 => parent2
1365
# no rename for child1 (counting only renames during apply)
1366
self.assertEqual(2, transform.rename_count)
1368
def test_cancel_parent(self):
1369
"""Cancelling a parent doesn't cause deletion of a non-empty directory
1371
This is like the test_change_parent, except that we cancel the parent
1372
before adjusting the path. The transform must detect that the
1373
directory is non-empty, and move children to safe locations.
1375
transform, root = self.get_transform()
1376
parent1 = transform.new_directory('parent1', root)
1377
child1 = transform.new_file('child1', parent1, [b'contents'])
1378
child2 = transform.new_file('child2', parent1, [b'contents'])
1380
transform.cancel_creation(parent1)
1382
self.fail('Failed to move child1 before deleting parent1')
1383
transform.cancel_creation(child2)
1384
transform.create_directory(parent1)
1386
transform.cancel_creation(parent1)
1387
# If the transform incorrectly believes that child2 is still in
1388
# parent1's limbo directory, it will try to rename it and fail
1389
# because was already moved by the first cancel_creation.
1391
self.fail('Transform still thinks child2 is a child of parent1')
1392
parent2 = transform.new_directory('parent2', root)
1393
transform.adjust_path('child1', parent2, child1)
1395
self.assertPathDoesNotExist(self.wt.abspath('parent1'))
1396
self.assertPathExists(self.wt.abspath('parent2/child1'))
1397
# rename limbo/new-3 => parent2, rename limbo/new-2 => child1
1398
self.assertEqual(2, transform.rename_count)
1400
def test_adjust_and_cancel(self):
1401
"""Make sure adjust_path keeps track of limbo children properly"""
1402
transform, root = self.get_transform()
1403
parent1 = transform.new_directory('parent1', root)
1404
child1 = transform.new_file('child1', parent1, [b'contents'])
1405
parent2 = transform.new_directory('parent2', root)
1406
transform.adjust_path('child1', parent2, child1)
1407
transform.cancel_creation(child1)
1409
transform.cancel_creation(parent1)
1410
# if the transform thinks child1 is still in parent1's limbo
1411
# directory, it will attempt to move it and fail.
1413
self.fail('Transform still thinks child1 is a child of parent1')
1414
transform.finalize()
1416
def test_noname_contents(self):
1417
"""TreeTransform should permit deferring naming files."""
1418
transform, root = self.get_transform()
1419
parent = transform.trans_id_file_id(b'parent-id')
1421
transform.create_directory(parent)
1423
self.fail("Can't handle contents with no name")
1424
transform.finalize()
1426
def test_noname_contents_nested(self):
1427
"""TreeTransform should permit deferring naming files."""
1428
transform, root = self.get_transform()
1429
parent = transform.trans_id_file_id(b'parent-id')
1431
transform.create_directory(parent)
1433
self.fail("Can't handle contents with no name")
1434
transform.new_directory('child', parent)
1435
transform.adjust_path('parent', root, parent)
1437
self.assertPathExists(self.wt.abspath('parent/child'))
1438
self.assertEqual(1, transform.rename_count)
1440
def test_reuse_name(self):
1441
"""Avoid reusing the same limbo name for different files"""
1442
transform, root = self.get_transform()
1443
parent = transform.new_directory('parent', root)
1444
transform.new_directory('child', parent)
1446
child2 = transform.new_directory('child', parent)
1448
self.fail('Tranform tried to use the same limbo name twice')
1449
transform.adjust_path('child2', parent, child2)
1451
# limbo/new-1 => parent, limbo/new-3 => parent/child2
1452
# child2 is put into top-level limbo because child1 has already
1453
# claimed the direct limbo path when child2 is created. There is no
1454
# advantage in renaming files once they're in top-level limbo, except
1456
self.assertEqual(2, transform.rename_count)
1458
def test_reuse_when_first_moved(self):
1459
"""Don't avoid direct paths when it is safe to use them"""
1460
transform, root = self.get_transform()
1461
parent = transform.new_directory('parent', root)
1462
child1 = transform.new_directory('child', parent)
1463
transform.adjust_path('child1', parent, child1)
1464
transform.new_directory('child', parent)
1466
# limbo/new-1 => parent
1467
self.assertEqual(1, transform.rename_count)
1469
def test_reuse_after_cancel(self):
1470
"""Don't avoid direct paths when it is safe to use them"""
1471
transform, root = self.get_transform()
1472
parent2 = transform.new_directory('parent2', root)
1473
child1 = transform.new_directory('child1', parent2)
1474
transform.cancel_creation(parent2)
1475
transform.create_directory(parent2)
1476
transform.new_directory('child1', parent2)
1477
transform.adjust_path('child2', parent2, child1)
1479
# limbo/new-1 => parent2, limbo/new-2 => parent2/child1
1480
self.assertEqual(2, transform.rename_count)
1482
def test_finalize_order(self):
1483
"""Finalize must be done in child-to-parent order"""
1484
transform, root = self.get_transform()
1485
parent = transform.new_directory('parent', root)
1486
transform.new_directory('child', parent)
1488
transform.finalize()
1490
self.fail('Tried to remove parent before child1')
1492
def test_cancel_with_cancelled_child_should_succeed(self):
1493
transform, root = self.get_transform()
1494
parent = transform.new_directory('parent', root)
1495
child = transform.new_directory('child', parent)
1496
transform.cancel_creation(child)
1497
transform.cancel_creation(parent)
1498
transform.finalize()
1500
def test_rollback_on_directory_clash(self):
1502
wt = self.make_branch_and_tree('.')
1503
tt = TreeTransform(wt) # TreeTransform obtains write lock
1505
foo = tt.new_directory('foo', tt.root)
1506
tt.new_file('bar', foo, [b'foobar'])
1507
baz = tt.new_directory('baz', tt.root)
1508
tt.new_file('qux', baz, [b'quux'])
1509
# Ask for a rename 'foo' -> 'baz'
1510
tt.adjust_path('baz', tt.root, foo)
1511
# Lie to tt that we've already resolved all conflicts.
1512
tt.apply(no_conflicts=True)
1513
except BaseException:
1516
# The rename will fail because the target directory is not empty (but
1517
# raises FileExists anyway).
1518
err = self.assertRaises(errors.FileExists, tt_helper)
1519
self.assertEndsWith(err.path, "/baz")
1521
def test_two_directories_clash(self):
1523
wt = self.make_branch_and_tree('.')
1524
tt = TreeTransform(wt) # TreeTransform obtains write lock
1526
foo_1 = tt.new_directory('foo', tt.root)
1527
tt.new_directory('bar', foo_1)
1528
# Adding the same directory with a different content
1529
foo_2 = tt.new_directory('foo', tt.root)
1530
tt.new_directory('baz', foo_2)
1531
# Lie to tt that we've already resolved all conflicts.
1532
tt.apply(no_conflicts=True)
1533
except BaseException:
1536
err = self.assertRaises(errors.FileExists, tt_helper)
1537
self.assertEndsWith(err.path, "/foo")
1539
def test_two_directories_clash_finalize(self):
1541
wt = self.make_branch_and_tree('.')
1542
tt = TreeTransform(wt) # TreeTransform obtains write lock
1544
foo_1 = tt.new_directory('foo', tt.root)
1545
tt.new_directory('bar', foo_1)
1546
# Adding the same directory with a different content
1547
foo_2 = tt.new_directory('foo', tt.root)
1548
tt.new_directory('baz', foo_2)
1549
# Lie to tt that we've already resolved all conflicts.
1550
tt.apply(no_conflicts=True)
1551
except BaseException:
1554
err = self.assertRaises(errors.FileExists, tt_helper)
1555
self.assertEndsWith(err.path, "/foo")
1557
def test_file_to_directory(self):
1558
wt = self.make_branch_and_tree('.')
1559
self.build_tree(['foo'])
1562
tt = TreeTransform(wt)
1563
self.addCleanup(tt.finalize)
1564
foo_trans_id = tt.trans_id_tree_path("foo")
1565
tt.delete_contents(foo_trans_id)
1566
tt.create_directory(foo_trans_id)
1567
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1568
tt.create_file([b"aa\n"], bar_trans_id)
1569
tt.version_file(b"bar-1", bar_trans_id)
1571
self.assertPathExists("foo/bar")
1574
self.assertEqual(wt.kind("foo"), "directory")
1578
changes = wt.changes_from(wt.basis_tree())
1579
self.assertFalse(changes.has_changed(), changes)
1581
def test_file_to_symlink(self):
1582
self.requireFeature(SymlinkFeature)
1583
wt = self.make_branch_and_tree('.')
1584
self.build_tree(['foo'])
1587
tt = TreeTransform(wt)
1588
self.addCleanup(tt.finalize)
1589
foo_trans_id = tt.trans_id_tree_path("foo")
1590
tt.delete_contents(foo_trans_id)
1591
tt.create_symlink("bar", foo_trans_id)
1593
self.assertPathExists("foo")
1595
self.addCleanup(wt.unlock)
1596
self.assertEqual(wt.kind("foo"), "symlink")
1598
def test_file_to_symlink_unsupported(self):
1599
wt = self.make_branch_and_tree('.')
1600
self.build_tree(['foo'])
1603
self.overrideAttr(osutils, 'supports_symlinks', lambda p: False)
1604
tt = TreeTransform(wt)
1605
self.addCleanup(tt.finalize)
1606
foo_trans_id = tt.trans_id_tree_path("foo")
1607
tt.delete_contents(foo_trans_id)
1609
trace.push_log_file(log)
1610
tt.create_symlink("bar", foo_trans_id)
1612
self.assertContainsRe(
1614
b'Unable to create symlink "foo" on this filesystem')
1616
def test_dir_to_file(self):
1617
wt = self.make_branch_and_tree('.')
1618
self.build_tree(['foo/', 'foo/bar'])
1619
wt.add(['foo', 'foo/bar'])
1621
tt = TreeTransform(wt)
1622
self.addCleanup(tt.finalize)
1623
foo_trans_id = tt.trans_id_tree_path("foo")
1624
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1625
tt.delete_contents(foo_trans_id)
1626
tt.delete_versioned(bar_trans_id)
1627
tt.create_file([b"aa\n"], foo_trans_id)
1629
self.assertPathExists("foo")
1631
self.addCleanup(wt.unlock)
1632
self.assertEqual(wt.kind("foo"), "file")
1634
def test_dir_to_hardlink(self):
1635
self.requireFeature(HardlinkFeature)
1636
wt = self.make_branch_and_tree('.')
1637
self.build_tree(['foo/', 'foo/bar'])
1638
wt.add(['foo', 'foo/bar'])
1640
tt = TreeTransform(wt)
1641
self.addCleanup(tt.finalize)
1642
foo_trans_id = tt.trans_id_tree_path("foo")
1643
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1644
tt.delete_contents(foo_trans_id)
1645
tt.delete_versioned(bar_trans_id)
1646
self.build_tree(['baz'])
1647
tt.create_hardlink("baz", foo_trans_id)
1649
self.assertPathExists("foo")
1650
self.assertPathExists("baz")
1652
self.addCleanup(wt.unlock)
1653
self.assertEqual(wt.kind("foo"), "file")
1655
def test_no_final_path(self):
1656
transform, root = self.get_transform()
1657
trans_id = transform.trans_id_file_id(b'foo')
1658
transform.create_file([b'bar'], trans_id)
1659
transform.cancel_creation(trans_id)
1662
def test_create_from_tree(self):
1663
tree1 = self.make_branch_and_tree('tree1')
1664
self.build_tree_contents([('tree1/foo/',), ('tree1/bar', b'baz')])
1665
tree1.add(['foo', 'bar'], [b'foo-id', b'bar-id'])
1666
tree2 = self.make_branch_and_tree('tree2')
1667
tt = TreeTransform(tree2)
1668
foo_trans_id = tt.create_path('foo', tt.root)
1669
create_from_tree(tt, foo_trans_id, tree1, 'foo')
1670
bar_trans_id = tt.create_path('bar', tt.root)
1671
create_from_tree(tt, bar_trans_id, tree1, 'bar')
1673
self.assertEqual('directory', osutils.file_kind('tree2/foo'))
1674
self.assertFileEqual(b'baz', 'tree2/bar')
1676
def test_create_from_tree_bytes(self):
1677
"""Provided lines are used instead of tree content."""
1678
tree1 = self.make_branch_and_tree('tree1')
1679
self.build_tree_contents([('tree1/foo', b'bar'), ])
1680
tree1.add('foo', b'foo-id')
1681
tree2 = self.make_branch_and_tree('tree2')
1682
tt = TreeTransform(tree2)
1683
foo_trans_id = tt.create_path('foo', tt.root)
1684
create_from_tree(tt, foo_trans_id, tree1, 'foo', chunks=[b'qux'])
1686
self.assertFileEqual(b'qux', 'tree2/foo')
1688
def test_create_from_tree_symlink(self):
1689
self.requireFeature(SymlinkFeature)
1690
tree1 = self.make_branch_and_tree('tree1')
1691
os.symlink('bar', 'tree1/foo')
1692
tree1.add('foo', b'foo-id')
1693
tt = TreeTransform(self.make_branch_and_tree('tree2'))
1694
foo_trans_id = tt.create_path('foo', tt.root)
1695
create_from_tree(tt, foo_trans_id, tree1, 'foo')
1697
self.assertEqual('bar', os.readlink('tree2/foo'))
494
1700
class TransformGroup(object):
495
def __init__(self, dirname):
1702
def __init__(self, dirname, root_id):
496
1703
self.name = dirname
497
1704
os.mkdir(dirname)
498
self.wt = BzrDir.create_standalone_workingtree(dirname)
1705
self.wt = ControlDir.create_standalone_workingtree(dirname)
1706
self.wt.set_root_id(root_id)
499
1707
self.b = self.wt.branch
500
1708
self.tt = TreeTransform(self.wt)
501
self.root = self.tt.trans_id_tree_file_id(self.wt.get_root_id())
1709
self.root = self.tt.trans_id_tree_path('')
503
1712
def conflict_text(tree, merge):
504
template = '%s TREE\n%s%s\n%s%s MERGE-SOURCE\n'
505
return template % ('<' * 7, tree, '=' * 7, merge, '>' * 7)
1713
template = b'%s TREE\n%s%s\n%s%s MERGE-SOURCE\n'
1714
return template % (b'<' * 7, tree, b'=' * 7, merge, b'>' * 7)
1717
class TestInventoryAltered(tests.TestCaseWithTransport):
1719
def test_inventory_altered_unchanged(self):
1720
tree = self.make_branch_and_tree('tree')
1721
self.build_tree(['tree/foo'])
1722
tree.add('foo', b'foo-id')
1723
with TransformPreview(tree) as tt:
1724
self.assertEqual([], tt._inventory_altered())
1726
def test_inventory_altered_changed_parent_id(self):
1727
tree = self.make_branch_and_tree('tree')
1728
self.build_tree(['tree/foo'])
1729
tree.add('foo', b'foo-id')
1730
with TransformPreview(tree) as tt:
1731
tt.unversion_file(tt.root)
1732
tt.version_file(b'new-id', tt.root)
1733
foo_trans_id = tt.trans_id_tree_path('foo')
1734
foo_tuple = ('foo', foo_trans_id)
1735
root_tuple = ('', tt.root)
1736
self.assertEqual([root_tuple, foo_tuple], tt._inventory_altered())
1738
def test_inventory_altered_noop_changed_parent_id(self):
1739
tree = self.make_branch_and_tree('tree')
1740
self.build_tree(['tree/foo'])
1741
tree.add('foo', b'foo-id')
1742
with TransformPreview(tree) as tt:
1743
tt.unversion_file(tt.root)
1744
tt.version_file(tree.get_root_id(), tt.root)
1745
tt.trans_id_tree_path('foo')
1746
self.assertEqual([], tt._inventory_altered())
508
1749
class TestTransformMerge(TestCaseInTempDir):
509
1751
def test_text_merge(self):
510
base = TransformGroup("base")
511
base.tt.new_file('a', base.root, 'a\nb\nc\nd\be\n', 'a')
512
base.tt.new_file('b', base.root, 'b1', 'b')
513
base.tt.new_file('c', base.root, 'c', 'c')
514
base.tt.new_file('d', base.root, 'd', 'd')
515
base.tt.new_file('e', base.root, 'e', 'e')
516
base.tt.new_file('f', base.root, 'f', 'f')
517
base.tt.new_directory('g', base.root, 'g')
518
base.tt.new_directory('h', base.root, 'h')
1752
root_id = generate_ids.gen_root_id()
1753
base = TransformGroup("base", root_id)
1754
base.tt.new_file('a', base.root, [b'a\nb\nc\nd\be\n'], b'a')
1755
base.tt.new_file('b', base.root, [b'b1'], b'b')
1756
base.tt.new_file('c', base.root, [b'c'], b'c')
1757
base.tt.new_file('d', base.root, [b'd'], b'd')
1758
base.tt.new_file('e', base.root, [b'e'], b'e')
1759
base.tt.new_file('f', base.root, [b'f'], b'f')
1760
base.tt.new_directory('g', base.root, b'g')
1761
base.tt.new_directory('h', base.root, b'h')
520
other = TransformGroup("other")
521
other.tt.new_file('a', other.root, 'y\nb\nc\nd\be\n', 'a')
522
other.tt.new_file('b', other.root, 'b2', 'b')
523
other.tt.new_file('c', other.root, 'c2', 'c')
524
other.tt.new_file('d', other.root, 'd', 'd')
525
other.tt.new_file('e', other.root, 'e2', 'e')
526
other.tt.new_file('f', other.root, 'f', 'f')
527
other.tt.new_file('g', other.root, 'g', 'g')
528
other.tt.new_file('h', other.root, 'h\ni\nj\nk\n', 'h')
529
other.tt.new_file('i', other.root, 'h\ni\nj\nk\n', 'i')
1763
other = TransformGroup("other", root_id)
1764
other.tt.new_file('a', other.root, [b'y\nb\nc\nd\be\n'], b'a')
1765
other.tt.new_file('b', other.root, [b'b2'], b'b')
1766
other.tt.new_file('c', other.root, [b'c2'], b'c')
1767
other.tt.new_file('d', other.root, [b'd'], b'd')
1768
other.tt.new_file('e', other.root, [b'e2'], b'e')
1769
other.tt.new_file('f', other.root, [b'f'], b'f')
1770
other.tt.new_file('g', other.root, [b'g'], b'g')
1771
other.tt.new_file('h', other.root, [b'h\ni\nj\nk\n'], b'h')
1772
other.tt.new_file('i', other.root, [b'h\ni\nj\nk\n'], b'i')
530
1773
other.tt.apply()
531
this = TransformGroup("this")
532
this.tt.new_file('a', this.root, 'a\nb\nc\nd\bz\n', 'a')
533
this.tt.new_file('b', this.root, 'b', 'b')
534
this.tt.new_file('c', this.root, 'c', 'c')
535
this.tt.new_file('d', this.root, 'd2', 'd')
536
this.tt.new_file('e', this.root, 'e2', 'e')
537
this.tt.new_file('f', this.root, 'f', 'f')
538
this.tt.new_file('g', this.root, 'g', 'g')
539
this.tt.new_file('h', this.root, '1\n2\n3\n4\n', 'h')
540
this.tt.new_file('i', this.root, '1\n2\n3\n4\n', 'i')
1774
this = TransformGroup("this", root_id)
1775
this.tt.new_file('a', this.root, [b'a\nb\nc\nd\bz\n'], b'a')
1776
this.tt.new_file('b', this.root, [b'b'], b'b')
1777
this.tt.new_file('c', this.root, [b'c'], b'c')
1778
this.tt.new_file('d', this.root, [b'd2'], b'd')
1779
this.tt.new_file('e', this.root, [b'e2'], b'e')
1780
this.tt.new_file('f', this.root, [b'f'], b'f')
1781
this.tt.new_file('g', this.root, [b'g'], b'g')
1782
this.tt.new_file('h', this.root, [b'1\n2\n3\n4\n'], b'h')
1783
this.tt.new_file('i', this.root, [b'1\n2\n3\n4\n'], b'i')
542
1785
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
544
self.assertEqual(this.wt.get_file('a').read(), 'y\nb\nc\nd\bz\n')
1788
with this.wt.get_file(this.wt.id2path(b'a')) as f:
1789
self.assertEqual(f.read(), b'y\nb\nc\nd\bz\n')
545
1790
# three-way text conflict
546
self.assertEqual(this.wt.get_file('b').read(),
547
conflict_text('b', 'b2'))
1791
with this.wt.get_file(this.wt.id2path(b'b')) as f:
1792
self.assertEqual(f.read(), conflict_text(b'b', b'b2'))
549
self.assertEqual(this.wt.get_file('c').read(), 'c2')
1794
self.assertEqual(this.wt.get_file(this.wt.id2path(b'c')).read(), b'c2')
551
self.assertEqual(this.wt.get_file('d').read(), 'd2')
1796
self.assertEqual(this.wt.get_file(this.wt.id2path(b'd')).read(), b'd2')
552
1797
# Ambigious clean merge
553
self.assertEqual(this.wt.get_file('e').read(), 'e2')
1798
self.assertEqual(this.wt.get_file(this.wt.id2path(b'e')).read(), b'e2')
555
self.assertEqual(this.wt.get_file('f').read(), 'f')
556
# Correct correct results when THIS == OTHER
557
self.assertEqual(this.wt.get_file('g').read(), 'g')
1800
self.assertEqual(this.wt.get_file(this.wt.id2path(b'f')).read(), b'f')
1801
# Correct correct results when THIS == OTHER
1802
self.assertEqual(this.wt.get_file(this.wt.id2path(b'g')).read(), b'g')
558
1803
# Text conflict when THIS & OTHER are text and BASE is dir
559
self.assertEqual(this.wt.get_file('h').read(),
560
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
561
self.assertEqual(this.wt.get_file_byname('h.THIS').read(),
563
self.assertEqual(this.wt.get_file_byname('h.OTHER').read(),
1804
self.assertEqual(this.wt.get_file(this.wt.id2path(b'h')).read(),
1805
conflict_text(b'1\n2\n3\n4\n', b'h\ni\nj\nk\n'))
1806
self.assertEqual(this.wt.get_file('h.THIS').read(),
1808
self.assertEqual(this.wt.get_file('h.OTHER').read(),
565
1810
self.assertEqual(file_kind(this.wt.abspath('h.BASE')), 'directory')
566
self.assertEqual(this.wt.get_file('i').read(),
567
conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
568
self.assertEqual(this.wt.get_file_byname('i.THIS').read(),
570
self.assertEqual(this.wt.get_file_byname('i.OTHER').read(),
1811
self.assertEqual(this.wt.get_file(this.wt.id2path(b'i')).read(),
1812
conflict_text(b'1\n2\n3\n4\n', b'h\ni\nj\nk\n'))
1813
self.assertEqual(this.wt.get_file('i.THIS').read(),
1815
self.assertEqual(this.wt.get_file('i.OTHER').read(),
572
1817
self.assertEqual(os.path.exists(this.wt.abspath('i.BASE')), False)
573
1818
modified = ['a', 'b', 'c', 'h', 'i']
574
1819
merge_modified = this.wt.merge_modified()
575
1820
self.assertSubset(merge_modified, modified)
576
1821
self.assertEqual(len(merge_modified), len(modified))
577
file(this.wt.id2abspath('a'), 'wb').write('booga')
1822
with open(this.wt.abspath(this.wt.id2path(b'a')), 'wb') as f:
579
1825
merge_modified = this.wt.merge_modified()
580
1826
self.assertSubset(merge_modified, modified)
581
1827
self.assertEqual(len(merge_modified), len(modified))
582
1828
this.wt.remove('b')
585
1831
def test_file_merge(self):
586
if not has_symlinks():
587
raise TestSkipped('Symlinks are not supported on this platform')
588
base = TransformGroup("BASE")
589
this = TransformGroup("THIS")
590
other = TransformGroup("OTHER")
1832
self.requireFeature(SymlinkFeature)
1833
root_id = generate_ids.gen_root_id()
1834
base = TransformGroup("BASE", root_id)
1835
this = TransformGroup("THIS", root_id)
1836
other = TransformGroup("OTHER", root_id)
591
1837
for tg in this, base, other:
592
tg.tt.new_directory('a', tg.root, 'a')
593
tg.tt.new_symlink('b', tg.root, 'b', 'b')
594
tg.tt.new_file('c', tg.root, 'c', 'c')
595
tg.tt.new_symlink('d', tg.root, tg.name, 'd')
596
targets = ((base, 'base-e', 'base-f', None, None),
597
(this, 'other-e', 'this-f', 'other-g', 'this-h'),
1838
tg.tt.new_directory('a', tg.root, b'a')
1839
tg.tt.new_symlink('b', tg.root, 'b', b'b')
1840
tg.tt.new_file('c', tg.root, [b'c'], b'c')
1841
tg.tt.new_symlink('d', tg.root, tg.name, b'd')
1842
targets = ((base, 'base-e', 'base-f', None, None),
1843
(this, 'other-e', 'this-f', 'other-g', 'this-h'),
598
1844
(other, 'other-e', None, 'other-g', 'other-h'))
599
1845
for tg, e_target, f_target, g_target, h_target in targets:
600
for link, target in (('e', e_target), ('f', f_target),
1846
for link, target in (('e', e_target), ('f', f_target),
601
1847
('g', g_target), ('h', h_target)):
602
1848
if target is not None:
603
tg.tt.new_symlink(link, tg.root, target, link)
1849
tg.tt.new_symlink(link, tg.root, target,
1850
link.encode('ascii'))
605
1852
for tg in this, base, other:
625
1873
self.assertIs(os.path.lexists(this.wt.abspath('h.OTHER')), True)
627
1875
def test_filename_merge(self):
628
base = TransformGroup("BASE")
629
this = TransformGroup("THIS")
630
other = TransformGroup("OTHER")
631
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
632
for t in [base, this, other]]
633
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
634
for t in [base, this, other]]
635
base.tt.new_directory('c', base_a, 'c')
636
this.tt.new_directory('c1', this_a, 'c')
637
other.tt.new_directory('c', other_b, 'c')
639
base.tt.new_directory('d', base_a, 'd')
640
this.tt.new_directory('d1', this_b, 'd')
641
other.tt.new_directory('d', other_a, 'd')
643
base.tt.new_directory('e', base_a, 'e')
644
this.tt.new_directory('e', this_a, 'e')
645
other.tt.new_directory('e1', other_b, 'e')
647
base.tt.new_directory('f', base_a, 'f')
648
this.tt.new_directory('f1', this_b, 'f')
649
other.tt.new_directory('f1', other_b, 'f')
1876
root_id = generate_ids.gen_root_id()
1877
base = TransformGroup("BASE", root_id)
1878
this = TransformGroup("THIS", root_id)
1879
other = TransformGroup("OTHER", root_id)
1880
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, b'a')
1881
for t in [base, this, other]]
1882
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, b'b')
1883
for t in [base, this, other]]
1884
base.tt.new_directory('c', base_a, b'c')
1885
this.tt.new_directory('c1', this_a, b'c')
1886
other.tt.new_directory('c', other_b, b'c')
1888
base.tt.new_directory('d', base_a, b'd')
1889
this.tt.new_directory('d1', this_b, b'd')
1890
other.tt.new_directory('d', other_a, b'd')
1892
base.tt.new_directory('e', base_a, b'e')
1893
this.tt.new_directory('e', this_a, b'e')
1894
other.tt.new_directory('e1', other_b, b'e')
1896
base.tt.new_directory('f', base_a, b'f')
1897
this.tt.new_directory('f1', this_b, b'f')
1898
other.tt.new_directory('f1', other_b, b'f')
651
1900
for tg in [this, base, other]:
653
1902
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
654
self.assertEqual(this.wt.id2path('c'), pathjoin('b/c1'))
655
self.assertEqual(this.wt.id2path('d'), pathjoin('b/d1'))
656
self.assertEqual(this.wt.id2path('e'), pathjoin('b/e1'))
657
self.assertEqual(this.wt.id2path('f'), pathjoin('b/f1'))
1903
self.assertEqual(this.wt.id2path(b'c'), pathjoin('b/c1'))
1904
self.assertEqual(this.wt.id2path(b'd'), pathjoin('b/d1'))
1905
self.assertEqual(this.wt.id2path(b'e'), pathjoin('b/e1'))
1906
self.assertEqual(this.wt.id2path(b'f'), pathjoin('b/f1'))
659
1908
def test_filename_merge_conflicts(self):
660
base = TransformGroup("BASE")
661
this = TransformGroup("THIS")
662
other = TransformGroup("OTHER")
663
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
664
for t in [base, this, other]]
665
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
666
for t in [base, this, other]]
668
base.tt.new_file('g', base_a, 'g', 'g')
669
other.tt.new_file('g1', other_b, 'g1', 'g')
671
base.tt.new_file('h', base_a, 'h', 'h')
672
this.tt.new_file('h1', this_b, 'h1', 'h')
674
base.tt.new_file('i', base.root, 'i', 'i')
675
other.tt.new_directory('i1', this_b, 'i')
1909
root_id = generate_ids.gen_root_id()
1910
base = TransformGroup("BASE", root_id)
1911
this = TransformGroup("THIS", root_id)
1912
other = TransformGroup("OTHER", root_id)
1913
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, b'a')
1914
for t in [base, this, other]]
1915
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, b'b')
1916
for t in [base, this, other]]
1918
base.tt.new_file('g', base_a, [b'g'], b'g')
1919
other.tt.new_file('g1', other_b, [b'g1'], b'g')
1921
base.tt.new_file('h', base_a, [b'h'], b'h')
1922
this.tt.new_file('h1', this_b, [b'h1'], b'h')
1924
base.tt.new_file('i', base.root, [b'i'], b'i')
1925
other.tt.new_directory('i1', this_b, b'i')
677
1927
for tg in [this, base, other]:
679
1929
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
681
self.assertEqual(this.wt.id2path('g'), pathjoin('b/g1.OTHER'))
1931
self.assertEqual(this.wt.id2path(b'g'), pathjoin('b/g1.OTHER'))
682
1932
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.BASE')), True)
683
1933
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.THIS')), False)
684
self.assertEqual(this.wt.id2path('h'), pathjoin('b/h1.THIS'))
1934
self.assertEqual(this.wt.id2path(b'h'), pathjoin('b/h1.THIS'))
685
1935
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.BASE')), True)
686
1936
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.OTHER')), False)
687
self.assertEqual(this.wt.id2path('i'), pathjoin('b/i1.OTHER'))
689
class TestBuildTree(TestCaseInTempDir):
690
def test_build_tree(self):
691
if not has_symlinks():
692
raise TestSkipped('Test requires symlink support')
1937
self.assertEqual(this.wt.id2path(b'i'), pathjoin('b/i1.OTHER'))
1940
class TestBuildTree(tests.TestCaseWithTransport):
1942
def test_build_tree_with_symlinks(self):
1943
self.requireFeature(SymlinkFeature)
694
a = BzrDir.create_standalone_workingtree('a')
1945
a = ControlDir.create_standalone_workingtree('a')
695
1946
os.mkdir('a/foo')
696
file('a/foo/bar', 'wb').write('contents')
1947
with open('a/foo/bar', 'wb') as f:
1948
f.write(b'contents')
697
1949
os.symlink('a/foo/bar', 'a/foo/baz')
698
1950
a.add(['foo', 'foo/bar', 'foo/baz'])
699
1951
a.commit('initial commit')
700
b = BzrDir.create_standalone_workingtree('b')
701
build_tree(a.basis_tree(), b)
1952
b = ControlDir.create_standalone_workingtree('b')
1953
basis = a.basis_tree()
1955
self.addCleanup(basis.unlock)
1956
build_tree(basis, b)
702
1957
self.assertIs(os.path.isdir('b/foo'), True)
703
self.assertEqual(file('b/foo/bar', 'rb').read(), "contents")
1958
with open('b/foo/bar', 'rb') as f:
1959
self.assertEqual(f.read(), b"contents")
704
1960
self.assertEqual(os.readlink('b/foo/baz'), 'a/foo/bar')
706
class MockTransform(object):
708
def has_named_child(self, by_parent, parent_id, name):
709
for child_id in by_parent[parent_id]:
713
elif name == "name.~%s~" % child_id:
717
class MockEntry(object):
719
object.__init__(self)
722
class TestGetBackupName(TestCase):
723
def test_get_backup_name(self):
725
name = get_backup_name(MockEntry(), {'a':[]}, 'a', tt)
726
self.assertEqual(name, 'name.~1~')
727
name = get_backup_name(MockEntry(), {'a':['1']}, 'a', tt)
728
self.assertEqual(name, 'name.~2~')
729
name = get_backup_name(MockEntry(), {'a':['2']}, 'a', tt)
730
self.assertEqual(name, 'name.~1~')
731
name = get_backup_name(MockEntry(), {'a':['2'], 'b':[]}, 'b', tt)
732
self.assertEqual(name, 'name.~1~')
733
name = get_backup_name(MockEntry(), {'a':['1', '2', '3']}, 'a', tt)
734
self.assertEqual(name, 'name.~4~')
1962
def test_build_with_references(self):
1963
tree = self.make_branch_and_tree('source',
1964
format='development-subtree')
1965
subtree = self.make_branch_and_tree('source/subtree',
1966
format='development-subtree')
1967
tree.add_reference(subtree)
1968
tree.commit('a revision')
1969
tree.branch.create_checkout('target')
1970
self.assertPathExists('target')
1971
self.assertPathExists('target/subtree')
1973
def test_file_conflict_handling(self):
1974
"""Ensure that when building trees, conflict handling is done"""
1975
source = self.make_branch_and_tree('source')
1976
target = self.make_branch_and_tree('target')
1977
self.build_tree(['source/file', 'target/file'])
1978
source.add('file', b'new-file')
1979
source.commit('added file')
1980
build_tree(source.basis_tree(), target)
1982
[DuplicateEntry('Moved existing file to', 'file.moved',
1983
'file', None, 'new-file')],
1985
target2 = self.make_branch_and_tree('target2')
1986
with open('target2/file', 'wb') as target_file, \
1987
open('source/file', 'rb') as source_file:
1988
target_file.write(source_file.read())
1989
build_tree(source.basis_tree(), target2)
1990
self.assertEqual([], target2.conflicts())
1992
def test_symlink_conflict_handling(self):
1993
"""Ensure that when building trees, conflict handling is done"""
1994
self.requireFeature(SymlinkFeature)
1995
source = self.make_branch_and_tree('source')
1996
os.symlink('foo', 'source/symlink')
1997
source.add('symlink', b'new-symlink')
1998
source.commit('added file')
1999
target = self.make_branch_and_tree('target')
2000
os.symlink('bar', 'target/symlink')
2001
build_tree(source.basis_tree(), target)
2003
[DuplicateEntry('Moved existing file to', 'symlink.moved',
2004
'symlink', None, 'new-symlink')],
2006
target = self.make_branch_and_tree('target2')
2007
os.symlink('foo', 'target2/symlink')
2008
build_tree(source.basis_tree(), target)
2009
self.assertEqual([], target.conflicts())
2011
def test_directory_conflict_handling(self):
2012
"""Ensure that when building trees, conflict handling is done"""
2013
source = self.make_branch_and_tree('source')
2014
target = self.make_branch_and_tree('target')
2015
self.build_tree(['source/dir1/', 'source/dir1/file', 'target/dir1/'])
2016
source.add(['dir1', 'dir1/file'], [b'new-dir1', b'new-file'])
2017
source.commit('added file')
2018
build_tree(source.basis_tree(), target)
2019
self.assertEqual([], target.conflicts())
2020
self.assertPathExists('target/dir1/file')
2022
# Ensure contents are merged
2023
target = self.make_branch_and_tree('target2')
2024
self.build_tree(['target2/dir1/', 'target2/dir1/file2'])
2025
build_tree(source.basis_tree(), target)
2026
self.assertEqual([], target.conflicts())
2027
self.assertPathExists('target2/dir1/file2')
2028
self.assertPathExists('target2/dir1/file')
2030
# Ensure new contents are suppressed for existing branches
2031
target = self.make_branch_and_tree('target3')
2032
self.make_branch('target3/dir1')
2033
self.build_tree(['target3/dir1/file2'])
2034
build_tree(source.basis_tree(), target)
2035
self.assertPathDoesNotExist('target3/dir1/file')
2036
self.assertPathExists('target3/dir1/file2')
2037
self.assertPathExists('target3/dir1.diverted/file')
2039
[DuplicateEntry('Diverted to', 'dir1.diverted',
2040
'dir1', 'new-dir1', None)],
2043
target = self.make_branch_and_tree('target4')
2044
self.build_tree(['target4/dir1/'])
2045
self.make_branch('target4/dir1/file')
2046
build_tree(source.basis_tree(), target)
2047
self.assertPathExists('target4/dir1/file')
2048
self.assertEqual('directory', file_kind('target4/dir1/file'))
2049
self.assertPathExists('target4/dir1/file.diverted')
2051
[DuplicateEntry('Diverted to', 'dir1/file.diverted',
2052
'dir1/file', 'new-file', None)],
2055
def test_mixed_conflict_handling(self):
2056
"""Ensure that when building trees, conflict handling is done"""
2057
source = self.make_branch_and_tree('source')
2058
target = self.make_branch_and_tree('target')
2059
self.build_tree(['source/name', 'target/name/'])
2060
source.add('name', b'new-name')
2061
source.commit('added file')
2062
build_tree(source.basis_tree(), target)
2064
[DuplicateEntry('Moved existing file to',
2065
'name.moved', 'name', None, 'new-name')],
2068
def test_raises_in_populated(self):
2069
source = self.make_branch_and_tree('source')
2070
self.build_tree(['source/name'])
2072
source.commit('added name')
2073
target = self.make_branch_and_tree('target')
2074
self.build_tree(['target/name'])
2076
self.assertRaises(errors.WorkingTreeAlreadyPopulated,
2077
build_tree, source.basis_tree(), target)
2079
def test_build_tree_rename_count(self):
2080
source = self.make_branch_and_tree('source')
2081
self.build_tree(['source/file1', 'source/dir1/'])
2082
source.add(['file1', 'dir1'])
2083
source.commit('add1')
2084
target1 = self.make_branch_and_tree('target1')
2085
transform_result = build_tree(source.basis_tree(), target1)
2086
self.assertEqual(2, transform_result.rename_count)
2088
self.build_tree(['source/dir1/file2'])
2089
source.add(['dir1/file2'])
2090
source.commit('add3')
2091
target2 = self.make_branch_and_tree('target2')
2092
transform_result = build_tree(source.basis_tree(), target2)
2093
# children of non-root directories should not be renamed
2094
self.assertEqual(2, transform_result.rename_count)
2096
def create_ab_tree(self):
2097
"""Create a committed test tree with two files"""
2098
source = self.make_branch_and_tree('source')
2099
self.build_tree_contents([('source/file1', b'A')])
2100
self.build_tree_contents([('source/file2', b'B')])
2101
source.add(['file1', 'file2'], [b'file1-id', b'file2-id'])
2102
source.commit('commit files')
2104
self.addCleanup(source.unlock)
2107
def test_build_tree_accelerator_tree(self):
2108
source = self.create_ab_tree()
2109
self.build_tree_contents([('source/file2', b'C')])
2111
real_source_get_file = source.get_file
2115
return real_source_get_file(path)
2116
source.get_file = get_file
2117
target = self.make_branch_and_tree('target')
2118
revision_tree = source.basis_tree()
2119
revision_tree.lock_read()
2120
self.addCleanup(revision_tree.unlock)
2121
build_tree(revision_tree, target, source)
2122
self.assertEqual(['file1'], calls)
2124
self.addCleanup(target.unlock)
2125
self.assertEqual([], list(target.iter_changes(revision_tree)))
2127
def test_build_tree_accelerator_tree_observes_sha1(self):
2128
source = self.create_ab_tree()
2129
sha1 = osutils.sha_string(b'A')
2130
target = self.make_branch_and_tree('target')
2132
self.addCleanup(target.unlock)
2133
state = target.current_dirstate()
2134
state._cutoff_time = time.time() + 60
2135
build_tree(source.basis_tree(), target, source)
2136
entry = state._get_entry(0, path_utf8=b'file1')
2137
self.assertEqual(sha1, entry[1][0][1])
2139
def test_build_tree_accelerator_tree_missing_file(self):
2140
source = self.create_ab_tree()
2141
os.unlink('source/file1')
2142
source.remove(['file2'])
2143
target = self.make_branch_and_tree('target')
2144
revision_tree = source.basis_tree()
2145
revision_tree.lock_read()
2146
self.addCleanup(revision_tree.unlock)
2147
build_tree(revision_tree, target, source)
2149
self.addCleanup(target.unlock)
2150
self.assertEqual([], list(target.iter_changes(revision_tree)))
2152
def test_build_tree_accelerator_wrong_kind(self):
2153
self.requireFeature(SymlinkFeature)
2154
source = self.make_branch_and_tree('source')
2155
self.build_tree_contents([('source/file1', b'')])
2156
self.build_tree_contents([('source/file2', b'')])
2157
source.add(['file1', 'file2'], [b'file1-id', b'file2-id'])
2158
source.commit('commit files')
2159
os.unlink('source/file2')
2160
self.build_tree_contents([('source/file2/', b'C')])
2161
os.unlink('source/file1')
2162
os.symlink('file2', 'source/file1')
2164
real_source_get_file = source.get_file
2168
return real_source_get_file(path)
2169
source.get_file = get_file
2170
target = self.make_branch_and_tree('target')
2171
revision_tree = source.basis_tree()
2172
revision_tree.lock_read()
2173
self.addCleanup(revision_tree.unlock)
2174
build_tree(revision_tree, target, source)
2175
self.assertEqual([], calls)
2177
self.addCleanup(target.unlock)
2178
self.assertEqual([], list(target.iter_changes(revision_tree)))
2180
def test_build_tree_hardlink(self):
2181
self.requireFeature(HardlinkFeature)
2182
source = self.create_ab_tree()
2183
target = self.make_branch_and_tree('target')
2184
revision_tree = source.basis_tree()
2185
revision_tree.lock_read()
2186
self.addCleanup(revision_tree.unlock)
2187
build_tree(revision_tree, target, source, hardlink=True)
2189
self.addCleanup(target.unlock)
2190
self.assertEqual([], list(target.iter_changes(revision_tree)))
2191
source_stat = os.stat('source/file1')
2192
target_stat = os.stat('target/file1')
2193
self.assertEqual(source_stat, target_stat)
2195
# Explicitly disallowing hardlinks should prevent them.
2196
target2 = self.make_branch_and_tree('target2')
2197
build_tree(revision_tree, target2, source, hardlink=False)
2199
self.addCleanup(target2.unlock)
2200
self.assertEqual([], list(target2.iter_changes(revision_tree)))
2201
source_stat = os.stat('source/file1')
2202
target2_stat = os.stat('target2/file1')
2203
self.assertNotEqual(source_stat, target2_stat)
2205
def test_build_tree_accelerator_tree_moved(self):
2206
source = self.make_branch_and_tree('source')
2207
self.build_tree_contents([('source/file1', b'A')])
2208
source.add(['file1'], [b'file1-id'])
2209
source.commit('commit files')
2210
source.rename_one('file1', 'file2')
2212
self.addCleanup(source.unlock)
2213
target = self.make_branch_and_tree('target')
2214
revision_tree = source.basis_tree()
2215
revision_tree.lock_read()
2216
self.addCleanup(revision_tree.unlock)
2217
build_tree(revision_tree, target, source)
2219
self.addCleanup(target.unlock)
2220
self.assertEqual([], list(target.iter_changes(revision_tree)))
2222
def test_build_tree_hardlinks_preserve_execute(self):
2223
self.requireFeature(HardlinkFeature)
2224
source = self.create_ab_tree()
2225
tt = TreeTransform(source)
2226
trans_id = tt.trans_id_tree_path('file1')
2227
tt.set_executability(True, trans_id)
2229
self.assertTrue(source.is_executable('file1'))
2230
target = self.make_branch_and_tree('target')
2231
revision_tree = source.basis_tree()
2232
revision_tree.lock_read()
2233
self.addCleanup(revision_tree.unlock)
2234
build_tree(revision_tree, target, source, hardlink=True)
2236
self.addCleanup(target.unlock)
2237
self.assertEqual([], list(target.iter_changes(revision_tree)))
2238
self.assertTrue(source.is_executable('file1'))
2240
def install_rot13_content_filter(self, pattern):
2242
# self.addCleanup(filters._reset_registry, filters._reset_registry())
2243
# below, but that looks a bit... hard to read even if it's exactly
2245
original_registry = filters._reset_registry()
2247
def restore_registry():
2248
filters._reset_registry(original_registry)
2249
self.addCleanup(restore_registry)
2251
def rot13(chunks, context=None):
2253
codecs.encode(chunk.decode('ascii'), 'rot13').encode('ascii')
2254
for chunk in chunks]
2255
rot13filter = filters.ContentFilter(rot13, rot13)
2256
filters.filter_stacks_registry.register(
2257
'rot13', {'yes': [rot13filter]}.get)
2258
os.mkdir(self.test_home_dir + '/.bazaar')
2259
rules_filename = self.test_home_dir + '/.bazaar/rules'
2260
with open(rules_filename, 'wb') as f:
2261
f.write(b'[name %s]\nrot13=yes\n' % (pattern,))
2263
def uninstall_rules():
2264
os.remove(rules_filename)
2266
self.addCleanup(uninstall_rules)
2269
def test_build_tree_content_filtered_files_are_not_hardlinked(self):
2270
"""build_tree will not hardlink files that have content filtering rules
2271
applied to them (but will still hardlink other files from the same tree
2274
self.requireFeature(HardlinkFeature)
2275
self.install_rot13_content_filter(b'file1')
2276
source = self.create_ab_tree()
2277
target = self.make_branch_and_tree('target')
2278
revision_tree = source.basis_tree()
2279
revision_tree.lock_read()
2280
self.addCleanup(revision_tree.unlock)
2281
build_tree(revision_tree, target, source, hardlink=True)
2283
self.addCleanup(target.unlock)
2284
self.assertEqual([], list(target.iter_changes(revision_tree)))
2285
source_stat = os.stat('source/file1')
2286
target_stat = os.stat('target/file1')
2287
self.assertNotEqual(source_stat, target_stat)
2288
source_stat = os.stat('source/file2')
2289
target_stat = os.stat('target/file2')
2290
self.assertEqualStat(source_stat, target_stat)
2292
def test_case_insensitive_build_tree_inventory(self):
2293
if (features.CaseInsensitiveFilesystemFeature.available()
2294
or features.CaseInsCasePresFilenameFeature.available()):
2295
raise tests.UnavailableFeature('Fully case sensitive filesystem')
2296
source = self.make_branch_and_tree('source')
2297
self.build_tree(['source/file', 'source/FILE'])
2298
source.add(['file', 'FILE'], [b'lower-id', b'upper-id'])
2299
source.commit('added files')
2300
# Don't try this at home, kids!
2301
# Force the tree to report that it is case insensitive
2302
target = self.make_branch_and_tree('target')
2303
target.case_sensitive = False
2304
build_tree(source.basis_tree(), target, source, delta_from_tree=True)
2305
self.assertEqual('file.moved', target.id2path(b'lower-id'))
2306
self.assertEqual('FILE', target.id2path(b'upper-id'))
2308
def test_build_tree_observes_sha(self):
2309
source = self.make_branch_and_tree('source')
2310
self.build_tree(['source/file1', 'source/dir/', 'source/dir/file2'])
2311
source.add(['file1', 'dir', 'dir/file2'],
2312
[b'file1-id', b'dir-id', b'file2-id'])
2313
source.commit('new files')
2314
target = self.make_branch_and_tree('target')
2316
self.addCleanup(target.unlock)
2317
# We make use of the fact that DirState caches its cutoff time. So we
2318
# set the 'safe' time to one minute in the future.
2319
state = target.current_dirstate()
2320
state._cutoff_time = time.time() + 60
2321
build_tree(source.basis_tree(), target)
2322
entry1_sha = osutils.sha_file_by_name('source/file1')
2323
entry2_sha = osutils.sha_file_by_name('source/dir/file2')
2324
# entry[1] is the state information, entry[1][0] is the state of the
2325
# working tree, entry[1][0][1] is the sha value for the current working
2327
entry1 = state._get_entry(0, path_utf8=b'file1')
2328
self.assertEqual(entry1_sha, entry1[1][0][1])
2329
# The 'size' field must also be set.
2330
self.assertEqual(25, entry1[1][0][2])
2331
entry1_state = entry1[1][0]
2332
entry2 = state._get_entry(0, path_utf8=b'dir/file2')
2333
self.assertEqual(entry2_sha, entry2[1][0][1])
2334
self.assertEqual(29, entry2[1][0][2])
2335
entry2_state = entry2[1][0]
2336
# Now, make sure that we don't have to re-read the content. The
2337
# packed_stat should match exactly.
2338
self.assertEqual(entry1_sha, target.get_file_sha1('file1'))
2339
self.assertEqual(entry2_sha, target.get_file_sha1('dir/file2'))
2340
self.assertEqual(entry1_state, entry1[1][0])
2341
self.assertEqual(entry2_state, entry2[1][0])
2344
class TestCommitTransform(tests.TestCaseWithTransport):
2346
def get_branch(self):
2347
tree = self.make_branch_and_tree('tree')
2349
self.addCleanup(tree.unlock)
2350
tree.commit('empty commit')
2353
def get_branch_and_transform(self):
2354
branch = self.get_branch()
2355
tt = TransformPreview(branch.basis_tree())
2356
self.addCleanup(tt.finalize)
2359
def test_commit_wrong_basis(self):
2360
branch = self.get_branch()
2361
basis = branch.repository.revision_tree(
2362
_mod_revision.NULL_REVISION)
2363
tt = TransformPreview(basis)
2364
self.addCleanup(tt.finalize)
2365
e = self.assertRaises(ValueError, tt.commit, branch, '')
2366
self.assertEqual('TreeTransform not based on branch basis: null:',
2369
def test_empy_commit(self):
2370
branch, tt = self.get_branch_and_transform()
2371
rev = tt.commit(branch, 'my message')
2372
self.assertEqual(2, branch.revno())
2373
repo = branch.repository
2374
self.assertEqual('my message', repo.get_revision(rev).message)
2376
def test_merge_parents(self):
2377
branch, tt = self.get_branch_and_transform()
2378
tt.commit(branch, 'my message', [b'rev1b', b'rev1c'])
2379
self.assertEqual([b'rev1b', b'rev1c'],
2380
branch.basis_tree().get_parent_ids()[1:])
2382
def test_first_commit(self):
2383
branch = self.make_branch('branch')
2385
self.addCleanup(branch.unlock)
2386
tt = TransformPreview(branch.basis_tree())
2387
self.addCleanup(tt.finalize)
2388
tt.new_directory('', ROOT_PARENT, b'TREE_ROOT')
2389
tt.commit(branch, 'my message')
2390
self.assertEqual([], branch.basis_tree().get_parent_ids())
2391
self.assertNotEqual(_mod_revision.NULL_REVISION,
2392
branch.last_revision())
2394
def test_first_commit_with_merge_parents(self):
2395
branch = self.make_branch('branch')
2397
self.addCleanup(branch.unlock)
2398
tt = TransformPreview(branch.basis_tree())
2399
self.addCleanup(tt.finalize)
2400
e = self.assertRaises(ValueError, tt.commit, branch,
2401
'my message', [b'rev1b-id'])
2402
self.assertEqual('Cannot supply merge parents for first commit.',
2404
self.assertEqual(_mod_revision.NULL_REVISION, branch.last_revision())
2406
def test_add_files(self):
2407
branch, tt = self.get_branch_and_transform()
2408
tt.new_file('file', tt.root, [b'contents'], b'file-id')
2409
trans_id = tt.new_directory('dir', tt.root, b'dir-id')
2410
if SymlinkFeature.available():
2411
tt.new_symlink('symlink', trans_id, 'target', b'symlink-id')
2412
tt.commit(branch, 'message')
2413
tree = branch.basis_tree()
2414
self.assertEqual('file', tree.id2path(b'file-id'))
2415
self.assertEqual(b'contents', tree.get_file_text('file'))
2416
self.assertEqual('dir', tree.id2path(b'dir-id'))
2417
if SymlinkFeature.available():
2418
self.assertEqual('dir/symlink', tree.id2path(b'symlink-id'))
2419
self.assertEqual('target', tree.get_symlink_target('dir/symlink'))
2421
def test_add_unversioned(self):
2422
branch, tt = self.get_branch_and_transform()
2423
tt.new_file('file', tt.root, [b'contents'])
2424
self.assertRaises(errors.StrictCommitFailed, tt.commit, branch,
2425
'message', strict=True)
2427
def test_modify_strict(self):
2428
branch, tt = self.get_branch_and_transform()
2429
tt.new_file('file', tt.root, [b'contents'], b'file-id')
2430
tt.commit(branch, 'message', strict=True)
2431
tt = TransformPreview(branch.basis_tree())
2432
self.addCleanup(tt.finalize)
2433
trans_id = tt.trans_id_file_id(b'file-id')
2434
tt.delete_contents(trans_id)
2435
tt.create_file([b'contents'], trans_id)
2436
tt.commit(branch, 'message', strict=True)
2438
def test_commit_malformed(self):
2439
"""Committing a malformed transform should raise an exception.
2441
In this case, we are adding a file without adding its parent.
2443
branch, tt = self.get_branch_and_transform()
2444
parent_id = tt.trans_id_file_id(b'parent-id')
2445
tt.new_file('file', parent_id, [b'contents'], b'file-id')
2446
self.assertRaises(errors.MalformedTransform, tt.commit, branch,
2449
def test_commit_rich_revision_data(self):
2450
branch, tt = self.get_branch_and_transform()
2451
rev_id = tt.commit(branch, 'message', timestamp=1, timezone=43201,
2452
committer='me <me@example.com>',
2453
revprops={u'foo': 'bar'}, revision_id=b'revid-1',
2454
authors=['Author1 <author1@example.com>',
2455
'Author2 <author2@example.com>',
2457
self.assertEqual(b'revid-1', rev_id)
2458
revision = branch.repository.get_revision(rev_id)
2459
self.assertEqual(1, revision.timestamp)
2460
self.assertEqual(43201, revision.timezone)
2461
self.assertEqual('me <me@example.com>', revision.committer)
2462
self.assertEqual(['Author1 <author1@example.com>',
2463
'Author2 <author2@example.com>'],
2464
revision.get_apparent_authors())
2465
del revision.properties['authors']
2466
self.assertEqual({'foo': 'bar',
2467
'branch-nick': 'tree'},
2468
revision.properties)
2470
def test_no_explicit_revprops(self):
2471
branch, tt = self.get_branch_and_transform()
2472
rev_id = tt.commit(branch, 'message', authors=[
2473
'Author1 <author1@example.com>',
2474
'Author2 <author2@example.com>', ])
2475
revision = branch.repository.get_revision(rev_id)
2476
self.assertEqual(['Author1 <author1@example.com>',
2477
'Author2 <author2@example.com>'],
2478
revision.get_apparent_authors())
2479
self.assertEqual('tree', revision.properties['branch-nick'])
2482
class TestFileMover(tests.TestCaseWithTransport):
2484
def test_file_mover(self):
2485
self.build_tree(['a/', 'a/b', 'c/', 'c/d'])
2486
mover = _FileMover()
2487
mover.rename('a', 'q')
2488
self.assertPathExists('q')
2489
self.assertPathDoesNotExist('a')
2490
self.assertPathExists('q/b')
2491
self.assertPathExists('c')
2492
self.assertPathExists('c/d')
2494
def test_pre_delete_rollback(self):
2495
self.build_tree(['a/'])
2496
mover = _FileMover()
2497
mover.pre_delete('a', 'q')
2498
self.assertPathExists('q')
2499
self.assertPathDoesNotExist('a')
2501
self.assertPathDoesNotExist('q')
2502
self.assertPathExists('a')
2504
def test_apply_deletions(self):
2505
self.build_tree(['a/', 'b/'])
2506
mover = _FileMover()
2507
mover.pre_delete('a', 'q')
2508
mover.pre_delete('b', 'r')
2509
self.assertPathExists('q')
2510
self.assertPathExists('r')
2511
self.assertPathDoesNotExist('a')
2512
self.assertPathDoesNotExist('b')
2513
mover.apply_deletions()
2514
self.assertPathDoesNotExist('q')
2515
self.assertPathDoesNotExist('r')
2516
self.assertPathDoesNotExist('a')
2517
self.assertPathDoesNotExist('b')
2519
def test_file_mover_rollback(self):
2520
self.build_tree(['a/', 'a/b', 'c/', 'c/d/', 'c/e/'])
2521
mover = _FileMover()
2522
mover.rename('c/d', 'c/f')
2523
mover.rename('c/e', 'c/d')
2525
mover.rename('a', 'c')
2526
except errors.FileExists:
2528
self.assertPathExists('a')
2529
self.assertPathExists('c/d')
2532
class Bogus(Exception):
2536
class TestTransformRollback(tests.TestCaseWithTransport):
2538
class ExceptionFileMover(_FileMover):
2540
def __init__(self, bad_source=None, bad_target=None):
2541
_FileMover.__init__(self)
2542
self.bad_source = bad_source
2543
self.bad_target = bad_target
2545
def rename(self, source, target):
2546
if (self.bad_source is not None and
2547
source.endswith(self.bad_source)):
2549
elif (self.bad_target is not None and
2550
target.endswith(self.bad_target)):
2553
_FileMover.rename(self, source, target)
2555
def test_rollback_rename(self):
2556
tree = self.make_branch_and_tree('.')
2557
self.build_tree(['a/', 'a/b'])
2558
tt = TreeTransform(tree)
2559
self.addCleanup(tt.finalize)
2560
a_id = tt.trans_id_tree_path('a')
2561
tt.adjust_path('c', tt.root, a_id)
2562
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
2563
self.assertRaises(Bogus, tt.apply,
2564
_mover=self.ExceptionFileMover(bad_source='a'))
2565
self.assertPathExists('a')
2566
self.assertPathExists('a/b')
2568
self.assertPathExists('c')
2569
self.assertPathExists('c/d')
2571
def test_rollback_rename_into_place(self):
2572
tree = self.make_branch_and_tree('.')
2573
self.build_tree(['a/', 'a/b'])
2574
tt = TreeTransform(tree)
2575
self.addCleanup(tt.finalize)
2576
a_id = tt.trans_id_tree_path('a')
2577
tt.adjust_path('c', tt.root, a_id)
2578
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
2579
self.assertRaises(Bogus, tt.apply,
2580
_mover=self.ExceptionFileMover(bad_target='c/d'))
2581
self.assertPathExists('a')
2582
self.assertPathExists('a/b')
2584
self.assertPathExists('c')
2585
self.assertPathExists('c/d')
2587
def test_rollback_deletion(self):
2588
tree = self.make_branch_and_tree('.')
2589
self.build_tree(['a/', 'a/b'])
2590
tt = TreeTransform(tree)
2591
self.addCleanup(tt.finalize)
2592
a_id = tt.trans_id_tree_path('a')
2593
tt.delete_contents(a_id)
2594
tt.adjust_path('d', tt.root, tt.trans_id_tree_path('a/b'))
2595
self.assertRaises(Bogus, tt.apply,
2596
_mover=self.ExceptionFileMover(bad_target='d'))
2597
self.assertPathExists('a')
2598
self.assertPathExists('a/b')
2601
class TestFinalizeRobustness(tests.TestCaseWithTransport):
2602
"""Ensure treetransform creation errors can be safely cleaned up after"""
2604
def _override_globals_in_method(self, instance, method_name, globals):
2605
"""Replace method on instance with one with updated globals"""
2607
func = getattr(instance, method_name).__func__
2608
new_globals = dict(func.__globals__)
2609
new_globals.update(globals)
2610
new_func = types.FunctionType(func.__code__, new_globals,
2611
func.__name__, func.__defaults__)
2613
setattr(instance, method_name,
2614
types.MethodType(new_func, instance))
2616
setattr(instance, method_name,
2617
types.MethodType(new_func, instance, instance.__class__))
2618
self.addCleanup(delattr, instance, method_name)
2621
def _fake_open_raises_before(name, mode):
2622
"""Like open() but raises before doing anything"""
2626
def _fake_open_raises_after(name, mode):
2627
"""Like open() but raises after creating file without returning"""
2628
open(name, mode).close()
2631
def create_transform_and_root_trans_id(self):
2632
"""Setup a transform creating a file in limbo"""
2633
tree = self.make_branch_and_tree('.')
2634
tt = TreeTransform(tree)
2635
return tt, tt.create_path("a", tt.root)
2637
def create_transform_and_subdir_trans_id(self):
2638
"""Setup a transform creating a directory containing a file in limbo"""
2639
tree = self.make_branch_and_tree('.')
2640
tt = TreeTransform(tree)
2641
d_trans_id = tt.create_path("d", tt.root)
2642
tt.create_directory(d_trans_id)
2643
f_trans_id = tt.create_path("a", d_trans_id)
2644
tt.adjust_path("a", d_trans_id, f_trans_id)
2645
return tt, f_trans_id
2647
def test_root_create_file_open_raises_before_creation(self):
2648
tt, trans_id = self.create_transform_and_root_trans_id()
2649
self._override_globals_in_method(
2650
tt, "create_file", {"open": self._fake_open_raises_before})
2651
self.assertRaises(RuntimeError, tt.create_file,
2652
[b"contents"], trans_id)
2653
path = tt._limbo_name(trans_id)
2654
self.assertPathDoesNotExist(path)
2656
self.assertPathDoesNotExist(tt._limbodir)
2658
def test_root_create_file_open_raises_after_creation(self):
2659
tt, trans_id = self.create_transform_and_root_trans_id()
2660
self._override_globals_in_method(
2661
tt, "create_file", {"open": self._fake_open_raises_after})
2662
self.assertRaises(RuntimeError, tt.create_file,
2663
[b"contents"], trans_id)
2664
path = tt._limbo_name(trans_id)
2665
self.assertPathExists(path)
2667
self.assertPathDoesNotExist(path)
2668
self.assertPathDoesNotExist(tt._limbodir)
2670
def test_subdir_create_file_open_raises_before_creation(self):
2671
tt, trans_id = self.create_transform_and_subdir_trans_id()
2672
self._override_globals_in_method(
2673
tt, "create_file", {"open": self._fake_open_raises_before})
2674
self.assertRaises(RuntimeError, tt.create_file,
2675
[b"contents"], trans_id)
2676
path = tt._limbo_name(trans_id)
2677
self.assertPathDoesNotExist(path)
2679
self.assertPathDoesNotExist(tt._limbodir)
2681
def test_subdir_create_file_open_raises_after_creation(self):
2682
tt, trans_id = self.create_transform_and_subdir_trans_id()
2683
self._override_globals_in_method(
2684
tt, "create_file", {"open": self._fake_open_raises_after})
2685
self.assertRaises(RuntimeError, tt.create_file,
2686
[b"contents"], trans_id)
2687
path = tt._limbo_name(trans_id)
2688
self.assertPathExists(path)
2690
self.assertPathDoesNotExist(path)
2691
self.assertPathDoesNotExist(tt._limbodir)
2693
def test_rename_in_limbo_rename_raises_after_rename(self):
2694
tt, trans_id = self.create_transform_and_root_trans_id()
2695
parent1 = tt.new_directory('parent1', tt.root)
2696
child1 = tt.new_file('child1', parent1, [b'contents'])
2697
parent2 = tt.new_directory('parent2', tt.root)
2699
class FakeOSModule(object):
2700
def rename(self, old, new):
2703
self._override_globals_in_method(tt, "_rename_in_limbo",
2704
{"os": FakeOSModule()})
2706
RuntimeError, tt.adjust_path, "child1", parent2, child1)
2707
path = osutils.pathjoin(tt._limbo_name(parent2), "child1")
2708
self.assertPathExists(path)
2710
self.assertPathDoesNotExist(path)
2711
self.assertPathDoesNotExist(tt._limbodir)
2713
def test_rename_in_limbo_rename_raises_before_rename(self):
2714
tt, trans_id = self.create_transform_and_root_trans_id()
2715
parent1 = tt.new_directory('parent1', tt.root)
2716
child1 = tt.new_file('child1', parent1, [b'contents'])
2717
parent2 = tt.new_directory('parent2', tt.root)
2719
class FakeOSModule(object):
2720
def rename(self, old, new):
2722
self._override_globals_in_method(tt, "_rename_in_limbo",
2723
{"os": FakeOSModule()})
2725
RuntimeError, tt.adjust_path, "child1", parent2, child1)
2726
path = osutils.pathjoin(tt._limbo_name(parent1), "child1")
2727
self.assertPathExists(path)
2729
self.assertPathDoesNotExist(path)
2730
self.assertPathDoesNotExist(tt._limbodir)
2733
class TestTransformMissingParent(tests.TestCaseWithTransport):
2735
def make_tt_with_versioned_dir(self):
2736
wt = self.make_branch_and_tree('.')
2737
self.build_tree(['dir/', ])
2738
wt.add(['dir'], [b'dir-id'])
2739
wt.commit('Create dir')
2740
tt = TreeTransform(wt)
2741
self.addCleanup(tt.finalize)
2744
def test_resolve_create_parent_for_versioned_file(self):
2745
wt, tt = self.make_tt_with_versioned_dir()
2746
dir_tid = tt.trans_id_tree_path('dir')
2747
tt.new_file('file', dir_tid, [b'Contents'], file_id=b'file-id')
2748
tt.delete_contents(dir_tid)
2749
tt.unversion_file(dir_tid)
2750
conflicts = resolve_conflicts(tt)
2751
# one conflict for the missing directory, one for the unversioned
2753
self.assertLength(2, conflicts)
2755
def test_non_versioned_file_create_conflict(self):
2756
wt, tt = self.make_tt_with_versioned_dir()
2757
dir_tid = tt.trans_id_tree_path('dir')
2758
tt.new_file('file', dir_tid, [b'Contents'])
2759
tt.delete_contents(dir_tid)
2760
tt.unversion_file(dir_tid)
2761
conflicts = resolve_conflicts(tt)
2762
# no conflicts or rather: orphaning 'file' resolve the 'dir' conflict
2763
self.assertLength(1, conflicts)
2764
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
2768
A_ENTRY = (b'a-id', ('a', 'a'), True, (True, True),
2769
(b'TREE_ROOT', b'TREE_ROOT'), ('a', 'a'), ('file', 'file'),
2771
ROOT_ENTRY = (b'TREE_ROOT', ('', ''), False, (True, True), (None, None),
2772
('', ''), ('directory', 'directory'), (False, False))
2775
class TestTransformPreview(tests.TestCaseWithTransport):
2777
def create_tree(self):
2778
tree = self.make_branch_and_tree('.')
2779
self.build_tree_contents([('a', b'content 1')])
2780
tree.set_root_id(b'TREE_ROOT')
2781
tree.add('a', b'a-id')
2782
tree.commit('rev1', rev_id=b'rev1')
2783
return tree.branch.repository.revision_tree(b'rev1')
2785
def get_empty_preview(self):
2786
repository = self.make_repository('repo')
2787
tree = repository.revision_tree(_mod_revision.NULL_REVISION)
2788
preview = TransformPreview(tree)
2789
self.addCleanup(preview.finalize)
2792
def test_transform_preview(self):
2793
revision_tree = self.create_tree()
2794
preview = TransformPreview(revision_tree)
2795
self.addCleanup(preview.finalize)
2797
def test_transform_preview_tree(self):
2798
revision_tree = self.create_tree()
2799
preview = TransformPreview(revision_tree)
2800
self.addCleanup(preview.finalize)
2801
preview.get_preview_tree()
2803
def test_transform_new_file(self):
2804
revision_tree = self.create_tree()
2805
preview = TransformPreview(revision_tree)
2806
self.addCleanup(preview.finalize)
2807
preview.new_file('file2', preview.root, [b'content B\n'], b'file2-id')
2808
preview_tree = preview.get_preview_tree()
2809
self.assertEqual(preview_tree.kind('file2'), 'file')
2810
with preview_tree.get_file('file2') as f:
2811
self.assertEqual(f.read(), b'content B\n')
2813
def test_diff_preview_tree(self):
2814
revision_tree = self.create_tree()
2815
preview = TransformPreview(revision_tree)
2816
self.addCleanup(preview.finalize)
2817
preview.new_file('file2', preview.root, [b'content B\n'], b'file2-id')
2818
preview_tree = preview.get_preview_tree()
2820
show_diff_trees(revision_tree, preview_tree, out)
2821
lines = out.getvalue().splitlines()
2822
self.assertEqual(lines[0], b"=== added file 'file2'")
2823
# 3 lines of diff administrivia
2824
self.assertEqual(lines[4], b"+content B")
2826
def test_unsupported_symlink_diff(self):
2827
self.requireFeature(SymlinkFeature)
2828
tree = self.make_branch_and_tree('.')
2829
self.build_tree_contents([('a', 'content 1')])
2830
tree.set_root_id(b'TREE_ROOT')
2831
tree.add('a', b'a-id')
2832
os.symlink('a', 'foo')
2833
tree.add('foo', b'foo-id')
2834
tree.commit('rev1', rev_id=b'rev1')
2835
revision_tree = tree.branch.repository.revision_tree(b'rev1')
2836
preview = TransformPreview(revision_tree)
2837
self.addCleanup(preview.finalize)
2838
preview.delete_versioned(preview.trans_id_tree_path('foo'))
2839
preview_tree = preview.get_preview_tree()
2842
trace.push_log_file(log)
2843
os_symlink = getattr(os, 'symlink', None)
2846
show_diff_trees(revision_tree, preview_tree, out)
2847
lines = out.getvalue().splitlines()
2849
os.symlink = os_symlink
2850
self.assertContainsRe(
2852
b'Ignoring "foo" as symlinks are not supported on this filesystem')
2854
def test_transform_conflicts(self):
2855
revision_tree = self.create_tree()
2856
preview = TransformPreview(revision_tree)
2857
self.addCleanup(preview.finalize)
2858
preview.new_file('a', preview.root, [b'content 2'])
2859
resolve_conflicts(preview)
2860
trans_id = preview.trans_id_file_id(b'a-id')
2861
self.assertEqual('a.moved', preview.final_name(trans_id))
2863
def get_tree_and_preview_tree(self):
2864
revision_tree = self.create_tree()
2865
preview = TransformPreview(revision_tree)
2866
self.addCleanup(preview.finalize)
2867
a_trans_id = preview.trans_id_file_id(b'a-id')
2868
preview.delete_contents(a_trans_id)
2869
preview.create_file([b'b content'], a_trans_id)
2870
preview_tree = preview.get_preview_tree()
2871
return revision_tree, preview_tree
2873
def test_iter_changes(self):
2874
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2875
root = revision_tree.get_root_id()
2876
self.assertEqual([(b'a-id', ('a', 'a'), True, (True, True),
2877
(root, root), ('a', 'a'), ('file', 'file'),
2879
list(preview_tree.iter_changes(revision_tree)))
2881
def test_include_unchanged_succeeds(self):
2882
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2883
changes = preview_tree.iter_changes(revision_tree,
2884
include_unchanged=True)
2885
self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
2887
def test_specific_files(self):
2888
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2889
changes = preview_tree.iter_changes(revision_tree,
2890
specific_files=[''])
2891
self.assertEqual([A_ENTRY], list(changes))
2893
def test_want_unversioned(self):
2894
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2895
changes = preview_tree.iter_changes(revision_tree,
2896
want_unversioned=True)
2897
self.assertEqual([A_ENTRY], list(changes))
2899
def test_ignore_extra_trees_no_specific_files(self):
2900
# extra_trees is harmless without specific_files, so we'll silently
2901
# accept it, even though we won't use it.
2902
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2903
preview_tree.iter_changes(revision_tree, extra_trees=[preview_tree])
2905
def test_ignore_require_versioned_no_specific_files(self):
2906
# require_versioned is meaningless without specific_files.
2907
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2908
preview_tree.iter_changes(revision_tree, require_versioned=False)
2910
def test_ignore_pb(self):
2911
# pb could be supported, but TT.iter_changes doesn't support it.
2912
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2913
preview_tree.iter_changes(revision_tree)
2915
def test_kind(self):
2916
revision_tree = self.create_tree()
2917
preview = TransformPreview(revision_tree)
2918
self.addCleanup(preview.finalize)
2919
preview.new_file('file', preview.root, [b'contents'], b'file-id')
2920
preview.new_directory('directory', preview.root, b'dir-id')
2921
preview_tree = preview.get_preview_tree()
2922
self.assertEqual('file', preview_tree.kind('file'))
2923
self.assertEqual('directory', preview_tree.kind('directory'))
2925
def test_get_file_mtime(self):
2926
preview = self.get_empty_preview()
2927
file_trans_id = preview.new_file('file', preview.root, [b'contents'],
2929
limbo_path = preview._limbo_name(file_trans_id)
2930
preview_tree = preview.get_preview_tree()
2931
self.assertEqual(os.stat(limbo_path).st_mtime,
2932
preview_tree.get_file_mtime('file'))
2934
def test_get_file_mtime_renamed(self):
2935
work_tree = self.make_branch_and_tree('tree')
2936
self.build_tree(['tree/file'])
2937
work_tree.add('file', b'file-id')
2938
preview = TransformPreview(work_tree)
2939
self.addCleanup(preview.finalize)
2940
file_trans_id = preview.trans_id_tree_path('file')
2941
preview.adjust_path('renamed', preview.root, file_trans_id)
2942
preview_tree = preview.get_preview_tree()
2943
preview_mtime = preview_tree.get_file_mtime('renamed')
2944
work_mtime = work_tree.get_file_mtime('file')
2946
def test_get_file_size(self):
2947
work_tree = self.make_branch_and_tree('tree')
2948
self.build_tree_contents([('tree/old', b'old')])
2949
work_tree.add('old', b'old-id')
2950
preview = TransformPreview(work_tree)
2951
self.addCleanup(preview.finalize)
2952
preview.new_file('name', preview.root, [b'contents'], b'new-id',
2954
tree = preview.get_preview_tree()
2955
self.assertEqual(len('old'), tree.get_file_size('old'))
2956
self.assertEqual(len('contents'), tree.get_file_size('name'))
2958
def test_get_file(self):
2959
preview = self.get_empty_preview()
2960
preview.new_file('file', preview.root, [b'contents'], b'file-id')
2961
preview_tree = preview.get_preview_tree()
2962
with preview_tree.get_file('file') as tree_file:
2963
self.assertEqual(b'contents', tree_file.read())
2965
def test_get_symlink_target(self):
2966
self.requireFeature(SymlinkFeature)
2967
preview = self.get_empty_preview()
2968
preview.new_symlink('symlink', preview.root, 'target', b'symlink-id')
2969
preview_tree = preview.get_preview_tree()
2970
self.assertEqual('target',
2971
preview_tree.get_symlink_target('symlink'))
2973
def test_all_file_ids(self):
2974
tree = self.make_branch_and_tree('tree')
2975
self.build_tree(['tree/a', 'tree/b', 'tree/c'])
2976
tree.add(['a', 'b', 'c'], [b'a-id', b'b-id', b'c-id'])
2977
preview = TransformPreview(tree)
2978
self.addCleanup(preview.finalize)
2979
preview.unversion_file(preview.trans_id_file_id(b'b-id'))
2980
c_trans_id = preview.trans_id_file_id(b'c-id')
2981
preview.unversion_file(c_trans_id)
2982
preview.version_file(b'c-id', c_trans_id)
2983
preview_tree = preview.get_preview_tree()
2984
self.assertEqual({b'a-id', b'c-id', tree.get_root_id()},
2985
preview_tree.all_file_ids())
2987
def test_path2id_deleted_unchanged(self):
2988
tree = self.make_branch_and_tree('tree')
2989
self.build_tree(['tree/unchanged', 'tree/deleted'])
2990
tree.add(['unchanged', 'deleted'], [b'unchanged-id', b'deleted-id'])
2991
preview = TransformPreview(tree)
2992
self.addCleanup(preview.finalize)
2993
preview.unversion_file(preview.trans_id_file_id(b'deleted-id'))
2994
preview_tree = preview.get_preview_tree()
2995
self.assertEqual(b'unchanged-id', preview_tree.path2id('unchanged'))
2996
self.assertFalse(preview_tree.is_versioned('deleted'))
2998
def test_path2id_created(self):
2999
tree = self.make_branch_and_tree('tree')
3000
self.build_tree(['tree/unchanged'])
3001
tree.add(['unchanged'], [b'unchanged-id'])
3002
preview = TransformPreview(tree)
3003
self.addCleanup(preview.finalize)
3004
preview.new_file('new', preview.trans_id_file_id(b'unchanged-id'),
3005
[b'contents'], b'new-id')
3006
preview_tree = preview.get_preview_tree()
3007
self.assertEqual(b'new-id', preview_tree.path2id('unchanged/new'))
3009
def test_path2id_moved(self):
3010
tree = self.make_branch_and_tree('tree')
3011
self.build_tree(['tree/old_parent/', 'tree/old_parent/child'])
3012
tree.add(['old_parent', 'old_parent/child'],
3013
[b'old_parent-id', b'child-id'])
3014
preview = TransformPreview(tree)
3015
self.addCleanup(preview.finalize)
3016
new_parent = preview.new_directory('new_parent', preview.root,
3018
preview.adjust_path('child', new_parent,
3019
preview.trans_id_file_id(b'child-id'))
3020
preview_tree = preview.get_preview_tree()
3021
self.assertFalse(preview_tree.is_versioned('old_parent/child'))
3022
self.assertEqual(b'child-id', preview_tree.path2id('new_parent/child'))
3024
def test_path2id_renamed_parent(self):
3025
tree = self.make_branch_and_tree('tree')
3026
self.build_tree(['tree/old_name/', 'tree/old_name/child'])
3027
tree.add(['old_name', 'old_name/child'],
3028
[b'parent-id', b'child-id'])
3029
preview = TransformPreview(tree)
3030
self.addCleanup(preview.finalize)
3031
preview.adjust_path('new_name', preview.root,
3032
preview.trans_id_file_id(b'parent-id'))
3033
preview_tree = preview.get_preview_tree()
3034
self.assertFalse(preview_tree.is_versioned('old_name/child'))
3035
self.assertEqual(b'child-id', preview_tree.path2id('new_name/child'))
3037
def assertMatchingIterEntries(self, tt, specific_files=None):
3038
preview_tree = tt.get_preview_tree()
3039
preview_result = list(preview_tree.iter_entries_by_dir(
3040
specific_files=specific_files))
3043
actual_result = list(tree.iter_entries_by_dir(
3044
specific_files=specific_files))
3045
self.assertEqual(actual_result, preview_result)
3047
def test_iter_entries_by_dir_new(self):
3048
tree = self.make_branch_and_tree('tree')
3049
tt = TreeTransform(tree)
3050
tt.new_file('new', tt.root, [b'contents'], b'new-id')
3051
self.assertMatchingIterEntries(tt)
3053
def test_iter_entries_by_dir_deleted(self):
3054
tree = self.make_branch_and_tree('tree')
3055
self.build_tree(['tree/deleted'])
3056
tree.add('deleted', b'deleted-id')
3057
tt = TreeTransform(tree)
3058
tt.delete_contents(tt.trans_id_file_id(b'deleted-id'))
3059
self.assertMatchingIterEntries(tt)
3061
def test_iter_entries_by_dir_unversioned(self):
3062
tree = self.make_branch_and_tree('tree')
3063
self.build_tree(['tree/removed'])
3064
tree.add('removed', b'removed-id')
3065
tt = TreeTransform(tree)
3066
tt.unversion_file(tt.trans_id_file_id(b'removed-id'))
3067
self.assertMatchingIterEntries(tt)
3069
def test_iter_entries_by_dir_moved(self):
3070
tree = self.make_branch_and_tree('tree')
3071
self.build_tree(['tree/moved', 'tree/new_parent/'])
3072
tree.add(['moved', 'new_parent'], [b'moved-id', b'new_parent-id'])
3073
tt = TreeTransform(tree)
3074
tt.adjust_path('moved', tt.trans_id_file_id(b'new_parent-id'),
3075
tt.trans_id_file_id(b'moved-id'))
3076
self.assertMatchingIterEntries(tt)
3078
def test_iter_entries_by_dir_specific_files(self):
3079
tree = self.make_branch_and_tree('tree')
3080
tree.set_root_id(b'tree-root-id')
3081
self.build_tree(['tree/parent/', 'tree/parent/child'])
3082
tree.add(['parent', 'parent/child'], [b'parent-id', b'child-id'])
3083
tt = TreeTransform(tree)
3084
self.assertMatchingIterEntries(tt, ['', 'parent/child'])
3086
def test_symlink_content_summary(self):
3087
self.requireFeature(SymlinkFeature)
3088
preview = self.get_empty_preview()
3089
preview.new_symlink('path', preview.root, 'target', b'path-id')
3090
summary = preview.get_preview_tree().path_content_summary('path')
3091
self.assertEqual(('symlink', None, None, 'target'), summary)
3093
def test_missing_content_summary(self):
3094
preview = self.get_empty_preview()
3095
summary = preview.get_preview_tree().path_content_summary('path')
3096
self.assertEqual(('missing', None, None, None), summary)
3098
def test_deleted_content_summary(self):
3099
tree = self.make_branch_and_tree('tree')
3100
self.build_tree(['tree/path/'])
3102
preview = TransformPreview(tree)
3103
self.addCleanup(preview.finalize)
3104
preview.delete_contents(preview.trans_id_tree_path('path'))
3105
summary = preview.get_preview_tree().path_content_summary('path')
3106
self.assertEqual(('missing', None, None, None), summary)
3108
def test_file_content_summary_executable(self):
3109
preview = self.get_empty_preview()
3110
path_id = preview.new_file('path', preview.root, [
3111
b'contents'], b'path-id')
3112
preview.set_executability(True, path_id)
3113
summary = preview.get_preview_tree().path_content_summary('path')
3114
self.assertEqual(4, len(summary))
3115
self.assertEqual('file', summary[0])
3116
# size must be known
3117
self.assertEqual(len('contents'), summary[1])
3119
self.assertEqual(True, summary[2])
3120
# will not have hash (not cheap to determine)
3121
self.assertIs(None, summary[3])
3123
def test_change_executability(self):
3124
tree = self.make_branch_and_tree('tree')
3125
self.build_tree(['tree/path'])
3127
preview = TransformPreview(tree)
3128
self.addCleanup(preview.finalize)
3129
path_id = preview.trans_id_tree_path('path')
3130
preview.set_executability(True, path_id)
3131
summary = preview.get_preview_tree().path_content_summary('path')
3132
self.assertEqual(True, summary[2])
3134
def test_file_content_summary_non_exec(self):
3135
preview = self.get_empty_preview()
3136
preview.new_file('path', preview.root, [b'contents'], b'path-id')
3137
summary = preview.get_preview_tree().path_content_summary('path')
3138
self.assertEqual(4, len(summary))
3139
self.assertEqual('file', summary[0])
3140
# size must be known
3141
self.assertEqual(len('contents'), summary[1])
3143
self.assertEqual(False, summary[2])
3144
# will not have hash (not cheap to determine)
3145
self.assertIs(None, summary[3])
3147
def test_dir_content_summary(self):
3148
preview = self.get_empty_preview()
3149
preview.new_directory('path', preview.root, b'path-id')
3150
summary = preview.get_preview_tree().path_content_summary('path')
3151
self.assertEqual(('directory', None, None, None), summary)
3153
def test_tree_content_summary(self):
3154
preview = self.get_empty_preview()
3155
path = preview.new_directory('path', preview.root, b'path-id')
3156
preview.set_tree_reference(b'rev-1', path)
3157
summary = preview.get_preview_tree().path_content_summary('path')
3158
self.assertEqual(4, len(summary))
3159
self.assertEqual('tree-reference', summary[0])
3161
def test_annotate(self):
3162
tree = self.make_branch_and_tree('tree')
3163
self.build_tree_contents([('tree/file', b'a\n')])
3164
tree.add('file', b'file-id')
3165
tree.commit('a', rev_id=b'one')
3166
self.build_tree_contents([('tree/file', b'a\nb\n')])
3167
preview = TransformPreview(tree)
3168
self.addCleanup(preview.finalize)
3169
file_trans_id = preview.trans_id_file_id(b'file-id')
3170
preview.delete_contents(file_trans_id)
3171
preview.create_file([b'a\nb\nc\n'], file_trans_id)
3172
preview_tree = preview.get_preview_tree()
3178
annotation = preview_tree.annotate_iter(
3179
'file', default_revision=b'me:')
3180
self.assertEqual(expected, annotation)
3182
def test_annotate_missing(self):
3183
preview = self.get_empty_preview()
3184
preview.new_file('file', preview.root, [b'a\nb\nc\n'], b'file-id')
3185
preview_tree = preview.get_preview_tree()
3191
annotation = preview_tree.annotate_iter(
3192
'file', default_revision=b'me:')
3193
self.assertEqual(expected, annotation)
3195
def test_annotate_rename(self):
3196
tree = self.make_branch_and_tree('tree')
3197
self.build_tree_contents([('tree/file', b'a\n')])
3198
tree.add('file', b'file-id')
3199
tree.commit('a', rev_id=b'one')
3200
preview = TransformPreview(tree)
3201
self.addCleanup(preview.finalize)
3202
file_trans_id = preview.trans_id_file_id(b'file-id')
3203
preview.adjust_path('newname', preview.root, file_trans_id)
3204
preview_tree = preview.get_preview_tree()
3208
annotation = preview_tree.annotate_iter(
3209
'file', default_revision=b'me:')
3210
self.assertEqual(expected, annotation)
3212
def test_annotate_deleted(self):
3213
tree = self.make_branch_and_tree('tree')
3214
self.build_tree_contents([('tree/file', b'a\n')])
3215
tree.add('file', b'file-id')
3216
tree.commit('a', rev_id=b'one')
3217
self.build_tree_contents([('tree/file', b'a\nb\n')])
3218
preview = TransformPreview(tree)
3219
self.addCleanup(preview.finalize)
3220
file_trans_id = preview.trans_id_file_id(b'file-id')
3221
preview.delete_contents(file_trans_id)
3222
preview_tree = preview.get_preview_tree()
3223
annotation = preview_tree.annotate_iter(
3224
'file', default_revision=b'me:')
3225
self.assertIs(None, annotation)
3227
def test_stored_kind(self):
3228
preview = self.get_empty_preview()
3229
preview.new_file('file', preview.root, [b'a\nb\nc\n'], b'file-id')
3230
preview_tree = preview.get_preview_tree()
3231
self.assertEqual('file', preview_tree.stored_kind('file'))
3233
def test_is_executable(self):
3234
preview = self.get_empty_preview()
3235
preview.new_file('file', preview.root, [b'a\nb\nc\n'], b'file-id')
3236
preview.set_executability(True, preview.trans_id_file_id(b'file-id'))
3237
preview_tree = preview.get_preview_tree()
3238
self.assertEqual(True, preview_tree.is_executable('file'))
3240
def test_get_set_parent_ids(self):
3241
revision_tree, preview_tree = self.get_tree_and_preview_tree()
3242
self.assertEqual([], preview_tree.get_parent_ids())
3243
preview_tree.set_parent_ids([b'rev-1'])
3244
self.assertEqual([b'rev-1'], preview_tree.get_parent_ids())
3246
def test_plan_file_merge(self):
3247
work_a = self.make_branch_and_tree('wta')
3248
self.build_tree_contents([('wta/file', b'a\nb\nc\nd\n')])
3249
work_a.add('file', b'file-id')
3250
base_id = work_a.commit('base version')
3251
tree_b = work_a.controldir.sprout('wtb').open_workingtree()
3252
preview = TransformPreview(work_a)
3253
self.addCleanup(preview.finalize)
3254
trans_id = preview.trans_id_file_id(b'file-id')
3255
preview.delete_contents(trans_id)
3256
preview.create_file([b'b\nc\nd\ne\n'], trans_id)
3257
self.build_tree_contents([('wtb/file', b'a\nc\nd\nf\n')])
3258
tree_a = preview.get_preview_tree()
3259
tree_a.set_parent_ids([base_id])
3261
('killed-a', b'a\n'),
3262
('killed-b', b'b\n'),
3263
('unchanged', b'c\n'),
3264
('unchanged', b'd\n'),
3267
], list(tree_a.plan_file_merge('file', tree_b)))
3269
def test_plan_file_merge_revision_tree(self):
3270
work_a = self.make_branch_and_tree('wta')
3271
self.build_tree_contents([('wta/file', b'a\nb\nc\nd\n')])
3272
work_a.add('file', b'file-id')
3273
base_id = work_a.commit('base version')
3274
tree_b = work_a.controldir.sprout('wtb').open_workingtree()
3275
preview = TransformPreview(work_a.basis_tree())
3276
self.addCleanup(preview.finalize)
3277
trans_id = preview.trans_id_file_id(b'file-id')
3278
preview.delete_contents(trans_id)
3279
preview.create_file([b'b\nc\nd\ne\n'], trans_id)
3280
self.build_tree_contents([('wtb/file', b'a\nc\nd\nf\n')])
3281
tree_a = preview.get_preview_tree()
3282
tree_a.set_parent_ids([base_id])
3284
('killed-a', b'a\n'),
3285
('killed-b', b'b\n'),
3286
('unchanged', b'c\n'),
3287
('unchanged', b'd\n'),
3290
], list(tree_a.plan_file_merge('file', tree_b)))
3292
def test_walkdirs(self):
3293
preview = self.get_empty_preview()
3294
preview.new_directory('', ROOT_PARENT, b'tree-root')
3295
# FIXME: new_directory should mark root.
3296
preview.fixup_new_roots()
3297
preview_tree = preview.get_preview_tree()
3298
preview.new_file('a', preview.root, [b'contents'], b'a-id')
3299
expected = [(('', b'tree-root'),
3300
[('a', 'a', 'file', None, b'a-id', 'file')])]
3301
self.assertEqual(expected, list(preview_tree.walkdirs()))
3303
def test_extras(self):
3304
work_tree = self.make_branch_and_tree('tree')
3305
self.build_tree(['tree/removed-file', 'tree/existing-file',
3306
'tree/not-removed-file'])
3307
work_tree.add(['removed-file', 'not-removed-file'])
3308
preview = TransformPreview(work_tree)
3309
self.addCleanup(preview.finalize)
3310
preview.new_file('new-file', preview.root, [b'contents'])
3311
preview.new_file('new-versioned-file', preview.root, [b'contents'],
3312
b'new-versioned-id')
3313
tree = preview.get_preview_tree()
3314
preview.unversion_file(preview.trans_id_tree_path('removed-file'))
3315
self.assertEqual({'new-file', 'removed-file', 'existing-file'},
3318
def test_merge_into_preview(self):
3319
work_tree = self.make_branch_and_tree('tree')
3320
self.build_tree_contents([('tree/file', b'b\n')])
3321
work_tree.add('file', b'file-id')
3322
work_tree.commit('first commit')
3323
child_tree = work_tree.controldir.sprout('child').open_workingtree()
3324
self.build_tree_contents([('child/file', b'b\nc\n')])
3325
child_tree.commit('child commit')
3326
child_tree.lock_write()
3327
self.addCleanup(child_tree.unlock)
3328
work_tree.lock_write()
3329
self.addCleanup(work_tree.unlock)
3330
preview = TransformPreview(work_tree)
3331
self.addCleanup(preview.finalize)
3332
file_trans_id = preview.trans_id_file_id(b'file-id')
3333
preview.delete_contents(file_trans_id)
3334
preview.create_file([b'a\nb\n'], file_trans_id)
3335
preview_tree = preview.get_preview_tree()
3336
merger = Merger.from_revision_ids(preview_tree,
3337
child_tree.branch.last_revision(),
3338
other_branch=child_tree.branch,
3339
tree_branch=work_tree.branch)
3340
merger.merge_type = Merge3Merger
3341
tt = merger.make_merger().make_preview_transform()
3342
self.addCleanup(tt.finalize)
3343
final_tree = tt.get_preview_tree()
3346
final_tree.get_file_text(final_tree.id2path(b'file-id')))
3348
def test_merge_preview_into_workingtree(self):
3349
tree = self.make_branch_and_tree('tree')
3350
tree.set_root_id(b'TREE_ROOT')
3351
tt = TransformPreview(tree)
3352
self.addCleanup(tt.finalize)
3353
tt.new_file('name', tt.root, [b'content'], b'file-id')
3354
tree2 = self.make_branch_and_tree('tree2')
3355
tree2.set_root_id(b'TREE_ROOT')
3356
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
3358
merger.merge_type = Merge3Merger
3361
def test_merge_preview_into_workingtree_handles_conflicts(self):
3362
tree = self.make_branch_and_tree('tree')
3363
self.build_tree_contents([('tree/foo', b'bar')])
3364
tree.add('foo', b'foo-id')
3366
tt = TransformPreview(tree)
3367
self.addCleanup(tt.finalize)
3368
trans_id = tt.trans_id_file_id(b'foo-id')
3369
tt.delete_contents(trans_id)
3370
tt.create_file([b'baz'], trans_id)
3371
tree2 = tree.controldir.sprout('tree2').open_workingtree()
3372
self.build_tree_contents([('tree2/foo', b'qux')])
3373
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
3375
merger.merge_type = Merge3Merger
3378
def test_has_filename(self):
3379
wt = self.make_branch_and_tree('tree')
3380
self.build_tree(['tree/unmodified', 'tree/removed', 'tree/modified'])
3381
tt = TransformPreview(wt)
3382
removed_id = tt.trans_id_tree_path('removed')
3383
tt.delete_contents(removed_id)
3384
tt.new_file('new', tt.root, [b'contents'])
3385
modified_id = tt.trans_id_tree_path('modified')
3386
tt.delete_contents(modified_id)
3387
tt.create_file([b'modified-contents'], modified_id)
3388
self.addCleanup(tt.finalize)
3389
tree = tt.get_preview_tree()
3390
self.assertTrue(tree.has_filename('unmodified'))
3391
self.assertFalse(tree.has_filename('not-present'))
3392
self.assertFalse(tree.has_filename('removed'))
3393
self.assertTrue(tree.has_filename('new'))
3394
self.assertTrue(tree.has_filename('modified'))
3396
def test_is_executable(self):
3397
tree = self.make_branch_and_tree('tree')
3398
preview = TransformPreview(tree)
3399
self.addCleanup(preview.finalize)
3400
preview.new_file('foo', preview.root, [b'bar'], b'baz-id')
3401
preview_tree = preview.get_preview_tree()
3402
self.assertEqual(False, preview_tree.is_executable('tree/foo'))
3404
def test_commit_preview_tree(self):
3405
tree = self.make_branch_and_tree('tree')
3406
rev_id = tree.commit('rev1')
3407
tree.branch.lock_write()
3408
self.addCleanup(tree.branch.unlock)
3409
tt = TransformPreview(tree)
3410
tt.new_file('file', tt.root, [b'contents'], b'file_id')
3411
self.addCleanup(tt.finalize)
3412
preview = tt.get_preview_tree()
3413
preview.set_parent_ids([rev_id])
3414
builder = tree.branch.get_commit_builder([rev_id])
3415
list(builder.record_iter_changes(preview, rev_id, tt.iter_changes()))
3416
builder.finish_inventory()
3417
rev2_id = builder.commit('rev2')
3418
rev2_tree = tree.branch.repository.revision_tree(rev2_id)
3419
self.assertEqual(b'contents', rev2_tree.get_file_text('file'))
3421
def test_ascii_limbo_paths(self):
3422
self.requireFeature(features.UnicodeFilenameFeature)
3423
branch = self.make_branch('any')
3424
tree = branch.repository.revision_tree(_mod_revision.NULL_REVISION)
3425
tt = TransformPreview(tree)
3426
self.addCleanup(tt.finalize)
3427
foo_id = tt.new_directory('', ROOT_PARENT)
3428
bar_id = tt.new_file(u'\u1234bar', foo_id, [b'contents'])
3429
limbo_path = tt._limbo_name(bar_id)
3430
self.assertEqual(limbo_path, limbo_path)
3433
class FakeSerializer(object):
3434
"""Serializer implementation that simply returns the input.
3436
The input is returned in the order used by pack.ContainerPushParser.
3439
def bytes_record(bytes, names):
3443
class TestSerializeTransform(tests.TestCaseWithTransport):
3445
_test_needs_features = [features.UnicodeFilenameFeature]
3447
def get_preview(self, tree=None):
3449
tree = self.make_branch_and_tree('tree')
3450
tt = TransformPreview(tree)
3451
self.addCleanup(tt.finalize)
3454
def assertSerializesTo(self, expected, tt):
3455
records = list(tt.serialize(FakeSerializer()))
3456
self.assertEqual(expected, records)
3459
def default_attribs():
3464
b'_new_executability': {},
3466
b'_tree_path_ids': {b'': b'new-0'},
3468
b'_removed_contents': [],
3469
b'_non_present_ids': {},
3472
def make_records(self, attribs, contents):
3474
((((b'attribs'),),), bencode.bencode(attribs))]
3475
records.extend([(((n, k),), c) for n, k, c in contents])
3478
def creation_records(self):
3479
attribs = self.default_attribs()
3480
attribs[b'_id_number'] = 3
3481
attribs[b'_new_name'] = {
3482
b'new-1': u'foo\u1234'.encode('utf-8'), b'new-2': b'qux'}
3483
attribs[b'_new_id'] = {b'new-1': b'baz', b'new-2': b'quxx'}
3484
attribs[b'_new_parent'] = {b'new-1': b'new-0', b'new-2': b'new-0'}
3485
attribs[b'_new_executability'] = {b'new-1': 1}
3487
(b'new-1', b'file', b'i 1\nbar\n'),
3488
(b'new-2', b'directory', b''),
3490
return self.make_records(attribs, contents)
3492
def test_serialize_creation(self):
3493
tt = self.get_preview()
3494
tt.new_file(u'foo\u1234', tt.root, [b'bar'], b'baz', True)
3495
tt.new_directory('qux', tt.root, b'quxx')
3496
self.assertSerializesTo(self.creation_records(), tt)
3498
def test_deserialize_creation(self):
3499
tt = self.get_preview()
3500
tt.deserialize(iter(self.creation_records()))
3501
self.assertEqual(3, tt._id_number)
3502
self.assertEqual({'new-1': u'foo\u1234',
3503
'new-2': 'qux'}, tt._new_name)
3504
self.assertEqual({'new-1': b'baz', 'new-2': b'quxx'}, tt._new_id)
3505
self.assertEqual({'new-1': tt.root, 'new-2': tt.root}, tt._new_parent)
3506
self.assertEqual({b'baz': 'new-1', b'quxx': 'new-2'}, tt._r_new_id)
3507
self.assertEqual({'new-1': True}, tt._new_executability)
3508
self.assertEqual({'new-1': 'file',
3509
'new-2': 'directory'}, tt._new_contents)
3510
foo_limbo = open(tt._limbo_name('new-1'), 'rb')
3512
foo_content = foo_limbo.read()
3515
self.assertEqual(b'bar', foo_content)
3517
def symlink_creation_records(self):
3518
attribs = self.default_attribs()
3519
attribs[b'_id_number'] = 2
3520
attribs[b'_new_name'] = {b'new-1': u'foo\u1234'.encode('utf-8')}
3521
attribs[b'_new_parent'] = {b'new-1': b'new-0'}
3522
contents = [(b'new-1', b'symlink', u'bar\u1234'.encode('utf-8'))]
3523
return self.make_records(attribs, contents)
3525
def test_serialize_symlink_creation(self):
3526
self.requireFeature(features.SymlinkFeature)
3527
tt = self.get_preview()
3528
tt.new_symlink(u'foo\u1234', tt.root, u'bar\u1234')
3529
self.assertSerializesTo(self.symlink_creation_records(), tt)
3531
def test_deserialize_symlink_creation(self):
3532
self.requireFeature(features.SymlinkFeature)
3533
tt = self.get_preview()
3534
tt.deserialize(iter(self.symlink_creation_records()))
3535
abspath = tt._limbo_name('new-1')
3536
foo_content = osutils.readlink(abspath)
3537
self.assertEqual(u'bar\u1234', foo_content)
3539
def make_destruction_preview(self):
3540
tree = self.make_branch_and_tree('.')
3541
self.build_tree([u'foo\u1234', 'bar'])
3542
tree.add([u'foo\u1234', 'bar'], [b'foo-id', b'bar-id'])
3543
return self.get_preview(tree)
3545
def destruction_records(self):
3546
attribs = self.default_attribs()
3547
attribs[b'_id_number'] = 3
3548
attribs[b'_removed_id'] = [b'new-1']
3549
attribs[b'_removed_contents'] = [b'new-2']
3550
attribs[b'_tree_path_ids'] = {
3552
u'foo\u1234'.encode('utf-8'): b'new-1',
3555
return self.make_records(attribs, [])
3557
def test_serialize_destruction(self):
3558
tt = self.make_destruction_preview()
3559
foo_trans_id = tt.trans_id_tree_path(u'foo\u1234')
3560
tt.unversion_file(foo_trans_id)
3561
bar_trans_id = tt.trans_id_tree_path('bar')
3562
tt.delete_contents(bar_trans_id)
3563
self.assertSerializesTo(self.destruction_records(), tt)
3565
def test_deserialize_destruction(self):
3566
tt = self.make_destruction_preview()
3567
tt.deserialize(iter(self.destruction_records()))
3568
self.assertEqual({u'foo\u1234': 'new-1',
3570
'': tt.root}, tt._tree_path_ids)
3571
self.assertEqual({'new-1': u'foo\u1234',
3573
tt.root: ''}, tt._tree_id_paths)
3574
self.assertEqual({'new-1'}, tt._removed_id)
3575
self.assertEqual({'new-2'}, tt._removed_contents)
3577
def missing_records(self):
3578
attribs = self.default_attribs()
3579
attribs[b'_id_number'] = 2
3580
attribs[b'_non_present_ids'] = {
3582
return self.make_records(attribs, [])
3584
def test_serialize_missing(self):
3585
tt = self.get_preview()
3586
tt.trans_id_file_id(b'boo')
3587
self.assertSerializesTo(self.missing_records(), tt)
3589
def test_deserialize_missing(self):
3590
tt = self.get_preview()
3591
tt.deserialize(iter(self.missing_records()))
3592
self.assertEqual({b'boo': 'new-1'}, tt._non_present_ids)
3594
def make_modification_preview(self):
3595
LINES_ONE = b'aa\nbb\ncc\ndd\n'
3596
LINES_TWO = b'z\nbb\nx\ndd\n'
3597
tree = self.make_branch_and_tree('tree')
3598
self.build_tree_contents([('tree/file', LINES_ONE)])
3599
tree.add('file', b'file-id')
3600
return self.get_preview(tree), [LINES_TWO]
3602
def modification_records(self):
3603
attribs = self.default_attribs()
3604
attribs[b'_id_number'] = 2
3605
attribs[b'_tree_path_ids'] = {
3608
attribs[b'_removed_contents'] = [b'new-1']
3609
contents = [(b'new-1', b'file',
3610
b'i 1\nz\n\nc 0 1 1 1\ni 1\nx\n\nc 0 3 3 1\n')]
3611
return self.make_records(attribs, contents)
3613
def test_serialize_modification(self):
3614
tt, LINES = self.make_modification_preview()
3615
trans_id = tt.trans_id_file_id(b'file-id')
3616
tt.delete_contents(trans_id)
3617
tt.create_file(LINES, trans_id)
3618
self.assertSerializesTo(self.modification_records(), tt)
3620
def test_deserialize_modification(self):
3621
tt, LINES = self.make_modification_preview()
3622
tt.deserialize(iter(self.modification_records()))
3623
self.assertFileEqual(b''.join(LINES), tt._limbo_name('new-1'))
3625
def make_kind_change_preview(self):
3626
LINES = b'a\nb\nc\nd\n'
3627
tree = self.make_branch_and_tree('tree')
3628
self.build_tree(['tree/foo/'])
3629
tree.add('foo', b'foo-id')
3630
return self.get_preview(tree), [LINES]
3632
def kind_change_records(self):
3633
attribs = self.default_attribs()
3634
attribs[b'_id_number'] = 2
3635
attribs[b'_tree_path_ids'] = {
3638
attribs[b'_removed_contents'] = [b'new-1']
3639
contents = [(b'new-1', b'file',
3640
b'i 4\na\nb\nc\nd\n\n')]
3641
return self.make_records(attribs, contents)
3643
def test_serialize_kind_change(self):
3644
tt, LINES = self.make_kind_change_preview()
3645
trans_id = tt.trans_id_file_id(b'foo-id')
3646
tt.delete_contents(trans_id)
3647
tt.create_file(LINES, trans_id)
3648
self.assertSerializesTo(self.kind_change_records(), tt)
3650
def test_deserialize_kind_change(self):
3651
tt, LINES = self.make_kind_change_preview()
3652
tt.deserialize(iter(self.kind_change_records()))
3653
self.assertFileEqual(b''.join(LINES), tt._limbo_name('new-1'))
3655
def make_add_contents_preview(self):
3656
LINES = b'a\nb\nc\nd\n'
3657
tree = self.make_branch_and_tree('tree')
3658
self.build_tree(['tree/foo'])
3660
os.unlink('tree/foo')
3661
return self.get_preview(tree), LINES
3663
def add_contents_records(self):
3664
attribs = self.default_attribs()
3665
attribs[b'_id_number'] = 2
3666
attribs[b'_tree_path_ids'] = {
3669
contents = [(b'new-1', b'file',
3670
b'i 4\na\nb\nc\nd\n\n')]
3671
return self.make_records(attribs, contents)
3673
def test_serialize_add_contents(self):
3674
tt, LINES = self.make_add_contents_preview()
3675
trans_id = tt.trans_id_tree_path('foo')
3676
tt.create_file([LINES], trans_id)
3677
self.assertSerializesTo(self.add_contents_records(), tt)
3679
def test_deserialize_add_contents(self):
3680
tt, LINES = self.make_add_contents_preview()
3681
tt.deserialize(iter(self.add_contents_records()))
3682
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
3684
def test_get_parents_lines(self):
3685
LINES_ONE = b'aa\nbb\ncc\ndd\n'
3686
tree = self.make_branch_and_tree('tree')
3687
self.build_tree_contents([('tree/file', LINES_ONE)])
3688
tree.add('file', b'file-id')
3689
tt = self.get_preview(tree)
3690
trans_id = tt.trans_id_tree_path('file')
3691
self.assertEqual(([b'aa\n', b'bb\n', b'cc\n', b'dd\n'],),
3692
tt._get_parents_lines(trans_id))
3694
def test_get_parents_texts(self):
3695
LINES_ONE = b'aa\nbb\ncc\ndd\n'
3696
tree = self.make_branch_and_tree('tree')
3697
self.build_tree_contents([('tree/file', LINES_ONE)])
3698
tree.add('file', b'file-id')
3699
tt = self.get_preview(tree)
3700
trans_id = tt.trans_id_tree_path('file')
3701
self.assertEqual((LINES_ONE,),
3702
tt._get_parents_texts(trans_id))
3705
class TestOrphan(tests.TestCaseWithTransport):
3707
def test_no_orphan_for_transform_preview(self):
3708
tree = self.make_branch_and_tree('tree')
3709
tt = transform.TransformPreview(tree)
3710
self.addCleanup(tt.finalize)
3711
self.assertRaises(NotImplementedError, tt.new_orphan, 'foo', 'bar')
3713
def _set_orphan_policy(self, wt, policy):
3714
wt.branch.get_config_stack().set('transform.orphan_policy',
3717
def _prepare_orphan(self, wt):
3718
self.build_tree(['dir/', 'dir/file', 'dir/foo'])
3719
wt.add(['dir', 'dir/file'], [b'dir-id', b'file-id'])
3720
wt.commit('add dir and file ignoring foo')
3721
tt = transform.TreeTransform(wt)
3722
self.addCleanup(tt.finalize)
3723
# dir and bar are deleted
3724
dir_tid = tt.trans_id_tree_path('dir')
3725
file_tid = tt.trans_id_tree_path('dir/file')
3726
orphan_tid = tt.trans_id_tree_path('dir/foo')
3727
tt.delete_contents(file_tid)
3728
tt.unversion_file(file_tid)
3729
tt.delete_contents(dir_tid)
3730
tt.unversion_file(dir_tid)
3731
# There should be a conflict because dir still contain foo
3732
raw_conflicts = tt.find_conflicts()
3733
self.assertLength(1, raw_conflicts)
3734
self.assertEqual(('missing parent', 'new-1'), raw_conflicts[0])
3735
return tt, orphan_tid
3737
def test_new_orphan_created(self):
3738
wt = self.make_branch_and_tree('.')
3739
self._set_orphan_policy(wt, 'move')
3740
tt, orphan_tid = self._prepare_orphan(wt)
3744
warnings.append(args[0] % args[1:])
3745
self.overrideAttr(trace, 'warning', warning)
3746
remaining_conflicts = resolve_conflicts(tt)
3747
self.assertEqual(['dir/foo has been orphaned in brz-orphans'],
3749
# Yeah for resolved conflicts !
3750
self.assertLength(0, remaining_conflicts)
3751
# We have a new orphan
3752
self.assertEqual('foo.~1~', tt.final_name(orphan_tid))
3753
self.assertEqual('brz-orphans',
3754
tt.final_name(tt.final_parent(orphan_tid)))
3756
def test_never_orphan(self):
3757
wt = self.make_branch_and_tree('.')
3758
self._set_orphan_policy(wt, 'conflict')
3759
tt, orphan_tid = self._prepare_orphan(wt)
3760
remaining_conflicts = resolve_conflicts(tt)
3761
self.assertLength(1, remaining_conflicts)
3762
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
3763
remaining_conflicts.pop())
3765
def test_orphan_error(self):
3766
def bogus_orphan(tt, orphan_id, parent_id):
3767
raise transform.OrphaningError(tt.final_name(orphan_id),
3768
tt.final_name(parent_id))
3769
transform.orphaning_registry.register('bogus', bogus_orphan,
3770
'Raise an error when orphaning')
3771
wt = self.make_branch_and_tree('.')
3772
self._set_orphan_policy(wt, 'bogus')
3773
tt, orphan_tid = self._prepare_orphan(wt)
3774
remaining_conflicts = resolve_conflicts(tt)
3775
self.assertLength(1, remaining_conflicts)
3776
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
3777
remaining_conflicts.pop())
3779
def test_unknown_orphan_policy(self):
3780
wt = self.make_branch_and_tree('.')
3781
# Set a fictional policy nobody ever implemented
3782
self._set_orphan_policy(wt, 'donttouchmypreciouuus')
3783
tt, orphan_tid = self._prepare_orphan(wt)
3787
warnings.append(args[0] % args[1:])
3788
self.overrideAttr(trace, 'warning', warning)
3789
remaining_conflicts = resolve_conflicts(tt)
3790
# We fallback to the default policy which create a conflict
3791
self.assertLength(1, remaining_conflicts)
3792
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
3793
remaining_conflicts.pop())
3794
self.assertLength(1, warnings)
3795
self.assertStartsWith(warnings[0], 'Value "donttouchmypreciouuus" ')
3798
class TestTransformHooks(tests.TestCaseWithTransport):
3801
super(TestTransformHooks, self).setUp()
3802
self.wt = self.make_branch_and_tree('.')
3805
def get_transform(self):
3806
transform = TreeTransform(self.wt)
3807
self.addCleanup(transform.finalize)
3808
return transform, transform.root
3810
def test_pre_commit_hooks(self):
3813
def record_pre_transform(tree, tt):
3814
calls.append((tree, tt))
3815
MutableTree.hooks.install_named_hook(
3816
'pre_transform', record_pre_transform, "Pre transform")
3817
transform, root = self.get_transform()
3818
old_root_id = transform.tree_file_id(root)
3820
self.assertEqual(old_root_id, self.wt.get_root_id())
3821
self.assertEqual([(self.wt, transform)], calls)
3823
def test_post_commit_hooks(self):
3826
def record_post_transform(tree, tt):
3827
calls.append((tree, tt))
3828
MutableTree.hooks.install_named_hook(
3829
'post_transform', record_post_transform, "Post transform")
3830
transform, root = self.get_transform()
3831
old_root_id = transform.tree_file_id(root)
3833
self.assertEqual(old_root_id, self.wt.get_root_id())
3834
self.assertEqual([(self.wt, transform)], calls)
3837
class TestLinkTree(tests.TestCaseWithTransport):
3839
_test_needs_features = [HardlinkFeature]
3842
tests.TestCaseWithTransport.setUp(self)
3843
self.parent_tree = self.make_branch_and_tree('parent')
3844
self.parent_tree.lock_write()
3845
self.addCleanup(self.parent_tree.unlock)
3846
self.build_tree_contents([('parent/foo', b'bar')])
3847
self.parent_tree.add('foo')
3848
self.parent_tree.commit('added foo')
3849
child_controldir = self.parent_tree.controldir.sprout('child')
3850
self.child_tree = child_controldir.open_workingtree()
3852
def hardlinked(self):
3853
parent_stat = os.lstat(self.parent_tree.abspath('foo'))
3854
child_stat = os.lstat(self.child_tree.abspath('foo'))
3855
return parent_stat.st_ino == child_stat.st_ino
3857
def test_link_fails_if_modified(self):
3858
"""If the file to be linked has modified text, don't link."""
3859
self.build_tree_contents([('child/foo', b'baz')])
3860
transform.link_tree(self.child_tree, self.parent_tree)
3861
self.assertFalse(self.hardlinked())
3863
def test_link_fails_if_execute_bit_changed(self):
3864
"""If the file to be linked has modified execute bit, don't link."""
3865
tt = TreeTransform(self.child_tree)
3867
trans_id = tt.trans_id_tree_path('foo')
3868
tt.set_executability(True, trans_id)
3872
transform.link_tree(self.child_tree, self.parent_tree)
3873
self.assertFalse(self.hardlinked())
3875
def test_link_succeeds_if_unmodified(self):
3876
"""If the file to be linked is unmodified, link"""
3877
transform.link_tree(self.child_tree, self.parent_tree)
3878
self.assertTrue(self.hardlinked())