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()
2851
os.symlink = os_symlink
2852
self.assertContainsRe(
2854
b'Ignoring "foo" as symlinks are not supported on this filesystem')
2856
def test_transform_conflicts(self):
2857
revision_tree = self.create_tree()
2858
preview = TransformPreview(revision_tree)
2859
self.addCleanup(preview.finalize)
2860
preview.new_file('a', preview.root, [b'content 2'])
2861
resolve_conflicts(preview)
2862
trans_id = preview.trans_id_file_id(b'a-id')
2863
self.assertEqual('a.moved', preview.final_name(trans_id))
2865
def get_tree_and_preview_tree(self):
2866
revision_tree = self.create_tree()
2867
preview = TransformPreview(revision_tree)
2868
self.addCleanup(preview.finalize)
2869
a_trans_id = preview.trans_id_file_id(b'a-id')
2870
preview.delete_contents(a_trans_id)
2871
preview.create_file([b'b content'], a_trans_id)
2872
preview_tree = preview.get_preview_tree()
2873
return revision_tree, preview_tree
2875
def test_iter_changes(self):
2876
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2877
root = revision_tree.get_root_id()
2878
self.assertEqual([(b'a-id', ('a', 'a'), True, (True, True),
2879
(root, root), ('a', 'a'), ('file', 'file'),
2881
list(preview_tree.iter_changes(revision_tree)))
2883
def test_include_unchanged_succeeds(self):
2884
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2885
changes = preview_tree.iter_changes(revision_tree,
2886
include_unchanged=True)
2887
self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
2889
def test_specific_files(self):
2890
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2891
changes = preview_tree.iter_changes(revision_tree,
2892
specific_files=[''])
2893
self.assertEqual([A_ENTRY], list(changes))
2895
def test_want_unversioned(self):
2896
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2897
changes = preview_tree.iter_changes(revision_tree,
2898
want_unversioned=True)
2899
self.assertEqual([A_ENTRY], list(changes))
2901
def test_ignore_extra_trees_no_specific_files(self):
2902
# extra_trees is harmless without specific_files, so we'll silently
2903
# accept it, even though we won't use it.
2904
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2905
preview_tree.iter_changes(revision_tree, extra_trees=[preview_tree])
2907
def test_ignore_require_versioned_no_specific_files(self):
2908
# require_versioned is meaningless without specific_files.
2909
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2910
preview_tree.iter_changes(revision_tree, require_versioned=False)
2912
def test_ignore_pb(self):
2913
# pb could be supported, but TT.iter_changes doesn't support it.
2914
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2915
preview_tree.iter_changes(revision_tree)
2917
def test_kind(self):
2918
revision_tree = self.create_tree()
2919
preview = TransformPreview(revision_tree)
2920
self.addCleanup(preview.finalize)
2921
preview.new_file('file', preview.root, [b'contents'], b'file-id')
2922
preview.new_directory('directory', preview.root, b'dir-id')
2923
preview_tree = preview.get_preview_tree()
2924
self.assertEqual('file', preview_tree.kind('file'))
2925
self.assertEqual('directory', preview_tree.kind('directory'))
2927
def test_get_file_mtime(self):
2928
preview = self.get_empty_preview()
2929
file_trans_id = preview.new_file('file', preview.root, [b'contents'],
2931
limbo_path = preview._limbo_name(file_trans_id)
2932
preview_tree = preview.get_preview_tree()
2933
self.assertEqual(os.stat(limbo_path).st_mtime,
2934
preview_tree.get_file_mtime('file'))
2936
def test_get_file_mtime_renamed(self):
2937
work_tree = self.make_branch_and_tree('tree')
2938
self.build_tree(['tree/file'])
2939
work_tree.add('file', b'file-id')
2940
preview = TransformPreview(work_tree)
2941
self.addCleanup(preview.finalize)
2942
file_trans_id = preview.trans_id_tree_path('file')
2943
preview.adjust_path('renamed', preview.root, file_trans_id)
2944
preview_tree = preview.get_preview_tree()
2945
preview_mtime = preview_tree.get_file_mtime('renamed')
2946
work_mtime = work_tree.get_file_mtime('file')
2948
def test_get_file_size(self):
2949
work_tree = self.make_branch_and_tree('tree')
2950
self.build_tree_contents([('tree/old', b'old')])
2951
work_tree.add('old', b'old-id')
2952
preview = TransformPreview(work_tree)
2953
self.addCleanup(preview.finalize)
2954
preview.new_file('name', preview.root, [b'contents'], b'new-id',
2956
tree = preview.get_preview_tree()
2957
self.assertEqual(len('old'), tree.get_file_size('old'))
2958
self.assertEqual(len('contents'), tree.get_file_size('name'))
2960
def test_get_file(self):
2961
preview = self.get_empty_preview()
2962
preview.new_file('file', preview.root, [b'contents'], b'file-id')
2963
preview_tree = preview.get_preview_tree()
2964
with preview_tree.get_file('file') as tree_file:
2965
self.assertEqual(b'contents', tree_file.read())
2967
def test_get_symlink_target(self):
2968
self.requireFeature(SymlinkFeature)
2969
preview = self.get_empty_preview()
2970
preview.new_symlink('symlink', preview.root, 'target', b'symlink-id')
2971
preview_tree = preview.get_preview_tree()
2972
self.assertEqual('target',
2973
preview_tree.get_symlink_target('symlink'))
2975
def test_all_file_ids(self):
2976
tree = self.make_branch_and_tree('tree')
2977
self.build_tree(['tree/a', 'tree/b', 'tree/c'])
2978
tree.add(['a', 'b', 'c'], [b'a-id', b'b-id', b'c-id'])
2979
preview = TransformPreview(tree)
2980
self.addCleanup(preview.finalize)
2981
preview.unversion_file(preview.trans_id_file_id(b'b-id'))
2982
c_trans_id = preview.trans_id_file_id(b'c-id')
2983
preview.unversion_file(c_trans_id)
2984
preview.version_file(b'c-id', c_trans_id)
2985
preview_tree = preview.get_preview_tree()
2986
self.assertEqual({b'a-id', b'c-id', tree.get_root_id()},
2987
preview_tree.all_file_ids())
2989
def test_path2id_deleted_unchanged(self):
2990
tree = self.make_branch_and_tree('tree')
2991
self.build_tree(['tree/unchanged', 'tree/deleted'])
2992
tree.add(['unchanged', 'deleted'], [b'unchanged-id', b'deleted-id'])
2993
preview = TransformPreview(tree)
2994
self.addCleanup(preview.finalize)
2995
preview.unversion_file(preview.trans_id_file_id(b'deleted-id'))
2996
preview_tree = preview.get_preview_tree()
2997
self.assertEqual(b'unchanged-id', preview_tree.path2id('unchanged'))
2998
self.assertFalse(preview_tree.is_versioned('deleted'))
3000
def test_path2id_created(self):
3001
tree = self.make_branch_and_tree('tree')
3002
self.build_tree(['tree/unchanged'])
3003
tree.add(['unchanged'], [b'unchanged-id'])
3004
preview = TransformPreview(tree)
3005
self.addCleanup(preview.finalize)
3006
preview.new_file('new', preview.trans_id_file_id(b'unchanged-id'),
3007
[b'contents'], b'new-id')
3008
preview_tree = preview.get_preview_tree()
3009
self.assertEqual(b'new-id', preview_tree.path2id('unchanged/new'))
3011
def test_path2id_moved(self):
3012
tree = self.make_branch_and_tree('tree')
3013
self.build_tree(['tree/old_parent/', 'tree/old_parent/child'])
3014
tree.add(['old_parent', 'old_parent/child'],
3015
[b'old_parent-id', b'child-id'])
3016
preview = TransformPreview(tree)
3017
self.addCleanup(preview.finalize)
3018
new_parent = preview.new_directory('new_parent', preview.root,
3020
preview.adjust_path('child', new_parent,
3021
preview.trans_id_file_id(b'child-id'))
3022
preview_tree = preview.get_preview_tree()
3023
self.assertFalse(preview_tree.is_versioned('old_parent/child'))
3024
self.assertEqual(b'child-id', preview_tree.path2id('new_parent/child'))
3026
def test_path2id_renamed_parent(self):
3027
tree = self.make_branch_and_tree('tree')
3028
self.build_tree(['tree/old_name/', 'tree/old_name/child'])
3029
tree.add(['old_name', 'old_name/child'],
3030
[b'parent-id', b'child-id'])
3031
preview = TransformPreview(tree)
3032
self.addCleanup(preview.finalize)
3033
preview.adjust_path('new_name', preview.root,
3034
preview.trans_id_file_id(b'parent-id'))
3035
preview_tree = preview.get_preview_tree()
3036
self.assertFalse(preview_tree.is_versioned('old_name/child'))
3037
self.assertEqual(b'child-id', preview_tree.path2id('new_name/child'))
3039
def assertMatchingIterEntries(self, tt, specific_files=None):
3040
preview_tree = tt.get_preview_tree()
3041
preview_result = list(preview_tree.iter_entries_by_dir(
3042
specific_files=specific_files))
3045
actual_result = list(tree.iter_entries_by_dir(
3046
specific_files=specific_files))
3047
self.assertEqual(actual_result, preview_result)
3049
def test_iter_entries_by_dir_new(self):
3050
tree = self.make_branch_and_tree('tree')
3051
tt = TreeTransform(tree)
3052
tt.new_file('new', tt.root, [b'contents'], b'new-id')
3053
self.assertMatchingIterEntries(tt)
3055
def test_iter_entries_by_dir_deleted(self):
3056
tree = self.make_branch_and_tree('tree')
3057
self.build_tree(['tree/deleted'])
3058
tree.add('deleted', b'deleted-id')
3059
tt = TreeTransform(tree)
3060
tt.delete_contents(tt.trans_id_file_id(b'deleted-id'))
3061
self.assertMatchingIterEntries(tt)
3063
def test_iter_entries_by_dir_unversioned(self):
3064
tree = self.make_branch_and_tree('tree')
3065
self.build_tree(['tree/removed'])
3066
tree.add('removed', b'removed-id')
3067
tt = TreeTransform(tree)
3068
tt.unversion_file(tt.trans_id_file_id(b'removed-id'))
3069
self.assertMatchingIterEntries(tt)
3071
def test_iter_entries_by_dir_moved(self):
3072
tree = self.make_branch_and_tree('tree')
3073
self.build_tree(['tree/moved', 'tree/new_parent/'])
3074
tree.add(['moved', 'new_parent'], [b'moved-id', b'new_parent-id'])
3075
tt = TreeTransform(tree)
3076
tt.adjust_path('moved', tt.trans_id_file_id(b'new_parent-id'),
3077
tt.trans_id_file_id(b'moved-id'))
3078
self.assertMatchingIterEntries(tt)
3080
def test_iter_entries_by_dir_specific_files(self):
3081
tree = self.make_branch_and_tree('tree')
3082
tree.set_root_id(b'tree-root-id')
3083
self.build_tree(['tree/parent/', 'tree/parent/child'])
3084
tree.add(['parent', 'parent/child'], [b'parent-id', b'child-id'])
3085
tt = TreeTransform(tree)
3086
self.assertMatchingIterEntries(tt, ['', 'parent/child'])
3088
def test_symlink_content_summary(self):
3089
self.requireFeature(SymlinkFeature)
3090
preview = self.get_empty_preview()
3091
preview.new_symlink('path', preview.root, 'target', b'path-id')
3092
summary = preview.get_preview_tree().path_content_summary('path')
3093
self.assertEqual(('symlink', None, None, 'target'), summary)
3095
def test_missing_content_summary(self):
3096
preview = self.get_empty_preview()
3097
summary = preview.get_preview_tree().path_content_summary('path')
3098
self.assertEqual(('missing', None, None, None), summary)
3100
def test_deleted_content_summary(self):
3101
tree = self.make_branch_and_tree('tree')
3102
self.build_tree(['tree/path/'])
3104
preview = TransformPreview(tree)
3105
self.addCleanup(preview.finalize)
3106
preview.delete_contents(preview.trans_id_tree_path('path'))
3107
summary = preview.get_preview_tree().path_content_summary('path')
3108
self.assertEqual(('missing', None, None, None), summary)
3110
def test_file_content_summary_executable(self):
3111
preview = self.get_empty_preview()
3112
path_id = preview.new_file('path', preview.root, [
3113
b'contents'], b'path-id')
3114
preview.set_executability(True, path_id)
3115
summary = preview.get_preview_tree().path_content_summary('path')
3116
self.assertEqual(4, len(summary))
3117
self.assertEqual('file', summary[0])
3118
# size must be known
3119
self.assertEqual(len('contents'), summary[1])
3121
self.assertEqual(True, summary[2])
3122
# will not have hash (not cheap to determine)
3123
self.assertIs(None, summary[3])
3125
def test_change_executability(self):
3126
tree = self.make_branch_and_tree('tree')
3127
self.build_tree(['tree/path'])
3129
preview = TransformPreview(tree)
3130
self.addCleanup(preview.finalize)
3131
path_id = preview.trans_id_tree_path('path')
3132
preview.set_executability(True, path_id)
3133
summary = preview.get_preview_tree().path_content_summary('path')
3134
self.assertEqual(True, summary[2])
3136
def test_file_content_summary_non_exec(self):
3137
preview = self.get_empty_preview()
3138
preview.new_file('path', preview.root, [b'contents'], b'path-id')
3139
summary = preview.get_preview_tree().path_content_summary('path')
3140
self.assertEqual(4, len(summary))
3141
self.assertEqual('file', summary[0])
3142
# size must be known
3143
self.assertEqual(len('contents'), summary[1])
3145
self.assertEqual(False, summary[2])
3146
# will not have hash (not cheap to determine)
3147
self.assertIs(None, summary[3])
3149
def test_dir_content_summary(self):
3150
preview = self.get_empty_preview()
3151
preview.new_directory('path', preview.root, b'path-id')
3152
summary = preview.get_preview_tree().path_content_summary('path')
3153
self.assertEqual(('directory', None, None, None), summary)
3155
def test_tree_content_summary(self):
3156
preview = self.get_empty_preview()
3157
path = preview.new_directory('path', preview.root, b'path-id')
3158
preview.set_tree_reference(b'rev-1', path)
3159
summary = preview.get_preview_tree().path_content_summary('path')
3160
self.assertEqual(4, len(summary))
3161
self.assertEqual('tree-reference', summary[0])
3163
def test_annotate(self):
3164
tree = self.make_branch_and_tree('tree')
3165
self.build_tree_contents([('tree/file', b'a\n')])
3166
tree.add('file', b'file-id')
3167
tree.commit('a', rev_id=b'one')
3168
self.build_tree_contents([('tree/file', b'a\nb\n')])
3169
preview = TransformPreview(tree)
3170
self.addCleanup(preview.finalize)
3171
file_trans_id = preview.trans_id_file_id(b'file-id')
3172
preview.delete_contents(file_trans_id)
3173
preview.create_file([b'a\nb\nc\n'], file_trans_id)
3174
preview_tree = preview.get_preview_tree()
3180
annotation = preview_tree.annotate_iter(
3181
'file', default_revision=b'me:')
3182
self.assertEqual(expected, annotation)
3184
def test_annotate_missing(self):
3185
preview = self.get_empty_preview()
3186
preview.new_file('file', preview.root, [b'a\nb\nc\n'], b'file-id')
3187
preview_tree = preview.get_preview_tree()
3193
annotation = preview_tree.annotate_iter(
3194
'file', default_revision=b'me:')
3195
self.assertEqual(expected, annotation)
3197
def test_annotate_rename(self):
3198
tree = self.make_branch_and_tree('tree')
3199
self.build_tree_contents([('tree/file', b'a\n')])
3200
tree.add('file', b'file-id')
3201
tree.commit('a', rev_id=b'one')
3202
preview = TransformPreview(tree)
3203
self.addCleanup(preview.finalize)
3204
file_trans_id = preview.trans_id_file_id(b'file-id')
3205
preview.adjust_path('newname', preview.root, file_trans_id)
3206
preview_tree = preview.get_preview_tree()
3210
annotation = preview_tree.annotate_iter(
3211
'file', default_revision=b'me:')
3212
self.assertEqual(expected, annotation)
3214
def test_annotate_deleted(self):
3215
tree = self.make_branch_and_tree('tree')
3216
self.build_tree_contents([('tree/file', b'a\n')])
3217
tree.add('file', b'file-id')
3218
tree.commit('a', rev_id=b'one')
3219
self.build_tree_contents([('tree/file', b'a\nb\n')])
3220
preview = TransformPreview(tree)
3221
self.addCleanup(preview.finalize)
3222
file_trans_id = preview.trans_id_file_id(b'file-id')
3223
preview.delete_contents(file_trans_id)
3224
preview_tree = preview.get_preview_tree()
3225
annotation = preview_tree.annotate_iter(
3226
'file', default_revision=b'me:')
3227
self.assertIs(None, annotation)
3229
def test_stored_kind(self):
3230
preview = self.get_empty_preview()
3231
preview.new_file('file', preview.root, [b'a\nb\nc\n'], b'file-id')
3232
preview_tree = preview.get_preview_tree()
3233
self.assertEqual('file', preview_tree.stored_kind('file'))
3235
def test_is_executable(self):
3236
preview = self.get_empty_preview()
3237
preview.new_file('file', preview.root, [b'a\nb\nc\n'], b'file-id')
3238
preview.set_executability(True, preview.trans_id_file_id(b'file-id'))
3239
preview_tree = preview.get_preview_tree()
3240
self.assertEqual(True, preview_tree.is_executable('file'))
3242
def test_get_set_parent_ids(self):
3243
revision_tree, preview_tree = self.get_tree_and_preview_tree()
3244
self.assertEqual([], preview_tree.get_parent_ids())
3245
preview_tree.set_parent_ids([b'rev-1'])
3246
self.assertEqual([b'rev-1'], preview_tree.get_parent_ids())
3248
def test_plan_file_merge(self):
3249
work_a = self.make_branch_and_tree('wta')
3250
self.build_tree_contents([('wta/file', b'a\nb\nc\nd\n')])
3251
work_a.add('file', b'file-id')
3252
base_id = work_a.commit('base version')
3253
tree_b = work_a.controldir.sprout('wtb').open_workingtree()
3254
preview = TransformPreview(work_a)
3255
self.addCleanup(preview.finalize)
3256
trans_id = preview.trans_id_file_id(b'file-id')
3257
preview.delete_contents(trans_id)
3258
preview.create_file([b'b\nc\nd\ne\n'], trans_id)
3259
self.build_tree_contents([('wtb/file', b'a\nc\nd\nf\n')])
3260
tree_a = preview.get_preview_tree()
3261
tree_a.set_parent_ids([base_id])
3263
('killed-a', b'a\n'),
3264
('killed-b', b'b\n'),
3265
('unchanged', b'c\n'),
3266
('unchanged', b'd\n'),
3269
], list(tree_a.plan_file_merge(b'file-id', tree_b)))
3271
def test_plan_file_merge_revision_tree(self):
3272
work_a = self.make_branch_and_tree('wta')
3273
self.build_tree_contents([('wta/file', b'a\nb\nc\nd\n')])
3274
work_a.add('file', b'file-id')
3275
base_id = work_a.commit('base version')
3276
tree_b = work_a.controldir.sprout('wtb').open_workingtree()
3277
preview = TransformPreview(work_a.basis_tree())
3278
self.addCleanup(preview.finalize)
3279
trans_id = preview.trans_id_file_id(b'file-id')
3280
preview.delete_contents(trans_id)
3281
preview.create_file([b'b\nc\nd\ne\n'], trans_id)
3282
self.build_tree_contents([('wtb/file', b'a\nc\nd\nf\n')])
3283
tree_a = preview.get_preview_tree()
3284
tree_a.set_parent_ids([base_id])
3286
('killed-a', b'a\n'),
3287
('killed-b', b'b\n'),
3288
('unchanged', b'c\n'),
3289
('unchanged', b'd\n'),
3292
], list(tree_a.plan_file_merge(b'file-id', tree_b)))
3294
def test_walkdirs(self):
3295
preview = self.get_empty_preview()
3296
preview.new_directory('', ROOT_PARENT, b'tree-root')
3297
# FIXME: new_directory should mark root.
3298
preview.fixup_new_roots()
3299
preview_tree = preview.get_preview_tree()
3300
preview.new_file('a', preview.root, [b'contents'], b'a-id')
3301
expected = [(('', b'tree-root'),
3302
[('a', 'a', 'file', None, b'a-id', 'file')])]
3303
self.assertEqual(expected, list(preview_tree.walkdirs()))
3305
def test_extras(self):
3306
work_tree = self.make_branch_and_tree('tree')
3307
self.build_tree(['tree/removed-file', 'tree/existing-file',
3308
'tree/not-removed-file'])
3309
work_tree.add(['removed-file', 'not-removed-file'])
3310
preview = TransformPreview(work_tree)
3311
self.addCleanup(preview.finalize)
3312
preview.new_file('new-file', preview.root, [b'contents'])
3313
preview.new_file('new-versioned-file', preview.root, [b'contents'],
3314
b'new-versioned-id')
3315
tree = preview.get_preview_tree()
3316
preview.unversion_file(preview.trans_id_tree_path('removed-file'))
3317
self.assertEqual({'new-file', 'removed-file', 'existing-file'},
3320
def test_merge_into_preview(self):
3321
work_tree = self.make_branch_and_tree('tree')
3322
self.build_tree_contents([('tree/file', b'b\n')])
3323
work_tree.add('file', b'file-id')
3324
work_tree.commit('first commit')
3325
child_tree = work_tree.controldir.sprout('child').open_workingtree()
3326
self.build_tree_contents([('child/file', b'b\nc\n')])
3327
child_tree.commit('child commit')
3328
child_tree.lock_write()
3329
self.addCleanup(child_tree.unlock)
3330
work_tree.lock_write()
3331
self.addCleanup(work_tree.unlock)
3332
preview = TransformPreview(work_tree)
3333
self.addCleanup(preview.finalize)
3334
file_trans_id = preview.trans_id_file_id(b'file-id')
3335
preview.delete_contents(file_trans_id)
3336
preview.create_file([b'a\nb\n'], file_trans_id)
3337
preview_tree = preview.get_preview_tree()
3338
merger = Merger.from_revision_ids(preview_tree,
3339
child_tree.branch.last_revision(),
3340
other_branch=child_tree.branch,
3341
tree_branch=work_tree.branch)
3342
merger.merge_type = Merge3Merger
3343
tt = merger.make_merger().make_preview_transform()
3344
self.addCleanup(tt.finalize)
3345
final_tree = tt.get_preview_tree()
3348
final_tree.get_file_text(final_tree.id2path(b'file-id')))
3350
def test_merge_preview_into_workingtree(self):
3351
tree = self.make_branch_and_tree('tree')
3352
tree.set_root_id(b'TREE_ROOT')
3353
tt = TransformPreview(tree)
3354
self.addCleanup(tt.finalize)
3355
tt.new_file('name', tt.root, [b'content'], b'file-id')
3356
tree2 = self.make_branch_and_tree('tree2')
3357
tree2.set_root_id(b'TREE_ROOT')
3358
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
3360
merger.merge_type = Merge3Merger
3363
def test_merge_preview_into_workingtree_handles_conflicts(self):
3364
tree = self.make_branch_and_tree('tree')
3365
self.build_tree_contents([('tree/foo', b'bar')])
3366
tree.add('foo', b'foo-id')
3368
tt = TransformPreview(tree)
3369
self.addCleanup(tt.finalize)
3370
trans_id = tt.trans_id_file_id(b'foo-id')
3371
tt.delete_contents(trans_id)
3372
tt.create_file([b'baz'], trans_id)
3373
tree2 = tree.controldir.sprout('tree2').open_workingtree()
3374
self.build_tree_contents([('tree2/foo', b'qux')])
3375
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
3377
merger.merge_type = Merge3Merger
3380
def test_has_filename(self):
3381
wt = self.make_branch_and_tree('tree')
3382
self.build_tree(['tree/unmodified', 'tree/removed', 'tree/modified'])
3383
tt = TransformPreview(wt)
3384
removed_id = tt.trans_id_tree_path('removed')
3385
tt.delete_contents(removed_id)
3386
tt.new_file('new', tt.root, [b'contents'])
3387
modified_id = tt.trans_id_tree_path('modified')
3388
tt.delete_contents(modified_id)
3389
tt.create_file([b'modified-contents'], modified_id)
3390
self.addCleanup(tt.finalize)
3391
tree = tt.get_preview_tree()
3392
self.assertTrue(tree.has_filename('unmodified'))
3393
self.assertFalse(tree.has_filename('not-present'))
3394
self.assertFalse(tree.has_filename('removed'))
3395
self.assertTrue(tree.has_filename('new'))
3396
self.assertTrue(tree.has_filename('modified'))
3398
def test_is_executable(self):
3399
tree = self.make_branch_and_tree('tree')
3400
preview = TransformPreview(tree)
3401
self.addCleanup(preview.finalize)
3402
preview.new_file('foo', preview.root, [b'bar'], b'baz-id')
3403
preview_tree = preview.get_preview_tree()
3404
self.assertEqual(False, preview_tree.is_executable('tree/foo'))
3406
def test_commit_preview_tree(self):
3407
tree = self.make_branch_and_tree('tree')
3408
rev_id = tree.commit('rev1')
3409
tree.branch.lock_write()
3410
self.addCleanup(tree.branch.unlock)
3411
tt = TransformPreview(tree)
3412
tt.new_file('file', tt.root, [b'contents'], b'file_id')
3413
self.addCleanup(tt.finalize)
3414
preview = tt.get_preview_tree()
3415
preview.set_parent_ids([rev_id])
3416
builder = tree.branch.get_commit_builder([rev_id])
3417
list(builder.record_iter_changes(preview, rev_id, tt.iter_changes()))
3418
builder.finish_inventory()
3419
rev2_id = builder.commit('rev2')
3420
rev2_tree = tree.branch.repository.revision_tree(rev2_id)
3421
self.assertEqual(b'contents', rev2_tree.get_file_text('file'))
3423
def test_ascii_limbo_paths(self):
3424
self.requireFeature(features.UnicodeFilenameFeature)
3425
branch = self.make_branch('any')
3426
tree = branch.repository.revision_tree(_mod_revision.NULL_REVISION)
3427
tt = TransformPreview(tree)
3428
self.addCleanup(tt.finalize)
3429
foo_id = tt.new_directory('', ROOT_PARENT)
3430
bar_id = tt.new_file(u'\u1234bar', foo_id, [b'contents'])
3431
limbo_path = tt._limbo_name(bar_id)
3432
self.assertEqual(limbo_path, limbo_path)
3435
class FakeSerializer(object):
3436
"""Serializer implementation that simply returns the input.
3438
The input is returned in the order used by pack.ContainerPushParser.
3441
def bytes_record(bytes, names):
3445
class TestSerializeTransform(tests.TestCaseWithTransport):
3447
_test_needs_features = [features.UnicodeFilenameFeature]
3449
def get_preview(self, tree=None):
3451
tree = self.make_branch_and_tree('tree')
3452
tt = TransformPreview(tree)
3453
self.addCleanup(tt.finalize)
3456
def assertSerializesTo(self, expected, tt):
3457
records = list(tt.serialize(FakeSerializer()))
3458
self.assertEqual(expected, records)
3461
def default_attribs():
3466
b'_new_executability': {},
3468
b'_tree_path_ids': {b'': b'new-0'},
3470
b'_removed_contents': [],
3471
b'_non_present_ids': {},
3474
def make_records(self, attribs, contents):
3476
((((b'attribs'),),), bencode.bencode(attribs))]
3477
records.extend([(((n, k),), c) for n, k, c in contents])
3480
def creation_records(self):
3481
attribs = self.default_attribs()
3482
attribs[b'_id_number'] = 3
3483
attribs[b'_new_name'] = {
3484
b'new-1': u'foo\u1234'.encode('utf-8'), b'new-2': b'qux'}
3485
attribs[b'_new_id'] = {b'new-1': b'baz', b'new-2': b'quxx'}
3486
attribs[b'_new_parent'] = {b'new-1': b'new-0', b'new-2': b'new-0'}
3487
attribs[b'_new_executability'] = {b'new-1': 1}
3489
(b'new-1', b'file', b'i 1\nbar\n'),
3490
(b'new-2', b'directory', b''),
3492
return self.make_records(attribs, contents)
3494
def test_serialize_creation(self):
3495
tt = self.get_preview()
3496
tt.new_file(u'foo\u1234', tt.root, [b'bar'], b'baz', True)
3497
tt.new_directory('qux', tt.root, b'quxx')
3498
self.assertSerializesTo(self.creation_records(), tt)
3500
def test_deserialize_creation(self):
3501
tt = self.get_preview()
3502
tt.deserialize(iter(self.creation_records()))
3503
self.assertEqual(3, tt._id_number)
3504
self.assertEqual({'new-1': u'foo\u1234',
3505
'new-2': 'qux'}, tt._new_name)
3506
self.assertEqual({'new-1': b'baz', 'new-2': b'quxx'}, tt._new_id)
3507
self.assertEqual({'new-1': tt.root, 'new-2': tt.root}, tt._new_parent)
3508
self.assertEqual({b'baz': 'new-1', b'quxx': 'new-2'}, tt._r_new_id)
3509
self.assertEqual({'new-1': True}, tt._new_executability)
3510
self.assertEqual({'new-1': 'file',
3511
'new-2': 'directory'}, tt._new_contents)
3512
foo_limbo = open(tt._limbo_name('new-1'), 'rb')
3514
foo_content = foo_limbo.read()
3517
self.assertEqual(b'bar', foo_content)
3519
def symlink_creation_records(self):
3520
attribs = self.default_attribs()
3521
attribs[b'_id_number'] = 2
3522
attribs[b'_new_name'] = {b'new-1': u'foo\u1234'.encode('utf-8')}
3523
attribs[b'_new_parent'] = {b'new-1': b'new-0'}
3524
contents = [(b'new-1', b'symlink', u'bar\u1234'.encode('utf-8'))]
3525
return self.make_records(attribs, contents)
3527
def test_serialize_symlink_creation(self):
3528
self.requireFeature(features.SymlinkFeature)
3529
tt = self.get_preview()
3530
tt.new_symlink(u'foo\u1234', tt.root, u'bar\u1234')
3531
self.assertSerializesTo(self.symlink_creation_records(), tt)
3533
def test_deserialize_symlink_creation(self):
3534
self.requireFeature(features.SymlinkFeature)
3535
tt = self.get_preview()
3536
tt.deserialize(iter(self.symlink_creation_records()))
3537
abspath = tt._limbo_name('new-1')
3538
foo_content = osutils.readlink(abspath)
3539
self.assertEqual(u'bar\u1234', foo_content)
3541
def make_destruction_preview(self):
3542
tree = self.make_branch_and_tree('.')
3543
self.build_tree([u'foo\u1234', 'bar'])
3544
tree.add([u'foo\u1234', 'bar'], [b'foo-id', b'bar-id'])
3545
return self.get_preview(tree)
3547
def destruction_records(self):
3548
attribs = self.default_attribs()
3549
attribs[b'_id_number'] = 3
3550
attribs[b'_removed_id'] = [b'new-1']
3551
attribs[b'_removed_contents'] = [b'new-2']
3552
attribs[b'_tree_path_ids'] = {
3554
u'foo\u1234'.encode('utf-8'): b'new-1',
3557
return self.make_records(attribs, [])
3559
def test_serialize_destruction(self):
3560
tt = self.make_destruction_preview()
3561
foo_trans_id = tt.trans_id_tree_path(u'foo\u1234')
3562
tt.unversion_file(foo_trans_id)
3563
bar_trans_id = tt.trans_id_tree_path('bar')
3564
tt.delete_contents(bar_trans_id)
3565
self.assertSerializesTo(self.destruction_records(), tt)
3567
def test_deserialize_destruction(self):
3568
tt = self.make_destruction_preview()
3569
tt.deserialize(iter(self.destruction_records()))
3570
self.assertEqual({u'foo\u1234': 'new-1',
3572
'': tt.root}, tt._tree_path_ids)
3573
self.assertEqual({'new-1': u'foo\u1234',
3575
tt.root: ''}, tt._tree_id_paths)
3576
self.assertEqual({'new-1'}, tt._removed_id)
3577
self.assertEqual({'new-2'}, tt._removed_contents)
3579
def missing_records(self):
3580
attribs = self.default_attribs()
3581
attribs[b'_id_number'] = 2
3582
attribs[b'_non_present_ids'] = {
3584
return self.make_records(attribs, [])
3586
def test_serialize_missing(self):
3587
tt = self.get_preview()
3588
tt.trans_id_file_id(b'boo')
3589
self.assertSerializesTo(self.missing_records(), tt)
3591
def test_deserialize_missing(self):
3592
tt = self.get_preview()
3593
tt.deserialize(iter(self.missing_records()))
3594
self.assertEqual({b'boo': 'new-1'}, tt._non_present_ids)
3596
def make_modification_preview(self):
3597
LINES_ONE = b'aa\nbb\ncc\ndd\n'
3598
LINES_TWO = b'z\nbb\nx\ndd\n'
3599
tree = self.make_branch_and_tree('tree')
3600
self.build_tree_contents([('tree/file', LINES_ONE)])
3601
tree.add('file', b'file-id')
3602
return self.get_preview(tree), [LINES_TWO]
3604
def modification_records(self):
3605
attribs = self.default_attribs()
3606
attribs[b'_id_number'] = 2
3607
attribs[b'_tree_path_ids'] = {
3610
attribs[b'_removed_contents'] = [b'new-1']
3611
contents = [(b'new-1', b'file',
3612
b'i 1\nz\n\nc 0 1 1 1\ni 1\nx\n\nc 0 3 3 1\n')]
3613
return self.make_records(attribs, contents)
3615
def test_serialize_modification(self):
3616
tt, LINES = self.make_modification_preview()
3617
trans_id = tt.trans_id_file_id(b'file-id')
3618
tt.delete_contents(trans_id)
3619
tt.create_file(LINES, trans_id)
3620
self.assertSerializesTo(self.modification_records(), tt)
3622
def test_deserialize_modification(self):
3623
tt, LINES = self.make_modification_preview()
3624
tt.deserialize(iter(self.modification_records()))
3625
self.assertFileEqual(b''.join(LINES), tt._limbo_name('new-1'))
3627
def make_kind_change_preview(self):
3628
LINES = b'a\nb\nc\nd\n'
3629
tree = self.make_branch_and_tree('tree')
3630
self.build_tree(['tree/foo/'])
3631
tree.add('foo', b'foo-id')
3632
return self.get_preview(tree), [LINES]
3634
def kind_change_records(self):
3635
attribs = self.default_attribs()
3636
attribs[b'_id_number'] = 2
3637
attribs[b'_tree_path_ids'] = {
3640
attribs[b'_removed_contents'] = [b'new-1']
3641
contents = [(b'new-1', b'file',
3642
b'i 4\na\nb\nc\nd\n\n')]
3643
return self.make_records(attribs, contents)
3645
def test_serialize_kind_change(self):
3646
tt, LINES = self.make_kind_change_preview()
3647
trans_id = tt.trans_id_file_id(b'foo-id')
3648
tt.delete_contents(trans_id)
3649
tt.create_file(LINES, trans_id)
3650
self.assertSerializesTo(self.kind_change_records(), tt)
3652
def test_deserialize_kind_change(self):
3653
tt, LINES = self.make_kind_change_preview()
3654
tt.deserialize(iter(self.kind_change_records()))
3655
self.assertFileEqual(b''.join(LINES), tt._limbo_name('new-1'))
3657
def make_add_contents_preview(self):
3658
LINES = b'a\nb\nc\nd\n'
3659
tree = self.make_branch_and_tree('tree')
3660
self.build_tree(['tree/foo'])
3662
os.unlink('tree/foo')
3663
return self.get_preview(tree), LINES
3665
def add_contents_records(self):
3666
attribs = self.default_attribs()
3667
attribs[b'_id_number'] = 2
3668
attribs[b'_tree_path_ids'] = {
3671
contents = [(b'new-1', b'file',
3672
b'i 4\na\nb\nc\nd\n\n')]
3673
return self.make_records(attribs, contents)
3675
def test_serialize_add_contents(self):
3676
tt, LINES = self.make_add_contents_preview()
3677
trans_id = tt.trans_id_tree_path('foo')
3678
tt.create_file([LINES], trans_id)
3679
self.assertSerializesTo(self.add_contents_records(), tt)
3681
def test_deserialize_add_contents(self):
3682
tt, LINES = self.make_add_contents_preview()
3683
tt.deserialize(iter(self.add_contents_records()))
3684
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
3686
def test_get_parents_lines(self):
3687
LINES_ONE = b'aa\nbb\ncc\ndd\n'
3688
tree = self.make_branch_and_tree('tree')
3689
self.build_tree_contents([('tree/file', LINES_ONE)])
3690
tree.add('file', b'file-id')
3691
tt = self.get_preview(tree)
3692
trans_id = tt.trans_id_tree_path('file')
3693
self.assertEqual(([b'aa\n', b'bb\n', b'cc\n', b'dd\n'],),
3694
tt._get_parents_lines(trans_id))
3696
def test_get_parents_texts(self):
3697
LINES_ONE = b'aa\nbb\ncc\ndd\n'
3698
tree = self.make_branch_and_tree('tree')
3699
self.build_tree_contents([('tree/file', LINES_ONE)])
3700
tree.add('file', b'file-id')
3701
tt = self.get_preview(tree)
3702
trans_id = tt.trans_id_tree_path('file')
3703
self.assertEqual((LINES_ONE,),
3704
tt._get_parents_texts(trans_id))
3707
class TestOrphan(tests.TestCaseWithTransport):
3709
def test_no_orphan_for_transform_preview(self):
3710
tree = self.make_branch_and_tree('tree')
3711
tt = transform.TransformPreview(tree)
3712
self.addCleanup(tt.finalize)
3713
self.assertRaises(NotImplementedError, tt.new_orphan, 'foo', 'bar')
3715
def _set_orphan_policy(self, wt, policy):
3716
wt.branch.get_config_stack().set('transform.orphan_policy',
3719
def _prepare_orphan(self, wt):
3720
self.build_tree(['dir/', 'dir/file', 'dir/foo'])
3721
wt.add(['dir', 'dir/file'], [b'dir-id', b'file-id'])
3722
wt.commit('add dir and file ignoring foo')
3723
tt = transform.TreeTransform(wt)
3724
self.addCleanup(tt.finalize)
3725
# dir and bar are deleted
3726
dir_tid = tt.trans_id_tree_path('dir')
3727
file_tid = tt.trans_id_tree_path('dir/file')
3728
orphan_tid = tt.trans_id_tree_path('dir/foo')
3729
tt.delete_contents(file_tid)
3730
tt.unversion_file(file_tid)
3731
tt.delete_contents(dir_tid)
3732
tt.unversion_file(dir_tid)
3733
# There should be a conflict because dir still contain foo
3734
raw_conflicts = tt.find_conflicts()
3735
self.assertLength(1, raw_conflicts)
3736
self.assertEqual(('missing parent', 'new-1'), raw_conflicts[0])
3737
return tt, orphan_tid
3739
def test_new_orphan_created(self):
3740
wt = self.make_branch_and_tree('.')
3741
self._set_orphan_policy(wt, 'move')
3742
tt, orphan_tid = self._prepare_orphan(wt)
3746
warnings.append(args[0] % args[1:])
3747
self.overrideAttr(trace, 'warning', warning)
3748
remaining_conflicts = resolve_conflicts(tt)
3749
self.assertEqual(['dir/foo has been orphaned in brz-orphans'],
3751
# Yeah for resolved conflicts !
3752
self.assertLength(0, remaining_conflicts)
3753
# We have a new orphan
3754
self.assertEqual('foo.~1~', tt.final_name(orphan_tid))
3755
self.assertEqual('brz-orphans',
3756
tt.final_name(tt.final_parent(orphan_tid)))
3758
def test_never_orphan(self):
3759
wt = self.make_branch_and_tree('.')
3760
self._set_orphan_policy(wt, 'conflict')
3761
tt, orphan_tid = self._prepare_orphan(wt)
3762
remaining_conflicts = resolve_conflicts(tt)
3763
self.assertLength(1, remaining_conflicts)
3764
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
3765
remaining_conflicts.pop())
3767
def test_orphan_error(self):
3768
def bogus_orphan(tt, orphan_id, parent_id):
3769
raise transform.OrphaningError(tt.final_name(orphan_id),
3770
tt.final_name(parent_id))
3771
transform.orphaning_registry.register('bogus', bogus_orphan,
3772
'Raise an error when orphaning')
3773
wt = self.make_branch_and_tree('.')
3774
self._set_orphan_policy(wt, 'bogus')
3775
tt, orphan_tid = self._prepare_orphan(wt)
3776
remaining_conflicts = resolve_conflicts(tt)
3777
self.assertLength(1, remaining_conflicts)
3778
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
3779
remaining_conflicts.pop())
3781
def test_unknown_orphan_policy(self):
3782
wt = self.make_branch_and_tree('.')
3783
# Set a fictional policy nobody ever implemented
3784
self._set_orphan_policy(wt, 'donttouchmypreciouuus')
3785
tt, orphan_tid = self._prepare_orphan(wt)
3789
warnings.append(args[0] % args[1:])
3790
self.overrideAttr(trace, 'warning', warning)
3791
remaining_conflicts = resolve_conflicts(tt)
3792
# We fallback to the default policy which create a conflict
3793
self.assertLength(1, remaining_conflicts)
3794
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
3795
remaining_conflicts.pop())
3796
self.assertLength(1, warnings)
3797
self.assertStartsWith(warnings[0], 'Value "donttouchmypreciouuus" ')
3800
class TestTransformHooks(tests.TestCaseWithTransport):
3803
super(TestTransformHooks, self).setUp()
3804
self.wt = self.make_branch_and_tree('.')
3807
def get_transform(self):
3808
transform = TreeTransform(self.wt)
3809
self.addCleanup(transform.finalize)
3810
return transform, transform.root
3812
def test_pre_commit_hooks(self):
3815
def record_pre_transform(tree, tt):
3816
calls.append((tree, tt))
3817
MutableTree.hooks.install_named_hook(
3818
'pre_transform', record_pre_transform, "Pre transform")
3819
transform, root = self.get_transform()
3820
old_root_id = transform.tree_file_id(root)
3822
self.assertEqual(old_root_id, self.wt.get_root_id())
3823
self.assertEqual([(self.wt, transform)], calls)
3825
def test_post_commit_hooks(self):
3828
def record_post_transform(tree, tt):
3829
calls.append((tree, tt))
3830
MutableTree.hooks.install_named_hook(
3831
'post_transform', record_post_transform, "Post transform")
3832
transform, root = self.get_transform()
3833
old_root_id = transform.tree_file_id(root)
3835
self.assertEqual(old_root_id, self.wt.get_root_id())
3836
self.assertEqual([(self.wt, transform)], calls)
3839
class TestLinkTree(tests.TestCaseWithTransport):
3841
_test_needs_features = [HardlinkFeature]
3844
tests.TestCaseWithTransport.setUp(self)
3845
self.parent_tree = self.make_branch_and_tree('parent')
3846
self.parent_tree.lock_write()
3847
self.addCleanup(self.parent_tree.unlock)
3848
self.build_tree_contents([('parent/foo', b'bar')])
3849
self.parent_tree.add('foo')
3850
self.parent_tree.commit('added foo')
3851
child_controldir = self.parent_tree.controldir.sprout('child')
3852
self.child_tree = child_controldir.open_workingtree()
3854
def hardlinked(self):
3855
parent_stat = os.lstat(self.parent_tree.abspath('foo'))
3856
child_stat = os.lstat(self.child_tree.abspath('foo'))
3857
return parent_stat.st_ino == child_stat.st_ino
3859
def test_link_fails_if_modified(self):
3860
"""If the file to be linked has modified text, don't link."""
3861
self.build_tree_contents([('child/foo', b'baz')])
3862
transform.link_tree(self.child_tree, self.parent_tree)
3863
self.assertFalse(self.hardlinked())
3865
def test_link_fails_if_execute_bit_changed(self):
3866
"""If the file to be linked has modified execute bit, don't link."""
3867
tt = TreeTransform(self.child_tree)
3869
trans_id = tt.trans_id_tree_path('foo')
3870
tt.set_executability(True, trans_id)
3874
transform.link_tree(self.child_tree, self.parent_tree)
3875
self.assertFalse(self.hardlinked())
3877
def test_link_succeeds_if_unmodified(self):
3878
"""If the file to be linked is unmodified, link"""
3879
transform.link_tree(self.child_tree, self.parent_tree)
3880
self.assertTrue(self.hardlinked())