/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to breezy/tests/test_transform.py

  • Committer: Jelmer Vernooij
  • Date: 2020-07-05 12:50:01 UTC
  • mfrom: (7490.40.46 work)
  • mto: (7490.40.48 work)
  • mto: This revision was merged to the branch mainline in revision 7519.
  • Revision ID: jelmer@jelmer.uk-20200705125001-7s3vo0p55szbbws7
Merge lp:brz/3.1.

Show diffs side-by-side

added added

removed removed

Lines of Context:
94
94
    )
95
95
 
96
96
 
97
 
class TestTreeTransform(tests.TestCaseWithTransport):
98
 
 
99
 
    def setUp(self):
100
 
        super(TestTreeTransform, self).setUp()
101
 
        self.wt = self.make_branch_and_tree('.', format='development-subtree')
102
 
        os.chdir('..')
103
 
 
104
 
    def transform(self):
105
 
        transform = TreeTransform(self.wt)
106
 
        self.addCleanup(transform.finalize)
107
 
        return transform, transform.root
108
 
 
109
 
    def transform_for_sha1_test(self):
110
 
        trans, root = self.transform()
111
 
        self.wt.lock_tree_write()
112
 
        self.addCleanup(self.wt.unlock)
113
 
        contents = [b'just some content\n']
114
 
        sha1 = osutils.sha_strings(contents)
115
 
        # Roll back the clock
116
 
        trans._creation_mtime = time.time() - 20.0
117
 
        return trans, root, contents, sha1
118
 
 
119
 
    def test_existing_limbo(self):
120
 
        transform, root = self.transform()
121
 
        limbo_name = transform._limbodir
122
 
        deletion_path = transform._deletiondir
123
 
        os.mkdir(pathjoin(limbo_name, 'hehe'))
124
 
        self.assertRaises(ImmortalLimbo, transform.apply)
125
 
        self.assertRaises(LockError, self.wt.unlock)
126
 
        self.assertRaises(ExistingLimbo, self.transform)
127
 
        self.assertRaises(LockError, self.wt.unlock)
128
 
        os.rmdir(pathjoin(limbo_name, 'hehe'))
129
 
        os.rmdir(limbo_name)
130
 
        os.rmdir(deletion_path)
131
 
        transform, root = self.transform()
132
 
        transform.apply()
133
 
 
134
 
    def test_existing_pending_deletion(self):
135
 
        transform, root = self.transform()
136
 
        deletion_path = self._limbodir = urlutils.local_path_from_url(
137
 
            transform._tree._transport.abspath('pending-deletion'))
138
 
        os.mkdir(pathjoin(deletion_path, 'blocking-directory'))
139
 
        self.assertRaises(ImmortalPendingDeletion, transform.apply)
140
 
        self.assertRaises(LockError, self.wt.unlock)
141
 
        self.assertRaises(ExistingPendingDeletion, self.transform)
142
 
 
143
 
    def test_build(self):
144
 
        transform, root = self.transform()
145
 
        self.wt.lock_tree_write()
146
 
        self.addCleanup(self.wt.unlock)
147
 
        self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
148
 
        imaginary_id = transform.trans_id_tree_path('imaginary')
149
 
        imaginary_id2 = transform.trans_id_tree_path('imaginary/')
150
 
        self.assertEqual(imaginary_id, imaginary_id2)
151
 
        self.assertEqual(root, transform.get_tree_parent(imaginary_id))
152
 
        self.assertEqual('directory', transform.final_kind(root))
153
 
        self.assertEqual(self.wt.path2id(''), transform.final_file_id(root))
154
 
        trans_id = transform.create_path('name', root)
155
 
        self.assertIs(transform.final_file_id(trans_id), None)
156
 
        self.assertIs(None, transform.final_kind(trans_id))
157
 
        transform.create_file([b'contents'], trans_id)
158
 
        transform.set_executability(True, trans_id)
159
 
        transform.version_file(trans_id, file_id=b'my_pretties')
160
 
        self.assertRaises(DuplicateKey, transform.version_file,
161
 
                          trans_id, file_id=b'my_pretties')
162
 
        self.assertEqual(transform.final_file_id(trans_id), b'my_pretties')
163
 
        self.assertEqual(transform.final_parent(trans_id), root)
164
 
        self.assertIs(transform.final_parent(root), ROOT_PARENT)
165
 
        self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
166
 
        oz_id = transform.create_path('oz', root)
167
 
        transform.create_directory(oz_id)
168
 
        transform.version_file(oz_id, file_id=b'ozzie')
169
 
        trans_id2 = transform.create_path('name2', root)
170
 
        transform.create_file([b'contents'], trans_id2)
171
 
        transform.set_executability(False, trans_id2)
172
 
        transform.version_file(trans_id2, file_id=b'my_pretties2')
173
 
        modified_paths = transform.apply().modified_paths
174
 
        with self.wt.get_file('name') as f:
175
 
            self.assertEqual(b'contents', f.read())
176
 
        self.assertEqual(self.wt.path2id('name'), b'my_pretties')
177
 
        self.assertIs(self.wt.is_executable('name'), True)
178
 
        self.assertIs(self.wt.is_executable('name2'), False)
179
 
        self.assertEqual('directory', file_kind(self.wt.abspath('oz')))
180
 
        self.assertEqual(len(modified_paths), 3)
181
 
        tree_mod_paths = [self.wt.abspath(self.wt.id2path(f)) for f in
182
 
                          (b'ozzie', b'my_pretties', b'my_pretties2')]
183
 
        self.assertSubset(tree_mod_paths, modified_paths)
184
 
        # is it safe to finalize repeatedly?
185
 
        transform.finalize()
186
 
        transform.finalize()
187
 
 
188
 
    def test_apply_informs_tree_of_observed_sha1(self):
189
 
        trans, root, contents, sha1 = self.transform_for_sha1_test()
190
 
        trans_id = trans.new_file('file1', root, contents, file_id=b'file1-id',
191
 
                                  sha1=sha1)
192
 
        calls = []
193
 
        orig = self.wt._observed_sha1
194
 
 
195
 
        def _observed_sha1(*args):
196
 
            calls.append(args)
197
 
            orig(*args)
198
 
        self.wt._observed_sha1 = _observed_sha1
199
 
        trans.apply()
200
 
        self.assertEqual([('file1', trans._observed_sha1s[trans_id])],
201
 
                         calls)
202
 
 
203
 
    def test_create_file_caches_sha1(self):
204
 
        trans, root, contents, sha1 = self.transform_for_sha1_test()
205
 
        trans_id = trans.create_path('file1', root)
206
 
        trans.create_file(contents, trans_id, sha1=sha1)
207
 
        st_val = osutils.lstat(trans._limbo_name(trans_id))
208
 
        o_sha1, o_st_val = trans._observed_sha1s[trans_id]
209
 
        self.assertEqual(o_sha1, sha1)
210
 
        self.assertEqualStat(o_st_val, st_val)
211
 
 
212
 
    def test__apply_insertions_updates_sha1(self):
213
 
        trans, root, contents, sha1 = self.transform_for_sha1_test()
214
 
        trans_id = trans.create_path('file1', root)
215
 
        trans.create_file(contents, trans_id, sha1=sha1)
216
 
        st_val = osutils.lstat(trans._limbo_name(trans_id))
217
 
        o_sha1, o_st_val = trans._observed_sha1s[trans_id]
218
 
        self.assertEqual(o_sha1, sha1)
219
 
        self.assertEqualStat(o_st_val, st_val)
220
 
        creation_mtime = trans._creation_mtime + 10.0
221
 
        # We fake a time difference from when the file was created until now it
222
 
        # is being renamed by using os.utime. Note that the change we actually
223
 
        # want to see is the real ctime change from 'os.rename()', but as long
224
 
        # as we observe a new stat value, we should be fine.
225
 
        os.utime(trans._limbo_name(trans_id), (creation_mtime, creation_mtime))
226
 
        trans.apply()
227
 
        new_st_val = osutils.lstat(self.wt.abspath('file1'))
228
 
        o_sha1, o_st_val = trans._observed_sha1s[trans_id]
229
 
        self.assertEqual(o_sha1, sha1)
230
 
        self.assertEqualStat(o_st_val, new_st_val)
231
 
        self.assertNotEqual(st_val.st_mtime, new_st_val.st_mtime)
232
 
 
233
 
    def test_new_file_caches_sha1(self):
234
 
        trans, root, contents, sha1 = self.transform_for_sha1_test()
235
 
        trans_id = trans.new_file('file1', root, contents, file_id=b'file1-id',
236
 
                                  sha1=sha1)
237
 
        st_val = osutils.lstat(trans._limbo_name(trans_id))
238
 
        o_sha1, o_st_val = trans._observed_sha1s[trans_id]
239
 
        self.assertEqual(o_sha1, sha1)
240
 
        self.assertEqualStat(o_st_val, st_val)
241
 
 
242
 
    def test_cancel_creation_removes_observed_sha1(self):
243
 
        trans, root, contents, sha1 = self.transform_for_sha1_test()
244
 
        trans_id = trans.new_file('file1', root, contents, file_id=b'file1-id',
245
 
                                  sha1=sha1)
246
 
        self.assertTrue(trans_id in trans._observed_sha1s)
247
 
        trans.cancel_creation(trans_id)
248
 
        self.assertFalse(trans_id in trans._observed_sha1s)
249
 
 
250
 
    def test_create_files_same_timestamp(self):
251
 
        transform, root = self.transform()
252
 
        self.wt.lock_tree_write()
253
 
        self.addCleanup(self.wt.unlock)
254
 
        # Roll back the clock, so that we know everything is being set to the
255
 
        # exact time
256
 
        transform._creation_mtime = creation_mtime = time.time() - 20.0
257
 
        transform.create_file([b'content-one'],
258
 
                              transform.create_path('one', root))
259
 
        time.sleep(1)  # *ugly*
260
 
        transform.create_file([b'content-two'],
261
 
                              transform.create_path('two', root))
262
 
        transform.apply()
263
 
        fo, st1 = self.wt.get_file_with_stat('one', filtered=False)
264
 
        fo.close()
265
 
        fo, st2 = self.wt.get_file_with_stat('two', filtered=False)
266
 
        fo.close()
267
 
        # We only guarantee 2s resolution
268
 
        self.assertTrue(
269
 
            abs(creation_mtime - st1.st_mtime) < 2.0,
270
 
            "%s != %s within 2 seconds" % (creation_mtime, st1.st_mtime))
271
 
        # But if we have more than that, all files should get the same result
272
 
        self.assertEqual(st1.st_mtime, st2.st_mtime)
273
 
 
274
 
    def test_change_root_id(self):
275
 
        transform, root = self.transform()
276
 
        self.assertNotEqual(b'new-root-id', self.wt.path2id(''))
277
 
        transform.new_directory('', ROOT_PARENT, b'new-root-id')
278
 
        transform.delete_contents(root)
279
 
        transform.unversion_file(root)
280
 
        transform.fixup_new_roots()
281
 
        transform.apply()
282
 
        self.assertEqual(b'new-root-id', self.wt.path2id(''))
283
 
 
284
 
    def test_change_root_id_add_files(self):
285
 
        transform, root = self.transform()
286
 
        self.assertNotEqual(b'new-root-id', self.wt.path2id(''))
287
 
        new_trans_id = transform.new_directory('', ROOT_PARENT, b'new-root-id')
288
 
        transform.new_file('file', new_trans_id, [b'new-contents\n'],
289
 
                           b'new-file-id')
290
 
        transform.delete_contents(root)
291
 
        transform.unversion_file(root)
292
 
        transform.fixup_new_roots()
293
 
        transform.apply()
294
 
        self.assertEqual(b'new-root-id', self.wt.path2id(''))
295
 
        self.assertEqual(b'new-file-id', self.wt.path2id('file'))
296
 
        self.assertFileEqual(b'new-contents\n', self.wt.abspath('file'))
297
 
 
298
 
    def test_add_two_roots(self):
299
 
        transform, root = self.transform()
300
 
        transform.new_directory('', ROOT_PARENT, b'new-root-id')
301
 
        transform.new_directory('', ROOT_PARENT, b'alt-root-id')
302
 
        self.assertRaises(ValueError, transform.fixup_new_roots)
303
 
 
304
 
    def test_retain_existing_root(self):
305
 
        tt, root = self.transform()
306
 
        with tt:
307
 
            tt.new_directory('', ROOT_PARENT, b'new-root-id')
308
 
            tt.fixup_new_roots()
309
 
            self.assertNotEqual(b'new-root-id', tt.final_file_id(tt.root))
310
 
 
311
 
    def test_retain_existing_root_added_file(self):
312
 
        tt, root = self.transform()
313
 
        new_trans_id = tt.new_directory('', ROOT_PARENT, b'new-root-id')
314
 
        child = tt.new_directory('child', new_trans_id, b'child-id')
315
 
        tt.fixup_new_roots()
316
 
        self.assertEqual(tt.root, tt.final_parent(child))
317
 
 
318
 
    def test_add_unversioned_root(self):
319
 
        transform, root = self.transform()
320
 
        transform.new_directory('', ROOT_PARENT, None)
321
 
        transform.delete_contents(transform.root)
322
 
        transform.fixup_new_roots()
323
 
        self.assertNotIn(transform.root, transform._new_id)
324
 
 
325
 
    def test_remove_root_fixup(self):
326
 
        transform, root = self.transform()
327
 
        old_root_id = self.wt.path2id('')
328
 
        self.assertNotEqual(b'new-root-id', old_root_id)
329
 
        transform.delete_contents(root)
330
 
        transform.unversion_file(root)
331
 
        transform.fixup_new_roots()
332
 
        transform.apply()
333
 
        self.assertEqual(old_root_id, self.wt.path2id(''))
334
 
 
335
 
        transform, root = self.transform()
336
 
        transform.new_directory('', ROOT_PARENT, b'new-root-id')
337
 
        transform.new_directory('', ROOT_PARENT, b'alt-root-id')
338
 
        self.assertRaises(ValueError, transform.fixup_new_roots)
339
 
 
340
 
    def test_fixup_new_roots_permits_empty_tree(self):
341
 
        transform, root = self.transform()
342
 
        transform.delete_contents(root)
343
 
        transform.unversion_file(root)
344
 
        transform.fixup_new_roots()
345
 
        self.assertIs(None, transform.final_kind(root))
346
 
        self.assertIs(None, transform.final_file_id(root))
347
 
 
348
 
    def test_apply_retains_root_directory(self):
349
 
        # Do not attempt to delete the physical root directory, because that
350
 
        # is impossible.
351
 
        transform, root = self.transform()
352
 
        with transform:
353
 
            transform.delete_contents(root)
354
 
            e = self.assertRaises(AssertionError, self.assertRaises,
355
 
                                  TransformRenameFailed,
356
 
                                  transform.apply)
357
 
        self.assertContainsRe('TransformRenameFailed not raised', str(e))
358
 
 
359
 
    def test_apply_retains_file_id(self):
360
 
        transform, root = self.transform()
361
 
        old_root_id = transform.tree_file_id(root)
362
 
        transform.unversion_file(root)
363
 
        transform.apply()
364
 
        self.assertEqual(old_root_id, self.wt.path2id(''))
365
 
 
366
 
    def test_hardlink(self):
367
 
        self.requireFeature(HardlinkFeature)
368
 
        transform, root = self.transform()
369
 
        transform.new_file('file1', root, [b'contents'])
370
 
        transform.apply()
371
 
        target = self.make_branch_and_tree('target')
372
 
        tartransform = TreeTransform(target)
373
 
        trans_id = tartransform.create_path('file1', tartransform.root)
374
 
        tartransform.create_hardlink(self.wt.abspath('file1'), trans_id)
375
 
        tartransform.apply()
376
 
        self.assertPathExists('target/file1')
377
 
        source_stat = os.stat(self.wt.abspath('file1'))
378
 
        target_stat = os.stat('target/file1')
379
 
        self.assertEqual(source_stat, target_stat)
380
 
 
381
 
    def test_convenience(self):
382
 
        transform, root = self.transform()
383
 
        self.wt.lock_tree_write()
384
 
        self.addCleanup(self.wt.unlock)
385
 
        transform.new_file('name', root, [b'contents'], b'my_pretties', True)
386
 
        oz = transform.new_directory('oz', root, b'oz-id')
387
 
        dorothy = transform.new_directory('dorothy', oz, b'dorothy-id')
388
 
        transform.new_file('toto', dorothy, [b'toto-contents'], b'toto-id',
389
 
                           False)
390
 
 
391
 
        self.assertEqual(len(transform.find_conflicts()), 0)
392
 
        transform.apply()
393
 
        self.assertRaises(ReusingTransform, transform.find_conflicts)
394
 
        with open(self.wt.abspath('name'), 'r') as f:
395
 
            self.assertEqual('contents', f.read())
396
 
        self.assertEqual(self.wt.path2id('name'), b'my_pretties')
397
 
        self.assertIs(self.wt.is_executable('name'), True)
398
 
        self.assertEqual(self.wt.path2id('oz'), b'oz-id')
399
 
        self.assertEqual(self.wt.path2id('oz/dorothy'), b'dorothy-id')
400
 
        self.assertEqual(self.wt.path2id('oz/dorothy/toto'), b'toto-id')
401
 
 
402
 
        self.assertEqual(b'toto-contents',
403
 
                         self.wt.get_file('oz/dorothy/toto').read())
404
 
        self.assertIs(self.wt.is_executable('oz/dorothy/toto'), False)
405
 
 
406
 
    def test_tree_reference(self):
407
 
        transform, root = self.transform()
408
 
        tree = transform._tree
409
 
        trans_id = transform.new_directory('reference', root, b'subtree-id')
410
 
        transform.set_tree_reference(b'subtree-revision', trans_id)
411
 
        transform.apply()
412
 
        tree.lock_read()
413
 
        self.addCleanup(tree.unlock)
414
 
        self.assertEqual(
415
 
            b'subtree-revision',
416
 
            tree.root_inventory.get_entry(b'subtree-id').reference_revision)
417
 
 
418
 
    def test_conflicts(self):
419
 
        transform, root = self.transform()
420
 
        trans_id = transform.new_file('name', root, [b'contents'],
421
 
                                      b'my_pretties')
422
 
        self.assertEqual(len(transform.find_conflicts()), 0)
423
 
        trans_id2 = transform.new_file('name', root, [b'Crontents'], b'toto')
424
 
        self.assertEqual(transform.find_conflicts(),
425
 
                         [('duplicate', trans_id, trans_id2, 'name')])
426
 
        self.assertRaises(MalformedTransform, transform.apply)
427
 
        transform.adjust_path('name', trans_id, trans_id2)
428
 
        self.assertEqual(transform.find_conflicts(),
429
 
                         [('non-directory parent', trans_id)])
430
 
        tinman_id = transform.trans_id_tree_path('tinman')
431
 
        transform.adjust_path('name', tinman_id, trans_id2)
432
 
        self.assertEqual(transform.find_conflicts(),
433
 
                         [('unversioned parent', tinman_id),
434
 
                          ('missing parent', tinman_id)])
435
 
        lion_id = transform.create_path('lion', root)
436
 
        self.assertEqual(transform.find_conflicts(),
437
 
                         [('unversioned parent', tinman_id),
438
 
                          ('missing parent', tinman_id)])
439
 
        transform.adjust_path('name', lion_id, trans_id2)
440
 
        self.assertEqual(transform.find_conflicts(),
441
 
                         [('unversioned parent', lion_id),
442
 
                          ('missing parent', lion_id)])
443
 
        transform.version_file(lion_id, file_id=b"Courage")
444
 
        self.assertEqual(transform.find_conflicts(),
445
 
                         [('missing parent', lion_id),
446
 
                          ('versioning no contents', lion_id)])
447
 
        transform.adjust_path('name2', root, trans_id2)
448
 
        self.assertEqual(transform.find_conflicts(),
449
 
                         [('versioning no contents', lion_id)])
450
 
        transform.create_file([b'Contents, okay?'], lion_id)
451
 
        transform.adjust_path('name2', trans_id2, trans_id2)
452
 
        self.assertEqual(transform.find_conflicts(),
453
 
                         [('parent loop', trans_id2),
454
 
                          ('non-directory parent', trans_id2)])
455
 
        transform.adjust_path('name2', root, trans_id2)
456
 
        oz_id = transform.new_directory('oz', root)
457
 
        transform.set_executability(True, oz_id)
458
 
        self.assertEqual(transform.find_conflicts(),
459
 
                         [('unversioned executability', oz_id)])
460
 
        transform.version_file(oz_id, file_id=b'oz-id')
461
 
        self.assertEqual(transform.find_conflicts(),
462
 
                         [('non-file executability', oz_id)])
463
 
        transform.set_executability(None, oz_id)
464
 
        tip_id = transform.new_file('tip', oz_id, [b'ozma'], b'tip-id')
465
 
        transform.apply()
466
 
        self.assertEqual(self.wt.path2id('name'), b'my_pretties')
467
 
        with open(self.wt.abspath('name'), 'rb') as f:
468
 
            self.assertEqual(b'contents', f.read())
469
 
        transform2, root = self.transform()
470
 
        oz_id = transform2.trans_id_tree_path('oz')
471
 
        newtip = transform2.new_file('tip', oz_id, [b'other'], b'tip-id')
472
 
        result = transform2.find_conflicts()
473
 
        fp = FinalPaths(transform2)
474
 
        self.assertTrue('oz/tip' in transform2._tree_path_ids)
475
 
        self.assertEqual(fp.get_path(newtip), pathjoin('oz', 'tip'))
476
 
        self.assertEqual(len(result), 2)
477
 
        self.assertEqual((result[0][0], result[0][1]),
478
 
                         ('duplicate', newtip))
479
 
        self.assertEqual((result[1][0], result[1][2]),
480
 
                         ('duplicate id', newtip))
481
 
        transform2.finalize()
482
 
        transform3 = TreeTransform(self.wt)
483
 
        self.addCleanup(transform3.finalize)
484
 
        oz_id = transform3.trans_id_tree_path('oz')
485
 
        transform3.delete_contents(oz_id)
486
 
        self.assertEqual(transform3.find_conflicts(),
487
 
                         [('missing parent', oz_id)])
488
 
        root_id = transform3.root
489
 
        tip_id = transform3.trans_id_tree_path('oz/tip')
490
 
        transform3.adjust_path('tip', root_id, tip_id)
491
 
        transform3.apply()
492
 
 
493
 
    def test_conflict_on_case_insensitive(self):
494
 
        tree = self.make_branch_and_tree('tree')
495
 
        # Don't try this at home, kids!
496
 
        # Force the tree to report that it is case sensitive, for conflict
497
 
        # resolution tests
498
 
        tree.case_sensitive = True
499
 
        transform = TreeTransform(tree)
500
 
        self.addCleanup(transform.finalize)
501
 
        transform.new_file('file', transform.root, [b'content'])
502
 
        transform.new_file('FiLe', transform.root, [b'content'])
503
 
        result = transform.find_conflicts()
504
 
        self.assertEqual([], result)
505
 
        transform.finalize()
506
 
        # Force the tree to report that it is case insensitive, for conflict
507
 
        # generation tests
508
 
        tree.case_sensitive = False
509
 
        transform = TreeTransform(tree)
510
 
        self.addCleanup(transform.finalize)
511
 
        transform.new_file('file', transform.root, [b'content'])
512
 
        transform.new_file('FiLe', transform.root, [b'content'])
513
 
        result = transform.find_conflicts()
514
 
        self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
515
 
 
516
 
    def test_conflict_on_case_insensitive_existing(self):
517
 
        tree = self.make_branch_and_tree('tree')
518
 
        self.build_tree(['tree/FiLe'])
519
 
        # Don't try this at home, kids!
520
 
        # Force the tree to report that it is case sensitive, for conflict
521
 
        # resolution tests
522
 
        tree.case_sensitive = True
523
 
        transform = TreeTransform(tree)
524
 
        self.addCleanup(transform.finalize)
525
 
        transform.new_file('file', transform.root, [b'content'])
526
 
        result = transform.find_conflicts()
527
 
        self.assertEqual([], result)
528
 
        transform.finalize()
529
 
        # Force the tree to report that it is case insensitive, for conflict
530
 
        # generation tests
531
 
        tree.case_sensitive = False
532
 
        transform = TreeTransform(tree)
533
 
        self.addCleanup(transform.finalize)
534
 
        transform.new_file('file', transform.root, [b'content'])
535
 
        result = transform.find_conflicts()
536
 
        self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
537
 
 
538
 
    def test_resolve_case_insensitive_conflict(self):
539
 
        tree = self.make_branch_and_tree('tree')
540
 
        # Don't try this at home, kids!
541
 
        # Force the tree to report that it is case insensitive, for conflict
542
 
        # resolution tests
543
 
        tree.case_sensitive = False
544
 
        transform = TreeTransform(tree)
545
 
        self.addCleanup(transform.finalize)
546
 
        transform.new_file('file', transform.root, [b'content'])
547
 
        transform.new_file('FiLe', transform.root, [b'content'])
548
 
        resolve_conflicts(transform)
549
 
        transform.apply()
550
 
        self.assertPathExists('tree/file')
551
 
        self.assertPathExists('tree/FiLe.moved')
552
 
 
553
 
    def test_resolve_checkout_case_conflict(self):
554
 
        tree = self.make_branch_and_tree('tree')
555
 
        # Don't try this at home, kids!
556
 
        # Force the tree to report that it is case insensitive, for conflict
557
 
        # resolution tests
558
 
        tree.case_sensitive = False
559
 
        transform = TreeTransform(tree)
560
 
        self.addCleanup(transform.finalize)
561
 
        transform.new_file('file', transform.root, [b'content'])
562
 
        transform.new_file('FiLe', transform.root, [b'content'])
563
 
        resolve_conflicts(transform,
564
 
                          pass_func=lambda t, c: resolve_checkout(t, c, []))
565
 
        transform.apply()
566
 
        self.assertPathExists('tree/file')
567
 
        self.assertPathExists('tree/FiLe.moved')
568
 
 
569
 
    def test_apply_case_conflict(self):
570
 
        """Ensure that a transform with case conflicts can always be applied"""
571
 
        tree = self.make_branch_and_tree('tree')
572
 
        transform = TreeTransform(tree)
573
 
        self.addCleanup(transform.finalize)
574
 
        transform.new_file('file', transform.root, [b'content'])
575
 
        transform.new_file('FiLe', transform.root, [b'content'])
576
 
        dir = transform.new_directory('dir', transform.root)
577
 
        transform.new_file('dirfile', dir, [b'content'])
578
 
        transform.new_file('dirFiLe', dir, [b'content'])
579
 
        resolve_conflicts(transform)
580
 
        transform.apply()
581
 
        self.assertPathExists('tree/file')
582
 
        if not os.path.exists('tree/FiLe.moved'):
583
 
            self.assertPathExists('tree/FiLe')
584
 
        self.assertPathExists('tree/dir/dirfile')
585
 
        if not os.path.exists('tree/dir/dirFiLe.moved'):
586
 
            self.assertPathExists('tree/dir/dirFiLe')
587
 
 
588
 
    def test_case_insensitive_limbo(self):
589
 
        tree = self.make_branch_and_tree('tree')
590
 
        # Don't try this at home, kids!
591
 
        # Force the tree to report that it is case insensitive
592
 
        tree.case_sensitive = False
593
 
        transform = TreeTransform(tree)
594
 
        self.addCleanup(transform.finalize)
595
 
        dir = transform.new_directory('dir', transform.root)
596
 
        first = transform.new_file('file', dir, [b'content'])
597
 
        second = transform.new_file('FiLe', dir, [b'content'])
598
 
        self.assertContainsRe(transform._limbo_name(first), 'new-1/file')
599
 
        self.assertNotContainsRe(transform._limbo_name(second), 'new-1/FiLe')
600
 
 
601
 
    def test_adjust_path_updates_child_limbo_names(self):
602
 
        tree = self.make_branch_and_tree('tree')
603
 
        transform = TreeTransform(tree)
604
 
        self.addCleanup(transform.finalize)
605
 
        foo_id = transform.new_directory('foo', transform.root)
606
 
        bar_id = transform.new_directory('bar', foo_id)
607
 
        baz_id = transform.new_directory('baz', bar_id)
608
 
        qux_id = transform.new_directory('qux', baz_id)
609
 
        transform.adjust_path('quxx', foo_id, bar_id)
610
 
        self.assertStartsWith(transform._limbo_name(qux_id),
611
 
                              transform._limbo_name(bar_id))
612
 
 
613
 
    def test_add_del(self):
614
 
        start, root = self.transform()
615
 
        start.new_directory('a', root, b'a')
616
 
        start.apply()
617
 
        transform, root = self.transform()
618
 
        transform.delete_versioned(transform.trans_id_tree_path('a'))
619
 
        transform.new_directory('a', root, b'a')
620
 
        transform.apply()
621
 
 
622
 
    def test_unversioning(self):
623
 
        create_tree, root = self.transform()
624
 
        parent_id = create_tree.new_directory('parent', root, b'parent-id')
625
 
        create_tree.new_file('child', parent_id, [b'child'], b'child-id')
626
 
        create_tree.apply()
627
 
        unversion = TreeTransform(self.wt)
628
 
        self.addCleanup(unversion.finalize)
629
 
        parent = unversion.trans_id_tree_path('parent')
630
 
        unversion.unversion_file(parent)
631
 
        self.assertEqual(unversion.find_conflicts(),
632
 
                         [('unversioned parent', parent_id)])
633
 
        file_id = unversion.trans_id_tree_path('parent/child')
634
 
        unversion.unversion_file(file_id)
635
 
        unversion.apply()
636
 
 
637
 
    def test_name_invariants(self):
638
 
        create_tree, root = self.transform()
639
 
        # prepare tree
640
 
        root = create_tree.root
641
 
        create_tree.new_file('name1', root, [b'hello1'], b'name1')
642
 
        create_tree.new_file('name2', root, [b'hello2'], b'name2')
643
 
        ddir = create_tree.new_directory('dying_directory', root, b'ddir')
644
 
        create_tree.new_file('dying_file', ddir, [b'goodbye1'], b'dfile')
645
 
        create_tree.new_file('moving_file', ddir, [b'later1'], b'mfile')
646
 
        create_tree.new_file('moving_file2', root, [b'later2'], b'mfile2')
647
 
        create_tree.apply()
648
 
 
649
 
        mangle_tree, root = self.transform()
650
 
        root = mangle_tree.root
651
 
        # swap names
652
 
        name1 = mangle_tree.trans_id_tree_path('name1')
653
 
        name2 = mangle_tree.trans_id_tree_path('name2')
654
 
        mangle_tree.adjust_path('name2', root, name1)
655
 
        mangle_tree.adjust_path('name1', root, name2)
656
 
 
657
 
        # tests for deleting parent directories
658
 
        ddir = mangle_tree.trans_id_tree_path('dying_directory')
659
 
        mangle_tree.delete_contents(ddir)
660
 
        dfile = mangle_tree.trans_id_tree_path('dying_directory/dying_file')
661
 
        mangle_tree.delete_versioned(dfile)
662
 
        mangle_tree.unversion_file(dfile)
663
 
        mfile = mangle_tree.trans_id_tree_path('dying_directory/moving_file')
664
 
        mangle_tree.adjust_path('mfile', root, mfile)
665
 
 
666
 
        # tests for adding parent directories
667
 
        newdir = mangle_tree.new_directory('new_directory', root, b'newdir')
668
 
        mfile2 = mangle_tree.trans_id_tree_path('moving_file2')
669
 
        mangle_tree.adjust_path('mfile2', newdir, mfile2)
670
 
        mangle_tree.new_file('newfile', newdir, [b'hello3'], b'dfile')
671
 
        self.assertEqual(mangle_tree.final_file_id(mfile2), b'mfile2')
672
 
        self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
673
 
        self.assertEqual(mangle_tree.final_file_id(mfile2), b'mfile2')
674
 
        mangle_tree.apply()
675
 
        with open(self.wt.abspath('name1'), 'r') as f:
676
 
            self.assertEqual(f.read(), 'hello2')
677
 
        with open(self.wt.abspath('name2'), 'r') as f:
678
 
            self.assertEqual(f.read(), 'hello1')
679
 
        mfile2_path = self.wt.abspath(pathjoin('new_directory', 'mfile2'))
680
 
        self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
681
 
        with open(mfile2_path, 'r') as f:
682
 
            self.assertEqual(f.read(), 'later2')
683
 
        self.assertEqual(self.wt.id2path(b'mfile2'), 'new_directory/mfile2')
684
 
        self.assertEqual(self.wt.path2id('new_directory/mfile2'), b'mfile2')
685
 
        newfile_path = self.wt.abspath(pathjoin('new_directory', 'newfile'))
686
 
        with open(newfile_path, 'r') as f:
687
 
            self.assertEqual(f.read(), 'hello3')
688
 
        self.assertEqual(self.wt.path2id('dying_directory'), b'ddir')
689
 
        self.assertIs(self.wt.path2id('dying_directory/dying_file'), None)
690
 
        mfile2_path = self.wt.abspath(pathjoin('new_directory', 'mfile2'))
691
 
 
692
 
    def test_both_rename(self):
693
 
        create_tree, root = self.transform()
694
 
        newdir = create_tree.new_directory('selftest', root, b'selftest-id')
695
 
        create_tree.new_file('blackbox.py', newdir, [
696
 
                             b'hello1'], b'blackbox-id')
697
 
        create_tree.apply()
698
 
        mangle_tree, root = self.transform()
699
 
        selftest = mangle_tree.trans_id_tree_path('selftest')
700
 
        blackbox = mangle_tree.trans_id_tree_path('selftest/blackbox.py')
701
 
        mangle_tree.adjust_path('test', root, selftest)
702
 
        mangle_tree.adjust_path('test_too_much', root, selftest)
703
 
        mangle_tree.set_executability(True, blackbox)
704
 
        mangle_tree.apply()
705
 
 
706
 
    def test_both_rename2(self):
707
 
        create_tree, root = self.transform()
708
 
        breezy = create_tree.new_directory('breezy', root, b'breezy-id')
709
 
        tests = create_tree.new_directory('tests', breezy, b'tests-id')
710
 
        blackbox = create_tree.new_directory('blackbox', tests, b'blackbox-id')
711
 
        create_tree.new_file('test_too_much.py', blackbox, [b'hello1'],
712
 
                             b'test_too_much-id')
713
 
        create_tree.apply()
714
 
        mangle_tree, root = self.transform()
715
 
        breezy = mangle_tree.trans_id_tree_path('breezy')
716
 
        tests = mangle_tree.trans_id_tree_path('breezy/tests')
717
 
        test_too_much = mangle_tree.trans_id_tree_path(
718
 
            'breezy/tests/blackbox/test_too_much.py')
719
 
        mangle_tree.adjust_path('selftest', breezy, tests)
720
 
        mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
721
 
        mangle_tree.set_executability(True, test_too_much)
722
 
        mangle_tree.apply()
723
 
 
724
 
    def test_both_rename3(self):
725
 
        create_tree, root = self.transform()
726
 
        tests = create_tree.new_directory('tests', root, b'tests-id')
727
 
        create_tree.new_file('test_too_much.py', tests, [b'hello1'],
728
 
                             b'test_too_much-id')
729
 
        create_tree.apply()
730
 
        mangle_tree, root = self.transform()
731
 
        tests = mangle_tree.trans_id_tree_path('tests')
732
 
        test_too_much = mangle_tree.trans_id_tree_path(
733
 
            'tests/test_too_much.py')
734
 
        mangle_tree.adjust_path('selftest', root, tests)
735
 
        mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
736
 
        mangle_tree.set_executability(True, test_too_much)
737
 
        mangle_tree.apply()
738
 
 
739
 
    def test_move_dangling_ie(self):
740
 
        create_tree, root = self.transform()
741
 
        # prepare tree
742
 
        root = create_tree.root
743
 
        create_tree.new_file('name1', root, [b'hello1'], b'name1')
744
 
        create_tree.apply()
745
 
        delete_contents, root = self.transform()
746
 
        file = delete_contents.trans_id_tree_path('name1')
747
 
        delete_contents.delete_contents(file)
748
 
        delete_contents.apply()
749
 
        move_id, root = self.transform()
750
 
        name1 = move_id.trans_id_tree_path('name1')
751
 
        newdir = move_id.new_directory('dir', root, b'newdir')
752
 
        move_id.adjust_path('name2', newdir, name1)
753
 
        move_id.apply()
754
 
 
755
 
    def test_replace_dangling_ie(self):
756
 
        create_tree, root = self.transform()
757
 
        # prepare tree
758
 
        root = create_tree.root
759
 
        create_tree.new_file('name1', root, [b'hello1'], b'name1')
760
 
        create_tree.apply()
761
 
        delete_contents = TreeTransform(self.wt)
762
 
        self.addCleanup(delete_contents.finalize)
763
 
        file = delete_contents.trans_id_tree_path('name1')
764
 
        delete_contents.delete_contents(file)
765
 
        delete_contents.apply()
766
 
        delete_contents.finalize()
767
 
        replace = TreeTransform(self.wt)
768
 
        self.addCleanup(replace.finalize)
769
 
        name2 = replace.new_file('name2', root, [b'hello2'], b'name1')
770
 
        conflicts = replace.find_conflicts()
771
 
        name1 = replace.trans_id_tree_path('name1')
772
 
        self.assertEqual(conflicts, [('duplicate id', name1, name2)])
773
 
        resolve_conflicts(replace)
774
 
        replace.apply()
775
 
 
776
 
    def _test_symlinks(self, link_name1, link_target1,
777
 
                       link_name2, link_target2):
778
 
 
779
 
        def ozpath(p):
780
 
            return 'oz/' + p
781
 
 
782
 
        self.requireFeature(SymlinkFeature)
783
 
        transform, root = self.transform()
784
 
        oz_id = transform.new_directory('oz', root, b'oz-id')
785
 
        transform.new_symlink(link_name1, oz_id, link_target1, b'wizard-id')
786
 
        wiz_id = transform.create_path(link_name2, oz_id)
787
 
        transform.create_symlink(link_target2, wiz_id)
788
 
        transform.version_file(wiz_id, file_id=b'wiz-id2')
789
 
        transform.set_executability(True, wiz_id)
790
 
        self.assertEqual(transform.find_conflicts(),
791
 
                         [('non-file executability', wiz_id)])
792
 
        transform.set_executability(None, wiz_id)
793
 
        transform.apply()
794
 
        self.assertEqual(self.wt.path2id(ozpath(link_name1)), b'wizard-id')
795
 
        self.assertEqual('symlink',
796
 
                         file_kind(self.wt.abspath(ozpath(link_name1))))
797
 
        self.assertEqual(link_target2,
798
 
                         osutils.readlink(self.wt.abspath(ozpath(link_name2))))
799
 
        self.assertEqual(link_target1,
800
 
                         osutils.readlink(self.wt.abspath(ozpath(link_name1))))
801
 
 
802
 
    def test_symlinks(self):
803
 
        self._test_symlinks('wizard', 'wizard-target',
804
 
                            'wizard2', 'behind_curtain')
805
 
 
806
 
    def test_symlinks_unicode(self):
807
 
        self.requireFeature(features.UnicodeFilenameFeature)
808
 
        self._test_symlinks(u'\N{Euro Sign}wizard',
809
 
                            u'wizard-targ\N{Euro Sign}t',
810
 
                            u'\N{Euro Sign}wizard2',
811
 
                            u'b\N{Euro Sign}hind_curtain')
812
 
 
813
 
    def test_unsupported_symlink_no_conflict(self):
814
 
        def tt_helper():
815
 
            wt = self.make_branch_and_tree('.')
816
 
            tt = TreeTransform(wt)
817
 
            self.addCleanup(tt.finalize)
818
 
            tt.new_symlink('foo', tt.root, 'bar')
819
 
            result = tt.find_conflicts()
820
 
            self.assertEqual([], result)
821
 
        os_symlink = getattr(os, 'symlink', None)
822
 
        os.symlink = None
823
 
        try:
824
 
            tt_helper()
825
 
        finally:
826
 
            if os_symlink:
827
 
                os.symlink = os_symlink
828
 
 
829
 
    def get_conflicted(self):
830
 
        create, root = self.transform()
831
 
        create.new_file('dorothy', root, [b'dorothy'], b'dorothy-id')
832
 
        oz = create.new_directory('oz', root, b'oz-id')
833
 
        create.new_directory('emeraldcity', oz, b'emerald-id')
834
 
        create.apply()
835
 
        conflicts, root = self.transform()
836
 
        # set up duplicate entry, duplicate id
837
 
        new_dorothy = conflicts.new_file('dorothy', root, [b'dorothy'],
838
 
                                         b'dorothy-id')
839
 
        old_dorothy = conflicts.trans_id_tree_path('dorothy')
840
 
        oz = conflicts.trans_id_tree_path('oz')
841
 
        # set up DeletedParent parent conflict
842
 
        conflicts.delete_versioned(oz)
843
 
        emerald = conflicts.trans_id_tree_path('oz/emeraldcity')
844
 
        # set up MissingParent conflict
845
 
        munchkincity = conflicts.trans_id_file_id(b'munchkincity-id')
846
 
        conflicts.adjust_path('munchkincity', root, munchkincity)
847
 
        conflicts.new_directory('auntem', munchkincity, b'auntem-id')
848
 
        # set up parent loop
849
 
        conflicts.adjust_path('emeraldcity', emerald, emerald)
850
 
        return conflicts, emerald, oz, old_dorothy, new_dorothy
851
 
 
852
 
    def test_conflict_resolution(self):
853
 
        conflicts, emerald, oz, old_dorothy, new_dorothy =\
854
 
            self.get_conflicted()
855
 
        resolve_conflicts(conflicts)
856
 
        self.assertEqual(conflicts.final_name(old_dorothy), 'dorothy.moved')
857
 
        self.assertIs(conflicts.final_file_id(old_dorothy), None)
858
 
        self.assertEqual(conflicts.final_name(new_dorothy), 'dorothy')
859
 
        self.assertEqual(conflicts.final_file_id(new_dorothy), b'dorothy-id')
860
 
        self.assertEqual(conflicts.final_parent(emerald), oz)
861
 
        conflicts.apply()
862
 
 
863
 
    def test_cook_conflicts(self):
864
 
        tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
865
 
        raw_conflicts = resolve_conflicts(tt)
866
 
        cooked_conflicts = cook_conflicts(raw_conflicts, tt)
867
 
        duplicate = DuplicateEntry('Moved existing file to', 'dorothy.moved',
868
 
                                   'dorothy', None, b'dorothy-id')
869
 
        self.assertEqual(cooked_conflicts[0], duplicate)
870
 
        duplicate_id = DuplicateID('Unversioned existing file',
871
 
                                   'dorothy.moved', 'dorothy', None,
872
 
                                   b'dorothy-id')
873
 
        self.assertEqual(cooked_conflicts[1], duplicate_id)
874
 
        missing_parent = MissingParent('Created directory', 'munchkincity',
875
 
                                       b'munchkincity-id')
876
 
        deleted_parent = DeletingParent('Not deleting', 'oz', b'oz-id')
877
 
        self.assertEqual(cooked_conflicts[2], missing_parent)
878
 
        unversioned_parent = UnversionedParent('Versioned directory',
879
 
                                               'munchkincity',
880
 
                                               b'munchkincity-id')
881
 
        unversioned_parent2 = UnversionedParent('Versioned directory', 'oz',
882
 
                                                b'oz-id')
883
 
        self.assertEqual(cooked_conflicts[3], unversioned_parent)
884
 
        parent_loop = ParentLoop(
885
 
            'Cancelled move', 'oz/emeraldcity',
886
 
            'oz/emeraldcity', b'emerald-id', b'emerald-id')
887
 
        self.assertEqual(cooked_conflicts[4], deleted_parent)
888
 
        self.assertEqual(cooked_conflicts[5], unversioned_parent2)
889
 
        self.assertEqual(cooked_conflicts[6], parent_loop)
890
 
        self.assertEqual(len(cooked_conflicts), 7)
891
 
        tt.finalize()
892
 
 
893
 
    def test_string_conflicts(self):
894
 
        tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
895
 
        raw_conflicts = resolve_conflicts(tt)
896
 
        cooked_conflicts = cook_conflicts(raw_conflicts, tt)
897
 
        tt.finalize()
898
 
        conflicts_s = [text_type(c) for c in cooked_conflicts]
899
 
        self.assertEqual(len(cooked_conflicts), len(conflicts_s))
900
 
        self.assertEqual(conflicts_s[0], 'Conflict adding file dorothy.  '
901
 
                                         'Moved existing file to '
902
 
                                         'dorothy.moved.')
903
 
        self.assertEqual(conflicts_s[1], 'Conflict adding id to dorothy.  '
904
 
                                         'Unversioned existing file '
905
 
                                         'dorothy.moved.')
906
 
        self.assertEqual(conflicts_s[2], 'Conflict adding files to'
907
 
                                         ' munchkincity.  Created directory.')
908
 
        self.assertEqual(conflicts_s[3], 'Conflict because munchkincity is not'
909
 
                                         ' versioned, but has versioned'
910
 
                                         ' children.  Versioned directory.')
911
 
        self.assertEqualDiff(
912
 
            conflicts_s[4], "Conflict: can't delete oz because it"
913
 
                            " is not empty.  Not deleting.")
914
 
        self.assertEqual(conflicts_s[5], 'Conflict because oz is not'
915
 
                                         ' versioned, but has versioned'
916
 
                                         ' children.  Versioned directory.')
917
 
        self.assertEqual(conflicts_s[6], 'Conflict moving oz/emeraldcity into'
918
 
                                         ' oz/emeraldcity. Cancelled move.')
919
 
 
920
 
    def prepare_wrong_parent_kind(self):
921
 
        tt, root = self.transform()
922
 
        tt.new_file('parent', root, [b'contents'], b'parent-id')
923
 
        tt.apply()
924
 
        tt, root = self.transform()
925
 
        parent_id = tt.trans_id_file_id(b'parent-id')
926
 
        tt.new_file('child,', parent_id, [b'contents2'], b'file-id')
927
 
        return tt
928
 
 
929
 
    def test_find_conflicts_wrong_parent_kind(self):
930
 
        tt = self.prepare_wrong_parent_kind()
931
 
        tt.find_conflicts()
932
 
 
933
 
    def test_resolve_conflicts_wrong_existing_parent_kind(self):
934
 
        tt = self.prepare_wrong_parent_kind()
935
 
        raw_conflicts = resolve_conflicts(tt)
936
 
        self.assertEqual({('non-directory parent', 'Created directory',
937
 
                           'new-3')}, raw_conflicts)
938
 
        cooked_conflicts = cook_conflicts(raw_conflicts, tt)
939
 
        self.assertEqual([NonDirectoryParent('Created directory', 'parent.new',
940
 
                                             b'parent-id')], cooked_conflicts)
941
 
        tt.apply()
942
 
        self.assertFalse(self.wt.is_versioned('parent'))
943
 
        self.assertEqual(b'parent-id', self.wt.path2id('parent.new'))
944
 
 
945
 
    def test_resolve_conflicts_wrong_new_parent_kind(self):
946
 
        tt, root = self.transform()
947
 
        parent_id = tt.new_directory('parent', root, b'parent-id')
948
 
        tt.new_file('child,', parent_id, [b'contents2'], b'file-id')
949
 
        tt.apply()
950
 
        tt, root = self.transform()
951
 
        parent_id = tt.trans_id_file_id(b'parent-id')
952
 
        tt.delete_contents(parent_id)
953
 
        tt.create_file([b'contents'], parent_id)
954
 
        raw_conflicts = resolve_conflicts(tt)
955
 
        self.assertEqual({('non-directory parent', 'Created directory',
956
 
                           'new-3')}, raw_conflicts)
957
 
        tt.apply()
958
 
        self.assertFalse(self.wt.is_versioned('parent'))
959
 
        self.assertEqual(b'parent-id', self.wt.path2id('parent.new'))
960
 
 
961
 
    def test_resolve_conflicts_wrong_parent_kind_unversioned(self):
962
 
        tt, root = self.transform()
963
 
        parent_id = tt.new_directory('parent', root)
964
 
        tt.new_file('child,', parent_id, [b'contents2'])
965
 
        tt.apply()
966
 
        tt, root = self.transform()
967
 
        parent_id = tt.trans_id_tree_path('parent')
968
 
        tt.delete_contents(parent_id)
969
 
        tt.create_file([b'contents'], parent_id)
970
 
        resolve_conflicts(tt)
971
 
        tt.apply()
972
 
        self.assertFalse(self.wt.is_versioned('parent'))
973
 
        self.assertFalse(self.wt.is_versioned('parent.new'))
974
 
 
975
 
    def test_resolve_conflicts_missing_parent(self):
976
 
        wt = self.make_branch_and_tree('.')
977
 
        tt = TreeTransform(wt)
978
 
        self.addCleanup(tt.finalize)
979
 
        parent = tt.trans_id_file_id(b'parent-id')
980
 
        tt.new_file('file', parent, [b'Contents'])
981
 
        raw_conflicts = resolve_conflicts(tt)
982
 
        # Since the directory doesn't exist it's seen as 'missing'.  So
983
 
        # 'resolve_conflicts' create a conflict asking for it to be created.
984
 
        self.assertLength(1, raw_conflicts)
985
 
        self.assertEqual(('missing parent', 'Created directory', 'new-1'),
986
 
                         raw_conflicts.pop())
987
 
        # apply fail since the missing directory doesn't exist
988
 
        self.assertRaises(NoFinalPath, tt.apply)
989
 
 
990
 
    def test_moving_versioned_directories(self):
991
 
        create, root = self.transform()
992
 
        kansas = create.new_directory('kansas', root, b'kansas-id')
993
 
        create.new_directory('house', kansas, b'house-id')
994
 
        create.new_directory('oz', root, b'oz-id')
995
 
        create.apply()
996
 
        cyclone, root = self.transform()
997
 
        oz = cyclone.trans_id_tree_path('oz')
998
 
        house = cyclone.trans_id_tree_path('house')
999
 
        cyclone.adjust_path('house', oz, house)
1000
 
        cyclone.apply()
1001
 
 
1002
 
    def test_moving_root(self):
1003
 
        create, root = self.transform()
1004
 
        fun = create.new_directory('fun', root, b'fun-id')
1005
 
        create.new_directory('sun', root, b'sun-id')
1006
 
        create.new_directory('moon', root, b'moon')
1007
 
        create.apply()
1008
 
        transform, root = self.transform()
1009
 
        transform.adjust_root_path('oldroot', fun)
1010
 
        new_root = transform.trans_id_tree_path('')
1011
 
        transform.version_file(new_root, file_id=b'new-root')
1012
 
        transform.apply()
1013
 
 
1014
 
    def test_renames(self):
1015
 
        create, root = self.transform()
1016
 
        old = create.new_directory('old-parent', root, b'old-id')
1017
 
        intermediate = create.new_directory('intermediate', old, b'im-id')
1018
 
        myfile = create.new_file('myfile', intermediate, [b'myfile-text'],
1019
 
                                 b'myfile-id')
1020
 
        create.apply()
1021
 
        rename, root = self.transform()
1022
 
        old = rename.trans_id_file_id(b'old-id')
1023
 
        rename.adjust_path('new', root, old)
1024
 
        myfile = rename.trans_id_file_id(b'myfile-id')
1025
 
        rename.set_executability(True, myfile)
1026
 
        rename.apply()
1027
 
 
1028
 
    def test_rename_fails(self):
1029
 
        self.requireFeature(features.not_running_as_root)
1030
 
        # see https://bugs.launchpad.net/bzr/+bug/491763
1031
 
        create, root_id = self.transform()
1032
 
        create.new_directory('first-dir', root_id, b'first-id')
1033
 
        create.new_file('myfile', root_id, [b'myfile-text'], b'myfile-id')
1034
 
        create.apply()
1035
 
        if os.name == "posix" and sys.platform != "cygwin":
1036
 
            # posix filesystems fail on renaming if the readonly bit is set
1037
 
            osutils.make_readonly(self.wt.abspath('first-dir'))
1038
 
        elif os.name == "nt":
1039
 
            # windows filesystems fail on renaming open files
1040
 
            self.addCleanup(open(self.wt.abspath('myfile')).close)
1041
 
        else:
1042
 
            self.skipTest("Can't force a permissions error on rename")
1043
 
        # now transform to rename
1044
 
        rename_transform, root_id = self.transform()
1045
 
        file_trans_id = rename_transform.trans_id_file_id(b'myfile-id')
1046
 
        dir_id = rename_transform.trans_id_file_id(b'first-id')
1047
 
        rename_transform.adjust_path('newname', dir_id, file_trans_id)
1048
 
        e = self.assertRaises(TransformRenameFailed,
1049
 
                              rename_transform.apply)
1050
 
        # On nix looks like:
1051
 
        # "Failed to rename .../work/.bzr/checkout/limbo/new-1
1052
 
        # to .../first-dir/newname: [Errno 13] Permission denied"
1053
 
        # On windows looks like:
1054
 
        # "Failed to rename .../work/myfile to
1055
 
        # .../work/.bzr/checkout/limbo/new-1: [Errno 13] Permission denied"
1056
 
        # This test isn't concerned with exactly what the error looks like,
1057
 
        # and the strerror will vary across OS and locales, but the assert
1058
 
        # that the exeception attributes are what we expect
1059
 
        self.assertEqual(e.errno, errno.EACCES)
1060
 
        if os.name == "posix":
1061
 
            self.assertEndsWith(e.to_path, "/first-dir/newname")
1062
 
        else:
1063
 
            self.assertEqual(os.path.basename(e.from_path), "myfile")
1064
 
 
1065
 
    def test_set_executability_order(self):
1066
 
        """Ensure that executability behaves the same, no matter what order.
1067
 
 
1068
 
        - create file and set executability simultaneously
1069
 
        - create file and set executability afterward
1070
 
        - unsetting the executability of a file whose executability has not
1071
 
          been
1072
 
        declared should throw an exception (this may happen when a
1073
 
        merge attempts to create a file with a duplicate ID)
1074
 
        """
1075
 
        transform, root = self.transform()
1076
 
        wt = transform._tree
1077
 
        wt.lock_read()
1078
 
        self.addCleanup(wt.unlock)
1079
 
        transform.new_file('set_on_creation', root, [b'Set on creation'],
1080
 
                           b'soc', True)
1081
 
        sac = transform.new_file('set_after_creation', root,
1082
 
                                 [b'Set after creation'], b'sac')
1083
 
        transform.set_executability(True, sac)
1084
 
        uws = transform.new_file('unset_without_set', root, [b'Unset badly'],
1085
 
                                 b'uws')
1086
 
        self.assertRaises(KeyError, transform.set_executability, None, uws)
1087
 
        transform.apply()
1088
 
        self.assertTrue(wt.is_executable('set_on_creation'))
1089
 
        self.assertTrue(wt.is_executable('set_after_creation'))
1090
 
 
1091
 
    def test_preserve_mode(self):
1092
 
        """File mode is preserved when replacing content"""
1093
 
        if sys.platform == 'win32':
1094
 
            raise TestSkipped('chmod has no effect on win32')
1095
 
        transform, root = self.transform()
1096
 
        transform.new_file('file1', root, [b'contents'], b'file1-id', True)
1097
 
        transform.apply()
1098
 
        self.wt.lock_write()
1099
 
        self.addCleanup(self.wt.unlock)
1100
 
        self.assertTrue(self.wt.is_executable('file1'))
1101
 
        transform, root = self.transform()
1102
 
        file1_id = transform.trans_id_tree_path('file1')
1103
 
        transform.delete_contents(file1_id)
1104
 
        transform.create_file([b'contents2'], file1_id)
1105
 
        transform.apply()
1106
 
        self.assertTrue(self.wt.is_executable('file1'))
1107
 
 
1108
 
    def test__set_mode_stats_correctly(self):
1109
 
        """_set_mode stats to determine file mode."""
1110
 
        if sys.platform == 'win32':
1111
 
            raise TestSkipped('chmod has no effect on win32')
1112
 
 
1113
 
        stat_paths = []
1114
 
        real_stat = os.stat
1115
 
 
1116
 
        def instrumented_stat(path):
1117
 
            stat_paths.append(path)
1118
 
            return real_stat(path)
1119
 
 
1120
 
        transform, root = self.transform()
1121
 
 
1122
 
        bar1_id = transform.new_file('bar', root, [b'bar contents 1\n'],
1123
 
                                     file_id=b'bar-id-1', executable=False)
1124
 
        transform.apply()
1125
 
 
1126
 
        transform, root = self.transform()
1127
 
        bar1_id = transform.trans_id_tree_path('bar')
1128
 
        bar2_id = transform.trans_id_tree_path('bar2')
1129
 
        try:
1130
 
            os.stat = instrumented_stat
1131
 
            transform.create_file([b'bar2 contents\n'],
1132
 
                                  bar2_id, mode_id=bar1_id)
1133
 
        finally:
1134
 
            os.stat = real_stat
1135
 
            transform.finalize()
1136
 
 
1137
 
        bar1_abspath = self.wt.abspath('bar')
1138
 
        self.assertEqual([bar1_abspath], stat_paths)
1139
 
 
1140
 
    def test_iter_changes(self):
1141
 
        self.wt.set_root_id(b'eert_toor')
1142
 
        transform, root = self.transform()
1143
 
        transform.new_file('old', root, [b'blah'], b'id-1', True)
1144
 
        transform.apply()
1145
 
        transform, root = self.transform()
1146
 
        try:
1147
 
            self.assertEqual([], list(transform.iter_changes()))
1148
 
            old = transform.trans_id_tree_path('old')
1149
 
            transform.unversion_file(old)
1150
 
            self.assertEqual([(b'id-1', ('old', None), False, (True, False),
1151
 
                               (b'eert_toor', b'eert_toor'),
1152
 
                               ('old', 'old'), ('file', 'file'),
1153
 
                               (True, True), False)],
1154
 
                             list(transform.iter_changes()))
1155
 
            transform.new_directory('new', root, b'id-1')
1156
 
            self.assertEqual([(b'id-1', ('old', 'new'), True, (True, True),
1157
 
                               (b'eert_toor', b'eert_toor'), ('old', 'new'),
1158
 
                               ('file', 'directory'),
1159
 
                               (True, False), False)],
1160
 
                             list(transform.iter_changes()))
1161
 
        finally:
1162
 
            transform.finalize()
1163
 
 
1164
 
    def test_iter_changes_new(self):
1165
 
        self.wt.set_root_id(b'eert_toor')
1166
 
        transform, root = self.transform()
1167
 
        transform.new_file('old', root, [b'blah'])
1168
 
        transform.apply()
1169
 
        transform, root = self.transform()
1170
 
        try:
1171
 
            old = transform.trans_id_tree_path('old')
1172
 
            transform.version_file(old, file_id=b'id-1')
1173
 
            self.assertEqual([(b'id-1', (None, 'old'), False, (False, True),
1174
 
                               (b'eert_toor', b'eert_toor'),
1175
 
                               ('old', 'old'), ('file', 'file'),
1176
 
                               (False, False), False)],
1177
 
                             list(transform.iter_changes()))
1178
 
        finally:
1179
 
            transform.finalize()
1180
 
 
1181
 
    def test_iter_changes_modifications(self):
1182
 
        self.wt.set_root_id(b'eert_toor')
1183
 
        transform, root = self.transform()
1184
 
        transform.new_file('old', root, [b'blah'], b'id-1')
1185
 
        transform.new_file('new', root, [b'blah'])
1186
 
        transform.new_directory('subdir', root, b'subdir-id')
1187
 
        transform.apply()
1188
 
        transform, root = self.transform()
1189
 
        try:
1190
 
            old = transform.trans_id_tree_path('old')
1191
 
            subdir = transform.trans_id_tree_path('subdir')
1192
 
            new = transform.trans_id_tree_path('new')
1193
 
            self.assertEqual([], list(transform.iter_changes()))
1194
 
 
1195
 
            # content deletion
1196
 
            transform.delete_contents(old)
1197
 
            self.assertEqual([(b'id-1', ('old', 'old'), True, (True, True),
1198
 
                               (b'eert_toor', b'eert_toor'),
1199
 
                               ('old', 'old'), ('file', None),
1200
 
                               (False, False), False)],
1201
 
                             list(transform.iter_changes()))
1202
 
 
1203
 
            # content change
1204
 
            transform.create_file([b'blah'], old)
1205
 
            self.assertEqual([(b'id-1', ('old', 'old'), True, (True, True),
1206
 
                               (b'eert_toor', b'eert_toor'),
1207
 
                               ('old', 'old'), ('file', 'file'),
1208
 
                               (False, False), False)],
1209
 
                             list(transform.iter_changes()))
1210
 
            transform.cancel_deletion(old)
1211
 
            self.assertEqual([(b'id-1', ('old', 'old'), True, (True, True),
1212
 
                               (b'eert_toor', b'eert_toor'),
1213
 
                               ('old', 'old'), ('file', 'file'),
1214
 
                               (False, False), False)],
1215
 
                             list(transform.iter_changes()))
1216
 
            transform.cancel_creation(old)
1217
 
 
1218
 
            # move file_id to a different file
1219
 
            self.assertEqual([], list(transform.iter_changes()))
1220
 
            transform.unversion_file(old)
1221
 
            transform.version_file(new, file_id=b'id-1')
1222
 
            transform.adjust_path('old', root, new)
1223
 
            self.assertEqual([(b'id-1', ('old', 'old'), True, (True, True),
1224
 
                               (b'eert_toor', b'eert_toor'),
1225
 
                               ('old', 'old'), ('file', 'file'),
1226
 
                               (False, False), False)],
1227
 
                             list(transform.iter_changes()))
1228
 
            transform.cancel_versioning(new)
1229
 
            transform._removed_id = set()
1230
 
 
1231
 
            # execute bit
1232
 
            self.assertEqual([], list(transform.iter_changes()))
1233
 
            transform.set_executability(True, old)
1234
 
            self.assertEqual([(b'id-1', ('old', 'old'), False, (True, True),
1235
 
                               (b'eert_toor', b'eert_toor'),
1236
 
                               ('old', 'old'), ('file', 'file'),
1237
 
                               (False, True), False)],
1238
 
                             list(transform.iter_changes()))
1239
 
            transform.set_executability(None, old)
1240
 
 
1241
 
            # filename
1242
 
            self.assertEqual([], list(transform.iter_changes()))
1243
 
            transform.adjust_path('new', root, old)
1244
 
            transform._new_parent = {}
1245
 
            self.assertEqual([(b'id-1', ('old', 'new'), False, (True, True),
1246
 
                               (b'eert_toor', b'eert_toor'),
1247
 
                               ('old', 'new'), ('file', 'file'),
1248
 
                               (False, False), False)],
1249
 
                             list(transform.iter_changes()))
1250
 
            transform._new_name = {}
1251
 
 
1252
 
            # parent directory
1253
 
            self.assertEqual([], list(transform.iter_changes()))
1254
 
            transform.adjust_path('new', subdir, old)
1255
 
            transform._new_name = {}
1256
 
            self.assertEqual([(b'id-1', ('old', 'subdir/old'), False,
1257
 
                               (True, True), (b'eert_toor',
1258
 
                                              b'subdir-id'), ('old', 'old'),
1259
 
                               ('file', 'file'), (False, False), False)],
1260
 
                             list(transform.iter_changes()))
1261
 
            transform._new_path = {}
1262
 
 
1263
 
        finally:
1264
 
            transform.finalize()
1265
 
 
1266
 
    def test_iter_changes_modified_bleed(self):
1267
 
        self.wt.set_root_id(b'eert_toor')
1268
 
        """Modified flag should not bleed from one change to another"""
1269
 
        # unfortunately, we have no guarantee that file1 (which is modified)
1270
 
        # will be applied before file2.  And if it's applied after file2, it
1271
 
        # obviously can't bleed into file2's change output.  But for now, it
1272
 
        # works.
1273
 
        transform, root = self.transform()
1274
 
        transform.new_file('file1', root, [b'blah'], b'id-1')
1275
 
        transform.new_file('file2', root, [b'blah'], b'id-2')
1276
 
        transform.apply()
1277
 
        transform, root = self.transform()
1278
 
        try:
1279
 
            transform.delete_contents(transform.trans_id_file_id(b'id-1'))
1280
 
            transform.set_executability(True,
1281
 
                                        transform.trans_id_file_id(b'id-2'))
1282
 
            self.assertEqual(
1283
 
                [(b'id-1', (u'file1', u'file1'), True, (True, True),
1284
 
                 (b'eert_toor', b'eert_toor'), ('file1', u'file1'),
1285
 
                 ('file', None), (False, False), False),
1286
 
                 (b'id-2', (u'file2', u'file2'), False, (True, True),
1287
 
                 (b'eert_toor', b'eert_toor'), ('file2', u'file2'),
1288
 
                 ('file', 'file'), (False, True), False)],
1289
 
                list(transform.iter_changes()))
1290
 
        finally:
1291
 
            transform.finalize()
1292
 
 
1293
 
    def test_iter_changes_move_missing(self):
1294
 
        """Test moving ids with no files around"""
1295
 
        self.wt.set_root_id(b'toor_eert')
1296
 
        # Need two steps because versioning a non-existant file is a conflict.
1297
 
        transform, root = self.transform()
1298
 
        transform.new_directory('floater', root, b'floater-id')
1299
 
        transform.apply()
1300
 
        transform, root = self.transform()
1301
 
        transform.delete_contents(transform.trans_id_tree_path('floater'))
1302
 
        transform.apply()
1303
 
        transform, root = self.transform()
1304
 
        floater = transform.trans_id_tree_path('floater')
1305
 
        try:
1306
 
            transform.adjust_path('flitter', root, floater)
1307
 
            self.assertEqual([(b'floater-id', ('floater', 'flitter'), False,
1308
 
                               (True, True),
1309
 
                               (b'toor_eert', b'toor_eert'),
1310
 
                               ('floater', 'flitter'),
1311
 
                               (None, None), (False, False), False)],
1312
 
                             list(transform.iter_changes()))
1313
 
        finally:
1314
 
            transform.finalize()
1315
 
 
1316
 
    def test_iter_changes_pointless(self):
1317
 
        """Ensure that no-ops are not treated as modifications"""
1318
 
        self.wt.set_root_id(b'eert_toor')
1319
 
        transform, root = self.transform()
1320
 
        transform.new_file('old', root, [b'blah'], b'id-1')
1321
 
        transform.new_directory('subdir', root, b'subdir-id')
1322
 
        transform.apply()
1323
 
        transform, root = self.transform()
1324
 
        try:
1325
 
            old = transform.trans_id_tree_path('old')
1326
 
            subdir = transform.trans_id_tree_path('subdir')
1327
 
            self.assertEqual([], list(transform.iter_changes()))
1328
 
            transform.delete_contents(subdir)
1329
 
            transform.create_directory(subdir)
1330
 
            transform.set_executability(False, old)
1331
 
            transform.unversion_file(old)
1332
 
            transform.version_file(old, file_id=b'id-1')
1333
 
            transform.adjust_path('old', root, old)
1334
 
            self.assertEqual([], list(transform.iter_changes()))
1335
 
        finally:
1336
 
            transform.finalize()
1337
 
 
1338
 
    def test_rename_count(self):
1339
 
        transform, root = self.transform()
1340
 
        transform.new_file('name1', root, [b'contents'])
1341
 
        self.assertEqual(transform.rename_count, 0)
1342
 
        transform.apply()
1343
 
        self.assertEqual(transform.rename_count, 1)
1344
 
        transform2, root = self.transform()
1345
 
        transform2.adjust_path('name2', root,
1346
 
                               transform2.trans_id_tree_path('name1'))
1347
 
        self.assertEqual(transform2.rename_count, 0)
1348
 
        transform2.apply()
1349
 
        self.assertEqual(transform2.rename_count, 2)
1350
 
 
1351
 
    def test_change_parent(self):
1352
 
        """Ensure that after we change a parent, the results are still right.
1353
 
 
1354
 
        Renames and parent changes on pending transforms can happen as part
1355
 
        of conflict resolution, and are explicitly permitted by the
1356
 
        TreeTransform API.
1357
 
 
1358
 
        This test ensures they work correctly with the rename-avoidance
1359
 
        optimization.
1360
 
        """
1361
 
        transform, root = self.transform()
1362
 
        parent1 = transform.new_directory('parent1', root)
1363
 
        child1 = transform.new_file('child1', parent1, [b'contents'])
1364
 
        parent2 = transform.new_directory('parent2', root)
1365
 
        transform.adjust_path('child1', parent2, child1)
1366
 
        transform.apply()
1367
 
        self.assertPathDoesNotExist(self.wt.abspath('parent1/child1'))
1368
 
        self.assertPathExists(self.wt.abspath('parent2/child1'))
1369
 
        # rename limbo/new-1 => parent1, rename limbo/new-3 => parent2
1370
 
        # no rename for child1 (counting only renames during apply)
1371
 
        self.assertEqual(2, transform.rename_count)
1372
 
 
1373
 
    def test_cancel_parent(self):
1374
 
        """Cancelling a parent doesn't cause deletion of a non-empty directory
1375
 
 
1376
 
        This is like the test_change_parent, except that we cancel the parent
1377
 
        before adjusting the path.  The transform must detect that the
1378
 
        directory is non-empty, and move children to safe locations.
1379
 
        """
1380
 
        transform, root = self.transform()
1381
 
        parent1 = transform.new_directory('parent1', root)
1382
 
        child1 = transform.new_file('child1', parent1, [b'contents'])
1383
 
        child2 = transform.new_file('child2', parent1, [b'contents'])
1384
 
        try:
1385
 
            transform.cancel_creation(parent1)
1386
 
        except OSError:
1387
 
            self.fail('Failed to move child1 before deleting parent1')
1388
 
        transform.cancel_creation(child2)
1389
 
        transform.create_directory(parent1)
1390
 
        try:
1391
 
            transform.cancel_creation(parent1)
1392
 
        # If the transform incorrectly believes that child2 is still in
1393
 
        # parent1's limbo directory, it will try to rename it and fail
1394
 
        # because was already moved by the first cancel_creation.
1395
 
        except OSError:
1396
 
            self.fail('Transform still thinks child2 is a child of parent1')
1397
 
        parent2 = transform.new_directory('parent2', root)
1398
 
        transform.adjust_path('child1', parent2, child1)
1399
 
        transform.apply()
1400
 
        self.assertPathDoesNotExist(self.wt.abspath('parent1'))
1401
 
        self.assertPathExists(self.wt.abspath('parent2/child1'))
1402
 
        # rename limbo/new-3 => parent2, rename limbo/new-2 => child1
1403
 
        self.assertEqual(2, transform.rename_count)
1404
 
 
1405
 
    def test_adjust_and_cancel(self):
1406
 
        """Make sure adjust_path keeps track of limbo children properly"""
1407
 
        transform, root = self.transform()
1408
 
        parent1 = transform.new_directory('parent1', root)
1409
 
        child1 = transform.new_file('child1', parent1, [b'contents'])
1410
 
        parent2 = transform.new_directory('parent2', root)
1411
 
        transform.adjust_path('child1', parent2, child1)
1412
 
        transform.cancel_creation(child1)
1413
 
        try:
1414
 
            transform.cancel_creation(parent1)
1415
 
        # if the transform thinks child1 is still in parent1's limbo
1416
 
        # directory, it will attempt to move it and fail.
1417
 
        except OSError:
1418
 
            self.fail('Transform still thinks child1 is a child of parent1')
1419
 
        transform.finalize()
1420
 
 
1421
 
    def test_noname_contents(self):
1422
 
        """TreeTransform should permit deferring naming files."""
1423
 
        transform, root = self.transform()
1424
 
        parent = transform.trans_id_file_id(b'parent-id')
1425
 
        try:
1426
 
            transform.create_directory(parent)
1427
 
        except KeyError:
1428
 
            self.fail("Can't handle contents with no name")
1429
 
        transform.finalize()
1430
 
 
1431
 
    def test_noname_contents_nested(self):
1432
 
        """TreeTransform should permit deferring naming files."""
1433
 
        transform, root = self.transform()
1434
 
        parent = transform.trans_id_file_id(b'parent-id')
1435
 
        try:
1436
 
            transform.create_directory(parent)
1437
 
        except KeyError:
1438
 
            self.fail("Can't handle contents with no name")
1439
 
        transform.new_directory('child', parent)
1440
 
        transform.adjust_path('parent', root, parent)
1441
 
        transform.apply()
1442
 
        self.assertPathExists(self.wt.abspath('parent/child'))
1443
 
        self.assertEqual(1, transform.rename_count)
1444
 
 
1445
 
    def test_reuse_name(self):
1446
 
        """Avoid reusing the same limbo name for different files"""
1447
 
        transform, root = self.transform()
1448
 
        parent = transform.new_directory('parent', root)
1449
 
        transform.new_directory('child', parent)
1450
 
        try:
1451
 
            child2 = transform.new_directory('child', parent)
1452
 
        except OSError:
1453
 
            self.fail('Tranform tried to use the same limbo name twice')
1454
 
        transform.adjust_path('child2', parent, child2)
1455
 
        transform.apply()
1456
 
        # limbo/new-1 => parent, limbo/new-3 => parent/child2
1457
 
        # child2 is put into top-level limbo because child1 has already
1458
 
        # claimed the direct limbo path when child2 is created.  There is no
1459
 
        # advantage in renaming files once they're in top-level limbo, except
1460
 
        # as part of apply.
1461
 
        self.assertEqual(2, transform.rename_count)
1462
 
 
1463
 
    def test_reuse_when_first_moved(self):
1464
 
        """Don't avoid direct paths when it is safe to use them"""
1465
 
        transform, root = self.transform()
1466
 
        parent = transform.new_directory('parent', root)
1467
 
        child1 = transform.new_directory('child', parent)
1468
 
        transform.adjust_path('child1', parent, child1)
1469
 
        transform.new_directory('child', parent)
1470
 
        transform.apply()
1471
 
        # limbo/new-1 => parent
1472
 
        self.assertEqual(1, transform.rename_count)
1473
 
 
1474
 
    def test_reuse_after_cancel(self):
1475
 
        """Don't avoid direct paths when it is safe to use them"""
1476
 
        transform, root = self.transform()
1477
 
        parent2 = transform.new_directory('parent2', root)
1478
 
        child1 = transform.new_directory('child1', parent2)
1479
 
        transform.cancel_creation(parent2)
1480
 
        transform.create_directory(parent2)
1481
 
        transform.new_directory('child1', parent2)
1482
 
        transform.adjust_path('child2', parent2, child1)
1483
 
        transform.apply()
1484
 
        # limbo/new-1 => parent2, limbo/new-2 => parent2/child1
1485
 
        self.assertEqual(2, transform.rename_count)
1486
 
 
1487
 
    def test_finalize_order(self):
1488
 
        """Finalize must be done in child-to-parent order"""
1489
 
        transform, root = self.transform()
1490
 
        parent = transform.new_directory('parent', root)
1491
 
        transform.new_directory('child', parent)
1492
 
        try:
1493
 
            transform.finalize()
1494
 
        except OSError:
1495
 
            self.fail('Tried to remove parent before child1')
1496
 
 
1497
 
    def test_cancel_with_cancelled_child_should_succeed(self):
1498
 
        transform, root = self.transform()
1499
 
        parent = transform.new_directory('parent', root)
1500
 
        child = transform.new_directory('child', parent)
1501
 
        transform.cancel_creation(child)
1502
 
        transform.cancel_creation(parent)
1503
 
        transform.finalize()
1504
 
 
1505
 
    def test_rollback_on_directory_clash(self):
1506
 
        def tt_helper():
1507
 
            wt = self.make_branch_and_tree('.')
1508
 
            tt = TreeTransform(wt)  # TreeTransform obtains write lock
1509
 
            try:
1510
 
                foo = tt.new_directory('foo', tt.root)
1511
 
                tt.new_file('bar', foo, [b'foobar'])
1512
 
                baz = tt.new_directory('baz', tt.root)
1513
 
                tt.new_file('qux', baz, [b'quux'])
1514
 
                # Ask for a rename 'foo' -> 'baz'
1515
 
                tt.adjust_path('baz', tt.root, foo)
1516
 
                # Lie to tt that we've already resolved all conflicts.
1517
 
                tt.apply(no_conflicts=True)
1518
 
            except BaseException:
1519
 
                wt.unlock()
1520
 
                raise
1521
 
        # The rename will fail because the target directory is not empty (but
1522
 
        # raises FileExists anyway).
1523
 
        err = self.assertRaises(errors.FileExists, tt_helper)
1524
 
        self.assertEndsWith(err.path, "/baz")
1525
 
 
1526
 
    def test_two_directories_clash(self):
1527
 
        def tt_helper():
1528
 
            wt = self.make_branch_and_tree('.')
1529
 
            tt = TreeTransform(wt)  # TreeTransform obtains write lock
1530
 
            try:
1531
 
                foo_1 = tt.new_directory('foo', tt.root)
1532
 
                tt.new_directory('bar', foo_1)
1533
 
                # Adding the same directory with a different content
1534
 
                foo_2 = tt.new_directory('foo', tt.root)
1535
 
                tt.new_directory('baz', foo_2)
1536
 
                # Lie to tt that we've already resolved all conflicts.
1537
 
                tt.apply(no_conflicts=True)
1538
 
            except BaseException:
1539
 
                wt.unlock()
1540
 
                raise
1541
 
        err = self.assertRaises(errors.FileExists, tt_helper)
1542
 
        self.assertEndsWith(err.path, "/foo")
1543
 
 
1544
 
    def test_two_directories_clash_finalize(self):
1545
 
        def tt_helper():
1546
 
            wt = self.make_branch_and_tree('.')
1547
 
            tt = TreeTransform(wt)  # TreeTransform obtains write lock
1548
 
            try:
1549
 
                foo_1 = tt.new_directory('foo', tt.root)
1550
 
                tt.new_directory('bar', foo_1)
1551
 
                # Adding the same directory with a different content
1552
 
                foo_2 = tt.new_directory('foo', tt.root)
1553
 
                tt.new_directory('baz', foo_2)
1554
 
                # Lie to tt that we've already resolved all conflicts.
1555
 
                tt.apply(no_conflicts=True)
1556
 
            except BaseException:
1557
 
                tt.finalize()
1558
 
                raise
1559
 
        err = self.assertRaises(errors.FileExists, tt_helper)
1560
 
        self.assertEndsWith(err.path, "/foo")
1561
 
 
1562
 
    def test_file_to_directory(self):
1563
 
        wt = self.make_branch_and_tree('.')
1564
 
        self.build_tree(['foo'])
1565
 
        wt.add(['foo'])
1566
 
        wt.commit("one")
1567
 
        tt = TreeTransform(wt)
1568
 
        self.addCleanup(tt.finalize)
1569
 
        foo_trans_id = tt.trans_id_tree_path("foo")
1570
 
        tt.delete_contents(foo_trans_id)
1571
 
        tt.create_directory(foo_trans_id)
1572
 
        bar_trans_id = tt.trans_id_tree_path("foo/bar")
1573
 
        tt.create_file([b"aa\n"], bar_trans_id)
1574
 
        tt.version_file(bar_trans_id, file_id=b"bar-1")
1575
 
        tt.apply()
1576
 
        self.assertPathExists("foo/bar")
1577
 
        with wt.lock_read():
1578
 
            self.assertEqual(wt.kind("foo"), "directory")
1579
 
        wt.commit("two")
1580
 
        changes = wt.changes_from(wt.basis_tree())
1581
 
        self.assertFalse(changes.has_changed(), changes)
1582
 
 
1583
 
    def test_file_to_symlink(self):
1584
 
        self.requireFeature(SymlinkFeature)
1585
 
        wt = self.make_branch_and_tree('.')
1586
 
        self.build_tree(['foo'])
1587
 
        wt.add(['foo'])
1588
 
        wt.commit("one")
1589
 
        tt = TreeTransform(wt)
1590
 
        self.addCleanup(tt.finalize)
1591
 
        foo_trans_id = tt.trans_id_tree_path("foo")
1592
 
        tt.delete_contents(foo_trans_id)
1593
 
        tt.create_symlink("bar", foo_trans_id)
1594
 
        tt.apply()
1595
 
        self.assertPathExists("foo")
1596
 
        wt.lock_read()
1597
 
        self.addCleanup(wt.unlock)
1598
 
        self.assertEqual(wt.kind("foo"), "symlink")
1599
 
 
1600
 
    def test_file_to_symlink_unsupported(self):
1601
 
        wt = self.make_branch_and_tree('.')
1602
 
        self.build_tree(['foo'])
1603
 
        wt.add(['foo'])
1604
 
        wt.commit("one")
1605
 
        self.overrideAttr(osutils, 'supports_symlinks', lambda p: False)
1606
 
        tt = TreeTransform(wt)
1607
 
        self.addCleanup(tt.finalize)
1608
 
        foo_trans_id = tt.trans_id_tree_path("foo")
1609
 
        tt.delete_contents(foo_trans_id)
1610
 
        log = BytesIO()
1611
 
        trace.push_log_file(log)
1612
 
        tt.create_symlink("bar", foo_trans_id)
1613
 
        tt.apply()
1614
 
        self.assertContainsRe(
1615
 
            log.getvalue(),
1616
 
            b'Unable to create symlink "foo" on this filesystem')
1617
 
 
1618
 
    def test_dir_to_file(self):
1619
 
        wt = self.make_branch_and_tree('.')
1620
 
        self.build_tree(['foo/', 'foo/bar'])
1621
 
        wt.add(['foo', 'foo/bar'])
1622
 
        wt.commit("one")
1623
 
        tt = TreeTransform(wt)
1624
 
        self.addCleanup(tt.finalize)
1625
 
        foo_trans_id = tt.trans_id_tree_path("foo")
1626
 
        bar_trans_id = tt.trans_id_tree_path("foo/bar")
1627
 
        tt.delete_contents(foo_trans_id)
1628
 
        tt.delete_versioned(bar_trans_id)
1629
 
        tt.create_file([b"aa\n"], foo_trans_id)
1630
 
        tt.apply()
1631
 
        self.assertPathExists("foo")
1632
 
        wt.lock_read()
1633
 
        self.addCleanup(wt.unlock)
1634
 
        self.assertEqual(wt.kind("foo"), "file")
1635
 
 
1636
 
    def test_dir_to_hardlink(self):
1637
 
        self.requireFeature(HardlinkFeature)
1638
 
        wt = self.make_branch_and_tree('.')
1639
 
        self.build_tree(['foo/', 'foo/bar'])
1640
 
        wt.add(['foo', 'foo/bar'])
1641
 
        wt.commit("one")
1642
 
        tt = TreeTransform(wt)
1643
 
        self.addCleanup(tt.finalize)
1644
 
        foo_trans_id = tt.trans_id_tree_path("foo")
1645
 
        bar_trans_id = tt.trans_id_tree_path("foo/bar")
1646
 
        tt.delete_contents(foo_trans_id)
1647
 
        tt.delete_versioned(bar_trans_id)
1648
 
        self.build_tree(['baz'])
1649
 
        tt.create_hardlink("baz", foo_trans_id)
1650
 
        tt.apply()
1651
 
        self.assertPathExists("foo")
1652
 
        self.assertPathExists("baz")
1653
 
        wt.lock_read()
1654
 
        self.addCleanup(wt.unlock)
1655
 
        self.assertEqual(wt.kind("foo"), "file")
1656
 
 
1657
 
    def test_no_final_path(self):
1658
 
        transform, root = self.transform()
1659
 
        trans_id = transform.trans_id_file_id(b'foo')
1660
 
        transform.create_file([b'bar'], trans_id)
1661
 
        transform.cancel_creation(trans_id)
1662
 
        transform.apply()
1663
 
 
1664
 
    def test_create_from_tree(self):
1665
 
        tree1 = self.make_branch_and_tree('tree1')
1666
 
        self.build_tree_contents([('tree1/foo/',), ('tree1/bar', b'baz')])
1667
 
        tree1.add(['foo', 'bar'], [b'foo-id', b'bar-id'])
1668
 
        tree2 = self.make_branch_and_tree('tree2')
1669
 
        tt = TreeTransform(tree2)
1670
 
        foo_trans_id = tt.create_path('foo', tt.root)
1671
 
        create_from_tree(tt, foo_trans_id, tree1, 'foo')
1672
 
        bar_trans_id = tt.create_path('bar', tt.root)
1673
 
        create_from_tree(tt, bar_trans_id, tree1, 'bar')
1674
 
        tt.apply()
1675
 
        self.assertEqual('directory', osutils.file_kind('tree2/foo'))
1676
 
        self.assertFileEqual(b'baz', 'tree2/bar')
1677
 
 
1678
 
    def test_create_from_tree_bytes(self):
1679
 
        """Provided lines are used instead of tree content."""
1680
 
        tree1 = self.make_branch_and_tree('tree1')
1681
 
        self.build_tree_contents([('tree1/foo', b'bar'), ])
1682
 
        tree1.add('foo', b'foo-id')
1683
 
        tree2 = self.make_branch_and_tree('tree2')
1684
 
        tt = TreeTransform(tree2)
1685
 
        foo_trans_id = tt.create_path('foo', tt.root)
1686
 
        create_from_tree(tt, foo_trans_id, tree1, 'foo', chunks=[b'qux'])
1687
 
        tt.apply()
1688
 
        self.assertFileEqual(b'qux', 'tree2/foo')
1689
 
 
1690
 
    def test_create_from_tree_symlink(self):
1691
 
        self.requireFeature(SymlinkFeature)
1692
 
        tree1 = self.make_branch_and_tree('tree1')
1693
 
        os.symlink('bar', 'tree1/foo')
1694
 
        tree1.add('foo', b'foo-id')
1695
 
        tt = TreeTransform(self.make_branch_and_tree('tree2'))
1696
 
        foo_trans_id = tt.create_path('foo', tt.root)
1697
 
        create_from_tree(tt, foo_trans_id, tree1, 'foo')
1698
 
        tt.apply()
1699
 
        self.assertEqual('bar', os.readlink('tree2/foo'))
1700
 
 
1701
 
 
1702
97
class TransformGroup(object):
1703
98
 
1704
99
    def __init__(self, dirname, root_id):
1707
102
        self.wt = ControlDir.create_standalone_workingtree(dirname)
1708
103
        self.wt.set_root_id(root_id)
1709
104
        self.b = self.wt.branch
1710
 
        self.tt = TreeTransform(self.wt)
 
105
        self.tt = self.wt.transform()
1711
106
        self.root = self.tt.trans_id_tree_path('')
1712
107
 
1713
108
 
2192
587
    def test_build_tree_hardlinks_preserve_execute(self):
2193
588
        self.requireFeature(HardlinkFeature)
2194
589
        source = self.create_ab_tree()
2195
 
        tt = TreeTransform(source)
 
590
        tt = source.transform()
2196
591
        trans_id = tt.trans_id_tree_path('file1')
2197
592
        tt.set_executability(True, trans_id)
2198
593
        tt.apply()
2525
920
    def test_rollback_rename(self):
2526
921
        tree = self.make_branch_and_tree('.')
2527
922
        self.build_tree(['a/', 'a/b'])
2528
 
        tt = TreeTransform(tree)
 
923
        tt = tree.transform()
2529
924
        self.addCleanup(tt.finalize)
2530
925
        a_id = tt.trans_id_tree_path('a')
2531
926
        tt.adjust_path('c', tt.root, a_id)
2541
936
    def test_rollback_rename_into_place(self):
2542
937
        tree = self.make_branch_and_tree('.')
2543
938
        self.build_tree(['a/', 'a/b'])
2544
 
        tt = TreeTransform(tree)
 
939
        tt = tree.transform()
2545
940
        self.addCleanup(tt.finalize)
2546
941
        a_id = tt.trans_id_tree_path('a')
2547
942
        tt.adjust_path('c', tt.root, a_id)
2557
952
    def test_rollback_deletion(self):
2558
953
        tree = self.make_branch_and_tree('.')
2559
954
        self.build_tree(['a/', 'a/b'])
2560
 
        tt = TreeTransform(tree)
 
955
        tt = tree.transform()
2561
956
        self.addCleanup(tt.finalize)
2562
957
        a_id = tt.trans_id_tree_path('a')
2563
958
        tt.delete_contents(a_id)
2601
996
    def create_transform_and_root_trans_id(self):
2602
997
        """Setup a transform creating a file in limbo"""
2603
998
        tree = self.make_branch_and_tree('.')
2604
 
        tt = TreeTransform(tree)
 
999
        tt = tree.transform()
2605
1000
        return tt, tt.create_path("a", tt.root)
2606
1001
 
2607
1002
    def create_transform_and_subdir_trans_id(self):
2608
1003
        """Setup a transform creating a directory containing a file in limbo"""
2609
1004
        tree = self.make_branch_and_tree('.')
2610
 
        tt = TreeTransform(tree)
 
1005
        tt = tree.transform()
2611
1006
        d_trans_id = tt.create_path("d", tt.root)
2612
1007
        tt.create_directory(d_trans_id)
2613
1008
        f_trans_id = tt.create_path("a", d_trans_id)
2707
1102
        self.build_tree(['dir/', ])
2708
1103
        wt.add(['dir'], [b'dir-id'])
2709
1104
        wt.commit('Create dir')
2710
 
        tt = TreeTransform(wt)
 
1105
        tt = wt.transform()
2711
1106
        self.addCleanup(tt.finalize)
2712
1107
        return wt, tt
2713
1108
 
3016
1411
 
3017
1412
    def test_iter_entries_by_dir_new(self):
3018
1413
        tree = self.make_branch_and_tree('tree')
3019
 
        tt = TreeTransform(tree)
 
1414
        tt = tree.transform()
3020
1415
        tt.new_file('new', tt.root, [b'contents'], b'new-id')
3021
1416
        self.assertMatchingIterEntries(tt)
3022
1417
 
3024
1419
        tree = self.make_branch_and_tree('tree')
3025
1420
        self.build_tree(['tree/deleted'])
3026
1421
        tree.add('deleted', b'deleted-id')
3027
 
        tt = TreeTransform(tree)
 
1422
        tt = tree.transform()
3028
1423
        tt.delete_contents(tt.trans_id_file_id(b'deleted-id'))
3029
1424
        self.assertMatchingIterEntries(tt)
3030
1425
 
3032
1427
        tree = self.make_branch_and_tree('tree')
3033
1428
        self.build_tree(['tree/removed'])
3034
1429
        tree.add('removed', b'removed-id')
3035
 
        tt = TreeTransform(tree)
 
1430
        tt = tree.transform()
3036
1431
        tt.unversion_file(tt.trans_id_file_id(b'removed-id'))
3037
1432
        self.assertMatchingIterEntries(tt)
3038
1433
 
3040
1435
        tree = self.make_branch_and_tree('tree')
3041
1436
        self.build_tree(['tree/moved', 'tree/new_parent/'])
3042
1437
        tree.add(['moved', 'new_parent'], [b'moved-id', b'new_parent-id'])
3043
 
        tt = TreeTransform(tree)
 
1438
        tt = tree.transform()
3044
1439
        tt.adjust_path('moved', tt.trans_id_file_id(b'new_parent-id'),
3045
1440
                       tt.trans_id_file_id(b'moved-id'))
3046
1441
        self.assertMatchingIterEntries(tt)
3050
1445
        tree.set_root_id(b'tree-root-id')
3051
1446
        self.build_tree(['tree/parent/', 'tree/parent/child'])
3052
1447
        tree.add(['parent', 'parent/child'], [b'parent-id', b'child-id'])
3053
 
        tt = TreeTransform(tree)
 
1448
        tt = tree.transform()
3054
1449
        self.assertMatchingIterEntries(tt, ['', 'parent/child'])
3055
1450
 
3056
1451
    def test_symlink_content_summary(self):
3477
1872
        self.assertEqual({'new-1': True}, tt._new_executability)
3478
1873
        self.assertEqual({'new-1': 'file',
3479
1874
                          'new-2': 'directory'}, tt._new_contents)
3480
 
        foo_limbo = open(tt._limbo_name('new-1'), 'rb')
3481
 
        try:
 
1875
        with open(tt._limbo_name('new-1'), 'rb') as foo_limbo:
3482
1876
            foo_content = foo_limbo.read()
3483
 
        finally:
3484
 
            foo_limbo.close()
3485
1877
        self.assertEqual(b'bar', foo_content)
3486
1878
 
3487
1879
    def symlink_creation_records(self):
3688
2080
        self.build_tree(['dir/', 'dir/file', 'dir/foo'])
3689
2081
        wt.add(['dir', 'dir/file'], [b'dir-id', b'file-id'])
3690
2082
        wt.commit('add dir and file ignoring foo')
3691
 
        tt = transform.TreeTransform(wt)
 
2083
        tt = wt.transform()
3692
2084
        self.addCleanup(tt.finalize)
3693
2085
        # dir and bar are deleted
3694
2086
        dir_tid = tt.trans_id_tree_path('dir')
3773
2165
        os.chdir('..')
3774
2166
 
3775
2167
    def transform(self):
3776
 
        transform = TreeTransform(self.wt)
 
2168
        transform = self.wt.transform()
3777
2169
        self.addCleanup(transform.finalize)
3778
2170
        return transform, transform.root
3779
2171
 
3832
2224
 
3833
2225
    def test_link_fails_if_execute_bit_changed(self):
3834
2226
        """If the file to be linked has modified execute bit, don't link."""
3835
 
        tt = TreeTransform(self.child_tree)
 
2227
        tt = self.child_tree.transform()
3836
2228
        try:
3837
2229
            trans_id = tt.trans_id_tree_path('foo')
3838
2230
            tt.set_executability(True, trans_id)