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', file_id=b'foo-id')
1670
bar_trans_id = tt.create_path('bar', tt.root)
1671
create_from_tree(tt, bar_trans_id, tree1, 'bar', file_id=b'bar-id')
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', file_id=b'foo-id',
1687
self.assertFileEqual(b'qux', 'tree2/foo')
1689
def test_create_from_tree_symlink(self):
1690
self.requireFeature(SymlinkFeature)
1691
tree1 = self.make_branch_and_tree('tree1')
1692
os.symlink('bar', 'tree1/foo')
1693
tree1.add('foo', b'foo-id')
1694
tt = TreeTransform(self.make_branch_and_tree('tree2'))
1695
foo_trans_id = tt.create_path('foo', tt.root)
1696
create_from_tree(tt, foo_trans_id, tree1, 'foo', file_id=b'foo-id')
1698
self.assertEqual('bar', os.readlink('tree2/foo'))
494
1701
class TransformGroup(object):
495
def __init__(self, dirname):
1703
def __init__(self, dirname, root_id):
496
1704
self.name = dirname
497
1705
os.mkdir(dirname)
498
self.wt = BzrDir.create_standalone_workingtree(dirname)
1706
self.wt = ControlDir.create_standalone_workingtree(dirname)
1707
self.wt.set_root_id(root_id)
499
1708
self.b = self.wt.branch
500
1709
self.tt = TreeTransform(self.wt)
501
self.root = self.tt.trans_id_tree_file_id(self.wt.get_root_id())
1710
self.root = self.tt.trans_id_tree_path('')
503
1713
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)
1714
template = b'%s TREE\n%s%s\n%s%s MERGE-SOURCE\n'
1715
return template % (b'<' * 7, tree, b'=' * 7, merge, b'>' * 7)
1718
class TestInventoryAltered(tests.TestCaseWithTransport):
1720
def test_inventory_altered_unchanged(self):
1721
tree = self.make_branch_and_tree('tree')
1722
self.build_tree(['tree/foo'])
1723
tree.add('foo', b'foo-id')
1724
with TransformPreview(tree) as tt:
1725
self.assertEqual([], tt._inventory_altered())
1727
def test_inventory_altered_changed_parent_id(self):
1728
tree = self.make_branch_and_tree('tree')
1729
self.build_tree(['tree/foo'])
1730
tree.add('foo', b'foo-id')
1731
with TransformPreview(tree) as tt:
1732
tt.unversion_file(tt.root)
1733
tt.version_file(b'new-id', tt.root)
1734
foo_trans_id = tt.trans_id_tree_path('foo')
1735
foo_tuple = ('foo', foo_trans_id)
1736
root_tuple = ('', tt.root)
1737
self.assertEqual([root_tuple, foo_tuple], tt._inventory_altered())
1739
def test_inventory_altered_noop_changed_parent_id(self):
1740
tree = self.make_branch_and_tree('tree')
1741
self.build_tree(['tree/foo'])
1742
tree.add('foo', b'foo-id')
1743
with TransformPreview(tree) as tt:
1744
tt.unversion_file(tt.root)
1745
tt.version_file(tree.get_root_id(), tt.root)
1746
tt.trans_id_tree_path('foo')
1747
self.assertEqual([], tt._inventory_altered())
508
1750
class TestTransformMerge(TestCaseInTempDir):
509
1752
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')
1753
root_id = generate_ids.gen_root_id()
1754
base = TransformGroup("base", root_id)
1755
base.tt.new_file('a', base.root, [b'a\nb\nc\nd\be\n'], b'a')
1756
base.tt.new_file('b', base.root, [b'b1'], b'b')
1757
base.tt.new_file('c', base.root, [b'c'], b'c')
1758
base.tt.new_file('d', base.root, [b'd'], b'd')
1759
base.tt.new_file('e', base.root, [b'e'], b'e')
1760
base.tt.new_file('f', base.root, [b'f'], b'f')
1761
base.tt.new_directory('g', base.root, b'g')
1762
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')
1764
other = TransformGroup("other", root_id)
1765
other.tt.new_file('a', other.root, [b'y\nb\nc\nd\be\n'], b'a')
1766
other.tt.new_file('b', other.root, [b'b2'], b'b')
1767
other.tt.new_file('c', other.root, [b'c2'], b'c')
1768
other.tt.new_file('d', other.root, [b'd'], b'd')
1769
other.tt.new_file('e', other.root, [b'e2'], b'e')
1770
other.tt.new_file('f', other.root, [b'f'], b'f')
1771
other.tt.new_file('g', other.root, [b'g'], b'g')
1772
other.tt.new_file('h', other.root, [b'h\ni\nj\nk\n'], b'h')
1773
other.tt.new_file('i', other.root, [b'h\ni\nj\nk\n'], b'i')
530
1774
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')
1775
this = TransformGroup("this", root_id)
1776
this.tt.new_file('a', this.root, [b'a\nb\nc\nd\bz\n'], b'a')
1777
this.tt.new_file('b', this.root, [b'b'], b'b')
1778
this.tt.new_file('c', this.root, [b'c'], b'c')
1779
this.tt.new_file('d', this.root, [b'd2'], b'd')
1780
this.tt.new_file('e', this.root, [b'e2'], b'e')
1781
this.tt.new_file('f', this.root, [b'f'], b'f')
1782
this.tt.new_file('g', this.root, [b'g'], b'g')
1783
this.tt.new_file('h', this.root, [b'1\n2\n3\n4\n'], b'h')
1784
this.tt.new_file('i', this.root, [b'1\n2\n3\n4\n'], b'i')
542
1786
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
544
self.assertEqual(this.wt.get_file('a').read(), 'y\nb\nc\nd\bz\n')
1789
with this.wt.get_file(this.wt.id2path(b'a')) as f:
1790
self.assertEqual(f.read(), b'y\nb\nc\nd\bz\n')
545
1791
# three-way text conflict
546
self.assertEqual(this.wt.get_file('b').read(),
547
conflict_text('b', 'b2'))
1792
with this.wt.get_file(this.wt.id2path(b'b')) as f:
1793
self.assertEqual(f.read(), conflict_text(b'b', b'b2'))
549
self.assertEqual(this.wt.get_file('c').read(), 'c2')
1795
self.assertEqual(this.wt.get_file(this.wt.id2path(b'c')).read(), b'c2')
551
self.assertEqual(this.wt.get_file('d').read(), 'd2')
1797
self.assertEqual(this.wt.get_file(this.wt.id2path(b'd')).read(), b'd2')
552
1798
# Ambigious clean merge
553
self.assertEqual(this.wt.get_file('e').read(), 'e2')
1799
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')
1801
self.assertEqual(this.wt.get_file(this.wt.id2path(b'f')).read(), b'f')
1802
# Correct correct results when THIS == OTHER
1803
self.assertEqual(this.wt.get_file(this.wt.id2path(b'g')).read(), b'g')
558
1804
# 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(),
1805
self.assertEqual(this.wt.get_file(this.wt.id2path(b'h')).read(),
1806
conflict_text(b'1\n2\n3\n4\n', b'h\ni\nj\nk\n'))
1807
self.assertEqual(this.wt.get_file('h.THIS').read(),
1809
self.assertEqual(this.wt.get_file('h.OTHER').read(),
565
1811
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(),
1812
self.assertEqual(this.wt.get_file(this.wt.id2path(b'i')).read(),
1813
conflict_text(b'1\n2\n3\n4\n', b'h\ni\nj\nk\n'))
1814
self.assertEqual(this.wt.get_file('i.THIS').read(),
1816
self.assertEqual(this.wt.get_file('i.OTHER').read(),
572
1818
self.assertEqual(os.path.exists(this.wt.abspath('i.BASE')), False)
573
modified = ['a', 'b', 'c', 'h', 'i']
1819
modified = [b'a', b'b', b'c', b'h', b'i']
574
1820
merge_modified = this.wt.merge_modified()
575
1821
self.assertSubset(merge_modified, modified)
576
1822
self.assertEqual(len(merge_modified), len(modified))
577
file(this.wt.id2abspath('a'), 'wb').write('booga')
1823
with open(this.wt.abspath(this.wt.id2path(b'a')), 'wb') as f:
579
1826
merge_modified = this.wt.merge_modified()
580
1827
self.assertSubset(merge_modified, modified)
581
1828
self.assertEqual(len(merge_modified), len(modified))
582
1829
this.wt.remove('b')
585
1832
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")
1833
self.requireFeature(SymlinkFeature)
1834
root_id = generate_ids.gen_root_id()
1835
base = TransformGroup("BASE", root_id)
1836
this = TransformGroup("THIS", root_id)
1837
other = TransformGroup("OTHER", root_id)
591
1838
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'),
1839
tg.tt.new_directory('a', tg.root, b'a')
1840
tg.tt.new_symlink('b', tg.root, 'b', b'b')
1841
tg.tt.new_file('c', tg.root, [b'c'], b'c')
1842
tg.tt.new_symlink('d', tg.root, tg.name, b'd')
1843
targets = ((base, 'base-e', 'base-f', None, None),
1844
(this, 'other-e', 'this-f', 'other-g', 'this-h'),
598
1845
(other, 'other-e', None, 'other-g', 'other-h'))
599
1846
for tg, e_target, f_target, g_target, h_target in targets:
600
for link, target in (('e', e_target), ('f', f_target),
1847
for link, target in (('e', e_target), ('f', f_target),
601
1848
('g', g_target), ('h', h_target)):
602
1849
if target is not None:
603
tg.tt.new_symlink(link, tg.root, target, link)
1850
tg.tt.new_symlink(link, tg.root, target,
1851
link.encode('ascii'))
605
1853
for tg in this, base, other:
625
1874
self.assertIs(os.path.lexists(this.wt.abspath('h.OTHER')), True)
627
1876
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')
1877
root_id = generate_ids.gen_root_id()
1878
base = TransformGroup("BASE", root_id)
1879
this = TransformGroup("THIS", root_id)
1880
other = TransformGroup("OTHER", root_id)
1881
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, b'a')
1882
for t in [base, this, other]]
1883
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, b'b')
1884
for t in [base, this, other]]
1885
base.tt.new_directory('c', base_a, b'c')
1886
this.tt.new_directory('c1', this_a, b'c')
1887
other.tt.new_directory('c', other_b, b'c')
1889
base.tt.new_directory('d', base_a, b'd')
1890
this.tt.new_directory('d1', this_b, b'd')
1891
other.tt.new_directory('d', other_a, b'd')
1893
base.tt.new_directory('e', base_a, b'e')
1894
this.tt.new_directory('e', this_a, b'e')
1895
other.tt.new_directory('e1', other_b, b'e')
1897
base.tt.new_directory('f', base_a, b'f')
1898
this.tt.new_directory('f1', this_b, b'f')
1899
other.tt.new_directory('f1', other_b, b'f')
651
1901
for tg in [this, base, other]:
653
1903
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'))
1904
self.assertEqual(this.wt.id2path(b'c'), pathjoin('b/c1'))
1905
self.assertEqual(this.wt.id2path(b'd'), pathjoin('b/d1'))
1906
self.assertEqual(this.wt.id2path(b'e'), pathjoin('b/e1'))
1907
self.assertEqual(this.wt.id2path(b'f'), pathjoin('b/f1'))
659
1909
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')
1910
root_id = generate_ids.gen_root_id()
1911
base = TransformGroup("BASE", root_id)
1912
this = TransformGroup("THIS", root_id)
1913
other = TransformGroup("OTHER", root_id)
1914
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, b'a')
1915
for t in [base, this, other]]
1916
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, b'b')
1917
for t in [base, this, other]]
1919
base.tt.new_file('g', base_a, [b'g'], b'g')
1920
other.tt.new_file('g1', other_b, [b'g1'], b'g')
1922
base.tt.new_file('h', base_a, [b'h'], b'h')
1923
this.tt.new_file('h1', this_b, [b'h1'], b'h')
1925
base.tt.new_file('i', base.root, [b'i'], b'i')
1926
other.tt.new_directory('i1', this_b, b'i')
677
1928
for tg in [this, base, other]:
679
1930
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
681
self.assertEqual(this.wt.id2path('g'), pathjoin('b/g1.OTHER'))
1932
self.assertEqual(this.wt.id2path(b'g'), pathjoin('b/g1.OTHER'))
682
1933
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.BASE')), True)
683
1934
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.THIS')), False)
684
self.assertEqual(this.wt.id2path('h'), pathjoin('b/h1.THIS'))
1935
self.assertEqual(this.wt.id2path(b'h'), pathjoin('b/h1.THIS'))
685
1936
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.BASE')), True)
686
1937
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')
1938
self.assertEqual(this.wt.id2path(b'i'), pathjoin('b/i1.OTHER'))
1941
class TestBuildTree(tests.TestCaseWithTransport):
1943
def test_build_tree_with_symlinks(self):
1944
self.requireFeature(SymlinkFeature)
694
a = BzrDir.create_standalone_workingtree('a')
1946
a = ControlDir.create_standalone_workingtree('a')
695
1947
os.mkdir('a/foo')
696
file('a/foo/bar', 'wb').write('contents')
1948
with open('a/foo/bar', 'wb') as f:
1949
f.write(b'contents')
697
1950
os.symlink('a/foo/bar', 'a/foo/baz')
698
1951
a.add(['foo', 'foo/bar', 'foo/baz'])
699
1952
a.commit('initial commit')
700
b = BzrDir.create_standalone_workingtree('b')
701
build_tree(a.basis_tree(), b)
1953
b = ControlDir.create_standalone_workingtree('b')
1954
basis = a.basis_tree()
1956
self.addCleanup(basis.unlock)
1957
build_tree(basis, b)
702
1958
self.assertIs(os.path.isdir('b/foo'), True)
703
self.assertEqual(file('b/foo/bar', 'rb').read(), "contents")
1959
with open('b/foo/bar', 'rb') as f:
1960
self.assertEqual(f.read(), b"contents")
704
1961
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~')
1963
def test_build_with_references(self):
1964
tree = self.make_branch_and_tree('source',
1965
format='development-subtree')
1966
subtree = self.make_branch_and_tree('source/subtree',
1967
format='development-subtree')
1968
tree.add_reference(subtree)
1969
tree.commit('a revision')
1970
tree.branch.create_checkout('target')
1971
self.assertPathExists('target')
1972
self.assertPathExists('target/subtree')
1974
def test_file_conflict_handling(self):
1975
"""Ensure that when building trees, conflict handling is done"""
1976
source = self.make_branch_and_tree('source')
1977
target = self.make_branch_and_tree('target')
1978
self.build_tree(['source/file', 'target/file'])
1979
source.add('file', b'new-file')
1980
source.commit('added file')
1981
build_tree(source.basis_tree(), target)
1983
[DuplicateEntry('Moved existing file to', 'file.moved',
1984
'file', None, 'new-file')],
1986
target2 = self.make_branch_and_tree('target2')
1987
with open('target2/file', 'wb') as target_file, \
1988
open('source/file', 'rb') as source_file:
1989
target_file.write(source_file.read())
1990
build_tree(source.basis_tree(), target2)
1991
self.assertEqual([], target2.conflicts())
1993
def test_symlink_conflict_handling(self):
1994
"""Ensure that when building trees, conflict handling is done"""
1995
self.requireFeature(SymlinkFeature)
1996
source = self.make_branch_and_tree('source')
1997
os.symlink('foo', 'source/symlink')
1998
source.add('symlink', b'new-symlink')
1999
source.commit('added file')
2000
target = self.make_branch_and_tree('target')
2001
os.symlink('bar', 'target/symlink')
2002
build_tree(source.basis_tree(), target)
2004
[DuplicateEntry('Moved existing file to', 'symlink.moved',
2005
'symlink', None, 'new-symlink')],
2007
target = self.make_branch_and_tree('target2')
2008
os.symlink('foo', 'target2/symlink')
2009
build_tree(source.basis_tree(), target)
2010
self.assertEqual([], target.conflicts())
2012
def test_directory_conflict_handling(self):
2013
"""Ensure that when building trees, conflict handling is done"""
2014
source = self.make_branch_and_tree('source')
2015
target = self.make_branch_and_tree('target')
2016
self.build_tree(['source/dir1/', 'source/dir1/file', 'target/dir1/'])
2017
source.add(['dir1', 'dir1/file'], [b'new-dir1', b'new-file'])
2018
source.commit('added file')
2019
build_tree(source.basis_tree(), target)
2020
self.assertEqual([], target.conflicts())
2021
self.assertPathExists('target/dir1/file')
2023
# Ensure contents are merged
2024
target = self.make_branch_and_tree('target2')
2025
self.build_tree(['target2/dir1/', 'target2/dir1/file2'])
2026
build_tree(source.basis_tree(), target)
2027
self.assertEqual([], target.conflicts())
2028
self.assertPathExists('target2/dir1/file2')
2029
self.assertPathExists('target2/dir1/file')
2031
# Ensure new contents are suppressed for existing branches
2032
target = self.make_branch_and_tree('target3')
2033
self.make_branch('target3/dir1')
2034
self.build_tree(['target3/dir1/file2'])
2035
build_tree(source.basis_tree(), target)
2036
self.assertPathDoesNotExist('target3/dir1/file')
2037
self.assertPathExists('target3/dir1/file2')
2038
self.assertPathExists('target3/dir1.diverted/file')
2040
[DuplicateEntry('Diverted to', 'dir1.diverted',
2041
'dir1', 'new-dir1', None)],
2044
target = self.make_branch_and_tree('target4')
2045
self.build_tree(['target4/dir1/'])
2046
self.make_branch('target4/dir1/file')
2047
build_tree(source.basis_tree(), target)
2048
self.assertPathExists('target4/dir1/file')
2049
self.assertEqual('directory', file_kind('target4/dir1/file'))
2050
self.assertPathExists('target4/dir1/file.diverted')
2052
[DuplicateEntry('Diverted to', 'dir1/file.diverted',
2053
'dir1/file', 'new-file', None)],
2056
def test_mixed_conflict_handling(self):
2057
"""Ensure that when building trees, conflict handling is done"""
2058
source = self.make_branch_and_tree('source')
2059
target = self.make_branch_and_tree('target')
2060
self.build_tree(['source/name', 'target/name/'])
2061
source.add('name', b'new-name')
2062
source.commit('added file')
2063
build_tree(source.basis_tree(), target)
2065
[DuplicateEntry('Moved existing file to',
2066
'name.moved', 'name', None, 'new-name')],
2069
def test_raises_in_populated(self):
2070
source = self.make_branch_and_tree('source')
2071
self.build_tree(['source/name'])
2073
source.commit('added name')
2074
target = self.make_branch_and_tree('target')
2075
self.build_tree(['target/name'])
2077
self.assertRaises(errors.WorkingTreeAlreadyPopulated,
2078
build_tree, source.basis_tree(), target)
2080
def test_build_tree_rename_count(self):
2081
source = self.make_branch_and_tree('source')
2082
self.build_tree(['source/file1', 'source/dir1/'])
2083
source.add(['file1', 'dir1'])
2084
source.commit('add1')
2085
target1 = self.make_branch_and_tree('target1')
2086
transform_result = build_tree(source.basis_tree(), target1)
2087
self.assertEqual(2, transform_result.rename_count)
2089
self.build_tree(['source/dir1/file2'])
2090
source.add(['dir1/file2'])
2091
source.commit('add3')
2092
target2 = self.make_branch_and_tree('target2')
2093
transform_result = build_tree(source.basis_tree(), target2)
2094
# children of non-root directories should not be renamed
2095
self.assertEqual(2, transform_result.rename_count)
2097
def create_ab_tree(self):
2098
"""Create a committed test tree with two files"""
2099
source = self.make_branch_and_tree('source')
2100
self.build_tree_contents([('source/file1', b'A')])
2101
self.build_tree_contents([('source/file2', b'B')])
2102
source.add(['file1', 'file2'], [b'file1-id', b'file2-id'])
2103
source.commit('commit files')
2105
self.addCleanup(source.unlock)
2108
def test_build_tree_accelerator_tree(self):
2109
source = self.create_ab_tree()
2110
self.build_tree_contents([('source/file2', b'C')])
2112
real_source_get_file = source.get_file
2116
return real_source_get_file(path)
2117
source.get_file = get_file
2118
target = self.make_branch_and_tree('target')
2119
revision_tree = source.basis_tree()
2120
revision_tree.lock_read()
2121
self.addCleanup(revision_tree.unlock)
2122
build_tree(revision_tree, target, source)
2123
self.assertEqual(['file1'], calls)
2125
self.addCleanup(target.unlock)
2126
self.assertEqual([], list(target.iter_changes(revision_tree)))
2128
def test_build_tree_accelerator_tree_observes_sha1(self):
2129
source = self.create_ab_tree()
2130
sha1 = osutils.sha_string(b'A')
2131
target = self.make_branch_and_tree('target')
2133
self.addCleanup(target.unlock)
2134
state = target.current_dirstate()
2135
state._cutoff_time = time.time() + 60
2136
build_tree(source.basis_tree(), target, source)
2137
entry = state._get_entry(0, path_utf8=b'file1')
2138
self.assertEqual(sha1, entry[1][0][1])
2140
def test_build_tree_accelerator_tree_missing_file(self):
2141
source = self.create_ab_tree()
2142
os.unlink('source/file1')
2143
source.remove(['file2'])
2144
target = self.make_branch_and_tree('target')
2145
revision_tree = source.basis_tree()
2146
revision_tree.lock_read()
2147
self.addCleanup(revision_tree.unlock)
2148
build_tree(revision_tree, target, source)
2150
self.addCleanup(target.unlock)
2151
self.assertEqual([], list(target.iter_changes(revision_tree)))
2153
def test_build_tree_accelerator_wrong_kind(self):
2154
self.requireFeature(SymlinkFeature)
2155
source = self.make_branch_and_tree('source')
2156
self.build_tree_contents([('source/file1', b'')])
2157
self.build_tree_contents([('source/file2', b'')])
2158
source.add(['file1', 'file2'], [b'file1-id', b'file2-id'])
2159
source.commit('commit files')
2160
os.unlink('source/file2')
2161
self.build_tree_contents([('source/file2/', b'C')])
2162
os.unlink('source/file1')
2163
os.symlink('file2', 'source/file1')
2165
real_source_get_file = source.get_file
2169
return real_source_get_file(path)
2170
source.get_file = get_file
2171
target = self.make_branch_and_tree('target')
2172
revision_tree = source.basis_tree()
2173
revision_tree.lock_read()
2174
self.addCleanup(revision_tree.unlock)
2175
build_tree(revision_tree, target, source)
2176
self.assertEqual([], calls)
2178
self.addCleanup(target.unlock)
2179
self.assertEqual([], list(target.iter_changes(revision_tree)))
2181
def test_build_tree_hardlink(self):
2182
self.requireFeature(HardlinkFeature)
2183
source = self.create_ab_tree()
2184
target = self.make_branch_and_tree('target')
2185
revision_tree = source.basis_tree()
2186
revision_tree.lock_read()
2187
self.addCleanup(revision_tree.unlock)
2188
build_tree(revision_tree, target, source, hardlink=True)
2190
self.addCleanup(target.unlock)
2191
self.assertEqual([], list(target.iter_changes(revision_tree)))
2192
source_stat = os.stat('source/file1')
2193
target_stat = os.stat('target/file1')
2194
self.assertEqual(source_stat, target_stat)
2196
# Explicitly disallowing hardlinks should prevent them.
2197
target2 = self.make_branch_and_tree('target2')
2198
build_tree(revision_tree, target2, source, hardlink=False)
2200
self.addCleanup(target2.unlock)
2201
self.assertEqual([], list(target2.iter_changes(revision_tree)))
2202
source_stat = os.stat('source/file1')
2203
target2_stat = os.stat('target2/file1')
2204
self.assertNotEqual(source_stat, target2_stat)
2206
def test_build_tree_accelerator_tree_moved(self):
2207
source = self.make_branch_and_tree('source')
2208
self.build_tree_contents([('source/file1', b'A')])
2209
source.add(['file1'], [b'file1-id'])
2210
source.commit('commit files')
2211
source.rename_one('file1', 'file2')
2213
self.addCleanup(source.unlock)
2214
target = self.make_branch_and_tree('target')
2215
revision_tree = source.basis_tree()
2216
revision_tree.lock_read()
2217
self.addCleanup(revision_tree.unlock)
2218
build_tree(revision_tree, target, source)
2220
self.addCleanup(target.unlock)
2221
self.assertEqual([], list(target.iter_changes(revision_tree)))
2223
def test_build_tree_hardlinks_preserve_execute(self):
2224
self.requireFeature(HardlinkFeature)
2225
source = self.create_ab_tree()
2226
tt = TreeTransform(source)
2227
trans_id = tt.trans_id_tree_path('file1')
2228
tt.set_executability(True, trans_id)
2230
self.assertTrue(source.is_executable('file1'))
2231
target = self.make_branch_and_tree('target')
2232
revision_tree = source.basis_tree()
2233
revision_tree.lock_read()
2234
self.addCleanup(revision_tree.unlock)
2235
build_tree(revision_tree, target, source, hardlink=True)
2237
self.addCleanup(target.unlock)
2238
self.assertEqual([], list(target.iter_changes(revision_tree)))
2239
self.assertTrue(source.is_executable('file1'))
2241
def install_rot13_content_filter(self, pattern):
2243
# self.addCleanup(filters._reset_registry, filters._reset_registry())
2244
# below, but that looks a bit... hard to read even if it's exactly
2246
original_registry = filters._reset_registry()
2248
def restore_registry():
2249
filters._reset_registry(original_registry)
2250
self.addCleanup(restore_registry)
2252
def rot13(chunks, context=None):
2254
codecs.encode(chunk.decode('ascii'), 'rot13').encode('ascii')
2255
for chunk in chunks]
2256
rot13filter = filters.ContentFilter(rot13, rot13)
2257
filters.filter_stacks_registry.register(
2258
'rot13', {'yes': [rot13filter]}.get)
2259
os.mkdir(self.test_home_dir + '/.bazaar')
2260
rules_filename = self.test_home_dir + '/.bazaar/rules'
2261
with open(rules_filename, 'wb') as f:
2262
f.write(b'[name %s]\nrot13=yes\n' % (pattern,))
2264
def uninstall_rules():
2265
os.remove(rules_filename)
2267
self.addCleanup(uninstall_rules)
2270
def test_build_tree_content_filtered_files_are_not_hardlinked(self):
2271
"""build_tree will not hardlink files that have content filtering rules
2272
applied to them (but will still hardlink other files from the same tree
2275
self.requireFeature(HardlinkFeature)
2276
self.install_rot13_content_filter(b'file1')
2277
source = self.create_ab_tree()
2278
target = self.make_branch_and_tree('target')
2279
revision_tree = source.basis_tree()
2280
revision_tree.lock_read()
2281
self.addCleanup(revision_tree.unlock)
2282
build_tree(revision_tree, target, source, hardlink=True)
2284
self.addCleanup(target.unlock)
2285
self.assertEqual([], list(target.iter_changes(revision_tree)))
2286
source_stat = os.stat('source/file1')
2287
target_stat = os.stat('target/file1')
2288
self.assertNotEqual(source_stat, target_stat)
2289
source_stat = os.stat('source/file2')
2290
target_stat = os.stat('target/file2')
2291
self.assertEqualStat(source_stat, target_stat)
2293
def test_case_insensitive_build_tree_inventory(self):
2294
if (features.CaseInsensitiveFilesystemFeature.available()
2295
or features.CaseInsCasePresFilenameFeature.available()):
2296
raise tests.UnavailableFeature('Fully case sensitive filesystem')
2297
source = self.make_branch_and_tree('source')
2298
self.build_tree(['source/file', 'source/FILE'])
2299
source.add(['file', 'FILE'], [b'lower-id', b'upper-id'])
2300
source.commit('added files')
2301
# Don't try this at home, kids!
2302
# Force the tree to report that it is case insensitive
2303
target = self.make_branch_and_tree('target')
2304
target.case_sensitive = False
2305
build_tree(source.basis_tree(), target, source, delta_from_tree=True)
2306
self.assertEqual('file.moved', target.id2path(b'lower-id'))
2307
self.assertEqual('FILE', target.id2path(b'upper-id'))
2309
def test_build_tree_observes_sha(self):
2310
source = self.make_branch_and_tree('source')
2311
self.build_tree(['source/file1', 'source/dir/', 'source/dir/file2'])
2312
source.add(['file1', 'dir', 'dir/file2'],
2313
[b'file1-id', b'dir-id', b'file2-id'])
2314
source.commit('new files')
2315
target = self.make_branch_and_tree('target')
2317
self.addCleanup(target.unlock)
2318
# We make use of the fact that DirState caches its cutoff time. So we
2319
# set the 'safe' time to one minute in the future.
2320
state = target.current_dirstate()
2321
state._cutoff_time = time.time() + 60
2322
build_tree(source.basis_tree(), target)
2323
entry1_sha = osutils.sha_file_by_name('source/file1')
2324
entry2_sha = osutils.sha_file_by_name('source/dir/file2')
2325
# entry[1] is the state information, entry[1][0] is the state of the
2326
# working tree, entry[1][0][1] is the sha value for the current working
2328
entry1 = state._get_entry(0, path_utf8=b'file1')
2329
self.assertEqual(entry1_sha, entry1[1][0][1])
2330
# The 'size' field must also be set.
2331
self.assertEqual(25, entry1[1][0][2])
2332
entry1_state = entry1[1][0]
2333
entry2 = state._get_entry(0, path_utf8=b'dir/file2')
2334
self.assertEqual(entry2_sha, entry2[1][0][1])
2335
self.assertEqual(29, entry2[1][0][2])
2336
entry2_state = entry2[1][0]
2337
# Now, make sure that we don't have to re-read the content. The
2338
# packed_stat should match exactly.
2339
self.assertEqual(entry1_sha, target.get_file_sha1('file1'))
2340
self.assertEqual(entry2_sha, target.get_file_sha1('dir/file2'))
2341
self.assertEqual(entry1_state, entry1[1][0])
2342
self.assertEqual(entry2_state, entry2[1][0])
2345
class TestCommitTransform(tests.TestCaseWithTransport):
2347
def get_branch(self):
2348
tree = self.make_branch_and_tree('tree')
2350
self.addCleanup(tree.unlock)
2351
tree.commit('empty commit')
2354
def get_branch_and_transform(self):
2355
branch = self.get_branch()
2356
tt = TransformPreview(branch.basis_tree())
2357
self.addCleanup(tt.finalize)
2360
def test_commit_wrong_basis(self):
2361
branch = self.get_branch()
2362
basis = branch.repository.revision_tree(
2363
_mod_revision.NULL_REVISION)
2364
tt = TransformPreview(basis)
2365
self.addCleanup(tt.finalize)
2366
e = self.assertRaises(ValueError, tt.commit, branch, '')
2367
self.assertEqual('TreeTransform not based on branch basis: null:',
2370
def test_empy_commit(self):
2371
branch, tt = self.get_branch_and_transform()
2372
rev = tt.commit(branch, 'my message')
2373
self.assertEqual(2, branch.revno())
2374
repo = branch.repository
2375
self.assertEqual('my message', repo.get_revision(rev).message)
2377
def test_merge_parents(self):
2378
branch, tt = self.get_branch_and_transform()
2379
tt.commit(branch, 'my message', [b'rev1b', b'rev1c'])
2380
self.assertEqual([b'rev1b', b'rev1c'],
2381
branch.basis_tree().get_parent_ids()[1:])
2383
def test_first_commit(self):
2384
branch = self.make_branch('branch')
2386
self.addCleanup(branch.unlock)
2387
tt = TransformPreview(branch.basis_tree())
2388
self.addCleanup(tt.finalize)
2389
tt.new_directory('', ROOT_PARENT, b'TREE_ROOT')
2390
tt.commit(branch, 'my message')
2391
self.assertEqual([], branch.basis_tree().get_parent_ids())
2392
self.assertNotEqual(_mod_revision.NULL_REVISION,
2393
branch.last_revision())
2395
def test_first_commit_with_merge_parents(self):
2396
branch = self.make_branch('branch')
2398
self.addCleanup(branch.unlock)
2399
tt = TransformPreview(branch.basis_tree())
2400
self.addCleanup(tt.finalize)
2401
e = self.assertRaises(ValueError, tt.commit, branch,
2402
'my message', [b'rev1b-id'])
2403
self.assertEqual('Cannot supply merge parents for first commit.',
2405
self.assertEqual(_mod_revision.NULL_REVISION, branch.last_revision())
2407
def test_add_files(self):
2408
branch, tt = self.get_branch_and_transform()
2409
tt.new_file('file', tt.root, [b'contents'], b'file-id')
2410
trans_id = tt.new_directory('dir', tt.root, b'dir-id')
2411
if SymlinkFeature.available():
2412
tt.new_symlink('symlink', trans_id, 'target', b'symlink-id')
2413
tt.commit(branch, 'message')
2414
tree = branch.basis_tree()
2415
self.assertEqual('file', tree.id2path(b'file-id'))
2416
self.assertEqual(b'contents', tree.get_file_text('file'))
2417
self.assertEqual('dir', tree.id2path(b'dir-id'))
2418
if SymlinkFeature.available():
2419
self.assertEqual('dir/symlink', tree.id2path(b'symlink-id'))
2420
self.assertEqual('target', tree.get_symlink_target('dir/symlink'))
2422
def test_add_unversioned(self):
2423
branch, tt = self.get_branch_and_transform()
2424
tt.new_file('file', tt.root, [b'contents'])
2425
self.assertRaises(errors.StrictCommitFailed, tt.commit, branch,
2426
'message', strict=True)
2428
def test_modify_strict(self):
2429
branch, tt = self.get_branch_and_transform()
2430
tt.new_file('file', tt.root, [b'contents'], b'file-id')
2431
tt.commit(branch, 'message', strict=True)
2432
tt = TransformPreview(branch.basis_tree())
2433
self.addCleanup(tt.finalize)
2434
trans_id = tt.trans_id_file_id(b'file-id')
2435
tt.delete_contents(trans_id)
2436
tt.create_file([b'contents'], trans_id)
2437
tt.commit(branch, 'message', strict=True)
2439
def test_commit_malformed(self):
2440
"""Committing a malformed transform should raise an exception.
2442
In this case, we are adding a file without adding its parent.
2444
branch, tt = self.get_branch_and_transform()
2445
parent_id = tt.trans_id_file_id(b'parent-id')
2446
tt.new_file('file', parent_id, [b'contents'], b'file-id')
2447
self.assertRaises(errors.MalformedTransform, tt.commit, branch,
2450
def test_commit_rich_revision_data(self):
2451
branch, tt = self.get_branch_and_transform()
2452
rev_id = tt.commit(branch, 'message', timestamp=1, timezone=43201,
2453
committer='me <me@example.com>',
2454
revprops={u'foo': 'bar'}, revision_id=b'revid-1',
2455
authors=['Author1 <author1@example.com>',
2456
'Author2 <author2@example.com>',
2458
self.assertEqual(b'revid-1', rev_id)
2459
revision = branch.repository.get_revision(rev_id)
2460
self.assertEqual(1, revision.timestamp)
2461
self.assertEqual(43201, revision.timezone)
2462
self.assertEqual('me <me@example.com>', revision.committer)
2463
self.assertEqual(['Author1 <author1@example.com>',
2464
'Author2 <author2@example.com>'],
2465
revision.get_apparent_authors())
2466
del revision.properties['authors']
2467
self.assertEqual({'foo': 'bar',
2468
'branch-nick': 'tree'},
2469
revision.properties)
2471
def test_no_explicit_revprops(self):
2472
branch, tt = self.get_branch_and_transform()
2473
rev_id = tt.commit(branch, 'message', authors=[
2474
'Author1 <author1@example.com>',
2475
'Author2 <author2@example.com>', ])
2476
revision = branch.repository.get_revision(rev_id)
2477
self.assertEqual(['Author1 <author1@example.com>',
2478
'Author2 <author2@example.com>'],
2479
revision.get_apparent_authors())
2480
self.assertEqual('tree', revision.properties['branch-nick'])
2483
class TestFileMover(tests.TestCaseWithTransport):
2485
def test_file_mover(self):
2486
self.build_tree(['a/', 'a/b', 'c/', 'c/d'])
2487
mover = _FileMover()
2488
mover.rename('a', 'q')
2489
self.assertPathExists('q')
2490
self.assertPathDoesNotExist('a')
2491
self.assertPathExists('q/b')
2492
self.assertPathExists('c')
2493
self.assertPathExists('c/d')
2495
def test_pre_delete_rollback(self):
2496
self.build_tree(['a/'])
2497
mover = _FileMover()
2498
mover.pre_delete('a', 'q')
2499
self.assertPathExists('q')
2500
self.assertPathDoesNotExist('a')
2502
self.assertPathDoesNotExist('q')
2503
self.assertPathExists('a')
2505
def test_apply_deletions(self):
2506
self.build_tree(['a/', 'b/'])
2507
mover = _FileMover()
2508
mover.pre_delete('a', 'q')
2509
mover.pre_delete('b', 'r')
2510
self.assertPathExists('q')
2511
self.assertPathExists('r')
2512
self.assertPathDoesNotExist('a')
2513
self.assertPathDoesNotExist('b')
2514
mover.apply_deletions()
2515
self.assertPathDoesNotExist('q')
2516
self.assertPathDoesNotExist('r')
2517
self.assertPathDoesNotExist('a')
2518
self.assertPathDoesNotExist('b')
2520
def test_file_mover_rollback(self):
2521
self.build_tree(['a/', 'a/b', 'c/', 'c/d/', 'c/e/'])
2522
mover = _FileMover()
2523
mover.rename('c/d', 'c/f')
2524
mover.rename('c/e', 'c/d')
2526
mover.rename('a', 'c')
2527
except errors.FileExists:
2529
self.assertPathExists('a')
2530
self.assertPathExists('c/d')
2533
class Bogus(Exception):
2537
class TestTransformRollback(tests.TestCaseWithTransport):
2539
class ExceptionFileMover(_FileMover):
2541
def __init__(self, bad_source=None, bad_target=None):
2542
_FileMover.__init__(self)
2543
self.bad_source = bad_source
2544
self.bad_target = bad_target
2546
def rename(self, source, target):
2547
if (self.bad_source is not None and
2548
source.endswith(self.bad_source)):
2550
elif (self.bad_target is not None and
2551
target.endswith(self.bad_target)):
2554
_FileMover.rename(self, source, target)
2556
def test_rollback_rename(self):
2557
tree = self.make_branch_and_tree('.')
2558
self.build_tree(['a/', 'a/b'])
2559
tt = TreeTransform(tree)
2560
self.addCleanup(tt.finalize)
2561
a_id = tt.trans_id_tree_path('a')
2562
tt.adjust_path('c', tt.root, a_id)
2563
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
2564
self.assertRaises(Bogus, tt.apply,
2565
_mover=self.ExceptionFileMover(bad_source='a'))
2566
self.assertPathExists('a')
2567
self.assertPathExists('a/b')
2569
self.assertPathExists('c')
2570
self.assertPathExists('c/d')
2572
def test_rollback_rename_into_place(self):
2573
tree = self.make_branch_and_tree('.')
2574
self.build_tree(['a/', 'a/b'])
2575
tt = TreeTransform(tree)
2576
self.addCleanup(tt.finalize)
2577
a_id = tt.trans_id_tree_path('a')
2578
tt.adjust_path('c', tt.root, a_id)
2579
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
2580
self.assertRaises(Bogus, tt.apply,
2581
_mover=self.ExceptionFileMover(bad_target='c/d'))
2582
self.assertPathExists('a')
2583
self.assertPathExists('a/b')
2585
self.assertPathExists('c')
2586
self.assertPathExists('c/d')
2588
def test_rollback_deletion(self):
2589
tree = self.make_branch_and_tree('.')
2590
self.build_tree(['a/', 'a/b'])
2591
tt = TreeTransform(tree)
2592
self.addCleanup(tt.finalize)
2593
a_id = tt.trans_id_tree_path('a')
2594
tt.delete_contents(a_id)
2595
tt.adjust_path('d', tt.root, tt.trans_id_tree_path('a/b'))
2596
self.assertRaises(Bogus, tt.apply,
2597
_mover=self.ExceptionFileMover(bad_target='d'))
2598
self.assertPathExists('a')
2599
self.assertPathExists('a/b')
2602
class TestFinalizeRobustness(tests.TestCaseWithTransport):
2603
"""Ensure treetransform creation errors can be safely cleaned up after"""
2605
def _override_globals_in_method(self, instance, method_name, globals):
2606
"""Replace method on instance with one with updated globals"""
2608
func = getattr(instance, method_name).__func__
2609
new_globals = dict(func.__globals__)
2610
new_globals.update(globals)
2611
new_func = types.FunctionType(func.__code__, new_globals,
2612
func.__name__, func.__defaults__)
2614
setattr(instance, method_name,
2615
types.MethodType(new_func, instance))
2617
setattr(instance, method_name,
2618
types.MethodType(new_func, instance, instance.__class__))
2619
self.addCleanup(delattr, instance, method_name)
2622
def _fake_open_raises_before(name, mode):
2623
"""Like open() but raises before doing anything"""
2627
def _fake_open_raises_after(name, mode):
2628
"""Like open() but raises after creating file without returning"""
2629
open(name, mode).close()
2632
def create_transform_and_root_trans_id(self):
2633
"""Setup a transform creating a file in limbo"""
2634
tree = self.make_branch_and_tree('.')
2635
tt = TreeTransform(tree)
2636
return tt, tt.create_path("a", tt.root)
2638
def create_transform_and_subdir_trans_id(self):
2639
"""Setup a transform creating a directory containing a file in limbo"""
2640
tree = self.make_branch_and_tree('.')
2641
tt = TreeTransform(tree)
2642
d_trans_id = tt.create_path("d", tt.root)
2643
tt.create_directory(d_trans_id)
2644
f_trans_id = tt.create_path("a", d_trans_id)
2645
tt.adjust_path("a", d_trans_id, f_trans_id)
2646
return tt, f_trans_id
2648
def test_root_create_file_open_raises_before_creation(self):
2649
tt, trans_id = self.create_transform_and_root_trans_id()
2650
self._override_globals_in_method(
2651
tt, "create_file", {"open": self._fake_open_raises_before})
2652
self.assertRaises(RuntimeError, tt.create_file,
2653
[b"contents"], trans_id)
2654
path = tt._limbo_name(trans_id)
2655
self.assertPathDoesNotExist(path)
2657
self.assertPathDoesNotExist(tt._limbodir)
2659
def test_root_create_file_open_raises_after_creation(self):
2660
tt, trans_id = self.create_transform_and_root_trans_id()
2661
self._override_globals_in_method(
2662
tt, "create_file", {"open": self._fake_open_raises_after})
2663
self.assertRaises(RuntimeError, tt.create_file,
2664
[b"contents"], trans_id)
2665
path = tt._limbo_name(trans_id)
2666
self.assertPathExists(path)
2668
self.assertPathDoesNotExist(path)
2669
self.assertPathDoesNotExist(tt._limbodir)
2671
def test_subdir_create_file_open_raises_before_creation(self):
2672
tt, trans_id = self.create_transform_and_subdir_trans_id()
2673
self._override_globals_in_method(
2674
tt, "create_file", {"open": self._fake_open_raises_before})
2675
self.assertRaises(RuntimeError, tt.create_file,
2676
[b"contents"], trans_id)
2677
path = tt._limbo_name(trans_id)
2678
self.assertPathDoesNotExist(path)
2680
self.assertPathDoesNotExist(tt._limbodir)
2682
def test_subdir_create_file_open_raises_after_creation(self):
2683
tt, trans_id = self.create_transform_and_subdir_trans_id()
2684
self._override_globals_in_method(
2685
tt, "create_file", {"open": self._fake_open_raises_after})
2686
self.assertRaises(RuntimeError, tt.create_file,
2687
[b"contents"], trans_id)
2688
path = tt._limbo_name(trans_id)
2689
self.assertPathExists(path)
2691
self.assertPathDoesNotExist(path)
2692
self.assertPathDoesNotExist(tt._limbodir)
2694
def test_rename_in_limbo_rename_raises_after_rename(self):
2695
tt, trans_id = self.create_transform_and_root_trans_id()
2696
parent1 = tt.new_directory('parent1', tt.root)
2697
child1 = tt.new_file('child1', parent1, [b'contents'])
2698
parent2 = tt.new_directory('parent2', tt.root)
2700
class FakeOSModule(object):
2701
def rename(self, old, new):
2704
self._override_globals_in_method(tt, "_rename_in_limbo",
2705
{"os": FakeOSModule()})
2707
RuntimeError, tt.adjust_path, "child1", parent2, child1)
2708
path = osutils.pathjoin(tt._limbo_name(parent2), "child1")
2709
self.assertPathExists(path)
2711
self.assertPathDoesNotExist(path)
2712
self.assertPathDoesNotExist(tt._limbodir)
2714
def test_rename_in_limbo_rename_raises_before_rename(self):
2715
tt, trans_id = self.create_transform_and_root_trans_id()
2716
parent1 = tt.new_directory('parent1', tt.root)
2717
child1 = tt.new_file('child1', parent1, [b'contents'])
2718
parent2 = tt.new_directory('parent2', tt.root)
2720
class FakeOSModule(object):
2721
def rename(self, old, new):
2723
self._override_globals_in_method(tt, "_rename_in_limbo",
2724
{"os": FakeOSModule()})
2726
RuntimeError, tt.adjust_path, "child1", parent2, child1)
2727
path = osutils.pathjoin(tt._limbo_name(parent1), "child1")
2728
self.assertPathExists(path)
2730
self.assertPathDoesNotExist(path)
2731
self.assertPathDoesNotExist(tt._limbodir)
2734
class TestTransformMissingParent(tests.TestCaseWithTransport):
2736
def make_tt_with_versioned_dir(self):
2737
wt = self.make_branch_and_tree('.')
2738
self.build_tree(['dir/', ])
2739
wt.add(['dir'], [b'dir-id'])
2740
wt.commit('Create dir')
2741
tt = TreeTransform(wt)
2742
self.addCleanup(tt.finalize)
2745
def test_resolve_create_parent_for_versioned_file(self):
2746
wt, tt = self.make_tt_with_versioned_dir()
2747
dir_tid = tt.trans_id_tree_path('dir')
2748
tt.new_file('file', dir_tid, [b'Contents'], file_id=b'file-id')
2749
tt.delete_contents(dir_tid)
2750
tt.unversion_file(dir_tid)
2751
conflicts = resolve_conflicts(tt)
2752
# one conflict for the missing directory, one for the unversioned
2754
self.assertLength(2, conflicts)
2756
def test_non_versioned_file_create_conflict(self):
2757
wt, tt = self.make_tt_with_versioned_dir()
2758
dir_tid = tt.trans_id_tree_path('dir')
2759
tt.new_file('file', dir_tid, [b'Contents'])
2760
tt.delete_contents(dir_tid)
2761
tt.unversion_file(dir_tid)
2762
conflicts = resolve_conflicts(tt)
2763
# no conflicts or rather: orphaning 'file' resolve the 'dir' conflict
2764
self.assertLength(1, conflicts)
2765
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
2769
A_ENTRY = (b'a-id', ('a', 'a'), True, (True, True),
2770
(b'TREE_ROOT', b'TREE_ROOT'), ('a', 'a'), ('file', 'file'),
2772
ROOT_ENTRY = (b'TREE_ROOT', ('', ''), False, (True, True), (None, None),
2773
('', ''), ('directory', 'directory'), (False, False))
2776
class TestTransformPreview(tests.TestCaseWithTransport):
2778
def create_tree(self):
2779
tree = self.make_branch_and_tree('.')
2780
self.build_tree_contents([('a', b'content 1')])
2781
tree.set_root_id(b'TREE_ROOT')
2782
tree.add('a', b'a-id')
2783
tree.commit('rev1', rev_id=b'rev1')
2784
return tree.branch.repository.revision_tree(b'rev1')
2786
def get_empty_preview(self):
2787
repository = self.make_repository('repo')
2788
tree = repository.revision_tree(_mod_revision.NULL_REVISION)
2789
preview = TransformPreview(tree)
2790
self.addCleanup(preview.finalize)
2793
def test_transform_preview(self):
2794
revision_tree = self.create_tree()
2795
preview = TransformPreview(revision_tree)
2796
self.addCleanup(preview.finalize)
2798
def test_transform_preview_tree(self):
2799
revision_tree = self.create_tree()
2800
preview = TransformPreview(revision_tree)
2801
self.addCleanup(preview.finalize)
2802
preview.get_preview_tree()
2804
def test_transform_new_file(self):
2805
revision_tree = self.create_tree()
2806
preview = TransformPreview(revision_tree)
2807
self.addCleanup(preview.finalize)
2808
preview.new_file('file2', preview.root, [b'content B\n'], b'file2-id')
2809
preview_tree = preview.get_preview_tree()
2810
self.assertEqual(preview_tree.kind('file2'), 'file')
2811
with preview_tree.get_file('file2') as f:
2812
self.assertEqual(f.read(), b'content B\n')
2814
def test_diff_preview_tree(self):
2815
revision_tree = self.create_tree()
2816
preview = TransformPreview(revision_tree)
2817
self.addCleanup(preview.finalize)
2818
preview.new_file('file2', preview.root, [b'content B\n'], b'file2-id')
2819
preview_tree = preview.get_preview_tree()
2821
show_diff_trees(revision_tree, preview_tree, out)
2822
lines = out.getvalue().splitlines()
2823
self.assertEqual(lines[0], b"=== added file 'file2'")
2824
# 3 lines of diff administrivia
2825
self.assertEqual(lines[4], b"+content B")
2827
def test_unsupported_symlink_diff(self):
2828
self.requireFeature(SymlinkFeature)
2829
tree = self.make_branch_and_tree('.')
2830
self.build_tree_contents([('a', 'content 1')])
2831
tree.set_root_id(b'TREE_ROOT')
2832
tree.add('a', b'a-id')
2833
os.symlink('a', 'foo')
2834
tree.add('foo', b'foo-id')
2835
tree.commit('rev1', rev_id=b'rev1')
2836
revision_tree = tree.branch.repository.revision_tree(b'rev1')
2837
preview = TransformPreview(revision_tree)
2838
self.addCleanup(preview.finalize)
2839
preview.delete_versioned(preview.trans_id_tree_path('foo'))
2840
preview_tree = preview.get_preview_tree()
2843
trace.push_log_file(log)
2844
os_symlink = getattr(os, 'symlink', None)
2847
show_diff_trees(revision_tree, preview_tree, out)
2848
lines = out.getvalue().splitlines()
2850
os.symlink = os_symlink
2851
self.assertContainsRe(
2853
b'Ignoring "foo" as symlinks are not supported on this filesystem')
2855
def test_transform_conflicts(self):
2856
revision_tree = self.create_tree()
2857
preview = TransformPreview(revision_tree)
2858
self.addCleanup(preview.finalize)
2859
preview.new_file('a', preview.root, [b'content 2'])
2860
resolve_conflicts(preview)
2861
trans_id = preview.trans_id_file_id(b'a-id')
2862
self.assertEqual('a.moved', preview.final_name(trans_id))
2864
def get_tree_and_preview_tree(self):
2865
revision_tree = self.create_tree()
2866
preview = TransformPreview(revision_tree)
2867
self.addCleanup(preview.finalize)
2868
a_trans_id = preview.trans_id_file_id(b'a-id')
2869
preview.delete_contents(a_trans_id)
2870
preview.create_file([b'b content'], a_trans_id)
2871
preview_tree = preview.get_preview_tree()
2872
return revision_tree, preview_tree
2874
def test_iter_changes(self):
2875
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2876
root = revision_tree.get_root_id()
2877
self.assertEqual([(b'a-id', ('a', 'a'), True, (True, True),
2878
(root, root), ('a', 'a'), ('file', 'file'),
2880
list(preview_tree.iter_changes(revision_tree)))
2882
def test_include_unchanged_succeeds(self):
2883
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2884
changes = preview_tree.iter_changes(revision_tree,
2885
include_unchanged=True)
2886
self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
2888
def test_specific_files(self):
2889
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2890
changes = preview_tree.iter_changes(revision_tree,
2891
specific_files=[''])
2892
self.assertEqual([A_ENTRY], list(changes))
2894
def test_want_unversioned(self):
2895
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2896
changes = preview_tree.iter_changes(revision_tree,
2897
want_unversioned=True)
2898
self.assertEqual([A_ENTRY], list(changes))
2900
def test_ignore_extra_trees_no_specific_files(self):
2901
# extra_trees is harmless without specific_files, so we'll silently
2902
# accept it, even though we won't use it.
2903
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2904
preview_tree.iter_changes(revision_tree, extra_trees=[preview_tree])
2906
def test_ignore_require_versioned_no_specific_files(self):
2907
# require_versioned is meaningless without specific_files.
2908
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2909
preview_tree.iter_changes(revision_tree, require_versioned=False)
2911
def test_ignore_pb(self):
2912
# pb could be supported, but TT.iter_changes doesn't support it.
2913
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2914
preview_tree.iter_changes(revision_tree)
2916
def test_kind(self):
2917
revision_tree = self.create_tree()
2918
preview = TransformPreview(revision_tree)
2919
self.addCleanup(preview.finalize)
2920
preview.new_file('file', preview.root, [b'contents'], b'file-id')
2921
preview.new_directory('directory', preview.root, b'dir-id')
2922
preview_tree = preview.get_preview_tree()
2923
self.assertEqual('file', preview_tree.kind('file'))
2924
self.assertEqual('directory', preview_tree.kind('directory'))
2926
def test_get_file_mtime(self):
2927
preview = self.get_empty_preview()
2928
file_trans_id = preview.new_file('file', preview.root, [b'contents'],
2930
limbo_path = preview._limbo_name(file_trans_id)
2931
preview_tree = preview.get_preview_tree()
2932
self.assertEqual(os.stat(limbo_path).st_mtime,
2933
preview_tree.get_file_mtime('file'))
2935
def test_get_file_mtime_renamed(self):
2936
work_tree = self.make_branch_and_tree('tree')
2937
self.build_tree(['tree/file'])
2938
work_tree.add('file', b'file-id')
2939
preview = TransformPreview(work_tree)
2940
self.addCleanup(preview.finalize)
2941
file_trans_id = preview.trans_id_tree_path('file')
2942
preview.adjust_path('renamed', preview.root, file_trans_id)
2943
preview_tree = preview.get_preview_tree()
2944
preview_mtime = preview_tree.get_file_mtime('renamed')
2945
work_mtime = work_tree.get_file_mtime('file')
2947
def test_get_file_size(self):
2948
work_tree = self.make_branch_and_tree('tree')
2949
self.build_tree_contents([('tree/old', b'old')])
2950
work_tree.add('old', b'old-id')
2951
preview = TransformPreview(work_tree)
2952
self.addCleanup(preview.finalize)
2953
preview.new_file('name', preview.root, [b'contents'], b'new-id',
2955
tree = preview.get_preview_tree()
2956
self.assertEqual(len('old'), tree.get_file_size('old'))
2957
self.assertEqual(len('contents'), tree.get_file_size('name'))
2959
def test_get_file(self):
2960
preview = self.get_empty_preview()
2961
preview.new_file('file', preview.root, [b'contents'], b'file-id')
2962
preview_tree = preview.get_preview_tree()
2963
with preview_tree.get_file('file') as tree_file:
2964
self.assertEqual(b'contents', tree_file.read())
2966
def test_get_symlink_target(self):
2967
self.requireFeature(SymlinkFeature)
2968
preview = self.get_empty_preview()
2969
preview.new_symlink('symlink', preview.root, 'target', b'symlink-id')
2970
preview_tree = preview.get_preview_tree()
2971
self.assertEqual('target',
2972
preview_tree.get_symlink_target('symlink'))
2974
def test_all_file_ids(self):
2975
tree = self.make_branch_and_tree('tree')
2976
self.build_tree(['tree/a', 'tree/b', 'tree/c'])
2977
tree.add(['a', 'b', 'c'], [b'a-id', b'b-id', b'c-id'])
2978
preview = TransformPreview(tree)
2979
self.addCleanup(preview.finalize)
2980
preview.unversion_file(preview.trans_id_file_id(b'b-id'))
2981
c_trans_id = preview.trans_id_file_id(b'c-id')
2982
preview.unversion_file(c_trans_id)
2983
preview.version_file(b'c-id', c_trans_id)
2984
preview_tree = preview.get_preview_tree()
2985
self.assertEqual({b'a-id', b'c-id', tree.get_root_id()},
2986
preview_tree.all_file_ids())
2988
def test_path2id_deleted_unchanged(self):
2989
tree = self.make_branch_and_tree('tree')
2990
self.build_tree(['tree/unchanged', 'tree/deleted'])
2991
tree.add(['unchanged', 'deleted'], [b'unchanged-id', b'deleted-id'])
2992
preview = TransformPreview(tree)
2993
self.addCleanup(preview.finalize)
2994
preview.unversion_file(preview.trans_id_file_id(b'deleted-id'))
2995
preview_tree = preview.get_preview_tree()
2996
self.assertEqual(b'unchanged-id', preview_tree.path2id('unchanged'))
2997
self.assertFalse(preview_tree.is_versioned('deleted'))
2999
def test_path2id_created(self):
3000
tree = self.make_branch_and_tree('tree')
3001
self.build_tree(['tree/unchanged'])
3002
tree.add(['unchanged'], [b'unchanged-id'])
3003
preview = TransformPreview(tree)
3004
self.addCleanup(preview.finalize)
3005
preview.new_file('new', preview.trans_id_file_id(b'unchanged-id'),
3006
[b'contents'], b'new-id')
3007
preview_tree = preview.get_preview_tree()
3008
self.assertEqual(b'new-id', preview_tree.path2id('unchanged/new'))
3010
def test_path2id_moved(self):
3011
tree = self.make_branch_and_tree('tree')
3012
self.build_tree(['tree/old_parent/', 'tree/old_parent/child'])
3013
tree.add(['old_parent', 'old_parent/child'],
3014
[b'old_parent-id', b'child-id'])
3015
preview = TransformPreview(tree)
3016
self.addCleanup(preview.finalize)
3017
new_parent = preview.new_directory('new_parent', preview.root,
3019
preview.adjust_path('child', new_parent,
3020
preview.trans_id_file_id(b'child-id'))
3021
preview_tree = preview.get_preview_tree()
3022
self.assertFalse(preview_tree.is_versioned('old_parent/child'))
3023
self.assertEqual(b'child-id', preview_tree.path2id('new_parent/child'))
3025
def test_path2id_renamed_parent(self):
3026
tree = self.make_branch_and_tree('tree')
3027
self.build_tree(['tree/old_name/', 'tree/old_name/child'])
3028
tree.add(['old_name', 'old_name/child'],
3029
[b'parent-id', b'child-id'])
3030
preview = TransformPreview(tree)
3031
self.addCleanup(preview.finalize)
3032
preview.adjust_path('new_name', preview.root,
3033
preview.trans_id_file_id(b'parent-id'))
3034
preview_tree = preview.get_preview_tree()
3035
self.assertFalse(preview_tree.is_versioned('old_name/child'))
3036
self.assertEqual(b'child-id', preview_tree.path2id('new_name/child'))
3038
def assertMatchingIterEntries(self, tt, specific_files=None):
3039
preview_tree = tt.get_preview_tree()
3040
preview_result = list(preview_tree.iter_entries_by_dir(
3041
specific_files=specific_files))
3044
actual_result = list(tree.iter_entries_by_dir(
3045
specific_files=specific_files))
3046
self.assertEqual(actual_result, preview_result)
3048
def test_iter_entries_by_dir_new(self):
3049
tree = self.make_branch_and_tree('tree')
3050
tt = TreeTransform(tree)
3051
tt.new_file('new', tt.root, [b'contents'], b'new-id')
3052
self.assertMatchingIterEntries(tt)
3054
def test_iter_entries_by_dir_deleted(self):
3055
tree = self.make_branch_and_tree('tree')
3056
self.build_tree(['tree/deleted'])
3057
tree.add('deleted', b'deleted-id')
3058
tt = TreeTransform(tree)
3059
tt.delete_contents(tt.trans_id_file_id(b'deleted-id'))
3060
self.assertMatchingIterEntries(tt)
3062
def test_iter_entries_by_dir_unversioned(self):
3063
tree = self.make_branch_and_tree('tree')
3064
self.build_tree(['tree/removed'])
3065
tree.add('removed', b'removed-id')
3066
tt = TreeTransform(tree)
3067
tt.unversion_file(tt.trans_id_file_id(b'removed-id'))
3068
self.assertMatchingIterEntries(tt)
3070
def test_iter_entries_by_dir_moved(self):
3071
tree = self.make_branch_and_tree('tree')
3072
self.build_tree(['tree/moved', 'tree/new_parent/'])
3073
tree.add(['moved', 'new_parent'], [b'moved-id', b'new_parent-id'])
3074
tt = TreeTransform(tree)
3075
tt.adjust_path('moved', tt.trans_id_file_id(b'new_parent-id'),
3076
tt.trans_id_file_id(b'moved-id'))
3077
self.assertMatchingIterEntries(tt)
3079
def test_iter_entries_by_dir_specific_files(self):
3080
tree = self.make_branch_and_tree('tree')
3081
tree.set_root_id(b'tree-root-id')
3082
self.build_tree(['tree/parent/', 'tree/parent/child'])
3083
tree.add(['parent', 'parent/child'], [b'parent-id', b'child-id'])
3084
tt = TreeTransform(tree)
3085
self.assertMatchingIterEntries(tt, ['', 'parent/child'])
3087
def test_symlink_content_summary(self):
3088
self.requireFeature(SymlinkFeature)
3089
preview = self.get_empty_preview()
3090
preview.new_symlink('path', preview.root, 'target', b'path-id')
3091
summary = preview.get_preview_tree().path_content_summary('path')
3092
self.assertEqual(('symlink', None, None, 'target'), summary)
3094
def test_missing_content_summary(self):
3095
preview = self.get_empty_preview()
3096
summary = preview.get_preview_tree().path_content_summary('path')
3097
self.assertEqual(('missing', None, None, None), summary)
3099
def test_deleted_content_summary(self):
3100
tree = self.make_branch_and_tree('tree')
3101
self.build_tree(['tree/path/'])
3103
preview = TransformPreview(tree)
3104
self.addCleanup(preview.finalize)
3105
preview.delete_contents(preview.trans_id_tree_path('path'))
3106
summary = preview.get_preview_tree().path_content_summary('path')
3107
self.assertEqual(('missing', None, None, None), summary)
3109
def test_file_content_summary_executable(self):
3110
preview = self.get_empty_preview()
3111
path_id = preview.new_file('path', preview.root, [
3112
b'contents'], b'path-id')
3113
preview.set_executability(True, path_id)
3114
summary = preview.get_preview_tree().path_content_summary('path')
3115
self.assertEqual(4, len(summary))
3116
self.assertEqual('file', summary[0])
3117
# size must be known
3118
self.assertEqual(len('contents'), summary[1])
3120
self.assertEqual(True, summary[2])
3121
# will not have hash (not cheap to determine)
3122
self.assertIs(None, summary[3])
3124
def test_change_executability(self):
3125
tree = self.make_branch_and_tree('tree')
3126
self.build_tree(['tree/path'])
3128
preview = TransformPreview(tree)
3129
self.addCleanup(preview.finalize)
3130
path_id = preview.trans_id_tree_path('path')
3131
preview.set_executability(True, path_id)
3132
summary = preview.get_preview_tree().path_content_summary('path')
3133
self.assertEqual(True, summary[2])
3135
def test_file_content_summary_non_exec(self):
3136
preview = self.get_empty_preview()
3137
preview.new_file('path', preview.root, [b'contents'], b'path-id')
3138
summary = preview.get_preview_tree().path_content_summary('path')
3139
self.assertEqual(4, len(summary))
3140
self.assertEqual('file', summary[0])
3141
# size must be known
3142
self.assertEqual(len('contents'), summary[1])
3144
self.assertEqual(False, summary[2])
3145
# will not have hash (not cheap to determine)
3146
self.assertIs(None, summary[3])
3148
def test_dir_content_summary(self):
3149
preview = self.get_empty_preview()
3150
preview.new_directory('path', preview.root, b'path-id')
3151
summary = preview.get_preview_tree().path_content_summary('path')
3152
self.assertEqual(('directory', None, None, None), summary)
3154
def test_tree_content_summary(self):
3155
preview = self.get_empty_preview()
3156
path = preview.new_directory('path', preview.root, b'path-id')
3157
preview.set_tree_reference(b'rev-1', path)
3158
summary = preview.get_preview_tree().path_content_summary('path')
3159
self.assertEqual(4, len(summary))
3160
self.assertEqual('tree-reference', summary[0])
3162
def test_annotate(self):
3163
tree = self.make_branch_and_tree('tree')
3164
self.build_tree_contents([('tree/file', b'a\n')])
3165
tree.add('file', b'file-id')
3166
tree.commit('a', rev_id=b'one')
3167
self.build_tree_contents([('tree/file', b'a\nb\n')])
3168
preview = TransformPreview(tree)
3169
self.addCleanup(preview.finalize)
3170
file_trans_id = preview.trans_id_file_id(b'file-id')
3171
preview.delete_contents(file_trans_id)
3172
preview.create_file([b'a\nb\nc\n'], file_trans_id)
3173
preview_tree = preview.get_preview_tree()
3179
annotation = preview_tree.annotate_iter(
3180
'file', default_revision=b'me:')
3181
self.assertEqual(expected, annotation)
3183
def test_annotate_missing(self):
3184
preview = self.get_empty_preview()
3185
preview.new_file('file', preview.root, [b'a\nb\nc\n'], b'file-id')
3186
preview_tree = preview.get_preview_tree()
3192
annotation = preview_tree.annotate_iter(
3193
'file', default_revision=b'me:')
3194
self.assertEqual(expected, annotation)
3196
def test_annotate_rename(self):
3197
tree = self.make_branch_and_tree('tree')
3198
self.build_tree_contents([('tree/file', b'a\n')])
3199
tree.add('file', b'file-id')
3200
tree.commit('a', rev_id=b'one')
3201
preview = TransformPreview(tree)
3202
self.addCleanup(preview.finalize)
3203
file_trans_id = preview.trans_id_file_id(b'file-id')
3204
preview.adjust_path('newname', preview.root, file_trans_id)
3205
preview_tree = preview.get_preview_tree()
3209
annotation = preview_tree.annotate_iter(
3210
'file', default_revision=b'me:')
3211
self.assertEqual(expected, annotation)
3213
def test_annotate_deleted(self):
3214
tree = self.make_branch_and_tree('tree')
3215
self.build_tree_contents([('tree/file', b'a\n')])
3216
tree.add('file', b'file-id')
3217
tree.commit('a', rev_id=b'one')
3218
self.build_tree_contents([('tree/file', b'a\nb\n')])
3219
preview = TransformPreview(tree)
3220
self.addCleanup(preview.finalize)
3221
file_trans_id = preview.trans_id_file_id(b'file-id')
3222
preview.delete_contents(file_trans_id)
3223
preview_tree = preview.get_preview_tree()
3224
annotation = preview_tree.annotate_iter(
3225
'file', default_revision=b'me:')
3226
self.assertIs(None, annotation)
3228
def test_stored_kind(self):
3229
preview = self.get_empty_preview()
3230
preview.new_file('file', preview.root, [b'a\nb\nc\n'], b'file-id')
3231
preview_tree = preview.get_preview_tree()
3232
self.assertEqual('file', preview_tree.stored_kind('file'))
3234
def test_is_executable(self):
3235
preview = self.get_empty_preview()
3236
preview.new_file('file', preview.root, [b'a\nb\nc\n'], b'file-id')
3237
preview.set_executability(True, preview.trans_id_file_id(b'file-id'))
3238
preview_tree = preview.get_preview_tree()
3239
self.assertEqual(True, preview_tree.is_executable('file'))
3241
def test_get_set_parent_ids(self):
3242
revision_tree, preview_tree = self.get_tree_and_preview_tree()
3243
self.assertEqual([], preview_tree.get_parent_ids())
3244
preview_tree.set_parent_ids([b'rev-1'])
3245
self.assertEqual([b'rev-1'], preview_tree.get_parent_ids())
3247
def test_plan_file_merge(self):
3248
work_a = self.make_branch_and_tree('wta')
3249
self.build_tree_contents([('wta/file', b'a\nb\nc\nd\n')])
3250
work_a.add('file', b'file-id')
3251
base_id = work_a.commit('base version')
3252
tree_b = work_a.controldir.sprout('wtb').open_workingtree()
3253
preview = TransformPreview(work_a)
3254
self.addCleanup(preview.finalize)
3255
trans_id = preview.trans_id_file_id(b'file-id')
3256
preview.delete_contents(trans_id)
3257
preview.create_file([b'b\nc\nd\ne\n'], trans_id)
3258
self.build_tree_contents([('wtb/file', b'a\nc\nd\nf\n')])
3259
tree_a = preview.get_preview_tree()
3260
tree_a.set_parent_ids([base_id])
3262
('killed-a', b'a\n'),
3263
('killed-b', b'b\n'),
3264
('unchanged', b'c\n'),
3265
('unchanged', b'd\n'),
3268
], list(tree_a.plan_file_merge(b'file-id', tree_b)))
3270
def test_plan_file_merge_revision_tree(self):
3271
work_a = self.make_branch_and_tree('wta')
3272
self.build_tree_contents([('wta/file', b'a\nb\nc\nd\n')])
3273
work_a.add('file', b'file-id')
3274
base_id = work_a.commit('base version')
3275
tree_b = work_a.controldir.sprout('wtb').open_workingtree()
3276
preview = TransformPreview(work_a.basis_tree())
3277
self.addCleanup(preview.finalize)
3278
trans_id = preview.trans_id_file_id(b'file-id')
3279
preview.delete_contents(trans_id)
3280
preview.create_file([b'b\nc\nd\ne\n'], trans_id)
3281
self.build_tree_contents([('wtb/file', b'a\nc\nd\nf\n')])
3282
tree_a = preview.get_preview_tree()
3283
tree_a.set_parent_ids([base_id])
3285
('killed-a', b'a\n'),
3286
('killed-b', b'b\n'),
3287
('unchanged', b'c\n'),
3288
('unchanged', b'd\n'),
3291
], list(tree_a.plan_file_merge(b'file-id', tree_b)))
3293
def test_walkdirs(self):
3294
preview = self.get_empty_preview()
3295
preview.new_directory('', ROOT_PARENT, b'tree-root')
3296
# FIXME: new_directory should mark root.
3297
preview.fixup_new_roots()
3298
preview_tree = preview.get_preview_tree()
3299
preview.new_file('a', preview.root, [b'contents'], b'a-id')
3300
expected = [(('', b'tree-root'),
3301
[('a', 'a', 'file', None, b'a-id', 'file')])]
3302
self.assertEqual(expected, list(preview_tree.walkdirs()))
3304
def test_extras(self):
3305
work_tree = self.make_branch_and_tree('tree')
3306
self.build_tree(['tree/removed-file', 'tree/existing-file',
3307
'tree/not-removed-file'])
3308
work_tree.add(['removed-file', 'not-removed-file'])
3309
preview = TransformPreview(work_tree)
3310
self.addCleanup(preview.finalize)
3311
preview.new_file('new-file', preview.root, [b'contents'])
3312
preview.new_file('new-versioned-file', preview.root, [b'contents'],
3313
b'new-versioned-id')
3314
tree = preview.get_preview_tree()
3315
preview.unversion_file(preview.trans_id_tree_path('removed-file'))
3316
self.assertEqual({'new-file', 'removed-file', 'existing-file'},
3319
def test_merge_into_preview(self):
3320
work_tree = self.make_branch_and_tree('tree')
3321
self.build_tree_contents([('tree/file', b'b\n')])
3322
work_tree.add('file', b'file-id')
3323
work_tree.commit('first commit')
3324
child_tree = work_tree.controldir.sprout('child').open_workingtree()
3325
self.build_tree_contents([('child/file', b'b\nc\n')])
3326
child_tree.commit('child commit')
3327
child_tree.lock_write()
3328
self.addCleanup(child_tree.unlock)
3329
work_tree.lock_write()
3330
self.addCleanup(work_tree.unlock)
3331
preview = TransformPreview(work_tree)
3332
self.addCleanup(preview.finalize)
3333
file_trans_id = preview.trans_id_file_id(b'file-id')
3334
preview.delete_contents(file_trans_id)
3335
preview.create_file([b'a\nb\n'], file_trans_id)
3336
preview_tree = preview.get_preview_tree()
3337
merger = Merger.from_revision_ids(preview_tree,
3338
child_tree.branch.last_revision(),
3339
other_branch=child_tree.branch,
3340
tree_branch=work_tree.branch)
3341
merger.merge_type = Merge3Merger
3342
tt = merger.make_merger().make_preview_transform()
3343
self.addCleanup(tt.finalize)
3344
final_tree = tt.get_preview_tree()
3347
final_tree.get_file_text(final_tree.id2path(b'file-id')))
3349
def test_merge_preview_into_workingtree(self):
3350
tree = self.make_branch_and_tree('tree')
3351
tree.set_root_id(b'TREE_ROOT')
3352
tt = TransformPreview(tree)
3353
self.addCleanup(tt.finalize)
3354
tt.new_file('name', tt.root, [b'content'], b'file-id')
3355
tree2 = self.make_branch_and_tree('tree2')
3356
tree2.set_root_id(b'TREE_ROOT')
3357
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
3359
merger.merge_type = Merge3Merger
3362
def test_merge_preview_into_workingtree_handles_conflicts(self):
3363
tree = self.make_branch_and_tree('tree')
3364
self.build_tree_contents([('tree/foo', b'bar')])
3365
tree.add('foo', b'foo-id')
3367
tt = TransformPreview(tree)
3368
self.addCleanup(tt.finalize)
3369
trans_id = tt.trans_id_file_id(b'foo-id')
3370
tt.delete_contents(trans_id)
3371
tt.create_file([b'baz'], trans_id)
3372
tree2 = tree.controldir.sprout('tree2').open_workingtree()
3373
self.build_tree_contents([('tree2/foo', b'qux')])
3374
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
3376
merger.merge_type = Merge3Merger
3379
def test_has_filename(self):
3380
wt = self.make_branch_and_tree('tree')
3381
self.build_tree(['tree/unmodified', 'tree/removed', 'tree/modified'])
3382
tt = TransformPreview(wt)
3383
removed_id = tt.trans_id_tree_path('removed')
3384
tt.delete_contents(removed_id)
3385
tt.new_file('new', tt.root, [b'contents'])
3386
modified_id = tt.trans_id_tree_path('modified')
3387
tt.delete_contents(modified_id)
3388
tt.create_file([b'modified-contents'], modified_id)
3389
self.addCleanup(tt.finalize)
3390
tree = tt.get_preview_tree()
3391
self.assertTrue(tree.has_filename('unmodified'))
3392
self.assertFalse(tree.has_filename('not-present'))
3393
self.assertFalse(tree.has_filename('removed'))
3394
self.assertTrue(tree.has_filename('new'))
3395
self.assertTrue(tree.has_filename('modified'))
3397
def test_is_executable(self):
3398
tree = self.make_branch_and_tree('tree')
3399
preview = TransformPreview(tree)
3400
self.addCleanup(preview.finalize)
3401
preview.new_file('foo', preview.root, [b'bar'], b'baz-id')
3402
preview_tree = preview.get_preview_tree()
3403
self.assertEqual(False, preview_tree.is_executable('tree/foo'))
3405
def test_commit_preview_tree(self):
3406
tree = self.make_branch_and_tree('tree')
3407
rev_id = tree.commit('rev1')
3408
tree.branch.lock_write()
3409
self.addCleanup(tree.branch.unlock)
3410
tt = TransformPreview(tree)
3411
tt.new_file('file', tt.root, [b'contents'], b'file_id')
3412
self.addCleanup(tt.finalize)
3413
preview = tt.get_preview_tree()
3414
preview.set_parent_ids([rev_id])
3415
builder = tree.branch.get_commit_builder([rev_id])
3416
list(builder.record_iter_changes(preview, rev_id, tt.iter_changes()))
3417
builder.finish_inventory()
3418
rev2_id = builder.commit('rev2')
3419
rev2_tree = tree.branch.repository.revision_tree(rev2_id)
3420
self.assertEqual(b'contents', rev2_tree.get_file_text('file'))
3422
def test_ascii_limbo_paths(self):
3423
self.requireFeature(features.UnicodeFilenameFeature)
3424
branch = self.make_branch('any')
3425
tree = branch.repository.revision_tree(_mod_revision.NULL_REVISION)
3426
tt = TransformPreview(tree)
3427
self.addCleanup(tt.finalize)
3428
foo_id = tt.new_directory('', ROOT_PARENT)
3429
bar_id = tt.new_file(u'\u1234bar', foo_id, [b'contents'])
3430
limbo_path = tt._limbo_name(bar_id)
3431
self.assertEqual(limbo_path, limbo_path)
3434
class FakeSerializer(object):
3435
"""Serializer implementation that simply returns the input.
3437
The input is returned in the order used by pack.ContainerPushParser.
3440
def bytes_record(bytes, names):
3444
class TestSerializeTransform(tests.TestCaseWithTransport):
3446
_test_needs_features = [features.UnicodeFilenameFeature]
3448
def get_preview(self, tree=None):
3450
tree = self.make_branch_and_tree('tree')
3451
tt = TransformPreview(tree)
3452
self.addCleanup(tt.finalize)
3455
def assertSerializesTo(self, expected, tt):
3456
records = list(tt.serialize(FakeSerializer()))
3457
self.assertEqual(expected, records)
3460
def default_attribs():
3465
b'_new_executability': {},
3467
b'_tree_path_ids': {b'': b'new-0'},
3469
b'_removed_contents': [],
3470
b'_non_present_ids': {},
3473
def make_records(self, attribs, contents):
3475
((((b'attribs'),),), bencode.bencode(attribs))]
3476
records.extend([(((n, k),), c) for n, k, c in contents])
3479
def creation_records(self):
3480
attribs = self.default_attribs()
3481
attribs[b'_id_number'] = 3
3482
attribs[b'_new_name'] = {
3483
b'new-1': u'foo\u1234'.encode('utf-8'), b'new-2': b'qux'}
3484
attribs[b'_new_id'] = {b'new-1': b'baz', b'new-2': b'quxx'}
3485
attribs[b'_new_parent'] = {b'new-1': b'new-0', b'new-2': b'new-0'}
3486
attribs[b'_new_executability'] = {b'new-1': 1}
3488
(b'new-1', b'file', b'i 1\nbar\n'),
3489
(b'new-2', b'directory', b''),
3491
return self.make_records(attribs, contents)
3493
def test_serialize_creation(self):
3494
tt = self.get_preview()
3495
tt.new_file(u'foo\u1234', tt.root, [b'bar'], b'baz', True)
3496
tt.new_directory('qux', tt.root, b'quxx')
3497
self.assertSerializesTo(self.creation_records(), tt)
3499
def test_deserialize_creation(self):
3500
tt = self.get_preview()
3501
tt.deserialize(iter(self.creation_records()))
3502
self.assertEqual(3, tt._id_number)
3503
self.assertEqual({'new-1': u'foo\u1234',
3504
'new-2': 'qux'}, tt._new_name)
3505
self.assertEqual({'new-1': b'baz', 'new-2': b'quxx'}, tt._new_id)
3506
self.assertEqual({'new-1': tt.root, 'new-2': tt.root}, tt._new_parent)
3507
self.assertEqual({b'baz': 'new-1', b'quxx': 'new-2'}, tt._r_new_id)
3508
self.assertEqual({'new-1': True}, tt._new_executability)
3509
self.assertEqual({'new-1': 'file',
3510
'new-2': 'directory'}, tt._new_contents)
3511
foo_limbo = open(tt._limbo_name('new-1'), 'rb')
3513
foo_content = foo_limbo.read()
3516
self.assertEqual(b'bar', foo_content)
3518
def symlink_creation_records(self):
3519
attribs = self.default_attribs()
3520
attribs[b'_id_number'] = 2
3521
attribs[b'_new_name'] = {b'new-1': u'foo\u1234'.encode('utf-8')}
3522
attribs[b'_new_parent'] = {b'new-1': b'new-0'}
3523
contents = [(b'new-1', b'symlink', u'bar\u1234'.encode('utf-8'))]
3524
return self.make_records(attribs, contents)
3526
def test_serialize_symlink_creation(self):
3527
self.requireFeature(features.SymlinkFeature)
3528
tt = self.get_preview()
3529
tt.new_symlink(u'foo\u1234', tt.root, u'bar\u1234')
3530
self.assertSerializesTo(self.symlink_creation_records(), tt)
3532
def test_deserialize_symlink_creation(self):
3533
self.requireFeature(features.SymlinkFeature)
3534
tt = self.get_preview()
3535
tt.deserialize(iter(self.symlink_creation_records()))
3536
abspath = tt._limbo_name('new-1')
3537
foo_content = osutils.readlink(abspath)
3538
self.assertEqual(u'bar\u1234', foo_content)
3540
def make_destruction_preview(self):
3541
tree = self.make_branch_and_tree('.')
3542
self.build_tree([u'foo\u1234', 'bar'])
3543
tree.add([u'foo\u1234', 'bar'], [b'foo-id', b'bar-id'])
3544
return self.get_preview(tree)
3546
def destruction_records(self):
3547
attribs = self.default_attribs()
3548
attribs[b'_id_number'] = 3
3549
attribs[b'_removed_id'] = [b'new-1']
3550
attribs[b'_removed_contents'] = [b'new-2']
3551
attribs[b'_tree_path_ids'] = {
3553
u'foo\u1234'.encode('utf-8'): b'new-1',
3556
return self.make_records(attribs, [])
3558
def test_serialize_destruction(self):
3559
tt = self.make_destruction_preview()
3560
foo_trans_id = tt.trans_id_tree_path(u'foo\u1234')
3561
tt.unversion_file(foo_trans_id)
3562
bar_trans_id = tt.trans_id_tree_path('bar')
3563
tt.delete_contents(bar_trans_id)
3564
self.assertSerializesTo(self.destruction_records(), tt)
3566
def test_deserialize_destruction(self):
3567
tt = self.make_destruction_preview()
3568
tt.deserialize(iter(self.destruction_records()))
3569
self.assertEqual({u'foo\u1234': 'new-1',
3571
'': tt.root}, tt._tree_path_ids)
3572
self.assertEqual({'new-1': u'foo\u1234',
3574
tt.root: ''}, tt._tree_id_paths)
3575
self.assertEqual({'new-1'}, tt._removed_id)
3576
self.assertEqual({'new-2'}, tt._removed_contents)
3578
def missing_records(self):
3579
attribs = self.default_attribs()
3580
attribs[b'_id_number'] = 2
3581
attribs[b'_non_present_ids'] = {
3583
return self.make_records(attribs, [])
3585
def test_serialize_missing(self):
3586
tt = self.get_preview()
3587
tt.trans_id_file_id(b'boo')
3588
self.assertSerializesTo(self.missing_records(), tt)
3590
def test_deserialize_missing(self):
3591
tt = self.get_preview()
3592
tt.deserialize(iter(self.missing_records()))
3593
self.assertEqual({b'boo': 'new-1'}, tt._non_present_ids)
3595
def make_modification_preview(self):
3596
LINES_ONE = b'aa\nbb\ncc\ndd\n'
3597
LINES_TWO = b'z\nbb\nx\ndd\n'
3598
tree = self.make_branch_and_tree('tree')
3599
self.build_tree_contents([('tree/file', LINES_ONE)])
3600
tree.add('file', b'file-id')
3601
return self.get_preview(tree), [LINES_TWO]
3603
def modification_records(self):
3604
attribs = self.default_attribs()
3605
attribs[b'_id_number'] = 2
3606
attribs[b'_tree_path_ids'] = {
3609
attribs[b'_removed_contents'] = [b'new-1']
3610
contents = [(b'new-1', b'file',
3611
b'i 1\nz\n\nc 0 1 1 1\ni 1\nx\n\nc 0 3 3 1\n')]
3612
return self.make_records(attribs, contents)
3614
def test_serialize_modification(self):
3615
tt, LINES = self.make_modification_preview()
3616
trans_id = tt.trans_id_file_id(b'file-id')
3617
tt.delete_contents(trans_id)
3618
tt.create_file(LINES, trans_id)
3619
self.assertSerializesTo(self.modification_records(), tt)
3621
def test_deserialize_modification(self):
3622
tt, LINES = self.make_modification_preview()
3623
tt.deserialize(iter(self.modification_records()))
3624
self.assertFileEqual(b''.join(LINES), tt._limbo_name('new-1'))
3626
def make_kind_change_preview(self):
3627
LINES = b'a\nb\nc\nd\n'
3628
tree = self.make_branch_and_tree('tree')
3629
self.build_tree(['tree/foo/'])
3630
tree.add('foo', b'foo-id')
3631
return self.get_preview(tree), [LINES]
3633
def kind_change_records(self):
3634
attribs = self.default_attribs()
3635
attribs[b'_id_number'] = 2
3636
attribs[b'_tree_path_ids'] = {
3639
attribs[b'_removed_contents'] = [b'new-1']
3640
contents = [(b'new-1', b'file',
3641
b'i 4\na\nb\nc\nd\n\n')]
3642
return self.make_records(attribs, contents)
3644
def test_serialize_kind_change(self):
3645
tt, LINES = self.make_kind_change_preview()
3646
trans_id = tt.trans_id_file_id(b'foo-id')
3647
tt.delete_contents(trans_id)
3648
tt.create_file(LINES, trans_id)
3649
self.assertSerializesTo(self.kind_change_records(), tt)
3651
def test_deserialize_kind_change(self):
3652
tt, LINES = self.make_kind_change_preview()
3653
tt.deserialize(iter(self.kind_change_records()))
3654
self.assertFileEqual(b''.join(LINES), tt._limbo_name('new-1'))
3656
def make_add_contents_preview(self):
3657
LINES = b'a\nb\nc\nd\n'
3658
tree = self.make_branch_and_tree('tree')
3659
self.build_tree(['tree/foo'])
3661
os.unlink('tree/foo')
3662
return self.get_preview(tree), LINES
3664
def add_contents_records(self):
3665
attribs = self.default_attribs()
3666
attribs[b'_id_number'] = 2
3667
attribs[b'_tree_path_ids'] = {
3670
contents = [(b'new-1', b'file',
3671
b'i 4\na\nb\nc\nd\n\n')]
3672
return self.make_records(attribs, contents)
3674
def test_serialize_add_contents(self):
3675
tt, LINES = self.make_add_contents_preview()
3676
trans_id = tt.trans_id_tree_path('foo')
3677
tt.create_file([LINES], trans_id)
3678
self.assertSerializesTo(self.add_contents_records(), tt)
3680
def test_deserialize_add_contents(self):
3681
tt, LINES = self.make_add_contents_preview()
3682
tt.deserialize(iter(self.add_contents_records()))
3683
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
3685
def test_get_parents_lines(self):
3686
LINES_ONE = b'aa\nbb\ncc\ndd\n'
3687
tree = self.make_branch_and_tree('tree')
3688
self.build_tree_contents([('tree/file', LINES_ONE)])
3689
tree.add('file', b'file-id')
3690
tt = self.get_preview(tree)
3691
trans_id = tt.trans_id_tree_path('file')
3692
self.assertEqual(([b'aa\n', b'bb\n', b'cc\n', b'dd\n'],),
3693
tt._get_parents_lines(trans_id))
3695
def test_get_parents_texts(self):
3696
LINES_ONE = b'aa\nbb\ncc\ndd\n'
3697
tree = self.make_branch_and_tree('tree')
3698
self.build_tree_contents([('tree/file', LINES_ONE)])
3699
tree.add('file', b'file-id')
3700
tt = self.get_preview(tree)
3701
trans_id = tt.trans_id_tree_path('file')
3702
self.assertEqual((LINES_ONE,),
3703
tt._get_parents_texts(trans_id))
3706
class TestOrphan(tests.TestCaseWithTransport):
3708
def test_no_orphan_for_transform_preview(self):
3709
tree = self.make_branch_and_tree('tree')
3710
tt = transform.TransformPreview(tree)
3711
self.addCleanup(tt.finalize)
3712
self.assertRaises(NotImplementedError, tt.new_orphan, 'foo', 'bar')
3714
def _set_orphan_policy(self, wt, policy):
3715
wt.branch.get_config_stack().set('transform.orphan_policy',
3718
def _prepare_orphan(self, wt):
3719
self.build_tree(['dir/', 'dir/file', 'dir/foo'])
3720
wt.add(['dir', 'dir/file'], [b'dir-id', b'file-id'])
3721
wt.commit('add dir and file ignoring foo')
3722
tt = transform.TreeTransform(wt)
3723
self.addCleanup(tt.finalize)
3724
# dir and bar are deleted
3725
dir_tid = tt.trans_id_tree_path('dir')
3726
file_tid = tt.trans_id_tree_path('dir/file')
3727
orphan_tid = tt.trans_id_tree_path('dir/foo')
3728
tt.delete_contents(file_tid)
3729
tt.unversion_file(file_tid)
3730
tt.delete_contents(dir_tid)
3731
tt.unversion_file(dir_tid)
3732
# There should be a conflict because dir still contain foo
3733
raw_conflicts = tt.find_conflicts()
3734
self.assertLength(1, raw_conflicts)
3735
self.assertEqual(('missing parent', 'new-1'), raw_conflicts[0])
3736
return tt, orphan_tid
3738
def test_new_orphan_created(self):
3739
wt = self.make_branch_and_tree('.')
3740
self._set_orphan_policy(wt, 'move')
3741
tt, orphan_tid = self._prepare_orphan(wt)
3745
warnings.append(args[0] % args[1:])
3746
self.overrideAttr(trace, 'warning', warning)
3747
remaining_conflicts = resolve_conflicts(tt)
3748
self.assertEqual(['dir/foo has been orphaned in brz-orphans'],
3750
# Yeah for resolved conflicts !
3751
self.assertLength(0, remaining_conflicts)
3752
# We have a new orphan
3753
self.assertEqual('foo.~1~', tt.final_name(orphan_tid))
3754
self.assertEqual('brz-orphans',
3755
tt.final_name(tt.final_parent(orphan_tid)))
3757
def test_never_orphan(self):
3758
wt = self.make_branch_and_tree('.')
3759
self._set_orphan_policy(wt, 'conflict')
3760
tt, orphan_tid = self._prepare_orphan(wt)
3761
remaining_conflicts = resolve_conflicts(tt)
3762
self.assertLength(1, remaining_conflicts)
3763
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
3764
remaining_conflicts.pop())
3766
def test_orphan_error(self):
3767
def bogus_orphan(tt, orphan_id, parent_id):
3768
raise transform.OrphaningError(tt.final_name(orphan_id),
3769
tt.final_name(parent_id))
3770
transform.orphaning_registry.register('bogus', bogus_orphan,
3771
'Raise an error when orphaning')
3772
wt = self.make_branch_and_tree('.')
3773
self._set_orphan_policy(wt, 'bogus')
3774
tt, orphan_tid = self._prepare_orphan(wt)
3775
remaining_conflicts = resolve_conflicts(tt)
3776
self.assertLength(1, remaining_conflicts)
3777
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
3778
remaining_conflicts.pop())
3780
def test_unknown_orphan_policy(self):
3781
wt = self.make_branch_and_tree('.')
3782
# Set a fictional policy nobody ever implemented
3783
self._set_orphan_policy(wt, 'donttouchmypreciouuus')
3784
tt, orphan_tid = self._prepare_orphan(wt)
3788
warnings.append(args[0] % args[1:])
3789
self.overrideAttr(trace, 'warning', warning)
3790
remaining_conflicts = resolve_conflicts(tt)
3791
# We fallback to the default policy which create a conflict
3792
self.assertLength(1, remaining_conflicts)
3793
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
3794
remaining_conflicts.pop())
3795
self.assertLength(1, warnings)
3796
self.assertStartsWith(warnings[0], 'Value "donttouchmypreciouuus" ')
3799
class TestTransformHooks(tests.TestCaseWithTransport):
3802
super(TestTransformHooks, self).setUp()
3803
self.wt = self.make_branch_and_tree('.')
3806
def get_transform(self):
3807
transform = TreeTransform(self.wt)
3808
self.addCleanup(transform.finalize)
3809
return transform, transform.root
3811
def test_pre_commit_hooks(self):
3814
def record_pre_transform(tree, tt):
3815
calls.append((tree, tt))
3816
MutableTree.hooks.install_named_hook(
3817
'pre_transform', record_pre_transform, "Pre transform")
3818
transform, root = self.get_transform()
3819
old_root_id = transform.tree_file_id(root)
3821
self.assertEqual(old_root_id, self.wt.get_root_id())
3822
self.assertEqual([(self.wt, transform)], calls)
3824
def test_post_commit_hooks(self):
3827
def record_post_transform(tree, tt):
3828
calls.append((tree, tt))
3829
MutableTree.hooks.install_named_hook(
3830
'post_transform', record_post_transform, "Post transform")
3831
transform, root = self.get_transform()
3832
old_root_id = transform.tree_file_id(root)
3834
self.assertEqual(old_root_id, self.wt.get_root_id())
3835
self.assertEqual([(self.wt, transform)], calls)
3838
class TestLinkTree(tests.TestCaseWithTransport):
3840
_test_needs_features = [HardlinkFeature]
3843
tests.TestCaseWithTransport.setUp(self)
3844
self.parent_tree = self.make_branch_and_tree('parent')
3845
self.parent_tree.lock_write()
3846
self.addCleanup(self.parent_tree.unlock)
3847
self.build_tree_contents([('parent/foo', b'bar')])
3848
self.parent_tree.add('foo')
3849
self.parent_tree.commit('added foo')
3850
child_controldir = self.parent_tree.controldir.sprout('child')
3851
self.child_tree = child_controldir.open_workingtree()
3853
def hardlinked(self):
3854
parent_stat = os.lstat(self.parent_tree.abspath('foo'))
3855
child_stat = os.lstat(self.child_tree.abspath('foo'))
3856
return parent_stat.st_ino == child_stat.st_ino
3858
def test_link_fails_if_modified(self):
3859
"""If the file to be linked has modified text, don't link."""
3860
self.build_tree_contents([('child/foo', b'baz')])
3861
transform.link_tree(self.child_tree, self.parent_tree)
3862
self.assertFalse(self.hardlinked())
3864
def test_link_fails_if_execute_bit_changed(self):
3865
"""If the file to be linked has modified execute bit, don't link."""
3866
tt = TreeTransform(self.child_tree)
3868
trans_id = tt.trans_id_tree_path('foo')
3869
tt.set_executability(True, trans_id)
3873
transform.link_tree(self.child_tree, self.parent_tree)
3874
self.assertFalse(self.hardlinked())
3876
def test_link_succeeds_if_unmodified(self):
3877
"""If the file to be linked is unmodified, link"""
3878
transform.link_tree(self.child_tree, self.parent_tree)
3879
self.assertTrue(self.hardlinked())