50
121
self.assertRaises(LockError, self.wt.unlock)
51
122
os.rmdir(pathjoin(limbo_name, 'hehe'))
52
123
os.rmdir(limbo_name)
124
os.rmdir(deletion_path)
53
125
transform, root = self.get_transform()
128
def test_existing_pending_deletion(self):
129
transform, root = self.get_transform()
130
deletion_path = self._limbodir = urlutils.local_path_from_url(
131
transform._tree._transport.abspath('pending-deletion'))
132
os.mkdir(pathjoin(deletion_path, 'blocking-directory'))
133
self.assertRaises(ImmortalPendingDeletion, transform.apply)
134
self.assertRaises(LockError, self.wt.unlock)
135
self.assertRaises(ExistingPendingDeletion, self.get_transform)
56
137
def test_build(self):
57
transform, root = self.get_transform()
138
transform, root = self.get_transform()
139
self.wt.lock_tree_write()
140
self.addCleanup(self.wt.unlock)
58
141
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
59
142
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())
143
imaginary_id2 = transform.trans_id_tree_path('imaginary/')
144
self.assertEqual(imaginary_id, imaginary_id2)
145
self.assertEqual(root, transform.get_tree_parent(imaginary_id))
146
self.assertEqual('directory', transform.final_kind(root))
147
self.assertEqual(self.wt.get_root_id(), transform.final_file_id(root))
63
148
trans_id = transform.create_path('name', root)
64
149
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)
150
self.assertIs(None, transform.final_kind(trans_id))
151
transform.create_file([b'contents'], trans_id)
67
152
transform.set_executability(True, trans_id)
68
transform.version_file('my_pretties', trans_id)
153
transform.version_file(b'my_pretties', trans_id)
69
154
self.assertRaises(DuplicateKey, transform.version_file,
70
'my_pretties', trans_id)
71
self.assertEqual(transform.final_file_id(trans_id), 'my_pretties')
155
b'my_pretties', trans_id)
156
self.assertEqual(transform.final_file_id(trans_id), b'my_pretties')
72
157
self.assertEqual(transform.final_parent(trans_id), root)
73
158
self.assertIs(transform.final_parent(root), ROOT_PARENT)
74
159
self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
75
160
oz_id = transform.create_path('oz', root)
76
161
transform.create_directory(oz_id)
77
transform.version_file('ozzie', oz_id)
162
transform.version_file(b'ozzie', oz_id)
78
163
trans_id2 = transform.create_path('name2', root)
79
transform.create_file('contents', trans_id2)
164
transform.create_file([b'contents'], trans_id2)
80
165
transform.set_executability(False, trans_id2)
81
transform.version_file('my_pretties2', trans_id2)
166
transform.version_file(b'my_pretties2', trans_id2)
82
167
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)
168
with self.wt.get_file('name') as f:
169
self.assertEqual(b'contents', f.read())
170
self.assertEqual(self.wt.path2id('name'), b'my_pretties')
171
self.assertIs(self.wt.is_executable('name'), True)
172
self.assertIs(self.wt.is_executable('name2'), False)
87
173
self.assertEqual('directory', file_kind(self.wt.abspath('oz')))
88
174
self.assertEqual(len(modified_paths), 3)
89
tree_mod_paths = [self.wt.id2abspath(f) for f in
90
('ozzie', 'my_pretties', 'my_pretties2')]
175
tree_mod_paths = [self.wt.abspath(self.wt.id2path(f)) for f in
176
(b'ozzie', b'my_pretties', b'my_pretties2')]
91
177
self.assertSubset(tree_mod_paths, modified_paths)
92
178
# is it safe to finalize repeatedly?
93
179
transform.finalize()
94
180
transform.finalize()
182
def test_apply_informs_tree_of_observed_sha1(self):
183
trans, root, contents, sha1 = self.get_transform_for_sha1_test()
184
trans_id = trans.new_file('file1', root, contents, file_id=b'file1-id',
187
orig = self.wt._observed_sha1
188
def _observed_sha1(*args):
191
self.wt._observed_sha1 = _observed_sha1
193
self.assertEqual([(None, 'file1', trans._observed_sha1s[trans_id])],
196
def test_create_file_caches_sha1(self):
197
trans, root, contents, sha1 = self.get_transform_for_sha1_test()
198
trans_id = trans.create_path('file1', root)
199
trans.create_file(contents, trans_id, sha1=sha1)
200
st_val = osutils.lstat(trans._limbo_name(trans_id))
201
o_sha1, o_st_val = trans._observed_sha1s[trans_id]
202
self.assertEqual(o_sha1, sha1)
203
self.assertEqualStat(o_st_val, st_val)
205
def test__apply_insertions_updates_sha1(self):
206
trans, root, contents, sha1 = self.get_transform_for_sha1_test()
207
trans_id = trans.create_path('file1', root)
208
trans.create_file(contents, trans_id, sha1=sha1)
209
st_val = osutils.lstat(trans._limbo_name(trans_id))
210
o_sha1, o_st_val = trans._observed_sha1s[trans_id]
211
self.assertEqual(o_sha1, sha1)
212
self.assertEqualStat(o_st_val, st_val)
213
creation_mtime = trans._creation_mtime + 10.0
214
# We fake a time difference from when the file was created until now it
215
# is being renamed by using os.utime. Note that the change we actually
216
# want to see is the real ctime change from 'os.rename()', but as long
217
# as we observe a new stat value, we should be fine.
218
os.utime(trans._limbo_name(trans_id), (creation_mtime, creation_mtime))
220
new_st_val = osutils.lstat(self.wt.abspath('file1'))
221
o_sha1, o_st_val = trans._observed_sha1s[trans_id]
222
self.assertEqual(o_sha1, sha1)
223
self.assertEqualStat(o_st_val, new_st_val)
224
self.assertNotEqual(st_val.st_mtime, new_st_val.st_mtime)
226
def test_new_file_caches_sha1(self):
227
trans, root, contents, sha1 = self.get_transform_for_sha1_test()
228
trans_id = trans.new_file('file1', root, contents, file_id=b'file1-id',
230
st_val = osutils.lstat(trans._limbo_name(trans_id))
231
o_sha1, o_st_val = trans._observed_sha1s[trans_id]
232
self.assertEqual(o_sha1, sha1)
233
self.assertEqualStat(o_st_val, st_val)
235
def test_cancel_creation_removes_observed_sha1(self):
236
trans, root, contents, sha1 = self.get_transform_for_sha1_test()
237
trans_id = trans.new_file('file1', root, contents, file_id=b'file1-id',
239
self.assertTrue(trans_id in trans._observed_sha1s)
240
trans.cancel_creation(trans_id)
241
self.assertFalse(trans_id in trans._observed_sha1s)
243
def test_create_files_same_timestamp(self):
244
transform, root = self.get_transform()
245
self.wt.lock_tree_write()
246
self.addCleanup(self.wt.unlock)
247
# Roll back the clock, so that we know everything is being set to the
249
transform._creation_mtime = creation_mtime = time.time() - 20.0
250
transform.create_file([b'content-one'],
251
transform.create_path('one', root))
252
time.sleep(1) # *ugly*
253
transform.create_file([b'content-two'],
254
transform.create_path('two', root))
256
fo, st1 = self.wt.get_file_with_stat('one', filtered=False)
258
fo, st2 = self.wt.get_file_with_stat('two', filtered=False)
260
# We only guarantee 2s resolution
261
self.assertTrue(abs(creation_mtime - st1.st_mtime) < 2.0,
262
"%s != %s within 2 seconds" % (creation_mtime, st1.st_mtime))
263
# But if we have more than that, all files should get the same result
264
self.assertEqual(st1.st_mtime, st2.st_mtime)
266
def test_change_root_id(self):
267
transform, root = self.get_transform()
268
self.assertNotEqual(b'new-root-id', self.wt.get_root_id())
269
transform.new_directory('', ROOT_PARENT, b'new-root-id')
270
transform.delete_contents(root)
271
transform.unversion_file(root)
272
transform.fixup_new_roots()
274
self.assertEqual(b'new-root-id', self.wt.get_root_id())
276
def test_change_root_id_add_files(self):
277
transform, root = self.get_transform()
278
self.assertNotEqual(b'new-root-id', self.wt.get_root_id())
279
new_trans_id = transform.new_directory('', ROOT_PARENT, b'new-root-id')
280
transform.new_file('file', new_trans_id, [b'new-contents\n'],
282
transform.delete_contents(root)
283
transform.unversion_file(root)
284
transform.fixup_new_roots()
286
self.assertEqual(b'new-root-id', self.wt.get_root_id())
287
self.assertEqual(b'new-file-id', self.wt.path2id('file'))
288
self.assertFileEqual(b'new-contents\n', self.wt.abspath('file'))
290
def test_add_two_roots(self):
291
transform, root = self.get_transform()
292
new_trans_id = transform.new_directory('', ROOT_PARENT, b'new-root-id')
293
new_trans_id = transform.new_directory('', ROOT_PARENT, b'alt-root-id')
294
self.assertRaises(ValueError, transform.fixup_new_roots)
296
def test_retain_existing_root(self):
297
tt, root = self.get_transform()
299
tt.new_directory('', ROOT_PARENT, b'new-root-id')
301
self.assertNotEqual(b'new-root-id', tt.final_file_id(tt.root))
303
def test_retain_existing_root_added_file(self):
304
tt, root = self.get_transform()
305
new_trans_id = tt.new_directory('', ROOT_PARENT, b'new-root-id')
306
child = tt.new_directory('child', new_trans_id, b'child-id')
308
self.assertEqual(tt.root, tt.final_parent(child))
310
def test_add_unversioned_root(self):
311
transform, root = self.get_transform()
312
new_trans_id = transform.new_directory('', ROOT_PARENT, None)
313
transform.delete_contents(transform.root)
314
transform.fixup_new_roots()
315
self.assertNotIn(transform.root, transform._new_id)
317
def test_remove_root_fixup(self):
318
transform, root = self.get_transform()
319
old_root_id = self.wt.get_root_id()
320
self.assertNotEqual(b'new-root-id', old_root_id)
321
transform.delete_contents(root)
322
transform.unversion_file(root)
323
transform.fixup_new_roots()
325
self.assertEqual(old_root_id, self.wt.get_root_id())
327
transform, root = self.get_transform()
328
new_trans_id = transform.new_directory('', ROOT_PARENT, b'new-root-id')
329
new_trans_id = transform.new_directory('', ROOT_PARENT, b'alt-root-id')
330
self.assertRaises(ValueError, transform.fixup_new_roots)
332
def test_fixup_new_roots_permits_empty_tree(self):
333
transform, root = self.get_transform()
334
transform.delete_contents(root)
335
transform.unversion_file(root)
336
transform.fixup_new_roots()
337
self.assertIs(None, transform.final_kind(root))
338
self.assertIs(None, transform.final_file_id(root))
340
def test_apply_retains_root_directory(self):
341
# Do not attempt to delete the physical root directory, because that
343
transform, root = self.get_transform()
345
transform.delete_contents(root)
346
e = self.assertRaises(AssertionError, self.assertRaises,
347
errors.TransformRenameFailed,
349
self.assertContainsRe('TransformRenameFailed not raised', str(e))
351
def test_apply_retains_file_id(self):
352
transform, root = self.get_transform()
353
old_root_id = transform.tree_file_id(root)
354
transform.unversion_file(root)
356
self.assertEqual(old_root_id, self.wt.get_root_id())
358
def test_hardlink(self):
359
self.requireFeature(HardlinkFeature)
360
transform, root = self.get_transform()
361
transform.new_file('file1', root, [b'contents'])
363
target = self.make_branch_and_tree('target')
364
target_transform = TreeTransform(target)
365
trans_id = target_transform.create_path('file1', target_transform.root)
366
target_transform.create_hardlink(self.wt.abspath('file1'), trans_id)
367
target_transform.apply()
368
self.assertPathExists('target/file1')
369
source_stat = os.stat(self.wt.abspath('file1'))
370
target_stat = os.stat('target/file1')
371
self.assertEqual(source_stat, target_stat)
96
373
def test_convenience(self):
97
374
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',
375
self.wt.lock_tree_write()
376
self.addCleanup(self.wt.unlock)
377
trans_id = transform.new_file('name', root, [b'contents'],
378
b'my_pretties', True)
379
oz = transform.new_directory('oz', root, b'oz-id')
380
dorothy = transform.new_directory('dorothy', oz, b'dorothy-id')
381
toto = transform.new_file('toto', dorothy, [b'toto-contents'],
105
384
self.assertEqual(len(transform.find_conflicts()), 0)
106
385
transform.apply()
107
386
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)
387
with open(self.wt.abspath('name'), 'r') as f:
388
self.assertEqual('contents', f.read())
389
self.assertEqual(self.wt.path2id('name'), b'my_pretties')
390
self.assertIs(self.wt.is_executable('name'), True)
391
self.assertEqual(self.wt.path2id('oz'), b'oz-id')
392
self.assertEqual(self.wt.path2id('oz/dorothy'), b'dorothy-id')
393
self.assertEqual(self.wt.path2id('oz/dorothy/toto'), b'toto-id')
395
self.assertEqual(b'toto-contents',
396
self.wt.get_file('oz/dorothy/toto').read())
397
self.assertIs(self.wt.is_executable('oz/dorothy/toto'), False)
399
def test_tree_reference(self):
400
transform, root = self.get_transform()
401
tree = transform._tree
402
trans_id = transform.new_directory('reference', root, b'subtree-id')
403
transform.set_tree_reference(b'subtree-revision', trans_id)
406
self.addCleanup(tree.unlock)
407
self.assertEqual(b'subtree-revision',
408
tree.root_inventory.get_entry(b'subtree-id').reference_revision)
119
410
def test_conflicts(self):
120
411
transform, root = self.get_transform()
121
trans_id = transform.new_file('name', root, 'contents',
412
trans_id = transform.new_file('name', root, [b'contents'],
123
414
self.assertEqual(len(transform.find_conflicts()), 0)
124
trans_id2 = transform.new_file('name', root, 'Crontents', 'toto')
125
self.assertEqual(transform.find_conflicts(),
415
trans_id2 = transform.new_file('name', root, [b'Crontents'], b'toto')
416
self.assertEqual(transform.find_conflicts(),
126
417
[('duplicate', trans_id, trans_id2, 'name')])
127
418
self.assertRaises(MalformedTransform, transform.apply)
128
419
transform.adjust_path('name', trans_id, trans_id2)
129
self.assertEqual(transform.find_conflicts(),
420
self.assertEqual(transform.find_conflicts(),
130
421
[('non-directory parent', trans_id)])
131
422
tinman_id = transform.trans_id_tree_path('tinman')
132
423
transform.adjust_path('name', tinman_id, trans_id2)
133
self.assertEqual(transform.find_conflicts(),
134
[('unversioned parent', tinman_id),
424
self.assertEqual(transform.find_conflicts(),
425
[('unversioned parent', tinman_id),
135
426
('missing parent', tinman_id)])
136
427
lion_id = transform.create_path('lion', root)
137
self.assertEqual(transform.find_conflicts(),
138
[('unversioned parent', tinman_id),
428
self.assertEqual(transform.find_conflicts(),
429
[('unversioned parent', tinman_id),
139
430
('missing parent', tinman_id)])
140
431
transform.adjust_path('name', lion_id, trans_id2)
141
self.assertEqual(transform.find_conflicts(),
432
self.assertEqual(transform.find_conflicts(),
142
433
[('unversioned parent', lion_id),
143
434
('missing parent', lion_id)])
144
transform.version_file("Courage", lion_id)
145
self.assertEqual(transform.find_conflicts(),
146
[('missing parent', lion_id),
435
transform.version_file(b"Courage", lion_id)
436
self.assertEqual(transform.find_conflicts(),
437
[('missing parent', lion_id),
147
438
('versioning no contents', lion_id)])
148
439
transform.adjust_path('name2', root, trans_id2)
149
self.assertEqual(transform.find_conflicts(),
440
self.assertEqual(transform.find_conflicts(),
150
441
[('versioning no contents', lion_id)])
151
transform.create_file('Contents, okay?', lion_id)
442
transform.create_file([b'Contents, okay?'], lion_id)
152
443
transform.adjust_path('name2', trans_id2, trans_id2)
153
self.assertEqual(transform.find_conflicts(),
154
[('parent loop', trans_id2),
444
self.assertEqual(transform.find_conflicts(),
445
[('parent loop', trans_id2),
155
446
('non-directory parent', trans_id2)])
156
447
transform.adjust_path('name2', root, trans_id2)
157
448
oz_id = transform.new_directory('oz', root)
158
449
transform.set_executability(True, oz_id)
159
self.assertEqual(transform.find_conflicts(),
450
self.assertEqual(transform.find_conflicts(),
160
451
[('unversioned executability', oz_id)])
161
transform.version_file('oz-id', oz_id)
162
self.assertEqual(transform.find_conflicts(),
452
transform.version_file(b'oz-id', oz_id)
453
self.assertEqual(transform.find_conflicts(),
163
454
[('non-file executability', oz_id)])
164
455
transform.set_executability(None, oz_id)
165
tip_id = transform.new_file('tip', oz_id, 'ozma', 'tip-id')
456
tip_id = transform.new_file('tip', oz_id, [b'ozma'], b'tip-id')
166
457
transform.apply()
167
self.assertEqual(self.wt.path2id('name'), 'my_pretties')
168
self.assertEqual('contents', file(self.wt.abspath('name')).read())
458
self.assertEqual(self.wt.path2id('name'), b'my_pretties')
459
with open(self.wt.abspath('name'), 'rb') as f:
460
self.assertEqual(b'contents', f.read())
169
461
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')
462
oz_id = transform2.trans_id_tree_path('oz')
463
newtip = transform2.new_file('tip', oz_id, [b'other'], b'tip-id')
172
464
result = transform2.find_conflicts()
173
465
fp = FinalPaths(transform2)
174
self.assert_('oz/tip' in transform2._tree_path_ids)
466
self.assertTrue('oz/tip' in transform2._tree_path_ids)
175
467
self.assertEqual(fp.get_path(newtip), pathjoin('oz', 'tip'))
176
468
self.assertEqual(len(result), 2)
177
self.assertEqual((result[0][0], result[0][1]),
469
self.assertEqual((result[0][0], result[0][1]),
178
470
('duplicate', newtip))
179
self.assertEqual((result[1][0], result[1][2]),
471
self.assertEqual((result[1][0], result[1][2]),
180
472
('duplicate id', newtip))
181
473
transform2.finalize()
182
474
transform3 = TreeTransform(self.wt)
183
475
self.addCleanup(transform3.finalize)
184
oz_id = transform3.trans_id_tree_file_id('oz-id')
476
oz_id = transform3.trans_id_tree_path('oz')
185
477
transform3.delete_contents(oz_id)
186
self.assertEqual(transform3.find_conflicts(),
478
self.assertEqual(transform3.find_conflicts(),
187
479
[('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')
480
root_id = transform3.root
481
tip_id = transform3.trans_id_tree_path('oz/tip')
190
482
transform3.adjust_path('tip', root_id, tip_id)
191
483
transform3.apply()
485
def test_conflict_on_case_insensitive(self):
486
tree = self.make_branch_and_tree('tree')
487
# Don't try this at home, kids!
488
# Force the tree to report that it is case sensitive, for conflict
490
tree.case_sensitive = True
491
transform = TreeTransform(tree)
492
self.addCleanup(transform.finalize)
493
transform.new_file('file', transform.root, [b'content'])
494
transform.new_file('FiLe', transform.root, [b'content'])
495
result = transform.find_conflicts()
496
self.assertEqual([], result)
498
# Force the tree to report that it is case insensitive, for conflict
500
tree.case_sensitive = False
501
transform = TreeTransform(tree)
502
self.addCleanup(transform.finalize)
503
transform.new_file('file', transform.root, [b'content'])
504
transform.new_file('FiLe', transform.root, [b'content'])
505
result = transform.find_conflicts()
506
self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
508
def test_conflict_on_case_insensitive_existing(self):
509
tree = self.make_branch_and_tree('tree')
510
self.build_tree(['tree/FiLe'])
511
# Don't try this at home, kids!
512
# Force the tree to report that it is case sensitive, for conflict
514
tree.case_sensitive = True
515
transform = TreeTransform(tree)
516
self.addCleanup(transform.finalize)
517
transform.new_file('file', transform.root, [b'content'])
518
result = transform.find_conflicts()
519
self.assertEqual([], result)
521
# Force the tree to report that it is case insensitive, for conflict
523
tree.case_sensitive = False
524
transform = TreeTransform(tree)
525
self.addCleanup(transform.finalize)
526
transform.new_file('file', transform.root, [b'content'])
527
result = transform.find_conflicts()
528
self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
530
def test_resolve_case_insensitive_conflict(self):
531
tree = self.make_branch_and_tree('tree')
532
# Don't try this at home, kids!
533
# Force the tree to report that it is case insensitive, for conflict
535
tree.case_sensitive = False
536
transform = TreeTransform(tree)
537
self.addCleanup(transform.finalize)
538
transform.new_file('file', transform.root, [b'content'])
539
transform.new_file('FiLe', transform.root, [b'content'])
540
resolve_conflicts(transform)
542
self.assertPathExists('tree/file')
543
self.assertPathExists('tree/FiLe.moved')
545
def test_resolve_checkout_case_conflict(self):
546
tree = self.make_branch_and_tree('tree')
547
# Don't try this at home, kids!
548
# Force the tree to report that it is case insensitive, for conflict
550
tree.case_sensitive = False
551
transform = TreeTransform(tree)
552
self.addCleanup(transform.finalize)
553
transform.new_file('file', transform.root, [b'content'])
554
transform.new_file('FiLe', transform.root, [b'content'])
555
resolve_conflicts(transform,
556
pass_func=lambda t, c: resolve_checkout(t, c, []))
558
self.assertPathExists('tree/file')
559
self.assertPathExists('tree/FiLe.moved')
561
def test_apply_case_conflict(self):
562
"""Ensure that a transform with case conflicts can always be applied"""
563
tree = self.make_branch_and_tree('tree')
564
transform = TreeTransform(tree)
565
self.addCleanup(transform.finalize)
566
transform.new_file('file', transform.root, [b'content'])
567
transform.new_file('FiLe', transform.root, [b'content'])
568
dir = transform.new_directory('dir', transform.root)
569
transform.new_file('dirfile', dir, [b'content'])
570
transform.new_file('dirFiLe', dir, [b'content'])
571
resolve_conflicts(transform)
573
self.assertPathExists('tree/file')
574
if not os.path.exists('tree/FiLe.moved'):
575
self.assertPathExists('tree/FiLe')
576
self.assertPathExists('tree/dir/dirfile')
577
if not os.path.exists('tree/dir/dirFiLe.moved'):
578
self.assertPathExists('tree/dir/dirFiLe')
580
def test_case_insensitive_limbo(self):
581
tree = self.make_branch_and_tree('tree')
582
# Don't try this at home, kids!
583
# Force the tree to report that it is case insensitive
584
tree.case_sensitive = False
585
transform = TreeTransform(tree)
586
self.addCleanup(transform.finalize)
587
dir = transform.new_directory('dir', transform.root)
588
first = transform.new_file('file', dir, [b'content'])
589
second = transform.new_file('FiLe', dir, [b'content'])
590
self.assertContainsRe(transform._limbo_name(first), 'new-1/file')
591
self.assertNotContainsRe(transform._limbo_name(second), 'new-1/FiLe')
593
def test_adjust_path_updates_child_limbo_names(self):
594
tree = self.make_branch_and_tree('tree')
595
transform = TreeTransform(tree)
596
self.addCleanup(transform.finalize)
597
foo_id = transform.new_directory('foo', transform.root)
598
bar_id = transform.new_directory('bar', foo_id)
599
baz_id = transform.new_directory('baz', bar_id)
600
qux_id = transform.new_directory('qux', baz_id)
601
transform.adjust_path('quxx', foo_id, bar_id)
602
self.assertStartsWith(transform._limbo_name(qux_id),
603
transform._limbo_name(bar_id))
193
605
def test_add_del(self):
194
606
start, root = self.get_transform()
195
start.new_directory('a', root, 'a')
607
start.new_directory('a', root, b'a')
197
609
transform, root = self.get_transform()
198
transform.delete_versioned(transform.trans_id_tree_file_id('a'))
199
transform.new_directory('a', root, 'a')
610
transform.delete_versioned(transform.trans_id_tree_path('a'))
611
transform.new_directory('a', root, b'a')
200
612
transform.apply()
202
614
def test_unversioning(self):
203
615
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')
616
parent_id = create_tree.new_directory('parent', root, b'parent-id')
617
create_tree.new_file('child', parent_id, [b'child'], b'child-id')
206
618
create_tree.apply()
207
619
unversion = TreeTransform(self.wt)
208
620
self.addCleanup(unversion.finalize)
209
621
parent = unversion.trans_id_tree_path('parent')
210
622
unversion.unversion_file(parent)
211
self.assertEqual(unversion.find_conflicts(),
623
self.assertEqual(unversion.find_conflicts(),
212
624
[('unversioned parent', parent_id)])
213
file_id = unversion.trans_id_tree_file_id('child-id')
625
file_id = unversion.trans_id_tree_path('parent/child')
214
626
unversion.unversion_file(file_id)
215
627
unversion.apply()
217
629
def test_name_invariants(self):
218
630
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')
632
root = create_tree.root
633
create_tree.new_file('name1', root, [b'hello1'], b'name1')
634
create_tree.new_file('name2', root, [b'hello2'], b'name2')
635
ddir = create_tree.new_directory('dying_directory', root, b'ddir')
636
create_tree.new_file('dying_file', ddir, [b'goodbye1'], b'dfile')
637
create_tree.new_file('moving_file', ddir, [b'later1'], b'mfile')
638
create_tree.new_file('moving_file2', root, [b'later2'], b'mfile2')
227
639
create_tree.apply()
229
mangle_tree,root = self.get_transform()
230
root = mangle_tree.trans_id_tree_file_id('TREE_ROOT')
641
mangle_tree, root = self.get_transform()
642
root = mangle_tree.root
232
name1 = mangle_tree.trans_id_tree_file_id('name1')
233
name2 = mangle_tree.trans_id_tree_file_id('name2')
644
name1 = mangle_tree.trans_id_tree_path('name1')
645
name2 = mangle_tree.trans_id_tree_path('name2')
234
646
mangle_tree.adjust_path('name2', root, name1)
235
647
mangle_tree.adjust_path('name1', root, name2)
237
#tests for deleting parent directories
238
ddir = mangle_tree.trans_id_tree_file_id('ddir')
649
#tests for deleting parent directories
650
ddir = mangle_tree.trans_id_tree_path('dying_directory')
239
651
mangle_tree.delete_contents(ddir)
240
dfile = mangle_tree.trans_id_tree_file_id('dfile')
652
dfile = mangle_tree.trans_id_tree_path('dying_directory/dying_file')
241
653
mangle_tree.delete_versioned(dfile)
242
654
mangle_tree.unversion_file(dfile)
243
mfile = mangle_tree.trans_id_tree_file_id('mfile')
655
mfile = mangle_tree.trans_id_tree_path('dying_directory/moving_file')
244
656
mangle_tree.adjust_path('mfile', root, mfile)
246
658
#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')
659
newdir = mangle_tree.new_directory('new_directory', root, b'newdir')
660
mfile2 = mangle_tree.trans_id_tree_path('moving_file2')
249
661
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')
662
mangle_tree.new_file('newfile', newdir, [b'hello3'], b'dfile')
663
self.assertEqual(mangle_tree.final_file_id(mfile2), b'mfile2')
252
664
self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
253
self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
665
self.assertEqual(mangle_tree.final_file_id(mfile2), b'mfile2')
254
666
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'))
667
with open(self.wt.abspath('name1'), 'r') as f:
668
self.assertEqual(f.read(), 'hello2')
669
with open(self.wt.abspath('name2'), 'r') as f:
670
self.assertEqual(f.read(), 'hello1')
671
mfile2_path = self.wt.abspath(pathjoin('new_directory', 'mfile2'))
258
672
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')
673
with open(mfile2_path, 'r') as f:
674
self.assertEqual(f.read(), 'later2')
675
self.assertEqual(self.wt.id2path(b'mfile2'), 'new_directory/mfile2')
676
self.assertEqual(self.wt.path2id('new_directory/mfile2'), b'mfile2')
677
newfile_path = self.wt.abspath(pathjoin('new_directory', 'newfile'))
678
with open(newfile_path, 'r') as f:
679
self.assertEqual(f.read(), 'hello3')
680
self.assertEqual(self.wt.path2id('dying_directory'), b'ddir')
265
681
self.assertIs(self.wt.path2id('dying_directory/dying_file'), None)
266
mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
682
mfile2_path = self.wt.abspath(pathjoin('new_directory', 'mfile2'))
268
684
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')
685
create_tree, root = self.get_transform()
686
newdir = create_tree.new_directory('selftest', root, b'selftest-id')
687
create_tree.new_file('blackbox.py', newdir, [b'hello1'], b'blackbox-id')
689
mangle_tree, root = self.get_transform()
690
selftest = mangle_tree.trans_id_tree_path('selftest')
691
blackbox = mangle_tree.trans_id_tree_path('selftest/blackbox.py')
276
692
mangle_tree.adjust_path('test', root, selftest)
277
693
mangle_tree.adjust_path('test_too_much', root, selftest)
278
694
mangle_tree.set_executability(True, blackbox)
279
695
mangle_tree.apply()
281
697
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)
698
create_tree, root = self.get_transform()
699
breezy = create_tree.new_directory('breezy', root, b'breezy-id')
700
tests = create_tree.new_directory('tests', breezy, b'tests-id')
701
blackbox = create_tree.new_directory('blackbox', tests, b'blackbox-id')
702
create_tree.new_file('test_too_much.py', blackbox, [b'hello1'],
705
mangle_tree, root = self.get_transform()
706
breezy = mangle_tree.trans_id_tree_path('breezy')
707
tests = mangle_tree.trans_id_tree_path('breezy/tests')
708
test_too_much = mangle_tree.trans_id_tree_path('breezy/tests/blackbox/test_too_much.py')
709
mangle_tree.adjust_path('selftest', breezy, tests)
710
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
295
711
mangle_tree.set_executability(True, test_too_much)
296
712
mangle_tree.apply()
298
714
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')
715
create_tree, root = self.get_transform()
716
tests = create_tree.new_directory('tests', root, b'tests-id')
717
create_tree.new_file('test_too_much.py', tests, [b'hello1'],
720
mangle_tree, root = self.get_transform()
721
tests = mangle_tree.trans_id_tree_path('tests')
722
test_too_much = mangle_tree.trans_id_tree_path('tests/test_too_much.py')
307
723
mangle_tree.adjust_path('selftest', root, tests)
308
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
724
mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
309
725
mangle_tree.set_executability(True, test_too_much)
310
726
mangle_tree.apply()
312
728
def test_move_dangling_ie(self):
313
729
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')
731
root = create_tree.root
732
create_tree.new_file('name1', root, [b'hello1'], b'name1')
317
733
create_tree.apply()
318
734
delete_contents, root = self.get_transform()
319
file = delete_contents.trans_id_tree_file_id('name1')
735
file = delete_contents.trans_id_tree_path('name1')
320
736
delete_contents.delete_contents(file)
321
737
delete_contents.apply()
322
738
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')
739
name1 = move_id.trans_id_tree_path('name1')
740
newdir = move_id.new_directory('dir', root, b'newdir')
325
741
move_id.adjust_path('name2', newdir, name1)
328
744
def test_replace_dangling_ie(self):
329
745
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')
747
root = create_tree.root
748
create_tree.new_file('name1', root, [b'hello1'], b'name1')
333
749
create_tree.apply()
334
750
delete_contents = TreeTransform(self.wt)
335
751
self.addCleanup(delete_contents.finalize)
336
file = delete_contents.trans_id_tree_file_id('name1')
752
file = delete_contents.trans_id_tree_path('name1')
337
753
delete_contents.delete_contents(file)
338
754
delete_contents.apply()
339
755
delete_contents.finalize()
340
756
replace = TreeTransform(self.wt)
341
757
self.addCleanup(replace.finalize)
342
name2 = replace.new_file('name2', root, 'hello2', 'name1')
758
name2 = replace.new_file('name2', root, [b'hello2'], b'name1')
343
759
conflicts = replace.find_conflicts()
344
name1 = replace.trans_id_tree_file_id('name1')
760
name1 = replace.trans_id_tree_path('name1')
345
761
self.assertEqual(conflicts, [('duplicate id', name1, name2)])
346
762
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)
765
def _test_symlinks(self, link_name1, link_target1,
766
link_name2, link_target2):
768
def ozpath(p): return 'oz/' + p
770
self.requireFeature(SymlinkFeature)
771
transform, root = self.get_transform()
772
oz_id = transform.new_directory('oz', root, b'oz-id')
773
wizard = transform.new_symlink(link_name1, oz_id, link_target1,
775
wiz_id = transform.create_path(link_name2, oz_id)
776
transform.create_symlink(link_target2, wiz_id)
777
transform.version_file(b'wiz-id2', wiz_id)
359
778
transform.set_executability(True, wiz_id)
360
self.assertEqual(transform.find_conflicts(),
779
self.assertEqual(transform.find_conflicts(),
361
780
[('non-file executability', wiz_id)])
362
781
transform.set_executability(None, wiz_id)
363
782
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')),
783
self.assertEqual(self.wt.path2id(ozpath(link_name1)), b'wizard-id')
784
self.assertEqual('symlink',
785
file_kind(self.wt.abspath(ozpath(link_name1))))
786
self.assertEqual(link_target2,
787
osutils.readlink(self.wt.abspath(ozpath(link_name2))))
788
self.assertEqual(link_target1,
789
osutils.readlink(self.wt.abspath(ozpath(link_name1))))
791
def test_symlinks(self):
792
self._test_symlinks('wizard', 'wizard-target',
793
'wizard2', 'behind_curtain')
795
def test_symlinks_unicode(self):
796
self.requireFeature(features.UnicodeFilenameFeature)
797
self._test_symlinks(u'\N{Euro Sign}wizard',
798
u'wizard-targ\N{Euro Sign}t',
799
u'\N{Euro Sign}wizard2',
800
u'b\N{Euro Sign}hind_curtain')
802
def test_unable_create_symlink(self):
804
wt = self.make_branch_and_tree('.')
805
tt = TreeTransform(wt) # TreeTransform obtains write lock
807
tt.new_symlink('foo', tt.root, 'bar')
811
os_symlink = getattr(os, 'symlink', None)
814
err = self.assertRaises(errors.UnableCreateSymlink, tt_helper)
816
"Unable to create symlink 'foo' on this platform",
820
os.symlink = os_symlink
372
822
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')
823
create, root = self.get_transform()
824
create.new_file('dorothy', root, [b'dorothy'], b'dorothy-id')
825
oz = create.new_directory('oz', root, b'oz-id')
826
create.new_directory('emeraldcity', oz, b'emerald-id')
378
conflicts,root = self.get_transform()
828
conflicts, root = self.get_transform()
379
829
# 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
830
new_dorothy = conflicts.new_file('dorothy', root, [b'dorothy'],
832
old_dorothy = conflicts.trans_id_tree_path('dorothy')
833
oz = conflicts.trans_id_tree_path('oz')
834
# set up DeletedParent parent conflict
385
835
conflicts.delete_versioned(oz)
386
emerald = conflicts.trans_id_tree_file_id('emerald-id')
836
emerald = conflicts.trans_id_tree_path('oz/emeraldcity')
837
# set up MissingParent conflict
838
munchkincity = conflicts.trans_id_file_id(b'munchkincity-id')
839
conflicts.adjust_path('munchkincity', root, munchkincity)
840
conflicts.new_directory('auntem', munchkincity, b'auntem-id')
387
841
# set up parent loop
388
842
conflicts.adjust_path('emeraldcity', emerald, emerald)
389
843
return conflicts, emerald, oz, old_dorothy, new_dorothy
434
895
self.assertEqual(conflicts_s[1], 'Conflict adding id to dorothy. '
435
896
'Unversioned existing file '
436
897
'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.')
898
self.assertEqual(conflicts_s[2], 'Conflict adding files to'
899
' munchkincity. Created directory.')
900
self.assertEqual(conflicts_s[3], 'Conflict because munchkincity is not'
901
' versioned, but has versioned'
902
' children. Versioned directory.')
903
self.assertEqualDiff(conflicts_s[4], "Conflict: can't delete oz because it"
904
" is not empty. Not deleting.")
905
self.assertEqual(conflicts_s[5], 'Conflict because oz is not'
906
' versioned, but has versioned'
907
' children. Versioned directory.')
908
self.assertEqual(conflicts_s[6], 'Conflict moving oz/emeraldcity into'
909
' oz/emeraldcity. Cancelled move.')
911
def prepare_wrong_parent_kind(self):
912
tt, root = self.get_transform()
913
tt.new_file('parent', root, [b'contents'], b'parent-id')
915
tt, root = self.get_transform()
916
parent_id = tt.trans_id_file_id(b'parent-id')
917
tt.new_file('child,', parent_id, [b'contents2'], b'file-id')
920
def test_find_conflicts_wrong_parent_kind(self):
921
tt = self.prepare_wrong_parent_kind()
924
def test_resolve_conflicts_wrong_existing_parent_kind(self):
925
tt = self.prepare_wrong_parent_kind()
926
raw_conflicts = resolve_conflicts(tt)
927
self.assertEqual({('non-directory parent', 'Created directory',
928
'new-3')}, raw_conflicts)
929
cooked_conflicts = cook_conflicts(raw_conflicts, tt)
930
self.assertEqual([NonDirectoryParent('Created directory', 'parent.new',
931
b'parent-id')], cooked_conflicts)
933
self.assertFalse(self.wt.is_versioned('parent'))
934
self.assertEqual(b'parent-id', self.wt.path2id('parent.new'))
936
def test_resolve_conflicts_wrong_new_parent_kind(self):
937
tt, root = self.get_transform()
938
parent_id = tt.new_directory('parent', root, b'parent-id')
939
tt.new_file('child,', parent_id, [b'contents2'], b'file-id')
941
tt, root = self.get_transform()
942
parent_id = tt.trans_id_file_id(b'parent-id')
943
tt.delete_contents(parent_id)
944
tt.create_file([b'contents'], parent_id)
945
raw_conflicts = resolve_conflicts(tt)
946
self.assertEqual({('non-directory parent', 'Created directory',
947
'new-3')}, raw_conflicts)
949
self.assertFalse(self.wt.is_versioned('parent'))
950
self.assertEqual(b'parent-id', self.wt.path2id('parent.new'))
952
def test_resolve_conflicts_wrong_parent_kind_unversioned(self):
953
tt, root = self.get_transform()
954
parent_id = tt.new_directory('parent', root)
955
tt.new_file('child,', parent_id, [b'contents2'])
957
tt, root = self.get_transform()
958
parent_id = tt.trans_id_tree_path('parent')
959
tt.delete_contents(parent_id)
960
tt.create_file([b'contents'], parent_id)
961
resolve_conflicts(tt)
963
self.assertFalse(self.wt.is_versioned('parent'))
964
self.assertFalse(self.wt.is_versioned('parent.new'))
966
def test_resolve_conflicts_missing_parent(self):
967
wt = self.make_branch_and_tree('.')
968
tt = TreeTransform(wt)
969
self.addCleanup(tt.finalize)
970
parent = tt.trans_id_file_id(b'parent-id')
971
tt.new_file('file', parent, [b'Contents'])
972
raw_conflicts = resolve_conflicts(tt)
973
# Since the directory doesn't exist it's seen as 'missing'. So
974
# 'resolve_conflicts' create a conflict asking for it to be created.
975
self.assertLength(1, raw_conflicts)
976
self.assertEqual(('missing parent', 'Created directory', 'new-1'),
978
# apply fail since the missing directory doesn't exist
979
self.assertRaises(errors.NoFinalPath, tt.apply)
444
981
def test_moving_versioned_directories(self):
445
982
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')
983
kansas = create.new_directory('kansas', root, b'kansas-id')
984
create.new_directory('house', kansas, b'house-id')
985
create.new_directory('oz', root, b'oz-id')
450
987
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')
988
oz = cyclone.trans_id_tree_path('oz')
989
house = cyclone.trans_id_tree_path('house')
453
990
cyclone.adjust_path('house', oz, house)
456
993
def test_moving_root(self):
457
994
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')
995
fun = create.new_directory('fun', root, b'fun-id')
996
create.new_directory('sun', root, b'sun-id')
997
create.new_directory('moon', root, b'moon')
462
999
transform, root = self.get_transform()
463
1000
transform.adjust_root_path('oldroot', fun)
464
new_root=transform.trans_id_tree_path('')
465
transform.version_file('new-root', new_root)
1001
new_root = transform.trans_id_tree_path('')
1002
transform.version_file(b'new-root', new_root)
466
1003
transform.apply()
468
1005
def test_renames(self):
469
1006
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',
1007
old = create.new_directory('old-parent', root, b'old-id')
1008
intermediate = create.new_directory('intermediate', old, b'im-id')
1009
myfile = create.new_file('myfile', intermediate, [b'myfile-text'],
475
1012
rename, root = self.get_transform()
476
old = rename.trans_id_file_id('old-id')
1013
old = rename.trans_id_file_id(b'old-id')
477
1014
rename.adjust_path('new', root, old)
478
myfile = rename.trans_id_file_id('myfile-id')
1015
myfile = rename.trans_id_file_id(b'myfile-id')
479
1016
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')
1019
def test_rename_fails(self):
1020
self.requireFeature(features.not_running_as_root)
1021
# see https://bugs.launchpad.net/bzr/+bug/491763
1022
create, root_id = self.get_transform()
1023
first_dir = create.new_directory('first-dir', root_id, b'first-id')
1024
myfile = create.new_file('myfile', root_id, [b'myfile-text'],
488
self.assertEqual(find_interesting(wt, wt, ['vfile']),
490
self.assertRaises(NotVersionedError, find_interesting, wt, wt,
1027
if os.name == "posix" and sys.platform != "cygwin":
1028
# posix filesystems fail on renaming if the readonly bit is set
1029
osutils.make_readonly(self.wt.abspath('first-dir'))
1030
elif os.name == "nt":
1031
# windows filesystems fail on renaming open files
1032
self.addCleanup(open(self.wt.abspath('myfile')).close)
1034
self.skipTest("Can't force a permissions error on rename")
1035
# now transform to rename
1036
rename_transform, root_id = self.get_transform()
1037
file_trans_id = rename_transform.trans_id_file_id(b'myfile-id')
1038
dir_id = rename_transform.trans_id_file_id(b'first-id')
1039
rename_transform.adjust_path('newname', dir_id, file_trans_id)
1040
e = self.assertRaises(errors.TransformRenameFailed,
1041
rename_transform.apply)
1042
# On nix looks like:
1043
# "Failed to rename .../work/.bzr/checkout/limbo/new-1
1044
# to .../first-dir/newname: [Errno 13] Permission denied"
1045
# On windows looks like:
1046
# "Failed to rename .../work/myfile to
1047
# .../work/.bzr/checkout/limbo/new-1: [Errno 13] Permission denied"
1048
# This test isn't concerned with exactly what the error looks like,
1049
# and the strerror will vary across OS and locales, but the assert
1050
# that the exeception attributes are what we expect
1051
self.assertEqual(e.errno, errno.EACCES)
1052
if os.name == "posix":
1053
self.assertEndsWith(e.to_path, "/first-dir/newname")
1055
self.assertEqual(os.path.basename(e.from_path), "myfile")
1057
def test_set_executability_order(self):
1058
"""Ensure that executability behaves the same, no matter what order.
1060
- create file and set executability simultaneously
1061
- create file and set executability afterward
1062
- unsetting the executability of a file whose executability has not been
1063
declared should throw an exception (this may happen when a
1064
merge attempts to create a file with a duplicate ID)
1066
transform, root = self.get_transform()
1067
wt = transform._tree
1069
self.addCleanup(wt.unlock)
1070
transform.new_file('set_on_creation', root, [b'Set on creation'], b'soc',
1072
sac = transform.new_file('set_after_creation', root,
1073
[b'Set after creation'], b'sac')
1074
transform.set_executability(True, sac)
1075
uws = transform.new_file('unset_without_set', root, [b'Unset badly'],
1077
self.assertRaises(KeyError, transform.set_executability, None, uws)
1079
self.assertTrue(wt.is_executable('set_on_creation'))
1080
self.assertTrue(wt.is_executable('set_after_creation'))
1082
def test_preserve_mode(self):
1083
"""File mode is preserved when replacing content"""
1084
if sys.platform == 'win32':
1085
raise TestSkipped('chmod has no effect on win32')
1086
transform, root = self.get_transform()
1087
transform.new_file('file1', root, [b'contents'], b'file1-id', True)
1089
self.wt.lock_write()
1090
self.addCleanup(self.wt.unlock)
1091
self.assertTrue(self.wt.is_executable('file1'))
1092
transform, root = self.get_transform()
1093
file1_id = transform.trans_id_tree_path('file1')
1094
transform.delete_contents(file1_id)
1095
transform.create_file([b'contents2'], file1_id)
1097
self.assertTrue(self.wt.is_executable('file1'))
1099
def test__set_mode_stats_correctly(self):
1100
"""_set_mode stats to determine file mode."""
1101
if sys.platform == 'win32':
1102
raise TestSkipped('chmod has no effect on win32')
1106
def instrumented_stat(path):
1107
stat_paths.append(path)
1108
return real_stat(path)
1110
transform, root = self.get_transform()
1112
bar1_id = transform.new_file('bar', root, [b'bar contents 1\n'],
1113
file_id=b'bar-id-1', executable=False)
1116
transform, root = self.get_transform()
1117
bar1_id = transform.trans_id_tree_path('bar')
1118
bar2_id = transform.trans_id_tree_path('bar2')
1120
os.stat = instrumented_stat
1121
transform.create_file([b'bar2 contents\n'], bar2_id, mode_id=bar1_id)
1124
transform.finalize()
1126
bar1_abspath = self.wt.abspath('bar')
1127
self.assertEqual([bar1_abspath], stat_paths)
1129
def test_iter_changes(self):
1130
self.wt.set_root_id(b'eert_toor')
1131
transform, root = self.get_transform()
1132
transform.new_file('old', root, [b'blah'], b'id-1', True)
1134
transform, root = self.get_transform()
1136
self.assertEqual([], list(transform.iter_changes()))
1137
old = transform.trans_id_tree_path('old')
1138
transform.unversion_file(old)
1139
self.assertEqual([(b'id-1', ('old', None), False, (True, False),
1140
(b'eert_toor', b'eert_toor'), ('old', 'old'), ('file', 'file'),
1141
(True, True))], list(transform.iter_changes()))
1142
transform.new_directory('new', root, b'id-1')
1143
self.assertEqual([(b'id-1', ('old', 'new'), True, (True, True),
1144
(b'eert_toor', b'eert_toor'), ('old', 'new'),
1145
('file', 'directory'),
1146
(True, False))], list(transform.iter_changes()))
1148
transform.finalize()
1150
def test_iter_changes_new(self):
1151
self.wt.set_root_id(b'eert_toor')
1152
transform, root = self.get_transform()
1153
transform.new_file('old', root, [b'blah'])
1155
transform, root = self.get_transform()
1157
old = transform.trans_id_tree_path('old')
1158
transform.version_file(b'id-1', old)
1159
self.assertEqual([(b'id-1', (None, 'old'), False, (False, True),
1160
(b'eert_toor', b'eert_toor'), ('old', 'old'), ('file', 'file'),
1161
(False, False))], list(transform.iter_changes()))
1163
transform.finalize()
1165
def test_iter_changes_modifications(self):
1166
self.wt.set_root_id(b'eert_toor')
1167
transform, root = self.get_transform()
1168
transform.new_file('old', root, [b'blah'], b'id-1')
1169
transform.new_file('new', root, [b'blah'])
1170
transform.new_directory('subdir', root, b'subdir-id')
1172
transform, root = self.get_transform()
1174
old = transform.trans_id_tree_path('old')
1175
subdir = transform.trans_id_tree_path('subdir')
1176
new = transform.trans_id_tree_path('new')
1177
self.assertEqual([], list(transform.iter_changes()))
1180
transform.delete_contents(old)
1181
self.assertEqual([(b'id-1', ('old', 'old'), True, (True, True),
1182
(b'eert_toor', b'eert_toor'), ('old', 'old'), ('file', None),
1183
(False, False))], list(transform.iter_changes()))
1186
transform.create_file([b'blah'], old)
1187
self.assertEqual([(b'id-1', ('old', 'old'), True, (True, True),
1188
(b'eert_toor', b'eert_toor'), ('old', 'old'), ('file', 'file'),
1189
(False, False))], list(transform.iter_changes()))
1190
transform.cancel_deletion(old)
1191
self.assertEqual([(b'id-1', ('old', 'old'), True, (True, True),
1192
(b'eert_toor', b'eert_toor'), ('old', 'old'), ('file', 'file'),
1193
(False, False))], list(transform.iter_changes()))
1194
transform.cancel_creation(old)
1196
# move file_id to a different file
1197
self.assertEqual([], list(transform.iter_changes()))
1198
transform.unversion_file(old)
1199
transform.version_file(b'id-1', new)
1200
transform.adjust_path('old', root, new)
1201
self.assertEqual([(b'id-1', ('old', 'old'), True, (True, True),
1202
(b'eert_toor', b'eert_toor'), ('old', 'old'), ('file', 'file'),
1203
(False, False))], list(transform.iter_changes()))
1204
transform.cancel_versioning(new)
1205
transform._removed_id = set()
1208
self.assertEqual([], list(transform.iter_changes()))
1209
transform.set_executability(True, old)
1210
self.assertEqual([(b'id-1', ('old', 'old'), False, (True, True),
1211
(b'eert_toor', b'eert_toor'), ('old', 'old'), ('file', 'file'),
1212
(False, True))], list(transform.iter_changes()))
1213
transform.set_executability(None, old)
1216
self.assertEqual([], list(transform.iter_changes()))
1217
transform.adjust_path('new', root, old)
1218
transform._new_parent = {}
1219
self.assertEqual([(b'id-1', ('old', 'new'), False, (True, True),
1220
(b'eert_toor', b'eert_toor'), ('old', 'new'), ('file', 'file'),
1221
(False, False))], list(transform.iter_changes()))
1222
transform._new_name = {}
1225
self.assertEqual([], list(transform.iter_changes()))
1226
transform.adjust_path('new', subdir, old)
1227
transform._new_name = {}
1228
self.assertEqual([(b'id-1', ('old', 'subdir/old'), False,
1229
(True, True), (b'eert_toor', b'subdir-id'), ('old', 'old'),
1230
('file', 'file'), (False, False))],
1231
list(transform.iter_changes()))
1232
transform._new_path = {}
1235
transform.finalize()
1237
def test_iter_changes_modified_bleed(self):
1238
self.wt.set_root_id(b'eert_toor')
1239
"""Modified flag should not bleed from one change to another"""
1240
# unfortunately, we have no guarantee that file1 (which is modified)
1241
# will be applied before file2. And if it's applied after file2, it
1242
# obviously can't bleed into file2's change output. But for now, it
1244
transform, root = self.get_transform()
1245
transform.new_file('file1', root, [b'blah'], b'id-1')
1246
transform.new_file('file2', root, [b'blah'], b'id-2')
1248
transform, root = self.get_transform()
1250
transform.delete_contents(transform.trans_id_file_id(b'id-1'))
1251
transform.set_executability(True,
1252
transform.trans_id_file_id(b'id-2'))
1253
self.assertEqual([(b'id-1', (u'file1', u'file1'), True, (True, True),
1254
(b'eert_toor', b'eert_toor'), ('file1', u'file1'),
1255
('file', None), (False, False)),
1256
(b'id-2', (u'file2', u'file2'), False, (True, True),
1257
(b'eert_toor', b'eert_toor'), ('file2', u'file2'),
1258
('file', 'file'), (False, True))],
1259
list(transform.iter_changes()))
1261
transform.finalize()
1263
def test_iter_changes_move_missing(self):
1264
"""Test moving ids with no files around"""
1265
self.wt.set_root_id(b'toor_eert')
1266
# Need two steps because versioning a non-existant file is a conflict.
1267
transform, root = self.get_transform()
1268
transform.new_directory('floater', root, b'floater-id')
1270
transform, root = self.get_transform()
1271
transform.delete_contents(transform.trans_id_tree_path('floater'))
1273
transform, root = self.get_transform()
1274
floater = transform.trans_id_tree_path('floater')
1276
transform.adjust_path('flitter', root, floater)
1277
self.assertEqual([(b'floater-id', ('floater', 'flitter'), False,
1278
(True, True), (b'toor_eert', b'toor_eert'), ('floater', 'flitter'),
1279
(None, None), (False, False))], list(transform.iter_changes()))
1281
transform.finalize()
1283
def test_iter_changes_pointless(self):
1284
"""Ensure that no-ops are not treated as modifications"""
1285
self.wt.set_root_id(b'eert_toor')
1286
transform, root = self.get_transform()
1287
transform.new_file('old', root, [b'blah'], b'id-1')
1288
transform.new_directory('subdir', root, b'subdir-id')
1290
transform, root = self.get_transform()
1292
old = transform.trans_id_tree_path('old')
1293
subdir = transform.trans_id_tree_path('subdir')
1294
self.assertEqual([], list(transform.iter_changes()))
1295
transform.delete_contents(subdir)
1296
transform.create_directory(subdir)
1297
transform.set_executability(False, old)
1298
transform.unversion_file(old)
1299
transform.version_file(b'id-1', old)
1300
transform.adjust_path('old', root, old)
1301
self.assertEqual([], list(transform.iter_changes()))
1303
transform.finalize()
1305
def test_rename_count(self):
1306
transform, root = self.get_transform()
1307
transform.new_file('name1', root, [b'contents'])
1308
self.assertEqual(transform.rename_count, 0)
1310
self.assertEqual(transform.rename_count, 1)
1311
transform2, root = self.get_transform()
1312
transform2.adjust_path('name2', root,
1313
transform2.trans_id_tree_path('name1'))
1314
self.assertEqual(transform2.rename_count, 0)
1316
self.assertEqual(transform2.rename_count, 2)
1318
def test_change_parent(self):
1319
"""Ensure that after we change a parent, the results are still right.
1321
Renames and parent changes on pending transforms can happen as part
1322
of conflict resolution, and are explicitly permitted by the
1325
This test ensures they work correctly with the rename-avoidance
1328
transform, root = self.get_transform()
1329
parent1 = transform.new_directory('parent1', root)
1330
child1 = transform.new_file('child1', parent1, [b'contents'])
1331
parent2 = transform.new_directory('parent2', root)
1332
transform.adjust_path('child1', parent2, child1)
1334
self.assertPathDoesNotExist(self.wt.abspath('parent1/child1'))
1335
self.assertPathExists(self.wt.abspath('parent2/child1'))
1336
# rename limbo/new-1 => parent1, rename limbo/new-3 => parent2
1337
# no rename for child1 (counting only renames during apply)
1338
self.assertEqual(2, transform.rename_count)
1340
def test_cancel_parent(self):
1341
"""Cancelling a parent doesn't cause deletion of a non-empty directory
1343
This is like the test_change_parent, except that we cancel the parent
1344
before adjusting the path. The transform must detect that the
1345
directory is non-empty, and move children to safe locations.
1347
transform, root = self.get_transform()
1348
parent1 = transform.new_directory('parent1', root)
1349
child1 = transform.new_file('child1', parent1, [b'contents'])
1350
child2 = transform.new_file('child2', parent1, [b'contents'])
1352
transform.cancel_creation(parent1)
1354
self.fail('Failed to move child1 before deleting parent1')
1355
transform.cancel_creation(child2)
1356
transform.create_directory(parent1)
1358
transform.cancel_creation(parent1)
1359
# If the transform incorrectly believes that child2 is still in
1360
# parent1's limbo directory, it will try to rename it and fail
1361
# because was already moved by the first cancel_creation.
1363
self.fail('Transform still thinks child2 is a child of parent1')
1364
parent2 = transform.new_directory('parent2', root)
1365
transform.adjust_path('child1', parent2, child1)
1367
self.assertPathDoesNotExist(self.wt.abspath('parent1'))
1368
self.assertPathExists(self.wt.abspath('parent2/child1'))
1369
# rename limbo/new-3 => parent2, rename limbo/new-2 => child1
1370
self.assertEqual(2, transform.rename_count)
1372
def test_adjust_and_cancel(self):
1373
"""Make sure adjust_path keeps track of limbo children properly"""
1374
transform, root = self.get_transform()
1375
parent1 = transform.new_directory('parent1', root)
1376
child1 = transform.new_file('child1', parent1, [b'contents'])
1377
parent2 = transform.new_directory('parent2', root)
1378
transform.adjust_path('child1', parent2, child1)
1379
transform.cancel_creation(child1)
1381
transform.cancel_creation(parent1)
1382
# if the transform thinks child1 is still in parent1's limbo
1383
# directory, it will attempt to move it and fail.
1385
self.fail('Transform still thinks child1 is a child of parent1')
1386
transform.finalize()
1388
def test_noname_contents(self):
1389
"""TreeTransform should permit deferring naming files."""
1390
transform, root = self.get_transform()
1391
parent = transform.trans_id_file_id(b'parent-id')
1393
transform.create_directory(parent)
1395
self.fail("Can't handle contents with no name")
1396
transform.finalize()
1398
def test_noname_contents_nested(self):
1399
"""TreeTransform should permit deferring naming files."""
1400
transform, root = self.get_transform()
1401
parent = transform.trans_id_file_id(b'parent-id')
1403
transform.create_directory(parent)
1405
self.fail("Can't handle contents with no name")
1406
child = transform.new_directory('child', parent)
1407
transform.adjust_path('parent', root, parent)
1409
self.assertPathExists(self.wt.abspath('parent/child'))
1410
self.assertEqual(1, transform.rename_count)
1412
def test_reuse_name(self):
1413
"""Avoid reusing the same limbo name for different files"""
1414
transform, root = self.get_transform()
1415
parent = transform.new_directory('parent', root)
1416
child1 = transform.new_directory('child', parent)
1418
child2 = transform.new_directory('child', parent)
1420
self.fail('Tranform tried to use the same limbo name twice')
1421
transform.adjust_path('child2', parent, child2)
1423
# limbo/new-1 => parent, limbo/new-3 => parent/child2
1424
# child2 is put into top-level limbo because child1 has already
1425
# claimed the direct limbo path when child2 is created. There is no
1426
# advantage in renaming files once they're in top-level limbo, except
1428
self.assertEqual(2, transform.rename_count)
1430
def test_reuse_when_first_moved(self):
1431
"""Don't avoid direct paths when it is safe to use them"""
1432
transform, root = self.get_transform()
1433
parent = transform.new_directory('parent', root)
1434
child1 = transform.new_directory('child', parent)
1435
transform.adjust_path('child1', parent, child1)
1436
child2 = transform.new_directory('child', parent)
1438
# limbo/new-1 => parent
1439
self.assertEqual(1, transform.rename_count)
1441
def test_reuse_after_cancel(self):
1442
"""Don't avoid direct paths when it is safe to use them"""
1443
transform, root = self.get_transform()
1444
parent2 = transform.new_directory('parent2', root)
1445
child1 = transform.new_directory('child1', parent2)
1446
transform.cancel_creation(parent2)
1447
transform.create_directory(parent2)
1448
child2 = transform.new_directory('child1', parent2)
1449
transform.adjust_path('child2', parent2, child1)
1451
# limbo/new-1 => parent2, limbo/new-2 => parent2/child1
1452
self.assertEqual(2, transform.rename_count)
1454
def test_finalize_order(self):
1455
"""Finalize must be done in child-to-parent order"""
1456
transform, root = self.get_transform()
1457
parent = transform.new_directory('parent', root)
1458
child = transform.new_directory('child', parent)
1460
transform.finalize()
1462
self.fail('Tried to remove parent before child1')
1464
def test_cancel_with_cancelled_child_should_succeed(self):
1465
transform, root = self.get_transform()
1466
parent = transform.new_directory('parent', root)
1467
child = transform.new_directory('child', parent)
1468
transform.cancel_creation(child)
1469
transform.cancel_creation(parent)
1470
transform.finalize()
1472
def test_rollback_on_directory_clash(self):
1474
wt = self.make_branch_and_tree('.')
1475
tt = TreeTransform(wt) # TreeTransform obtains write lock
1477
foo = tt.new_directory('foo', tt.root)
1478
tt.new_file('bar', foo, [b'foobar'])
1479
baz = tt.new_directory('baz', tt.root)
1480
tt.new_file('qux', baz, [b'quux'])
1481
# Ask for a rename 'foo' -> 'baz'
1482
tt.adjust_path('baz', tt.root, foo)
1483
# Lie to tt that we've already resolved all conflicts.
1484
tt.apply(no_conflicts=True)
1488
# The rename will fail because the target directory is not empty (but
1489
# raises FileExists anyway).
1490
err = self.assertRaises(errors.FileExists, tt_helper)
1491
self.assertEndsWith(err.path, "/baz")
1493
def test_two_directories_clash(self):
1495
wt = self.make_branch_and_tree('.')
1496
tt = TreeTransform(wt) # TreeTransform obtains write lock
1498
foo_1 = tt.new_directory('foo', tt.root)
1499
tt.new_directory('bar', foo_1)
1500
# Adding the same directory with a different content
1501
foo_2 = tt.new_directory('foo', tt.root)
1502
tt.new_directory('baz', foo_2)
1503
# Lie to tt that we've already resolved all conflicts.
1504
tt.apply(no_conflicts=True)
1508
err = self.assertRaises(errors.FileExists, tt_helper)
1509
self.assertEndsWith(err.path, "/foo")
1511
def test_two_directories_clash_finalize(self):
1513
wt = self.make_branch_and_tree('.')
1514
tt = TreeTransform(wt) # TreeTransform obtains write lock
1516
foo_1 = tt.new_directory('foo', tt.root)
1517
tt.new_directory('bar', foo_1)
1518
# Adding the same directory with a different content
1519
foo_2 = tt.new_directory('foo', tt.root)
1520
tt.new_directory('baz', foo_2)
1521
# Lie to tt that we've already resolved all conflicts.
1522
tt.apply(no_conflicts=True)
1526
err = self.assertRaises(errors.FileExists, tt_helper)
1527
self.assertEndsWith(err.path, "/foo")
1529
def test_file_to_directory(self):
1530
wt = self.make_branch_and_tree('.')
1531
self.build_tree(['foo'])
1534
tt = TreeTransform(wt)
1535
self.addCleanup(tt.finalize)
1536
foo_trans_id = tt.trans_id_tree_path("foo")
1537
tt.delete_contents(foo_trans_id)
1538
tt.create_directory(foo_trans_id)
1539
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1540
tt.create_file([b"aa\n"], bar_trans_id)
1541
tt.version_file(b"bar-1", bar_trans_id)
1543
self.assertPathExists("foo/bar")
1546
self.assertEqual(wt.kind("foo"), "directory")
1550
changes = wt.changes_from(wt.basis_tree())
1551
self.assertFalse(changes.has_changed(), changes)
1553
def test_file_to_symlink(self):
1554
self.requireFeature(SymlinkFeature)
1555
wt = self.make_branch_and_tree('.')
1556
self.build_tree(['foo'])
1559
tt = TreeTransform(wt)
1560
self.addCleanup(tt.finalize)
1561
foo_trans_id = tt.trans_id_tree_path("foo")
1562
tt.delete_contents(foo_trans_id)
1563
tt.create_symlink("bar", foo_trans_id)
1565
self.assertPathExists("foo")
1567
self.addCleanup(wt.unlock)
1568
self.assertEqual(wt.kind("foo"), "symlink")
1570
def test_dir_to_file(self):
1571
wt = self.make_branch_and_tree('.')
1572
self.build_tree(['foo/', 'foo/bar'])
1573
wt.add(['foo', 'foo/bar'])
1575
tt = TreeTransform(wt)
1576
self.addCleanup(tt.finalize)
1577
foo_trans_id = tt.trans_id_tree_path("foo")
1578
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1579
tt.delete_contents(foo_trans_id)
1580
tt.delete_versioned(bar_trans_id)
1581
tt.create_file([b"aa\n"], foo_trans_id)
1583
self.assertPathExists("foo")
1585
self.addCleanup(wt.unlock)
1586
self.assertEqual(wt.kind("foo"), "file")
1588
def test_dir_to_hardlink(self):
1589
self.requireFeature(HardlinkFeature)
1590
wt = self.make_branch_and_tree('.')
1591
self.build_tree(['foo/', 'foo/bar'])
1592
wt.add(['foo', 'foo/bar'])
1594
tt = TreeTransform(wt)
1595
self.addCleanup(tt.finalize)
1596
foo_trans_id = tt.trans_id_tree_path("foo")
1597
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1598
tt.delete_contents(foo_trans_id)
1599
tt.delete_versioned(bar_trans_id)
1600
self.build_tree(['baz'])
1601
tt.create_hardlink("baz", foo_trans_id)
1603
self.assertPathExists("foo")
1604
self.assertPathExists("baz")
1606
self.addCleanup(wt.unlock)
1607
self.assertEqual(wt.kind("foo"), "file")
1609
def test_no_final_path(self):
1610
transform, root = self.get_transform()
1611
trans_id = transform.trans_id_file_id(b'foo')
1612
transform.create_file([b'bar'], trans_id)
1613
transform.cancel_creation(trans_id)
1616
def test_create_from_tree(self):
1617
tree1 = self.make_branch_and_tree('tree1')
1618
self.build_tree_contents([('tree1/foo/',), ('tree1/bar', b'baz')])
1619
tree1.add(['foo', 'bar'], [b'foo-id', b'bar-id'])
1620
tree2 = self.make_branch_and_tree('tree2')
1621
tt = TreeTransform(tree2)
1622
foo_trans_id = tt.create_path('foo', tt.root)
1623
create_from_tree(tt, foo_trans_id, tree1, 'foo', file_id=b'foo-id')
1624
bar_trans_id = tt.create_path('bar', tt.root)
1625
create_from_tree(tt, bar_trans_id, tree1, 'bar', file_id=b'bar-id')
1627
self.assertEqual('directory', osutils.file_kind('tree2/foo'))
1628
self.assertFileEqual(b'baz', 'tree2/bar')
1630
def test_create_from_tree_bytes(self):
1631
"""Provided lines are used instead of tree content."""
1632
tree1 = self.make_branch_and_tree('tree1')
1633
self.build_tree_contents([('tree1/foo', b'bar'),])
1634
tree1.add('foo', b'foo-id')
1635
tree2 = self.make_branch_and_tree('tree2')
1636
tt = TreeTransform(tree2)
1637
foo_trans_id = tt.create_path('foo', tt.root)
1638
create_from_tree(tt, foo_trans_id, tree1, 'foo', file_id=b'foo-id',
1641
self.assertFileEqual(b'qux', 'tree2/foo')
1643
def test_create_from_tree_symlink(self):
1644
self.requireFeature(SymlinkFeature)
1645
tree1 = self.make_branch_and_tree('tree1')
1646
os.symlink('bar', 'tree1/foo')
1647
tree1.add('foo', b'foo-id')
1648
tt = TreeTransform(self.make_branch_and_tree('tree2'))
1649
foo_trans_id = tt.create_path('foo', tt.root)
1650
create_from_tree(tt, foo_trans_id, tree1, 'foo', file_id=b'foo-id')
1652
self.assertEqual('bar', os.readlink('tree2/foo'))
494
1655
class TransformGroup(object):
495
def __init__(self, dirname):
1657
def __init__(self, dirname, root_id):
496
1658
self.name = dirname
497
1659
os.mkdir(dirname)
498
self.wt = BzrDir.create_standalone_workingtree(dirname)
1660
self.wt = ControlDir.create_standalone_workingtree(dirname)
1661
self.wt.set_root_id(root_id)
499
1662
self.b = self.wt.branch
500
1663
self.tt = TreeTransform(self.wt)
501
self.root = self.tt.trans_id_tree_file_id(self.wt.get_root_id())
1664
self.root = self.tt.trans_id_tree_path('')
503
1667
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)
1668
template = b'%s TREE\n%s%s\n%s%s MERGE-SOURCE\n'
1669
return template % (b'<' * 7, tree, b'=' * 7, merge, b'>' * 7)
1672
class TestInventoryAltered(tests.TestCaseWithTransport):
1674
def test_inventory_altered_unchanged(self):
1675
tree = self.make_branch_and_tree('tree')
1676
self.build_tree(['tree/foo'])
1677
tree.add('foo', b'foo-id')
1678
with TransformPreview(tree) as tt:
1679
self.assertEqual([], tt._inventory_altered())
1681
def test_inventory_altered_changed_parent_id(self):
1682
tree = self.make_branch_and_tree('tree')
1683
self.build_tree(['tree/foo'])
1684
tree.add('foo', b'foo-id')
1685
with TransformPreview(tree) as tt:
1686
tt.unversion_file(tt.root)
1687
tt.version_file(b'new-id', tt.root)
1688
foo_trans_id = tt.trans_id_tree_path('foo')
1689
foo_tuple = ('foo', foo_trans_id)
1690
root_tuple = ('', tt.root)
1691
self.assertEqual([root_tuple, foo_tuple], tt._inventory_altered())
1693
def test_inventory_altered_noop_changed_parent_id(self):
1694
tree = self.make_branch_and_tree('tree')
1695
self.build_tree(['tree/foo'])
1696
tree.add('foo', b'foo-id')
1697
with TransformPreview(tree) as tt:
1698
tt.unversion_file(tt.root)
1699
tt.version_file(tree.get_root_id(), tt.root)
1700
foo_trans_id = tt.trans_id_tree_path('foo')
1701
self.assertEqual([], tt._inventory_altered())
508
1704
class TestTransformMerge(TestCaseInTempDir):
509
1706
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')
1707
root_id = generate_ids.gen_root_id()
1708
base = TransformGroup("base", root_id)
1709
base.tt.new_file('a', base.root, [b'a\nb\nc\nd\be\n'], b'a')
1710
base.tt.new_file('b', base.root, [b'b1'], b'b')
1711
base.tt.new_file('c', base.root, [b'c'], b'c')
1712
base.tt.new_file('d', base.root, [b'd'], b'd')
1713
base.tt.new_file('e', base.root, [b'e'], b'e')
1714
base.tt.new_file('f', base.root, [b'f'], b'f')
1715
base.tt.new_directory('g', base.root, b'g')
1716
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')
1718
other = TransformGroup("other", root_id)
1719
other.tt.new_file('a', other.root, [b'y\nb\nc\nd\be\n'], b'a')
1720
other.tt.new_file('b', other.root, [b'b2'], b'b')
1721
other.tt.new_file('c', other.root, [b'c2'], b'c')
1722
other.tt.new_file('d', other.root, [b'd'], b'd')
1723
other.tt.new_file('e', other.root, [b'e2'], b'e')
1724
other.tt.new_file('f', other.root, [b'f'], b'f')
1725
other.tt.new_file('g', other.root, [b'g'], b'g')
1726
other.tt.new_file('h', other.root, [b'h\ni\nj\nk\n'], b'h')
1727
other.tt.new_file('i', other.root, [b'h\ni\nj\nk\n'], b'i')
530
1728
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')
1729
this = TransformGroup("this", root_id)
1730
this.tt.new_file('a', this.root, [b'a\nb\nc\nd\bz\n'], b'a')
1731
this.tt.new_file('b', this.root, [b'b'], b'b')
1732
this.tt.new_file('c', this.root, [b'c'], b'c')
1733
this.tt.new_file('d', this.root, [b'd2'], b'd')
1734
this.tt.new_file('e', this.root, [b'e2'], b'e')
1735
this.tt.new_file('f', this.root, [b'f'], b'f')
1736
this.tt.new_file('g', this.root, [b'g'], b'g')
1737
this.tt.new_file('h', this.root, [b'1\n2\n3\n4\n'], b'h')
1738
this.tt.new_file('i', this.root, [b'1\n2\n3\n4\n'], b'i')
542
1740
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
544
self.assertEqual(this.wt.get_file('a').read(), 'y\nb\nc\nd\bz\n')
1743
with this.wt.get_file(this.wt.id2path(b'a')) as f:
1744
self.assertEqual(f.read(), b'y\nb\nc\nd\bz\n')
545
1745
# three-way text conflict
546
self.assertEqual(this.wt.get_file('b').read(),
547
conflict_text('b', 'b2'))
1746
with this.wt.get_file(this.wt.id2path(b'b')) as f:
1747
self.assertEqual(f.read(), conflict_text(b'b', b'b2'))
549
self.assertEqual(this.wt.get_file('c').read(), 'c2')
1749
self.assertEqual(this.wt.get_file(this.wt.id2path(b'c')).read(), b'c2')
551
self.assertEqual(this.wt.get_file('d').read(), 'd2')
1751
self.assertEqual(this.wt.get_file(this.wt.id2path(b'd')).read(), b'd2')
552
1752
# Ambigious clean merge
553
self.assertEqual(this.wt.get_file('e').read(), 'e2')
1753
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')
1755
self.assertEqual(this.wt.get_file(this.wt.id2path(b'f')).read(), b'f')
1756
# Correct correct results when THIS == OTHER
1757
self.assertEqual(this.wt.get_file(this.wt.id2path(b'g')).read(), b'g')
558
1758
# 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(),
1759
self.assertEqual(this.wt.get_file(this.wt.id2path(b'h')).read(),
1760
conflict_text(b'1\n2\n3\n4\n', b'h\ni\nj\nk\n'))
1761
self.assertEqual(this.wt.get_file('h.THIS').read(),
1763
self.assertEqual(this.wt.get_file('h.OTHER').read(),
565
1765
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(),
1766
self.assertEqual(this.wt.get_file(this.wt.id2path(b'i')).read(),
1767
conflict_text(b'1\n2\n3\n4\n', b'h\ni\nj\nk\n'))
1768
self.assertEqual(this.wt.get_file('i.THIS').read(),
1770
self.assertEqual(this.wt.get_file('i.OTHER').read(),
572
1772
self.assertEqual(os.path.exists(this.wt.abspath('i.BASE')), False)
573
modified = ['a', 'b', 'c', 'h', 'i']
1773
modified = [b'a', b'b', b'c', b'h', b'i']
574
1774
merge_modified = this.wt.merge_modified()
575
1775
self.assertSubset(merge_modified, modified)
576
1776
self.assertEqual(len(merge_modified), len(modified))
577
file(this.wt.id2abspath('a'), 'wb').write('booga')
1777
with open(this.wt.abspath(this.wt.id2path(b'a')), 'wb') as f: f.write(b'booga')
579
1779
merge_modified = this.wt.merge_modified()
580
1780
self.assertSubset(merge_modified, modified)
581
1781
self.assertEqual(len(merge_modified), len(modified))
582
1782
this.wt.remove('b')
585
1785
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")
1786
self.requireFeature(SymlinkFeature)
1787
root_id = generate_ids.gen_root_id()
1788
base = TransformGroup("BASE", root_id)
1789
this = TransformGroup("THIS", root_id)
1790
other = TransformGroup("OTHER", root_id)
591
1791
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'),
1792
tg.tt.new_directory('a', tg.root, b'a')
1793
tg.tt.new_symlink('b', tg.root, 'b', b'b')
1794
tg.tt.new_file('c', tg.root, [b'c'], b'c')
1795
tg.tt.new_symlink('d', tg.root, tg.name, b'd')
1796
targets = ((base, 'base-e', 'base-f', None, None),
1797
(this, 'other-e', 'this-f', 'other-g', 'this-h'),
598
1798
(other, 'other-e', None, 'other-g', 'other-h'))
599
1799
for tg, e_target, f_target, g_target, h_target in targets:
600
for link, target in (('e', e_target), ('f', f_target),
1800
for link, target in (('e', e_target), ('f', f_target),
601
1801
('g', g_target), ('h', h_target)):
602
1802
if target is not None:
603
tg.tt.new_symlink(link, tg.root, target, link)
1803
tg.tt.new_symlink(link, tg.root, target, link.encode('ascii'))
605
1805
for tg in this, base, other:
625
1825
self.assertIs(os.path.lexists(this.wt.abspath('h.OTHER')), True)
627
1827
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')
1828
root_id = generate_ids.gen_root_id()
1829
base = TransformGroup("BASE", root_id)
1830
this = TransformGroup("THIS", root_id)
1831
other = TransformGroup("OTHER", root_id)
1832
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, b'a')
1833
for t in [base, this, other]]
1834
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, b'b')
1835
for t in [base, this, other]]
1836
base.tt.new_directory('c', base_a, b'c')
1837
this.tt.new_directory('c1', this_a, b'c')
1838
other.tt.new_directory('c', other_b, b'c')
1840
base.tt.new_directory('d', base_a, b'd')
1841
this.tt.new_directory('d1', this_b, b'd')
1842
other.tt.new_directory('d', other_a, b'd')
1844
base.tt.new_directory('e', base_a, b'e')
1845
this.tt.new_directory('e', this_a, b'e')
1846
other.tt.new_directory('e1', other_b, b'e')
1848
base.tt.new_directory('f', base_a, b'f')
1849
this.tt.new_directory('f1', this_b, b'f')
1850
other.tt.new_directory('f1', other_b, b'f')
651
1852
for tg in [this, base, other]:
653
1854
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'))
1855
self.assertEqual(this.wt.id2path(b'c'), pathjoin('b/c1'))
1856
self.assertEqual(this.wt.id2path(b'd'), pathjoin('b/d1'))
1857
self.assertEqual(this.wt.id2path(b'e'), pathjoin('b/e1'))
1858
self.assertEqual(this.wt.id2path(b'f'), pathjoin('b/f1'))
659
1860
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')
1861
root_id = generate_ids.gen_root_id()
1862
base = TransformGroup("BASE", root_id)
1863
this = TransformGroup("THIS", root_id)
1864
other = TransformGroup("OTHER", root_id)
1865
base_a, this_a, other_a = [t.tt.new_directory('a', t.root, b'a')
1866
for t in [base, this, other]]
1867
base_b, this_b, other_b = [t.tt.new_directory('b', t.root, b'b')
1868
for t in [base, this, other]]
1870
base.tt.new_file('g', base_a, [b'g'], b'g')
1871
other.tt.new_file('g1', other_b, [b'g1'], b'g')
1873
base.tt.new_file('h', base_a, [b'h'], b'h')
1874
this.tt.new_file('h1', this_b, [b'h1'], b'h')
1876
base.tt.new_file('i', base.root, [b'i'], b'i')
1877
other.tt.new_directory('i1', this_b, b'i')
677
1879
for tg in [this, base, other]:
679
1881
Merge3Merger(this.wt, this.wt, base.wt, other.wt)
681
self.assertEqual(this.wt.id2path('g'), pathjoin('b/g1.OTHER'))
1883
self.assertEqual(this.wt.id2path(b'g'), pathjoin('b/g1.OTHER'))
682
1884
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.BASE')), True)
683
1885
self.assertIs(os.path.lexists(this.wt.abspath('b/g1.THIS')), False)
684
self.assertEqual(this.wt.id2path('h'), pathjoin('b/h1.THIS'))
1886
self.assertEqual(this.wt.id2path(b'h'), pathjoin('b/h1.THIS'))
685
1887
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.BASE')), True)
686
1888
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')
1889
self.assertEqual(this.wt.id2path(b'i'), pathjoin('b/i1.OTHER'))
1892
class TestBuildTree(tests.TestCaseWithTransport):
1894
def test_build_tree_with_symlinks(self):
1895
self.requireFeature(SymlinkFeature)
694
a = BzrDir.create_standalone_workingtree('a')
1897
a = ControlDir.create_standalone_workingtree('a')
695
1898
os.mkdir('a/foo')
696
file('a/foo/bar', 'wb').write('contents')
1899
with open('a/foo/bar', 'wb') as f: f.write(b'contents')
697
1900
os.symlink('a/foo/bar', 'a/foo/baz')
698
1901
a.add(['foo', 'foo/bar', 'foo/baz'])
699
1902
a.commit('initial commit')
700
b = BzrDir.create_standalone_workingtree('b')
701
build_tree(a.basis_tree(), b)
1903
b = ControlDir.create_standalone_workingtree('b')
1904
basis = a.basis_tree()
1906
self.addCleanup(basis.unlock)
1907
build_tree(basis, b)
702
1908
self.assertIs(os.path.isdir('b/foo'), True)
703
self.assertEqual(file('b/foo/bar', 'rb').read(), "contents")
1909
with open('b/foo/bar', 'rb') as f:
1910
self.assertEqual(f.read(), b"contents")
704
1911
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~')
1913
def test_build_with_references(self):
1914
tree = self.make_branch_and_tree('source',
1915
format='development-subtree')
1916
subtree = self.make_branch_and_tree('source/subtree',
1917
format='development-subtree')
1918
tree.add_reference(subtree)
1919
tree.commit('a revision')
1920
tree.branch.create_checkout('target')
1921
self.assertPathExists('target')
1922
self.assertPathExists('target/subtree')
1924
def test_file_conflict_handling(self):
1925
"""Ensure that when building trees, conflict handling is done"""
1926
source = self.make_branch_and_tree('source')
1927
target = self.make_branch_and_tree('target')
1928
self.build_tree(['source/file', 'target/file'])
1929
source.add('file', b'new-file')
1930
source.commit('added file')
1931
build_tree(source.basis_tree(), target)
1932
self.assertEqual([DuplicateEntry('Moved existing file to',
1933
'file.moved', 'file', None, 'new-file')],
1935
target2 = self.make_branch_and_tree('target2')
1936
with open('target2/file', 'wb') as target_file, \
1937
open('source/file', 'rb') as source_file:
1938
target_file.write(source_file.read())
1939
build_tree(source.basis_tree(), target2)
1940
self.assertEqual([], target2.conflicts())
1942
def test_symlink_conflict_handling(self):
1943
"""Ensure that when building trees, conflict handling is done"""
1944
self.requireFeature(SymlinkFeature)
1945
source = self.make_branch_and_tree('source')
1946
os.symlink('foo', 'source/symlink')
1947
source.add('symlink', b'new-symlink')
1948
source.commit('added file')
1949
target = self.make_branch_and_tree('target')
1950
os.symlink('bar', 'target/symlink')
1951
build_tree(source.basis_tree(), target)
1952
self.assertEqual([DuplicateEntry('Moved existing file to',
1953
'symlink.moved', 'symlink', None, 'new-symlink')],
1955
target = self.make_branch_and_tree('target2')
1956
os.symlink('foo', 'target2/symlink')
1957
build_tree(source.basis_tree(), target)
1958
self.assertEqual([], target.conflicts())
1960
def test_directory_conflict_handling(self):
1961
"""Ensure that when building trees, conflict handling is done"""
1962
source = self.make_branch_and_tree('source')
1963
target = self.make_branch_and_tree('target')
1964
self.build_tree(['source/dir1/', 'source/dir1/file', 'target/dir1/'])
1965
source.add(['dir1', 'dir1/file'], [b'new-dir1', b'new-file'])
1966
source.commit('added file')
1967
build_tree(source.basis_tree(), target)
1968
self.assertEqual([], target.conflicts())
1969
self.assertPathExists('target/dir1/file')
1971
# Ensure contents are merged
1972
target = self.make_branch_and_tree('target2')
1973
self.build_tree(['target2/dir1/', 'target2/dir1/file2'])
1974
build_tree(source.basis_tree(), target)
1975
self.assertEqual([], target.conflicts())
1976
self.assertPathExists('target2/dir1/file2')
1977
self.assertPathExists('target2/dir1/file')
1979
# Ensure new contents are suppressed for existing branches
1980
target = self.make_branch_and_tree('target3')
1981
self.make_branch('target3/dir1')
1982
self.build_tree(['target3/dir1/file2'])
1983
build_tree(source.basis_tree(), target)
1984
self.assertPathDoesNotExist('target3/dir1/file')
1985
self.assertPathExists('target3/dir1/file2')
1986
self.assertPathExists('target3/dir1.diverted/file')
1987
self.assertEqual([DuplicateEntry('Diverted to',
1988
'dir1.diverted', 'dir1', 'new-dir1', None)],
1991
target = self.make_branch_and_tree('target4')
1992
self.build_tree(['target4/dir1/'])
1993
self.make_branch('target4/dir1/file')
1994
build_tree(source.basis_tree(), target)
1995
self.assertPathExists('target4/dir1/file')
1996
self.assertEqual('directory', file_kind('target4/dir1/file'))
1997
self.assertPathExists('target4/dir1/file.diverted')
1998
self.assertEqual([DuplicateEntry('Diverted to',
1999
'dir1/file.diverted', 'dir1/file', 'new-file', None)],
2002
def test_mixed_conflict_handling(self):
2003
"""Ensure that when building trees, conflict handling is done"""
2004
source = self.make_branch_and_tree('source')
2005
target = self.make_branch_and_tree('target')
2006
self.build_tree(['source/name', 'target/name/'])
2007
source.add('name', b'new-name')
2008
source.commit('added file')
2009
build_tree(source.basis_tree(), target)
2010
self.assertEqual([DuplicateEntry('Moved existing file to',
2011
'name.moved', 'name', None, 'new-name')], target.conflicts())
2013
def test_raises_in_populated(self):
2014
source = self.make_branch_and_tree('source')
2015
self.build_tree(['source/name'])
2017
source.commit('added name')
2018
target = self.make_branch_and_tree('target')
2019
self.build_tree(['target/name'])
2021
self.assertRaises(errors.WorkingTreeAlreadyPopulated,
2022
build_tree, source.basis_tree(), target)
2024
def test_build_tree_rename_count(self):
2025
source = self.make_branch_and_tree('source')
2026
self.build_tree(['source/file1', 'source/dir1/'])
2027
source.add(['file1', 'dir1'])
2028
source.commit('add1')
2029
target1 = self.make_branch_and_tree('target1')
2030
transform_result = build_tree(source.basis_tree(), target1)
2031
self.assertEqual(2, transform_result.rename_count)
2033
self.build_tree(['source/dir1/file2'])
2034
source.add(['dir1/file2'])
2035
source.commit('add3')
2036
target2 = self.make_branch_and_tree('target2')
2037
transform_result = build_tree(source.basis_tree(), target2)
2038
# children of non-root directories should not be renamed
2039
self.assertEqual(2, transform_result.rename_count)
2041
def create_ab_tree(self):
2042
"""Create a committed test tree with two files"""
2043
source = self.make_branch_and_tree('source')
2044
self.build_tree_contents([('source/file1', b'A')])
2045
self.build_tree_contents([('source/file2', b'B')])
2046
source.add(['file1', 'file2'], [b'file1-id', b'file2-id'])
2047
source.commit('commit files')
2049
self.addCleanup(source.unlock)
2052
def test_build_tree_accelerator_tree(self):
2053
source = self.create_ab_tree()
2054
self.build_tree_contents([('source/file2', b'C')])
2056
real_source_get_file = source.get_file
2057
def get_file(path, file_id=None):
2058
calls.append(file_id)
2059
return real_source_get_file(path, file_id)
2060
source.get_file = get_file
2061
target = self.make_branch_and_tree('target')
2062
revision_tree = source.basis_tree()
2063
revision_tree.lock_read()
2064
self.addCleanup(revision_tree.unlock)
2065
build_tree(revision_tree, target, source)
2066
self.assertEqual([b'file1-id'], calls)
2068
self.addCleanup(target.unlock)
2069
self.assertEqual([], list(target.iter_changes(revision_tree)))
2071
def test_build_tree_accelerator_tree_observes_sha1(self):
2072
source = self.create_ab_tree()
2073
sha1 = osutils.sha_string(b'A')
2074
target = self.make_branch_and_tree('target')
2076
self.addCleanup(target.unlock)
2077
state = target.current_dirstate()
2078
state._cutoff_time = time.time() + 60
2079
build_tree(source.basis_tree(), target, source)
2080
entry = state._get_entry(0, path_utf8=b'file1')
2081
self.assertEqual(sha1, entry[1][0][1])
2083
def test_build_tree_accelerator_tree_missing_file(self):
2084
source = self.create_ab_tree()
2085
os.unlink('source/file1')
2086
source.remove(['file2'])
2087
target = self.make_branch_and_tree('target')
2088
revision_tree = source.basis_tree()
2089
revision_tree.lock_read()
2090
self.addCleanup(revision_tree.unlock)
2091
build_tree(revision_tree, target, source)
2093
self.addCleanup(target.unlock)
2094
self.assertEqual([], list(target.iter_changes(revision_tree)))
2096
def test_build_tree_accelerator_wrong_kind(self):
2097
self.requireFeature(SymlinkFeature)
2098
source = self.make_branch_and_tree('source')
2099
self.build_tree_contents([('source/file1', b'')])
2100
self.build_tree_contents([('source/file2', b'')])
2101
source.add(['file1', 'file2'], [b'file1-id', b'file2-id'])
2102
source.commit('commit files')
2103
os.unlink('source/file2')
2104
self.build_tree_contents([('source/file2/', b'C')])
2105
os.unlink('source/file1')
2106
os.symlink('file2', 'source/file1')
2108
real_source_get_file = source.get_file
2109
def get_file(path, file_id=None):
2110
calls.append(file_id)
2111
return real_source_get_file(path, file_id)
2112
source.get_file = get_file
2113
target = self.make_branch_and_tree('target')
2114
revision_tree = source.basis_tree()
2115
revision_tree.lock_read()
2116
self.addCleanup(revision_tree.unlock)
2117
build_tree(revision_tree, target, source)
2118
self.assertEqual([], calls)
2120
self.addCleanup(target.unlock)
2121
self.assertEqual([], list(target.iter_changes(revision_tree)))
2123
def test_build_tree_hardlink(self):
2124
self.requireFeature(HardlinkFeature)
2125
source = self.create_ab_tree()
2126
target = self.make_branch_and_tree('target')
2127
revision_tree = source.basis_tree()
2128
revision_tree.lock_read()
2129
self.addCleanup(revision_tree.unlock)
2130
build_tree(revision_tree, target, source, hardlink=True)
2132
self.addCleanup(target.unlock)
2133
self.assertEqual([], list(target.iter_changes(revision_tree)))
2134
source_stat = os.stat('source/file1')
2135
target_stat = os.stat('target/file1')
2136
self.assertEqual(source_stat, target_stat)
2138
# Explicitly disallowing hardlinks should prevent them.
2139
target2 = self.make_branch_and_tree('target2')
2140
build_tree(revision_tree, target2, source, hardlink=False)
2142
self.addCleanup(target2.unlock)
2143
self.assertEqual([], list(target2.iter_changes(revision_tree)))
2144
source_stat = os.stat('source/file1')
2145
target2_stat = os.stat('target2/file1')
2146
self.assertNotEqual(source_stat, target2_stat)
2148
def test_build_tree_accelerator_tree_moved(self):
2149
source = self.make_branch_and_tree('source')
2150
self.build_tree_contents([('source/file1', b'A')])
2151
source.add(['file1'], [b'file1-id'])
2152
source.commit('commit files')
2153
source.rename_one('file1', 'file2')
2155
self.addCleanup(source.unlock)
2156
target = self.make_branch_and_tree('target')
2157
revision_tree = source.basis_tree()
2158
revision_tree.lock_read()
2159
self.addCleanup(revision_tree.unlock)
2160
build_tree(revision_tree, target, source)
2162
self.addCleanup(target.unlock)
2163
self.assertEqual([], list(target.iter_changes(revision_tree)))
2165
def test_build_tree_hardlinks_preserve_execute(self):
2166
self.requireFeature(HardlinkFeature)
2167
source = self.create_ab_tree()
2168
tt = TreeTransform(source)
2169
trans_id = tt.trans_id_tree_path('file1')
2170
tt.set_executability(True, trans_id)
2172
self.assertTrue(source.is_executable('file1'))
2173
target = self.make_branch_and_tree('target')
2174
revision_tree = source.basis_tree()
2175
revision_tree.lock_read()
2176
self.addCleanup(revision_tree.unlock)
2177
build_tree(revision_tree, target, source, hardlink=True)
2179
self.addCleanup(target.unlock)
2180
self.assertEqual([], list(target.iter_changes(revision_tree)))
2181
self.assertTrue(source.is_executable('file1'))
2183
def install_rot13_content_filter(self, pattern):
2185
# self.addCleanup(filters._reset_registry, filters._reset_registry())
2186
# below, but that looks a bit... hard to read even if it's exactly
2188
original_registry = filters._reset_registry()
2189
def restore_registry():
2190
filters._reset_registry(original_registry)
2191
self.addCleanup(restore_registry)
2192
def rot13(chunks, context=None):
2193
return [codecs.encode(chunk.decode('ascii'), 'rot13').encode('ascii')
2194
for chunk in chunks]
2195
rot13filter = filters.ContentFilter(rot13, rot13)
2196
filters.filter_stacks_registry.register(
2197
'rot13', {'yes': [rot13filter]}.get)
2198
os.mkdir(self.test_home_dir + '/.bazaar')
2199
rules_filename = self.test_home_dir + '/.bazaar/rules'
2200
with open(rules_filename, 'wb') as f:
2201
f.write(b'[name %s]\nrot13=yes\n' % (pattern,))
2202
def uninstall_rules():
2203
os.remove(rules_filename)
2205
self.addCleanup(uninstall_rules)
2208
def test_build_tree_content_filtered_files_are_not_hardlinked(self):
2209
"""build_tree will not hardlink files that have content filtering rules
2210
applied to them (but will still hardlink other files from the same tree
2213
self.requireFeature(HardlinkFeature)
2214
self.install_rot13_content_filter(b'file1')
2215
source = self.create_ab_tree()
2216
target = self.make_branch_and_tree('target')
2217
revision_tree = source.basis_tree()
2218
revision_tree.lock_read()
2219
self.addCleanup(revision_tree.unlock)
2220
build_tree(revision_tree, target, source, hardlink=True)
2222
self.addCleanup(target.unlock)
2223
self.assertEqual([], list(target.iter_changes(revision_tree)))
2224
source_stat = os.stat('source/file1')
2225
target_stat = os.stat('target/file1')
2226
self.assertNotEqual(source_stat, target_stat)
2227
source_stat = os.stat('source/file2')
2228
target_stat = os.stat('target/file2')
2229
self.assertEqualStat(source_stat, target_stat)
2231
def test_case_insensitive_build_tree_inventory(self):
2232
if (features.CaseInsensitiveFilesystemFeature.available()
2233
or features.CaseInsCasePresFilenameFeature.available()):
2234
raise tests.UnavailableFeature('Fully case sensitive filesystem')
2235
source = self.make_branch_and_tree('source')
2236
self.build_tree(['source/file', 'source/FILE'])
2237
source.add(['file', 'FILE'], [b'lower-id', b'upper-id'])
2238
source.commit('added files')
2239
# Don't try this at home, kids!
2240
# Force the tree to report that it is case insensitive
2241
target = self.make_branch_and_tree('target')
2242
target.case_sensitive = False
2243
build_tree(source.basis_tree(), target, source, delta_from_tree=True)
2244
self.assertEqual('file.moved', target.id2path(b'lower-id'))
2245
self.assertEqual('FILE', target.id2path(b'upper-id'))
2247
def test_build_tree_observes_sha(self):
2248
source = self.make_branch_and_tree('source')
2249
self.build_tree(['source/file1', 'source/dir/', 'source/dir/file2'])
2250
source.add(['file1', 'dir', 'dir/file2'],
2251
[b'file1-id', b'dir-id', b'file2-id'])
2252
source.commit('new files')
2253
target = self.make_branch_and_tree('target')
2255
self.addCleanup(target.unlock)
2256
# We make use of the fact that DirState caches its cutoff time. So we
2257
# set the 'safe' time to one minute in the future.
2258
state = target.current_dirstate()
2259
state._cutoff_time = time.time() + 60
2260
build_tree(source.basis_tree(), target)
2261
entry1_sha = osutils.sha_file_by_name('source/file1')
2262
entry2_sha = osutils.sha_file_by_name('source/dir/file2')
2263
# entry[1] is the state information, entry[1][0] is the state of the
2264
# working tree, entry[1][0][1] is the sha value for the current working
2266
entry1 = state._get_entry(0, path_utf8=b'file1')
2267
self.assertEqual(entry1_sha, entry1[1][0][1])
2268
# The 'size' field must also be set.
2269
self.assertEqual(25, entry1[1][0][2])
2270
entry1_state = entry1[1][0]
2271
entry2 = state._get_entry(0, path_utf8=b'dir/file2')
2272
self.assertEqual(entry2_sha, entry2[1][0][1])
2273
self.assertEqual(29, entry2[1][0][2])
2274
entry2_state = entry2[1][0]
2275
# Now, make sure that we don't have to re-read the content. The
2276
# packed_stat should match exactly.
2277
self.assertEqual(entry1_sha, target.get_file_sha1('file1', b'file1-id'))
2278
self.assertEqual(entry2_sha,
2279
target.get_file_sha1('dir/file2', b'file2-id'))
2280
self.assertEqual(entry1_state, entry1[1][0])
2281
self.assertEqual(entry2_state, entry2[1][0])
2284
class TestCommitTransform(tests.TestCaseWithTransport):
2286
def get_branch(self):
2287
tree = self.make_branch_and_tree('tree')
2289
self.addCleanup(tree.unlock)
2290
tree.commit('empty commit')
2293
def get_branch_and_transform(self):
2294
branch = self.get_branch()
2295
tt = TransformPreview(branch.basis_tree())
2296
self.addCleanup(tt.finalize)
2299
def test_commit_wrong_basis(self):
2300
branch = self.get_branch()
2301
basis = branch.repository.revision_tree(
2302
_mod_revision.NULL_REVISION)
2303
tt = TransformPreview(basis)
2304
self.addCleanup(tt.finalize)
2305
e = self.assertRaises(ValueError, tt.commit, branch, '')
2306
self.assertEqual('TreeTransform not based on branch basis: null:',
2309
def test_empy_commit(self):
2310
branch, tt = self.get_branch_and_transform()
2311
rev = tt.commit(branch, 'my message')
2312
self.assertEqual(2, branch.revno())
2313
repo = branch.repository
2314
self.assertEqual('my message', repo.get_revision(rev).message)
2316
def test_merge_parents(self):
2317
branch, tt = self.get_branch_and_transform()
2318
rev = tt.commit(branch, 'my message', [b'rev1b', b'rev1c'])
2319
self.assertEqual([b'rev1b', b'rev1c'],
2320
branch.basis_tree().get_parent_ids()[1:])
2322
def test_first_commit(self):
2323
branch = self.make_branch('branch')
2325
self.addCleanup(branch.unlock)
2326
tt = TransformPreview(branch.basis_tree())
2327
self.addCleanup(tt.finalize)
2328
tt.new_directory('', ROOT_PARENT, b'TREE_ROOT')
2329
rev = tt.commit(branch, 'my message')
2330
self.assertEqual([], branch.basis_tree().get_parent_ids())
2331
self.assertNotEqual(_mod_revision.NULL_REVISION,
2332
branch.last_revision())
2334
def test_first_commit_with_merge_parents(self):
2335
branch = self.make_branch('branch')
2337
self.addCleanup(branch.unlock)
2338
tt = TransformPreview(branch.basis_tree())
2339
self.addCleanup(tt.finalize)
2340
e = self.assertRaises(ValueError, tt.commit, branch,
2341
'my message', [b'rev1b-id'])
2342
self.assertEqual('Cannot supply merge parents for first commit.',
2344
self.assertEqual(_mod_revision.NULL_REVISION, branch.last_revision())
2346
def test_add_files(self):
2347
branch, tt = self.get_branch_and_transform()
2348
tt.new_file('file', tt.root, [b'contents'], b'file-id')
2349
trans_id = tt.new_directory('dir', tt.root, b'dir-id')
2350
if SymlinkFeature.available():
2351
tt.new_symlink('symlink', trans_id, 'target', b'symlink-id')
2352
rev = tt.commit(branch, 'message')
2353
tree = branch.basis_tree()
2354
self.assertEqual('file', tree.id2path(b'file-id'))
2355
self.assertEqual(b'contents', tree.get_file_text('file', b'file-id'))
2356
self.assertEqual('dir', tree.id2path(b'dir-id'))
2357
if SymlinkFeature.available():
2358
self.assertEqual('dir/symlink', tree.id2path(b'symlink-id'))
2359
self.assertEqual('target', tree.get_symlink_target('dir/symlink'))
2361
def test_add_unversioned(self):
2362
branch, tt = self.get_branch_and_transform()
2363
tt.new_file('file', tt.root, [b'contents'])
2364
self.assertRaises(errors.StrictCommitFailed, tt.commit, branch,
2365
'message', strict=True)
2367
def test_modify_strict(self):
2368
branch, tt = self.get_branch_and_transform()
2369
tt.new_file('file', tt.root, [b'contents'], b'file-id')
2370
tt.commit(branch, 'message', strict=True)
2371
tt = TransformPreview(branch.basis_tree())
2372
self.addCleanup(tt.finalize)
2373
trans_id = tt.trans_id_file_id(b'file-id')
2374
tt.delete_contents(trans_id)
2375
tt.create_file([b'contents'], trans_id)
2376
tt.commit(branch, 'message', strict=True)
2378
def test_commit_malformed(self):
2379
"""Committing a malformed transform should raise an exception.
2381
In this case, we are adding a file without adding its parent.
2383
branch, tt = self.get_branch_and_transform()
2384
parent_id = tt.trans_id_file_id(b'parent-id')
2385
tt.new_file('file', parent_id, [b'contents'], b'file-id')
2386
self.assertRaises(errors.MalformedTransform, tt.commit, branch,
2389
def test_commit_rich_revision_data(self):
2390
branch, tt = self.get_branch_and_transform()
2391
rev_id = tt.commit(branch, 'message', timestamp=1, timezone=43201,
2392
committer='me <me@example.com>',
2393
revprops={u'foo': 'bar'}, revision_id=b'revid-1',
2394
authors=['Author1 <author1@example.com>',
2395
'Author2 <author2@example.com>',
2397
self.assertEqual(b'revid-1', rev_id)
2398
revision = branch.repository.get_revision(rev_id)
2399
self.assertEqual(1, revision.timestamp)
2400
self.assertEqual(43201, revision.timezone)
2401
self.assertEqual('me <me@example.com>', revision.committer)
2402
self.assertEqual(['Author1 <author1@example.com>',
2403
'Author2 <author2@example.com>'],
2404
revision.get_apparent_authors())
2405
del revision.properties['authors']
2406
self.assertEqual({'foo': 'bar',
2407
'branch-nick': 'tree'},
2408
revision.properties)
2410
def test_no_explicit_revprops(self):
2411
branch, tt = self.get_branch_and_transform()
2412
rev_id = tt.commit(branch, 'message', authors=[
2413
'Author1 <author1@example.com>',
2414
'Author2 <author2@example.com>', ])
2415
revision = branch.repository.get_revision(rev_id)
2416
self.assertEqual(['Author1 <author1@example.com>',
2417
'Author2 <author2@example.com>'],
2418
revision.get_apparent_authors())
2419
self.assertEqual('tree', revision.properties['branch-nick'])
2422
class TestFileMover(tests.TestCaseWithTransport):
2424
def test_file_mover(self):
2425
self.build_tree(['a/', 'a/b', 'c/', 'c/d'])
2426
mover = _FileMover()
2427
mover.rename('a', 'q')
2428
self.assertPathExists('q')
2429
self.assertPathDoesNotExist('a')
2430
self.assertPathExists('q/b')
2431
self.assertPathExists('c')
2432
self.assertPathExists('c/d')
2434
def test_pre_delete_rollback(self):
2435
self.build_tree(['a/'])
2436
mover = _FileMover()
2437
mover.pre_delete('a', 'q')
2438
self.assertPathExists('q')
2439
self.assertPathDoesNotExist('a')
2441
self.assertPathDoesNotExist('q')
2442
self.assertPathExists('a')
2444
def test_apply_deletions(self):
2445
self.build_tree(['a/', 'b/'])
2446
mover = _FileMover()
2447
mover.pre_delete('a', 'q')
2448
mover.pre_delete('b', 'r')
2449
self.assertPathExists('q')
2450
self.assertPathExists('r')
2451
self.assertPathDoesNotExist('a')
2452
self.assertPathDoesNotExist('b')
2453
mover.apply_deletions()
2454
self.assertPathDoesNotExist('q')
2455
self.assertPathDoesNotExist('r')
2456
self.assertPathDoesNotExist('a')
2457
self.assertPathDoesNotExist('b')
2459
def test_file_mover_rollback(self):
2460
self.build_tree(['a/', 'a/b', 'c/', 'c/d/', 'c/e/'])
2461
mover = _FileMover()
2462
mover.rename('c/d', 'c/f')
2463
mover.rename('c/e', 'c/d')
2465
mover.rename('a', 'c')
2466
except errors.FileExists as e:
2468
self.assertPathExists('a')
2469
self.assertPathExists('c/d')
2472
class Bogus(Exception):
2476
class TestTransformRollback(tests.TestCaseWithTransport):
2478
class ExceptionFileMover(_FileMover):
2480
def __init__(self, bad_source=None, bad_target=None):
2481
_FileMover.__init__(self)
2482
self.bad_source = bad_source
2483
self.bad_target = bad_target
2485
def rename(self, source, target):
2486
if (self.bad_source is not None and
2487
source.endswith(self.bad_source)):
2489
elif (self.bad_target is not None and
2490
target.endswith(self.bad_target)):
2493
_FileMover.rename(self, source, target)
2495
def test_rollback_rename(self):
2496
tree = self.make_branch_and_tree('.')
2497
self.build_tree(['a/', 'a/b'])
2498
tt = TreeTransform(tree)
2499
self.addCleanup(tt.finalize)
2500
a_id = tt.trans_id_tree_path('a')
2501
tt.adjust_path('c', tt.root, a_id)
2502
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
2503
self.assertRaises(Bogus, tt.apply,
2504
_mover=self.ExceptionFileMover(bad_source='a'))
2505
self.assertPathExists('a')
2506
self.assertPathExists('a/b')
2508
self.assertPathExists('c')
2509
self.assertPathExists('c/d')
2511
def test_rollback_rename_into_place(self):
2512
tree = self.make_branch_and_tree('.')
2513
self.build_tree(['a/', 'a/b'])
2514
tt = TreeTransform(tree)
2515
self.addCleanup(tt.finalize)
2516
a_id = tt.trans_id_tree_path('a')
2517
tt.adjust_path('c', tt.root, a_id)
2518
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
2519
self.assertRaises(Bogus, tt.apply,
2520
_mover=self.ExceptionFileMover(bad_target='c/d'))
2521
self.assertPathExists('a')
2522
self.assertPathExists('a/b')
2524
self.assertPathExists('c')
2525
self.assertPathExists('c/d')
2527
def test_rollback_deletion(self):
2528
tree = self.make_branch_and_tree('.')
2529
self.build_tree(['a/', 'a/b'])
2530
tt = TreeTransform(tree)
2531
self.addCleanup(tt.finalize)
2532
a_id = tt.trans_id_tree_path('a')
2533
tt.delete_contents(a_id)
2534
tt.adjust_path('d', tt.root, tt.trans_id_tree_path('a/b'))
2535
self.assertRaises(Bogus, tt.apply,
2536
_mover=self.ExceptionFileMover(bad_target='d'))
2537
self.assertPathExists('a')
2538
self.assertPathExists('a/b')
2541
class TestFinalizeRobustness(tests.TestCaseWithTransport):
2542
"""Ensure treetransform creation errors can be safely cleaned up after"""
2544
def _override_globals_in_method(self, instance, method_name, globals):
2545
"""Replace method on instance with one with updated globals"""
2547
func = getattr(instance, method_name).__func__
2548
new_globals = dict(func.__globals__)
2549
new_globals.update(globals)
2550
new_func = types.FunctionType(func.__code__, new_globals,
2551
func.__name__, func.__defaults__)
2553
setattr(instance, method_name,
2554
types.MethodType(new_func, instance))
2556
setattr(instance, method_name,
2557
types.MethodType(new_func, instance, instance.__class__))
2558
self.addCleanup(delattr, instance, method_name)
2561
def _fake_open_raises_before(name, mode):
2562
"""Like open() but raises before doing anything"""
2566
def _fake_open_raises_after(name, mode):
2567
"""Like open() but raises after creating file without returning"""
2568
open(name, mode).close()
2571
def create_transform_and_root_trans_id(self):
2572
"""Setup a transform creating a file in limbo"""
2573
tree = self.make_branch_and_tree('.')
2574
tt = TreeTransform(tree)
2575
return tt, tt.create_path("a", tt.root)
2577
def create_transform_and_subdir_trans_id(self):
2578
"""Setup a transform creating a directory containing a file in limbo"""
2579
tree = self.make_branch_and_tree('.')
2580
tt = TreeTransform(tree)
2581
d_trans_id = tt.create_path("d", tt.root)
2582
tt.create_directory(d_trans_id)
2583
f_trans_id = tt.create_path("a", d_trans_id)
2584
tt.adjust_path("a", d_trans_id, f_trans_id)
2585
return tt, f_trans_id
2587
def test_root_create_file_open_raises_before_creation(self):
2588
tt, trans_id = self.create_transform_and_root_trans_id()
2589
self._override_globals_in_method(tt, "create_file",
2590
{"open": self._fake_open_raises_before})
2591
self.assertRaises(RuntimeError, tt.create_file, [b"contents"], trans_id)
2592
path = tt._limbo_name(trans_id)
2593
self.assertPathDoesNotExist(path)
2595
self.assertPathDoesNotExist(tt._limbodir)
2597
def test_root_create_file_open_raises_after_creation(self):
2598
tt, trans_id = self.create_transform_and_root_trans_id()
2599
self._override_globals_in_method(tt, "create_file",
2600
{"open": self._fake_open_raises_after})
2601
self.assertRaises(RuntimeError, tt.create_file, [b"contents"], trans_id)
2602
path = tt._limbo_name(trans_id)
2603
self.assertPathExists(path)
2605
self.assertPathDoesNotExist(path)
2606
self.assertPathDoesNotExist(tt._limbodir)
2608
def test_subdir_create_file_open_raises_before_creation(self):
2609
tt, trans_id = self.create_transform_and_subdir_trans_id()
2610
self._override_globals_in_method(tt, "create_file",
2611
{"open": self._fake_open_raises_before})
2612
self.assertRaises(RuntimeError, tt.create_file, [b"contents"], trans_id)
2613
path = tt._limbo_name(trans_id)
2614
self.assertPathDoesNotExist(path)
2616
self.assertPathDoesNotExist(tt._limbodir)
2618
def test_subdir_create_file_open_raises_after_creation(self):
2619
tt, trans_id = self.create_transform_and_subdir_trans_id()
2620
self._override_globals_in_method(tt, "create_file",
2621
{"open": self._fake_open_raises_after})
2622
self.assertRaises(RuntimeError, tt.create_file, [b"contents"], trans_id)
2623
path = tt._limbo_name(trans_id)
2624
self.assertPathExists(path)
2626
self.assertPathDoesNotExist(path)
2627
self.assertPathDoesNotExist(tt._limbodir)
2629
def test_rename_in_limbo_rename_raises_after_rename(self):
2630
tt, trans_id = self.create_transform_and_root_trans_id()
2631
parent1 = tt.new_directory('parent1', tt.root)
2632
child1 = tt.new_file('child1', parent1, [b'contents'])
2633
parent2 = tt.new_directory('parent2', tt.root)
2635
class FakeOSModule(object):
2636
def rename(self, old, new):
2639
self._override_globals_in_method(tt, "_rename_in_limbo",
2640
{"os": FakeOSModule()})
2642
RuntimeError, tt.adjust_path, "child1", parent2, child1)
2643
path = osutils.pathjoin(tt._limbo_name(parent2), "child1")
2644
self.assertPathExists(path)
2646
self.assertPathDoesNotExist(path)
2647
self.assertPathDoesNotExist(tt._limbodir)
2649
def test_rename_in_limbo_rename_raises_before_rename(self):
2650
tt, trans_id = self.create_transform_and_root_trans_id()
2651
parent1 = tt.new_directory('parent1', tt.root)
2652
child1 = tt.new_file('child1', parent1, [b'contents'])
2653
parent2 = tt.new_directory('parent2', tt.root)
2655
class FakeOSModule(object):
2656
def rename(self, old, new):
2658
self._override_globals_in_method(tt, "_rename_in_limbo",
2659
{"os": FakeOSModule()})
2661
RuntimeError, tt.adjust_path, "child1", parent2, child1)
2662
path = osutils.pathjoin(tt._limbo_name(parent1), "child1")
2663
self.assertPathExists(path)
2665
self.assertPathDoesNotExist(path)
2666
self.assertPathDoesNotExist(tt._limbodir)
2669
class TestTransformMissingParent(tests.TestCaseWithTransport):
2671
def make_tt_with_versioned_dir(self):
2672
wt = self.make_branch_and_tree('.')
2673
self.build_tree(['dir/',])
2674
wt.add(['dir'], [b'dir-id'])
2675
wt.commit('Create dir')
2676
tt = TreeTransform(wt)
2677
self.addCleanup(tt.finalize)
2680
def test_resolve_create_parent_for_versioned_file(self):
2681
wt, tt = self.make_tt_with_versioned_dir()
2682
dir_tid = tt.trans_id_tree_path('dir')
2683
file_tid = tt.new_file('file', dir_tid, [b'Contents'], file_id=b'file-id')
2684
tt.delete_contents(dir_tid)
2685
tt.unversion_file(dir_tid)
2686
conflicts = resolve_conflicts(tt)
2687
# one conflict for the missing directory, one for the unversioned
2689
self.assertLength(2, conflicts)
2691
def test_non_versioned_file_create_conflict(self):
2692
wt, tt = self.make_tt_with_versioned_dir()
2693
dir_tid = tt.trans_id_tree_path('dir')
2694
tt.new_file('file', dir_tid, [b'Contents'])
2695
tt.delete_contents(dir_tid)
2696
tt.unversion_file(dir_tid)
2697
conflicts = resolve_conflicts(tt)
2698
# no conflicts or rather: orphaning 'file' resolve the 'dir' conflict
2699
self.assertLength(1, conflicts)
2700
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
2704
A_ENTRY = (b'a-id', ('a', 'a'), True, (True, True),
2705
(b'TREE_ROOT', b'TREE_ROOT'), ('a', 'a'), ('file', 'file'),
2707
ROOT_ENTRY = (b'TREE_ROOT', ('', ''), False, (True, True), (None, None),
2708
('', ''), ('directory', 'directory'), (False, False))
2711
class TestTransformPreview(tests.TestCaseWithTransport):
2713
def create_tree(self):
2714
tree = self.make_branch_and_tree('.')
2715
self.build_tree_contents([('a', b'content 1')])
2716
tree.set_root_id(b'TREE_ROOT')
2717
tree.add('a', b'a-id')
2718
tree.commit('rev1', rev_id=b'rev1')
2719
return tree.branch.repository.revision_tree(b'rev1')
2721
def get_empty_preview(self):
2722
repository = self.make_repository('repo')
2723
tree = repository.revision_tree(_mod_revision.NULL_REVISION)
2724
preview = TransformPreview(tree)
2725
self.addCleanup(preview.finalize)
2728
def test_transform_preview(self):
2729
revision_tree = self.create_tree()
2730
preview = TransformPreview(revision_tree)
2731
self.addCleanup(preview.finalize)
2733
def test_transform_preview_tree(self):
2734
revision_tree = self.create_tree()
2735
preview = TransformPreview(revision_tree)
2736
self.addCleanup(preview.finalize)
2737
preview.get_preview_tree()
2739
def test_transform_new_file(self):
2740
revision_tree = self.create_tree()
2741
preview = TransformPreview(revision_tree)
2742
self.addCleanup(preview.finalize)
2743
preview.new_file('file2', preview.root, [b'content B\n'], b'file2-id')
2744
preview_tree = preview.get_preview_tree()
2745
self.assertEqual(preview_tree.kind('file2'), 'file')
2746
with preview_tree.get_file('file2', b'file2-id') as f:
2747
self.assertEqual(f.read(), b'content B\n')
2749
def test_diff_preview_tree(self):
2750
revision_tree = self.create_tree()
2751
preview = TransformPreview(revision_tree)
2752
self.addCleanup(preview.finalize)
2753
preview.new_file('file2', preview.root, [b'content B\n'], b'file2-id')
2754
preview_tree = preview.get_preview_tree()
2756
show_diff_trees(revision_tree, preview_tree, out)
2757
lines = out.getvalue().splitlines()
2758
self.assertEqual(lines[0], b"=== added file 'file2'")
2759
# 3 lines of diff administrivia
2760
self.assertEqual(lines[4], b"+content B")
2762
def test_transform_conflicts(self):
2763
revision_tree = self.create_tree()
2764
preview = TransformPreview(revision_tree)
2765
self.addCleanup(preview.finalize)
2766
preview.new_file('a', preview.root, [b'content 2'])
2767
resolve_conflicts(preview)
2768
trans_id = preview.trans_id_file_id(b'a-id')
2769
self.assertEqual('a.moved', preview.final_name(trans_id))
2771
def get_tree_and_preview_tree(self):
2772
revision_tree = self.create_tree()
2773
preview = TransformPreview(revision_tree)
2774
self.addCleanup(preview.finalize)
2775
a_trans_id = preview.trans_id_file_id(b'a-id')
2776
preview.delete_contents(a_trans_id)
2777
preview.create_file([b'b content'], a_trans_id)
2778
preview_tree = preview.get_preview_tree()
2779
return revision_tree, preview_tree
2781
def test_iter_changes(self):
2782
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2783
root = revision_tree.get_root_id()
2784
self.assertEqual([(b'a-id', ('a', 'a'), True, (True, True),
2785
(root, root), ('a', 'a'), ('file', 'file'),
2787
list(preview_tree.iter_changes(revision_tree)))
2789
def test_include_unchanged_succeeds(self):
2790
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2791
changes = preview_tree.iter_changes(revision_tree,
2792
include_unchanged=True)
2793
root = revision_tree.get_root_id()
2795
self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
2797
def test_specific_files(self):
2798
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2799
changes = preview_tree.iter_changes(revision_tree,
2800
specific_files=[''])
2801
self.assertEqual([A_ENTRY], list(changes))
2803
def test_want_unversioned(self):
2804
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2805
changes = preview_tree.iter_changes(revision_tree,
2806
want_unversioned=True)
2807
self.assertEqual([A_ENTRY], list(changes))
2809
def test_ignore_extra_trees_no_specific_files(self):
2810
# extra_trees is harmless without specific_files, so we'll silently
2811
# accept it, even though we won't use it.
2812
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2813
preview_tree.iter_changes(revision_tree, extra_trees=[preview_tree])
2815
def test_ignore_require_versioned_no_specific_files(self):
2816
# require_versioned is meaningless without specific_files.
2817
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2818
preview_tree.iter_changes(revision_tree, require_versioned=False)
2820
def test_ignore_pb(self):
2821
# pb could be supported, but TT.iter_changes doesn't support it.
2822
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2823
preview_tree.iter_changes(revision_tree)
2825
def test_kind(self):
2826
revision_tree = self.create_tree()
2827
preview = TransformPreview(revision_tree)
2828
self.addCleanup(preview.finalize)
2829
preview.new_file('file', preview.root, [b'contents'], b'file-id')
2830
preview.new_directory('directory', preview.root, b'dir-id')
2831
preview_tree = preview.get_preview_tree()
2832
self.assertEqual('file', preview_tree.kind('file'))
2833
self.assertEqual('directory', preview_tree.kind('directory'))
2835
def test_get_file_mtime(self):
2836
preview = self.get_empty_preview()
2837
file_trans_id = preview.new_file('file', preview.root, [b'contents'],
2839
limbo_path = preview._limbo_name(file_trans_id)
2840
preview_tree = preview.get_preview_tree()
2841
self.assertEqual(os.stat(limbo_path).st_mtime,
2842
preview_tree.get_file_mtime('file', b'file-id'))
2844
def test_get_file_mtime_renamed(self):
2845
work_tree = self.make_branch_and_tree('tree')
2846
self.build_tree(['tree/file'])
2847
work_tree.add('file', b'file-id')
2848
preview = TransformPreview(work_tree)
2849
self.addCleanup(preview.finalize)
2850
file_trans_id = preview.trans_id_tree_path('file')
2851
preview.adjust_path('renamed', preview.root, file_trans_id)
2852
preview_tree = preview.get_preview_tree()
2853
preview_mtime = preview_tree.get_file_mtime('renamed', b'file-id')
2854
work_mtime = work_tree.get_file_mtime('file', b'file-id')
2856
def test_get_file_size(self):
2857
work_tree = self.make_branch_and_tree('tree')
2858
self.build_tree_contents([('tree/old', b'old')])
2859
work_tree.add('old', b'old-id')
2860
preview = TransformPreview(work_tree)
2861
self.addCleanup(preview.finalize)
2862
new_id = preview.new_file('name', preview.root, [b'contents'], b'new-id',
2864
tree = preview.get_preview_tree()
2865
self.assertEqual(len('old'), tree.get_file_size('old'))
2866
self.assertEqual(len('contents'), tree.get_file_size('name'))
2868
def test_get_file(self):
2869
preview = self.get_empty_preview()
2870
preview.new_file('file', preview.root, [b'contents'], b'file-id')
2871
preview_tree = preview.get_preview_tree()
2872
with preview_tree.get_file('file') as tree_file:
2873
self.assertEqual(b'contents', tree_file.read())
2875
def test_get_symlink_target(self):
2876
self.requireFeature(SymlinkFeature)
2877
preview = self.get_empty_preview()
2878
preview.new_symlink('symlink', preview.root, 'target', b'symlink-id')
2879
preview_tree = preview.get_preview_tree()
2880
self.assertEqual('target',
2881
preview_tree.get_symlink_target('symlink'))
2883
def test_all_file_ids(self):
2884
tree = self.make_branch_and_tree('tree')
2885
self.build_tree(['tree/a', 'tree/b', 'tree/c'])
2886
tree.add(['a', 'b', 'c'], [b'a-id', b'b-id', b'c-id'])
2887
preview = TransformPreview(tree)
2888
self.addCleanup(preview.finalize)
2889
preview.unversion_file(preview.trans_id_file_id(b'b-id'))
2890
c_trans_id = preview.trans_id_file_id(b'c-id')
2891
preview.unversion_file(c_trans_id)
2892
preview.version_file(b'c-id', c_trans_id)
2893
preview_tree = preview.get_preview_tree()
2894
self.assertEqual({b'a-id', b'c-id', tree.get_root_id()},
2895
preview_tree.all_file_ids())
2897
def test_path2id_deleted_unchanged(self):
2898
tree = self.make_branch_and_tree('tree')
2899
self.build_tree(['tree/unchanged', 'tree/deleted'])
2900
tree.add(['unchanged', 'deleted'], [b'unchanged-id', b'deleted-id'])
2901
preview = TransformPreview(tree)
2902
self.addCleanup(preview.finalize)
2903
preview.unversion_file(preview.trans_id_file_id(b'deleted-id'))
2904
preview_tree = preview.get_preview_tree()
2905
self.assertEqual(b'unchanged-id', preview_tree.path2id('unchanged'))
2906
self.assertFalse(preview_tree.is_versioned('deleted'))
2908
def test_path2id_created(self):
2909
tree = self.make_branch_and_tree('tree')
2910
self.build_tree(['tree/unchanged'])
2911
tree.add(['unchanged'], [b'unchanged-id'])
2912
preview = TransformPreview(tree)
2913
self.addCleanup(preview.finalize)
2914
preview.new_file('new', preview.trans_id_file_id(b'unchanged-id'),
2915
[b'contents'], b'new-id')
2916
preview_tree = preview.get_preview_tree()
2917
self.assertEqual(b'new-id', preview_tree.path2id('unchanged/new'))
2919
def test_path2id_moved(self):
2920
tree = self.make_branch_and_tree('tree')
2921
self.build_tree(['tree/old_parent/', 'tree/old_parent/child'])
2922
tree.add(['old_parent', 'old_parent/child'],
2923
[b'old_parent-id', b'child-id'])
2924
preview = TransformPreview(tree)
2925
self.addCleanup(preview.finalize)
2926
new_parent = preview.new_directory('new_parent', preview.root,
2928
preview.adjust_path('child', new_parent,
2929
preview.trans_id_file_id(b'child-id'))
2930
preview_tree = preview.get_preview_tree()
2931
self.assertFalse(preview_tree.is_versioned('old_parent/child'))
2932
self.assertEqual(b'child-id', preview_tree.path2id('new_parent/child'))
2934
def test_path2id_renamed_parent(self):
2935
tree = self.make_branch_and_tree('tree')
2936
self.build_tree(['tree/old_name/', 'tree/old_name/child'])
2937
tree.add(['old_name', 'old_name/child'],
2938
[b'parent-id', b'child-id'])
2939
preview = TransformPreview(tree)
2940
self.addCleanup(preview.finalize)
2941
preview.adjust_path('new_name', preview.root,
2942
preview.trans_id_file_id(b'parent-id'))
2943
preview_tree = preview.get_preview_tree()
2944
self.assertFalse(preview_tree.is_versioned('old_name/child'))
2945
self.assertEqual(b'child-id', preview_tree.path2id('new_name/child'))
2947
def assertMatchingIterEntries(self, tt, specific_files=None):
2948
preview_tree = tt.get_preview_tree()
2949
preview_result = list(preview_tree.iter_entries_by_dir(
2950
specific_files=specific_files))
2953
actual_result = list(tree.iter_entries_by_dir(
2954
specific_files=specific_files))
2955
self.assertEqual(actual_result, preview_result)
2957
def test_iter_entries_by_dir_new(self):
2958
tree = self.make_branch_and_tree('tree')
2959
tt = TreeTransform(tree)
2960
tt.new_file('new', tt.root, [b'contents'], b'new-id')
2961
self.assertMatchingIterEntries(tt)
2963
def test_iter_entries_by_dir_deleted(self):
2964
tree = self.make_branch_and_tree('tree')
2965
self.build_tree(['tree/deleted'])
2966
tree.add('deleted', b'deleted-id')
2967
tt = TreeTransform(tree)
2968
tt.delete_contents(tt.trans_id_file_id(b'deleted-id'))
2969
self.assertMatchingIterEntries(tt)
2971
def test_iter_entries_by_dir_unversioned(self):
2972
tree = self.make_branch_and_tree('tree')
2973
self.build_tree(['tree/removed'])
2974
tree.add('removed', b'removed-id')
2975
tt = TreeTransform(tree)
2976
tt.unversion_file(tt.trans_id_file_id(b'removed-id'))
2977
self.assertMatchingIterEntries(tt)
2979
def test_iter_entries_by_dir_moved(self):
2980
tree = self.make_branch_and_tree('tree')
2981
self.build_tree(['tree/moved', 'tree/new_parent/'])
2982
tree.add(['moved', 'new_parent'], [b'moved-id', b'new_parent-id'])
2983
tt = TreeTransform(tree)
2984
tt.adjust_path('moved', tt.trans_id_file_id(b'new_parent-id'),
2985
tt.trans_id_file_id(b'moved-id'))
2986
self.assertMatchingIterEntries(tt)
2988
def test_iter_entries_by_dir_specific_files(self):
2989
tree = self.make_branch_and_tree('tree')
2990
tree.set_root_id(b'tree-root-id')
2991
self.build_tree(['tree/parent/', 'tree/parent/child'])
2992
tree.add(['parent', 'parent/child'], [b'parent-id', b'child-id'])
2993
tt = TreeTransform(tree)
2994
self.assertMatchingIterEntries(tt, ['', 'parent/child'])
2996
def test_symlink_content_summary(self):
2997
self.requireFeature(SymlinkFeature)
2998
preview = self.get_empty_preview()
2999
preview.new_symlink('path', preview.root, 'target', b'path-id')
3000
summary = preview.get_preview_tree().path_content_summary('path')
3001
self.assertEqual(('symlink', None, None, 'target'), summary)
3003
def test_missing_content_summary(self):
3004
preview = self.get_empty_preview()
3005
summary = preview.get_preview_tree().path_content_summary('path')
3006
self.assertEqual(('missing', None, None, None), summary)
3008
def test_deleted_content_summary(self):
3009
tree = self.make_branch_and_tree('tree')
3010
self.build_tree(['tree/path/'])
3012
preview = TransformPreview(tree)
3013
self.addCleanup(preview.finalize)
3014
preview.delete_contents(preview.trans_id_tree_path('path'))
3015
summary = preview.get_preview_tree().path_content_summary('path')
3016
self.assertEqual(('missing', None, None, None), summary)
3018
def test_file_content_summary_executable(self):
3019
preview = self.get_empty_preview()
3020
path_id = preview.new_file('path', preview.root, [b'contents'], b'path-id')
3021
preview.set_executability(True, path_id)
3022
summary = preview.get_preview_tree().path_content_summary('path')
3023
self.assertEqual(4, len(summary))
3024
self.assertEqual('file', summary[0])
3025
# size must be known
3026
self.assertEqual(len('contents'), summary[1])
3028
self.assertEqual(True, summary[2])
3029
# will not have hash (not cheap to determine)
3030
self.assertIs(None, summary[3])
3032
def test_change_executability(self):
3033
tree = self.make_branch_and_tree('tree')
3034
self.build_tree(['tree/path'])
3036
preview = TransformPreview(tree)
3037
self.addCleanup(preview.finalize)
3038
path_id = preview.trans_id_tree_path('path')
3039
preview.set_executability(True, path_id)
3040
summary = preview.get_preview_tree().path_content_summary('path')
3041
self.assertEqual(True, summary[2])
3043
def test_file_content_summary_non_exec(self):
3044
preview = self.get_empty_preview()
3045
preview.new_file('path', preview.root, [b'contents'], b'path-id')
3046
summary = preview.get_preview_tree().path_content_summary('path')
3047
self.assertEqual(4, len(summary))
3048
self.assertEqual('file', summary[0])
3049
# size must be known
3050
self.assertEqual(len('contents'), summary[1])
3052
self.assertEqual(False, summary[2])
3053
# will not have hash (not cheap to determine)
3054
self.assertIs(None, summary[3])
3056
def test_dir_content_summary(self):
3057
preview = self.get_empty_preview()
3058
preview.new_directory('path', preview.root, b'path-id')
3059
summary = preview.get_preview_tree().path_content_summary('path')
3060
self.assertEqual(('directory', None, None, None), summary)
3062
def test_tree_content_summary(self):
3063
preview = self.get_empty_preview()
3064
path = preview.new_directory('path', preview.root, b'path-id')
3065
preview.set_tree_reference(b'rev-1', path)
3066
summary = preview.get_preview_tree().path_content_summary('path')
3067
self.assertEqual(4, len(summary))
3068
self.assertEqual('tree-reference', summary[0])
3070
def test_annotate(self):
3071
tree = self.make_branch_and_tree('tree')
3072
self.build_tree_contents([('tree/file', b'a\n')])
3073
tree.add('file', b'file-id')
3074
tree.commit('a', rev_id=b'one')
3075
self.build_tree_contents([('tree/file', b'a\nb\n')])
3076
preview = TransformPreview(tree)
3077
self.addCleanup(preview.finalize)
3078
file_trans_id = preview.trans_id_file_id(b'file-id')
3079
preview.delete_contents(file_trans_id)
3080
preview.create_file([b'a\nb\nc\n'], file_trans_id)
3081
preview_tree = preview.get_preview_tree()
3087
annotation = preview_tree.annotate_iter('file', default_revision=b'me:')
3088
self.assertEqual(expected, annotation)
3090
def test_annotate_missing(self):
3091
preview = self.get_empty_preview()
3092
preview.new_file('file', preview.root, [b'a\nb\nc\n'], b'file-id')
3093
preview_tree = preview.get_preview_tree()
3099
annotation = preview_tree.annotate_iter('file', default_revision=b'me:')
3100
self.assertEqual(expected, annotation)
3102
def test_annotate_rename(self):
3103
tree = self.make_branch_and_tree('tree')
3104
self.build_tree_contents([('tree/file', b'a\n')])
3105
tree.add('file', b'file-id')
3106
tree.commit('a', rev_id=b'one')
3107
preview = TransformPreview(tree)
3108
self.addCleanup(preview.finalize)
3109
file_trans_id = preview.trans_id_file_id(b'file-id')
3110
preview.adjust_path('newname', preview.root, file_trans_id)
3111
preview_tree = preview.get_preview_tree()
3115
annotation = preview_tree.annotate_iter('file', default_revision=b'me:')
3116
self.assertEqual(expected, annotation)
3118
def test_annotate_deleted(self):
3119
tree = self.make_branch_and_tree('tree')
3120
self.build_tree_contents([('tree/file', b'a\n')])
3121
tree.add('file', b'file-id')
3122
tree.commit('a', rev_id=b'one')
3123
self.build_tree_contents([('tree/file', b'a\nb\n')])
3124
preview = TransformPreview(tree)
3125
self.addCleanup(preview.finalize)
3126
file_trans_id = preview.trans_id_file_id(b'file-id')
3127
preview.delete_contents(file_trans_id)
3128
preview_tree = preview.get_preview_tree()
3129
annotation = preview_tree.annotate_iter('file', default_revision=b'me:')
3130
self.assertIs(None, annotation)
3132
def test_stored_kind(self):
3133
preview = self.get_empty_preview()
3134
preview.new_file('file', preview.root, [b'a\nb\nc\n'], b'file-id')
3135
preview_tree = preview.get_preview_tree()
3136
self.assertEqual('file', preview_tree.stored_kind('file'))
3138
def test_is_executable(self):
3139
preview = self.get_empty_preview()
3140
preview.new_file('file', preview.root, [b'a\nb\nc\n'], b'file-id')
3141
preview.set_executability(True, preview.trans_id_file_id(b'file-id'))
3142
preview_tree = preview.get_preview_tree()
3143
self.assertEqual(True, preview_tree.is_executable('file'))
3145
def test_get_set_parent_ids(self):
3146
revision_tree, preview_tree = self.get_tree_and_preview_tree()
3147
self.assertEqual([], preview_tree.get_parent_ids())
3148
preview_tree.set_parent_ids([b'rev-1'])
3149
self.assertEqual([b'rev-1'], preview_tree.get_parent_ids())
3151
def test_plan_file_merge(self):
3152
work_a = self.make_branch_and_tree('wta')
3153
self.build_tree_contents([('wta/file', b'a\nb\nc\nd\n')])
3154
work_a.add('file', b'file-id')
3155
base_id = work_a.commit('base version')
3156
tree_b = work_a.controldir.sprout('wtb').open_workingtree()
3157
preview = TransformPreview(work_a)
3158
self.addCleanup(preview.finalize)
3159
trans_id = preview.trans_id_file_id(b'file-id')
3160
preview.delete_contents(trans_id)
3161
preview.create_file([b'b\nc\nd\ne\n'], trans_id)
3162
self.build_tree_contents([('wtb/file', b'a\nc\nd\nf\n')])
3163
tree_a = preview.get_preview_tree()
3164
tree_a.set_parent_ids([base_id])
3166
('killed-a', b'a\n'),
3167
('killed-b', b'b\n'),
3168
('unchanged', b'c\n'),
3169
('unchanged', b'd\n'),
3172
], list(tree_a.plan_file_merge(b'file-id', tree_b)))
3174
def test_plan_file_merge_revision_tree(self):
3175
work_a = self.make_branch_and_tree('wta')
3176
self.build_tree_contents([('wta/file', b'a\nb\nc\nd\n')])
3177
work_a.add('file', b'file-id')
3178
base_id = work_a.commit('base version')
3179
tree_b = work_a.controldir.sprout('wtb').open_workingtree()
3180
preview = TransformPreview(work_a.basis_tree())
3181
self.addCleanup(preview.finalize)
3182
trans_id = preview.trans_id_file_id(b'file-id')
3183
preview.delete_contents(trans_id)
3184
preview.create_file([b'b\nc\nd\ne\n'], trans_id)
3185
self.build_tree_contents([('wtb/file', b'a\nc\nd\nf\n')])
3186
tree_a = preview.get_preview_tree()
3187
tree_a.set_parent_ids([base_id])
3189
('killed-a', b'a\n'),
3190
('killed-b', b'b\n'),
3191
('unchanged', b'c\n'),
3192
('unchanged', b'd\n'),
3195
], list(tree_a.plan_file_merge(b'file-id', tree_b)))
3197
def test_walkdirs(self):
3198
preview = self.get_empty_preview()
3199
root = preview.new_directory('', ROOT_PARENT, b'tree-root')
3200
# FIXME: new_directory should mark root.
3201
preview.fixup_new_roots()
3202
preview_tree = preview.get_preview_tree()
3203
file_trans_id = preview.new_file('a', preview.root, [b'contents'],
3205
expected = [(('', b'tree-root'),
3206
[('a', 'a', 'file', None, b'a-id', 'file')])]
3207
self.assertEqual(expected, list(preview_tree.walkdirs()))
3209
def test_extras(self):
3210
work_tree = self.make_branch_and_tree('tree')
3211
self.build_tree(['tree/removed-file', 'tree/existing-file',
3212
'tree/not-removed-file'])
3213
work_tree.add(['removed-file', 'not-removed-file'])
3214
preview = TransformPreview(work_tree)
3215
self.addCleanup(preview.finalize)
3216
preview.new_file('new-file', preview.root, [b'contents'])
3217
preview.new_file('new-versioned-file', preview.root, [b'contents'],
3218
b'new-versioned-id')
3219
tree = preview.get_preview_tree()
3220
preview.unversion_file(preview.trans_id_tree_path('removed-file'))
3221
self.assertEqual({'new-file', 'removed-file', 'existing-file'},
3224
def test_merge_into_preview(self):
3225
work_tree = self.make_branch_and_tree('tree')
3226
self.build_tree_contents([('tree/file', b'b\n')])
3227
work_tree.add('file', b'file-id')
3228
work_tree.commit('first commit')
3229
child_tree = work_tree.controldir.sprout('child').open_workingtree()
3230
self.build_tree_contents([('child/file', b'b\nc\n')])
3231
child_tree.commit('child commit')
3232
child_tree.lock_write()
3233
self.addCleanup(child_tree.unlock)
3234
work_tree.lock_write()
3235
self.addCleanup(work_tree.unlock)
3236
preview = TransformPreview(work_tree)
3237
self.addCleanup(preview.finalize)
3238
file_trans_id = preview.trans_id_file_id(b'file-id')
3239
preview.delete_contents(file_trans_id)
3240
preview.create_file([b'a\nb\n'], file_trans_id)
3241
preview_tree = preview.get_preview_tree()
3242
merger = Merger.from_revision_ids(preview_tree,
3243
child_tree.branch.last_revision(),
3244
other_branch=child_tree.branch,
3245
tree_branch=work_tree.branch)
3246
merger.merge_type = Merge3Merger
3247
tt = merger.make_merger().make_preview_transform()
3248
self.addCleanup(tt.finalize)
3249
final_tree = tt.get_preview_tree()
3252
final_tree.get_file_text(final_tree.id2path(b'file-id')))
3254
def test_merge_preview_into_workingtree(self):
3255
tree = self.make_branch_and_tree('tree')
3256
tree.set_root_id(b'TREE_ROOT')
3257
tt = TransformPreview(tree)
3258
self.addCleanup(tt.finalize)
3259
tt.new_file('name', tt.root, [b'content'], b'file-id')
3260
tree2 = self.make_branch_and_tree('tree2')
3261
tree2.set_root_id(b'TREE_ROOT')
3262
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
3264
merger.merge_type = Merge3Merger
3267
def test_merge_preview_into_workingtree_handles_conflicts(self):
3268
tree = self.make_branch_and_tree('tree')
3269
self.build_tree_contents([('tree/foo', b'bar')])
3270
tree.add('foo', b'foo-id')
3272
tt = TransformPreview(tree)
3273
self.addCleanup(tt.finalize)
3274
trans_id = tt.trans_id_file_id(b'foo-id')
3275
tt.delete_contents(trans_id)
3276
tt.create_file([b'baz'], trans_id)
3277
tree2 = tree.controldir.sprout('tree2').open_workingtree()
3278
self.build_tree_contents([('tree2/foo', b'qux')])
3279
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
3281
merger.merge_type = Merge3Merger
3284
def test_has_filename(self):
3285
wt = self.make_branch_and_tree('tree')
3286
self.build_tree(['tree/unmodified', 'tree/removed', 'tree/modified'])
3287
tt = TransformPreview(wt)
3288
removed_id = tt.trans_id_tree_path('removed')
3289
tt.delete_contents(removed_id)
3290
tt.new_file('new', tt.root, [b'contents'])
3291
modified_id = tt.trans_id_tree_path('modified')
3292
tt.delete_contents(modified_id)
3293
tt.create_file([b'modified-contents'], modified_id)
3294
self.addCleanup(tt.finalize)
3295
tree = tt.get_preview_tree()
3296
self.assertTrue(tree.has_filename('unmodified'))
3297
self.assertFalse(tree.has_filename('not-present'))
3298
self.assertFalse(tree.has_filename('removed'))
3299
self.assertTrue(tree.has_filename('new'))
3300
self.assertTrue(tree.has_filename('modified'))
3302
def test_is_executable(self):
3303
tree = self.make_branch_and_tree('tree')
3304
preview = TransformPreview(tree)
3305
self.addCleanup(preview.finalize)
3306
preview.new_file('foo', preview.root, [b'bar'], b'baz-id')
3307
preview_tree = preview.get_preview_tree()
3308
self.assertEqual(False, preview_tree.is_executable('tree/foo', b'baz-id'))
3309
self.assertEqual(False, preview_tree.is_executable('tree/foo'))
3311
def test_commit_preview_tree(self):
3312
tree = self.make_branch_and_tree('tree')
3313
rev_id = tree.commit('rev1')
3314
tree.branch.lock_write()
3315
self.addCleanup(tree.branch.unlock)
3316
tt = TransformPreview(tree)
3317
tt.new_file('file', tt.root, [b'contents'], b'file_id')
3318
self.addCleanup(tt.finalize)
3319
preview = tt.get_preview_tree()
3320
preview.set_parent_ids([rev_id])
3321
builder = tree.branch.get_commit_builder([rev_id])
3322
list(builder.record_iter_changes(preview, rev_id, tt.iter_changes()))
3323
builder.finish_inventory()
3324
rev2_id = builder.commit('rev2')
3325
rev2_tree = tree.branch.repository.revision_tree(rev2_id)
3326
self.assertEqual(b'contents', rev2_tree.get_file_text('file'))
3328
def test_ascii_limbo_paths(self):
3329
self.requireFeature(features.UnicodeFilenameFeature)
3330
branch = self.make_branch('any')
3331
tree = branch.repository.revision_tree(_mod_revision.NULL_REVISION)
3332
tt = TransformPreview(tree)
3333
self.addCleanup(tt.finalize)
3334
foo_id = tt.new_directory('', ROOT_PARENT)
3335
bar_id = tt.new_file(u'\u1234bar', foo_id, [b'contents'])
3336
limbo_path = tt._limbo_name(bar_id)
3337
self.assertEqual(limbo_path, limbo_path)
3340
class FakeSerializer(object):
3341
"""Serializer implementation that simply returns the input.
3343
The input is returned in the order used by pack.ContainerPushParser.
3346
def bytes_record(bytes, names):
3350
class TestSerializeTransform(tests.TestCaseWithTransport):
3352
_test_needs_features = [features.UnicodeFilenameFeature]
3354
def get_preview(self, tree=None):
3356
tree = self.make_branch_and_tree('tree')
3357
tt = TransformPreview(tree)
3358
self.addCleanup(tt.finalize)
3361
def assertSerializesTo(self, expected, tt):
3362
records = list(tt.serialize(FakeSerializer()))
3363
self.assertEqual(expected, records)
3366
def default_attribs():
3371
b'_new_executability': {},
3373
b'_tree_path_ids': {b'': b'new-0'},
3375
b'_removed_contents': [],
3376
b'_non_present_ids': {},
3379
def make_records(self, attribs, contents):
3381
((((b'attribs'),),), bencode.bencode(attribs))]
3382
records.extend([(((n, k),), c) for n, k, c in contents])
3385
def creation_records(self):
3386
attribs = self.default_attribs()
3387
attribs[b'_id_number'] = 3
3388
attribs[b'_new_name'] = {
3389
b'new-1': u'foo\u1234'.encode('utf-8'), b'new-2': b'qux'}
3390
attribs[b'_new_id'] = {b'new-1': b'baz', b'new-2': b'quxx'}
3391
attribs[b'_new_parent'] = {b'new-1': b'new-0', b'new-2': b'new-0'}
3392
attribs[b'_new_executability'] = {b'new-1': 1}
3394
(b'new-1', b'file', b'i 1\nbar\n'),
3395
(b'new-2', b'directory', b''),
3397
return self.make_records(attribs, contents)
3399
def test_serialize_creation(self):
3400
tt = self.get_preview()
3401
tt.new_file(u'foo\u1234', tt.root, [b'bar'], b'baz', True)
3402
tt.new_directory('qux', tt.root, b'quxx')
3403
self.assertSerializesTo(self.creation_records(), tt)
3405
def test_deserialize_creation(self):
3406
tt = self.get_preview()
3407
tt.deserialize(iter(self.creation_records()))
3408
self.assertEqual(3, tt._id_number)
3409
self.assertEqual({'new-1': u'foo\u1234',
3410
'new-2': 'qux'}, tt._new_name)
3411
self.assertEqual({'new-1': b'baz', 'new-2': b'quxx'}, tt._new_id)
3412
self.assertEqual({'new-1': tt.root, 'new-2': tt.root}, tt._new_parent)
3413
self.assertEqual({b'baz': 'new-1', b'quxx': 'new-2'}, tt._r_new_id)
3414
self.assertEqual({'new-1': True}, tt._new_executability)
3415
self.assertEqual({'new-1': 'file',
3416
'new-2': 'directory'}, tt._new_contents)
3417
foo_limbo = open(tt._limbo_name('new-1'), 'rb')
3419
foo_content = foo_limbo.read()
3422
self.assertEqual(b'bar', foo_content)
3424
def symlink_creation_records(self):
3425
attribs = self.default_attribs()
3426
attribs[b'_id_number'] = 2
3427
attribs[b'_new_name'] = {b'new-1': u'foo\u1234'.encode('utf-8')}
3428
attribs[b'_new_parent'] = {b'new-1': b'new-0'}
3429
contents = [(b'new-1', b'symlink', u'bar\u1234'.encode('utf-8'))]
3430
return self.make_records(attribs, contents)
3432
def test_serialize_symlink_creation(self):
3433
self.requireFeature(features.SymlinkFeature)
3434
tt = self.get_preview()
3435
tt.new_symlink(u'foo\u1234', tt.root, u'bar\u1234')
3436
self.assertSerializesTo(self.symlink_creation_records(), tt)
3438
def test_deserialize_symlink_creation(self):
3439
self.requireFeature(features.SymlinkFeature)
3440
tt = self.get_preview()
3441
tt.deserialize(iter(self.symlink_creation_records()))
3442
abspath = tt._limbo_name('new-1')
3443
foo_content = osutils.readlink(abspath)
3444
self.assertEqual(u'bar\u1234', foo_content)
3446
def make_destruction_preview(self):
3447
tree = self.make_branch_and_tree('.')
3448
self.build_tree([u'foo\u1234', 'bar'])
3449
tree.add([u'foo\u1234', 'bar'], [b'foo-id', b'bar-id'])
3450
return self.get_preview(tree)
3452
def destruction_records(self):
3453
attribs = self.default_attribs()
3454
attribs[b'_id_number'] = 3
3455
attribs[b'_removed_id'] = [b'new-1']
3456
attribs[b'_removed_contents'] = [b'new-2']
3457
attribs[b'_tree_path_ids'] = {
3459
u'foo\u1234'.encode('utf-8'): b'new-1',
3462
return self.make_records(attribs, [])
3464
def test_serialize_destruction(self):
3465
tt = self.make_destruction_preview()
3466
foo_trans_id = tt.trans_id_tree_path(u'foo\u1234')
3467
tt.unversion_file(foo_trans_id)
3468
bar_trans_id = tt.trans_id_tree_path('bar')
3469
tt.delete_contents(bar_trans_id)
3470
self.assertSerializesTo(self.destruction_records(), tt)
3472
def test_deserialize_destruction(self):
3473
tt = self.make_destruction_preview()
3474
tt.deserialize(iter(self.destruction_records()))
3475
self.assertEqual({u'foo\u1234': 'new-1',
3477
'': tt.root}, tt._tree_path_ids)
3478
self.assertEqual({'new-1': u'foo\u1234',
3480
tt.root: ''}, tt._tree_id_paths)
3481
self.assertEqual({'new-1'}, tt._removed_id)
3482
self.assertEqual({'new-2'}, tt._removed_contents)
3484
def missing_records(self):
3485
attribs = self.default_attribs()
3486
attribs[b'_id_number'] = 2
3487
attribs[b'_non_present_ids'] = {
3489
return self.make_records(attribs, [])
3491
def test_serialize_missing(self):
3492
tt = self.get_preview()
3493
boo_trans_id = tt.trans_id_file_id(b'boo')
3494
self.assertSerializesTo(self.missing_records(), tt)
3496
def test_deserialize_missing(self):
3497
tt = self.get_preview()
3498
tt.deserialize(iter(self.missing_records()))
3499
self.assertEqual({b'boo': 'new-1'}, tt._non_present_ids)
3501
def make_modification_preview(self):
3502
LINES_ONE = b'aa\nbb\ncc\ndd\n'
3503
LINES_TWO = b'z\nbb\nx\ndd\n'
3504
tree = self.make_branch_and_tree('tree')
3505
self.build_tree_contents([('tree/file', LINES_ONE)])
3506
tree.add('file', b'file-id')
3507
return self.get_preview(tree), [LINES_TWO]
3509
def modification_records(self):
3510
attribs = self.default_attribs()
3511
attribs[b'_id_number'] = 2
3512
attribs[b'_tree_path_ids'] = {
3515
attribs[b'_removed_contents'] = [b'new-1']
3516
contents = [(b'new-1', b'file',
3517
b'i 1\nz\n\nc 0 1 1 1\ni 1\nx\n\nc 0 3 3 1\n')]
3518
return self.make_records(attribs, contents)
3520
def test_serialize_modification(self):
3521
tt, LINES = self.make_modification_preview()
3522
trans_id = tt.trans_id_file_id(b'file-id')
3523
tt.delete_contents(trans_id)
3524
tt.create_file(LINES, trans_id)
3525
self.assertSerializesTo(self.modification_records(), tt)
3527
def test_deserialize_modification(self):
3528
tt, LINES = self.make_modification_preview()
3529
tt.deserialize(iter(self.modification_records()))
3530
self.assertFileEqual(b''.join(LINES), tt._limbo_name('new-1'))
3532
def make_kind_change_preview(self):
3533
LINES = b'a\nb\nc\nd\n'
3534
tree = self.make_branch_and_tree('tree')
3535
self.build_tree(['tree/foo/'])
3536
tree.add('foo', b'foo-id')
3537
return self.get_preview(tree), [LINES]
3539
def kind_change_records(self):
3540
attribs = self.default_attribs()
3541
attribs[b'_id_number'] = 2
3542
attribs[b'_tree_path_ids'] = {
3545
attribs[b'_removed_contents'] = [b'new-1']
3546
contents = [(b'new-1', b'file',
3547
b'i 4\na\nb\nc\nd\n\n')]
3548
return self.make_records(attribs, contents)
3550
def test_serialize_kind_change(self):
3551
tt, LINES = self.make_kind_change_preview()
3552
trans_id = tt.trans_id_file_id(b'foo-id')
3553
tt.delete_contents(trans_id)
3554
tt.create_file(LINES, trans_id)
3555
self.assertSerializesTo(self.kind_change_records(), tt)
3557
def test_deserialize_kind_change(self):
3558
tt, LINES = self.make_kind_change_preview()
3559
tt.deserialize(iter(self.kind_change_records()))
3560
self.assertFileEqual(b''.join(LINES), tt._limbo_name('new-1'))
3562
def make_add_contents_preview(self):
3563
LINES = b'a\nb\nc\nd\n'
3564
tree = self.make_branch_and_tree('tree')
3565
self.build_tree(['tree/foo'])
3567
os.unlink('tree/foo')
3568
return self.get_preview(tree), LINES
3570
def add_contents_records(self):
3571
attribs = self.default_attribs()
3572
attribs[b'_id_number'] = 2
3573
attribs[b'_tree_path_ids'] = {
3576
contents = [(b'new-1', b'file',
3577
b'i 4\na\nb\nc\nd\n\n')]
3578
return self.make_records(attribs, contents)
3580
def test_serialize_add_contents(self):
3581
tt, LINES = self.make_add_contents_preview()
3582
trans_id = tt.trans_id_tree_path('foo')
3583
tt.create_file([LINES], trans_id)
3584
self.assertSerializesTo(self.add_contents_records(), tt)
3586
def test_deserialize_add_contents(self):
3587
tt, LINES = self.make_add_contents_preview()
3588
tt.deserialize(iter(self.add_contents_records()))
3589
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
3591
def test_get_parents_lines(self):
3592
LINES_ONE = b'aa\nbb\ncc\ndd\n'
3593
LINES_TWO = b'z\nbb\nx\ndd\n'
3594
tree = self.make_branch_and_tree('tree')
3595
self.build_tree_contents([('tree/file', LINES_ONE)])
3596
tree.add('file', b'file-id')
3597
tt = self.get_preview(tree)
3598
trans_id = tt.trans_id_tree_path('file')
3599
self.assertEqual(([b'aa\n', b'bb\n', b'cc\n', b'dd\n'],),
3600
tt._get_parents_lines(trans_id))
3602
def test_get_parents_texts(self):
3603
LINES_ONE = b'aa\nbb\ncc\ndd\n'
3604
LINES_TWO = b'z\nbb\nx\ndd\n'
3605
tree = self.make_branch_and_tree('tree')
3606
self.build_tree_contents([('tree/file', LINES_ONE)])
3607
tree.add('file', b'file-id')
3608
tt = self.get_preview(tree)
3609
trans_id = tt.trans_id_tree_path('file')
3610
self.assertEqual((LINES_ONE,),
3611
tt._get_parents_texts(trans_id))
3614
class TestOrphan(tests.TestCaseWithTransport):
3616
def test_no_orphan_for_transform_preview(self):
3617
tree = self.make_branch_and_tree('tree')
3618
tt = transform.TransformPreview(tree)
3619
self.addCleanup(tt.finalize)
3620
self.assertRaises(NotImplementedError, tt.new_orphan, 'foo', 'bar')
3622
def _set_orphan_policy(self, wt, policy):
3623
wt.branch.get_config_stack().set('transform.orphan_policy',
3626
def _prepare_orphan(self, wt):
3627
self.build_tree(['dir/', 'dir/file', 'dir/foo'])
3628
wt.add(['dir', 'dir/file'], [b'dir-id', b'file-id'])
3629
wt.commit('add dir and file ignoring foo')
3630
tt = transform.TreeTransform(wt)
3631
self.addCleanup(tt.finalize)
3632
# dir and bar are deleted
3633
dir_tid = tt.trans_id_tree_path('dir')
3634
file_tid = tt.trans_id_tree_path('dir/file')
3635
orphan_tid = tt.trans_id_tree_path('dir/foo')
3636
tt.delete_contents(file_tid)
3637
tt.unversion_file(file_tid)
3638
tt.delete_contents(dir_tid)
3639
tt.unversion_file(dir_tid)
3640
# There should be a conflict because dir still contain foo
3641
raw_conflicts = tt.find_conflicts()
3642
self.assertLength(1, raw_conflicts)
3643
self.assertEqual(('missing parent', 'new-1'), raw_conflicts[0])
3644
return tt, orphan_tid
3646
def test_new_orphan_created(self):
3647
wt = self.make_branch_and_tree('.')
3648
self._set_orphan_policy(wt, 'move')
3649
tt, orphan_tid = self._prepare_orphan(wt)
3652
warnings.append(args[0] % args[1:])
3653
self.overrideAttr(trace, 'warning', warning)
3654
remaining_conflicts = resolve_conflicts(tt)
3655
self.assertEqual(['dir/foo has been orphaned in brz-orphans'],
3657
# Yeah for resolved conflicts !
3658
self.assertLength(0, remaining_conflicts)
3659
# We have a new orphan
3660
self.assertEqual('foo.~1~', tt.final_name(orphan_tid))
3661
self.assertEqual('brz-orphans',
3662
tt.final_name(tt.final_parent(orphan_tid)))
3664
def test_never_orphan(self):
3665
wt = self.make_branch_and_tree('.')
3666
self._set_orphan_policy(wt, 'conflict')
3667
tt, orphan_tid = self._prepare_orphan(wt)
3668
remaining_conflicts = resolve_conflicts(tt)
3669
self.assertLength(1, remaining_conflicts)
3670
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
3671
remaining_conflicts.pop())
3673
def test_orphan_error(self):
3674
def bogus_orphan(tt, orphan_id, parent_id):
3675
raise transform.OrphaningError(tt.final_name(orphan_id),
3676
tt.final_name(parent_id))
3677
transform.orphaning_registry.register('bogus', bogus_orphan,
3678
'Raise an error when orphaning')
3679
wt = self.make_branch_and_tree('.')
3680
self._set_orphan_policy(wt, 'bogus')
3681
tt, orphan_tid = self._prepare_orphan(wt)
3682
remaining_conflicts = resolve_conflicts(tt)
3683
self.assertLength(1, remaining_conflicts)
3684
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
3685
remaining_conflicts.pop())
3687
def test_unknown_orphan_policy(self):
3688
wt = self.make_branch_and_tree('.')
3689
# Set a fictional policy nobody ever implemented
3690
self._set_orphan_policy(wt, 'donttouchmypreciouuus')
3691
tt, orphan_tid = self._prepare_orphan(wt)
3694
warnings.append(args[0] % args[1:])
3695
self.overrideAttr(trace, 'warning', warning)
3696
remaining_conflicts = resolve_conflicts(tt)
3697
# We fallback to the default policy which create a conflict
3698
self.assertLength(1, remaining_conflicts)
3699
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
3700
remaining_conflicts.pop())
3701
self.assertLength(1, warnings)
3702
self.assertStartsWith(warnings[0], 'Value "donttouchmypreciouuus" ')
3705
class TestTransformHooks(tests.TestCaseWithTransport):
3708
super(TestTransformHooks, self).setUp()
3709
self.wt = self.make_branch_and_tree('.')
3712
def get_transform(self):
3713
transform = TreeTransform(self.wt)
3714
self.addCleanup(transform.finalize)
3715
return transform, transform.root
3717
def test_pre_commit_hooks(self):
3719
def record_pre_transform(tree, tt):
3720
calls.append((tree, tt))
3721
MutableTree.hooks.install_named_hook('pre_transform',
3722
record_pre_transform, "Pre transform")
3723
transform, root = self.get_transform()
3724
old_root_id = transform.tree_file_id(root)
3726
self.assertEqual(old_root_id, self.wt.get_root_id())
3727
self.assertEqual([(self.wt, transform)], calls)
3729
def test_post_commit_hooks(self):
3731
def record_post_transform(tree, tt):
3732
calls.append((tree, tt))
3733
MutableTree.hooks.install_named_hook('post_transform',
3734
record_post_transform, "Post transform")
3735
transform, root = self.get_transform()
3736
old_root_id = transform.tree_file_id(root)
3738
self.assertEqual(old_root_id, self.wt.get_root_id())
3739
self.assertEqual([(self.wt, transform)], calls)
3742
class TestLinkTree(tests.TestCaseWithTransport):
3744
_test_needs_features = [HardlinkFeature]
3747
tests.TestCaseWithTransport.setUp(self)
3748
self.parent_tree = self.make_branch_and_tree('parent')
3749
self.parent_tree.lock_write()
3750
self.addCleanup(self.parent_tree.unlock)
3751
self.build_tree_contents([('parent/foo', b'bar')])
3752
self.parent_tree.add('foo')
3753
self.parent_tree.commit('added foo')
3754
child_controldir = self.parent_tree.controldir.sprout('child')
3755
self.child_tree = child_controldir.open_workingtree()
3757
def hardlinked(self):
3758
parent_stat = os.lstat(self.parent_tree.abspath('foo'))
3759
child_stat = os.lstat(self.child_tree.abspath('foo'))
3760
return parent_stat.st_ino == child_stat.st_ino
3762
def test_link_fails_if_modified(self):
3763
"""If the file to be linked has modified text, don't link."""
3764
self.build_tree_contents([('child/foo', b'baz')])
3765
transform.link_tree(self.child_tree, self.parent_tree)
3766
self.assertFalse(self.hardlinked())
3768
def test_link_fails_if_execute_bit_changed(self):
3769
"""If the file to be linked has modified execute bit, don't link."""
3770
tt = TreeTransform(self.child_tree)
3772
trans_id = tt.trans_id_tree_path('foo')
3773
tt.set_executability(True, trans_id)
3777
transform.link_tree(self.child_tree, self.parent_tree)
3778
self.assertFalse(self.hardlinked())
3780
def test_link_succeeds_if_unmodified(self):
3781
"""If the file to be linked is unmodified, link"""
3782
transform.link_tree(self.child_tree, self.parent_tree)
3783
self.assertTrue(self.hardlinked())