/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: Breezy landing bot
  • Author(s): Jelmer Vernooij
  • Date: 2020-07-20 02:17:05 UTC
  • mfrom: (7518.1.2 merge-3.1)
  • Revision ID: breezy.the.bot@gmail.com-20200720021705-5f11tmo1hdqjxm6x
Merge lp:brz/3.1.

Merged from https://code.launchpad.net/~jelmer/brz/merge-3.1/+merge/387628

Show diffs side-by-side

added added

removed removed

Lines of Context:
51
51
    DuplicateKey,
52
52
    ExistingLimbo,
53
53
    ExistingPendingDeletion,
54
 
    ImmortalLimbo,
55
54
    ImmortalPendingDeletion,
56
55
    LockError,
57
 
    MalformedTransform,
58
 
    ReusingTransform,
59
56
)
60
57
from ..osutils import (
61
58
    file_kind,
81
78
    resolve_conflicts,
82
79
    resolve_checkout,
83
80
    ROOT_PARENT,
84
 
    TransformPreview,
85
 
    TreeTransform,
 
81
    ImmortalLimbo,
 
82
    MalformedTransform,
 
83
    NoFinalPath,
 
84
    ReusingTransform,
 
85
    TransformRenameFailed,
86
86
)
87
87
 
88
88
 
89
 
class TestTreeTransform(tests.TestCaseWithTransport):
90
 
 
91
 
    def setUp(self):
92
 
        super(TestTreeTransform, self).setUp()
93
 
        self.wt = self.make_branch_and_tree('.', format='development-subtree')
94
 
        os.chdir('..')
95
 
 
96
 
    def get_transform(self):
97
 
        transform = TreeTransform(self.wt)
98
 
        self.addCleanup(transform.finalize)
99
 
        return transform, transform.root
100
 
 
101
 
    def get_transform_for_sha1_test(self):
102
 
        trans, root = self.get_transform()
103
 
        self.wt.lock_tree_write()
104
 
        self.addCleanup(self.wt.unlock)
105
 
        contents = [b'just some content\n']
106
 
        sha1 = osutils.sha_strings(contents)
107
 
        # Roll back the clock
108
 
        trans._creation_mtime = time.time() - 20.0
109
 
        return trans, root, contents, sha1
110
 
 
111
 
    def test_existing_limbo(self):
112
 
        transform, root = self.get_transform()
113
 
        limbo_name = transform._limbodir
114
 
        deletion_path = transform._deletiondir
115
 
        os.mkdir(pathjoin(limbo_name, 'hehe'))
116
 
        self.assertRaises(ImmortalLimbo, transform.apply)
117
 
        self.assertRaises(LockError, self.wt.unlock)
118
 
        self.assertRaises(ExistingLimbo, self.get_transform)
119
 
        self.assertRaises(LockError, self.wt.unlock)
120
 
        os.rmdir(pathjoin(limbo_name, 'hehe'))
121
 
        os.rmdir(limbo_name)
122
 
        os.rmdir(deletion_path)
123
 
        transform, root = self.get_transform()
124
 
        transform.apply()
125
 
 
126
 
    def test_existing_pending_deletion(self):
127
 
        transform, root = self.get_transform()
128
 
        deletion_path = self._limbodir = urlutils.local_path_from_url(
129
 
            transform._tree._transport.abspath('pending-deletion'))
130
 
        os.mkdir(pathjoin(deletion_path, 'blocking-directory'))
131
 
        self.assertRaises(ImmortalPendingDeletion, transform.apply)
132
 
        self.assertRaises(LockError, self.wt.unlock)
133
 
        self.assertRaises(ExistingPendingDeletion, self.get_transform)
134
 
 
135
 
    def test_build(self):
136
 
        transform, root = self.get_transform()
137
 
        self.wt.lock_tree_write()
138
 
        self.addCleanup(self.wt.unlock)
139
 
        self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
140
 
        imaginary_id = transform.trans_id_tree_path('imaginary')
141
 
        imaginary_id2 = transform.trans_id_tree_path('imaginary/')
142
 
        self.assertEqual(imaginary_id, imaginary_id2)
143
 
        self.assertEqual(root, transform.get_tree_parent(imaginary_id))
144
 
        self.assertEqual('directory', transform.final_kind(root))
145
 
        self.assertEqual(self.wt.path2id(''), transform.final_file_id(root))
146
 
        trans_id = transform.create_path('name', root)
147
 
        self.assertIs(transform.final_file_id(trans_id), None)
148
 
        self.assertIs(None, transform.final_kind(trans_id))
149
 
        transform.create_file([b'contents'], trans_id)
150
 
        transform.set_executability(True, trans_id)
151
 
        transform.version_file(b'my_pretties', trans_id)
152
 
        self.assertRaises(DuplicateKey, transform.version_file,
153
 
                          b'my_pretties', trans_id)
154
 
        self.assertEqual(transform.final_file_id(trans_id), b'my_pretties')
155
 
        self.assertEqual(transform.final_parent(trans_id), root)
156
 
        self.assertIs(transform.final_parent(root), ROOT_PARENT)
157
 
        self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
158
 
        oz_id = transform.create_path('oz', root)
159
 
        transform.create_directory(oz_id)
160
 
        transform.version_file(b'ozzie', oz_id)
161
 
        trans_id2 = transform.create_path('name2', root)
162
 
        transform.create_file([b'contents'], trans_id2)
163
 
        transform.set_executability(False, trans_id2)
164
 
        transform.version_file(b'my_pretties2', trans_id2)
165
 
        modified_paths = transform.apply().modified_paths
166
 
        with self.wt.get_file('name') as f:
167
 
            self.assertEqual(b'contents', f.read())
168
 
        self.assertEqual(self.wt.path2id('name'), b'my_pretties')
169
 
        self.assertIs(self.wt.is_executable('name'), True)
170
 
        self.assertIs(self.wt.is_executable('name2'), False)
171
 
        self.assertEqual('directory', file_kind(self.wt.abspath('oz')))
172
 
        self.assertEqual(len(modified_paths), 3)
173
 
        tree_mod_paths = [self.wt.abspath(self.wt.id2path(f)) for f in
174
 
                          (b'ozzie', b'my_pretties', b'my_pretties2')]
175
 
        self.assertSubset(tree_mod_paths, modified_paths)
176
 
        # is it safe to finalize repeatedly?
177
 
        transform.finalize()
178
 
        transform.finalize()
179
 
 
180
 
    def test_apply_informs_tree_of_observed_sha1(self):
181
 
        trans, root, contents, sha1 = self.get_transform_for_sha1_test()
182
 
        trans_id = trans.new_file('file1', root, contents, file_id=b'file1-id',
183
 
                                  sha1=sha1)
184
 
        calls = []
185
 
        orig = self.wt._observed_sha1
186
 
 
187
 
        def _observed_sha1(*args):
188
 
            calls.append(args)
189
 
            orig(*args)
190
 
        self.wt._observed_sha1 = _observed_sha1
191
 
        trans.apply()
192
 
        self.assertEqual([('file1', trans._observed_sha1s[trans_id])],
193
 
                         calls)
194
 
 
195
 
    def test_create_file_caches_sha1(self):
196
 
        trans, root, contents, sha1 = self.get_transform_for_sha1_test()
197
 
        trans_id = trans.create_path('file1', root)
198
 
        trans.create_file(contents, trans_id, sha1=sha1)
199
 
        st_val = osutils.lstat(trans._limbo_name(trans_id))
200
 
        o_sha1, o_st_val = trans._observed_sha1s[trans_id]
201
 
        self.assertEqual(o_sha1, sha1)
202
 
        self.assertEqualStat(o_st_val, st_val)
203
 
 
204
 
    def test__apply_insertions_updates_sha1(self):
205
 
        trans, root, contents, sha1 = self.get_transform_for_sha1_test()
206
 
        trans_id = trans.create_path('file1', root)
207
 
        trans.create_file(contents, trans_id, sha1=sha1)
208
 
        st_val = osutils.lstat(trans._limbo_name(trans_id))
209
 
        o_sha1, o_st_val = trans._observed_sha1s[trans_id]
210
 
        self.assertEqual(o_sha1, sha1)
211
 
        self.assertEqualStat(o_st_val, st_val)
212
 
        creation_mtime = trans._creation_mtime + 10.0
213
 
        # We fake a time difference from when the file was created until now it
214
 
        # is being renamed by using os.utime. Note that the change we actually
215
 
        # want to see is the real ctime change from 'os.rename()', but as long
216
 
        # as we observe a new stat value, we should be fine.
217
 
        os.utime(trans._limbo_name(trans_id), (creation_mtime, creation_mtime))
218
 
        trans.apply()
219
 
        new_st_val = osutils.lstat(self.wt.abspath('file1'))
220
 
        o_sha1, o_st_val = trans._observed_sha1s[trans_id]
221
 
        self.assertEqual(o_sha1, sha1)
222
 
        self.assertEqualStat(o_st_val, new_st_val)
223
 
        self.assertNotEqual(st_val.st_mtime, new_st_val.st_mtime)
224
 
 
225
 
    def test_new_file_caches_sha1(self):
226
 
        trans, root, contents, sha1 = self.get_transform_for_sha1_test()
227
 
        trans_id = trans.new_file('file1', root, contents, file_id=b'file1-id',
228
 
                                  sha1=sha1)
229
 
        st_val = osutils.lstat(trans._limbo_name(trans_id))
230
 
        o_sha1, o_st_val = trans._observed_sha1s[trans_id]
231
 
        self.assertEqual(o_sha1, sha1)
232
 
        self.assertEqualStat(o_st_val, st_val)
233
 
 
234
 
    def test_cancel_creation_removes_observed_sha1(self):
235
 
        trans, root, contents, sha1 = self.get_transform_for_sha1_test()
236
 
        trans_id = trans.new_file('file1', root, contents, file_id=b'file1-id',
237
 
                                  sha1=sha1)
238
 
        self.assertTrue(trans_id in trans._observed_sha1s)
239
 
        trans.cancel_creation(trans_id)
240
 
        self.assertFalse(trans_id in trans._observed_sha1s)
241
 
 
242
 
    def test_create_files_same_timestamp(self):
243
 
        transform, root = self.get_transform()
244
 
        self.wt.lock_tree_write()
245
 
        self.addCleanup(self.wt.unlock)
246
 
        # Roll back the clock, so that we know everything is being set to the
247
 
        # exact time
248
 
        transform._creation_mtime = creation_mtime = time.time() - 20.0
249
 
        transform.create_file([b'content-one'],
250
 
                              transform.create_path('one', root))
251
 
        time.sleep(1)  # *ugly*
252
 
        transform.create_file([b'content-two'],
253
 
                              transform.create_path('two', root))
254
 
        transform.apply()
255
 
        fo, st1 = self.wt.get_file_with_stat('one', filtered=False)
256
 
        fo.close()
257
 
        fo, st2 = self.wt.get_file_with_stat('two', filtered=False)
258
 
        fo.close()
259
 
        # We only guarantee 2s resolution
260
 
        self.assertTrue(
261
 
            abs(creation_mtime - st1.st_mtime) < 2.0,
262
 
            "%s != %s within 2 seconds" % (creation_mtime, st1.st_mtime))
263
 
        # But if we have more than that, all files should get the same result
264
 
        self.assertEqual(st1.st_mtime, st2.st_mtime)
265
 
 
266
 
    def test_change_root_id(self):
267
 
        transform, root = self.get_transform()
268
 
        self.assertNotEqual(b'new-root-id', self.wt.path2id(''))
269
 
        transform.new_directory('', ROOT_PARENT, b'new-root-id')
270
 
        transform.delete_contents(root)
271
 
        transform.unversion_file(root)
272
 
        transform.fixup_new_roots()
273
 
        transform.apply()
274
 
        self.assertEqual(b'new-root-id', self.wt.path2id(''))
275
 
 
276
 
    def test_change_root_id_add_files(self):
277
 
        transform, root = self.get_transform()
278
 
        self.assertNotEqual(b'new-root-id', self.wt.path2id(''))
279
 
        new_trans_id = transform.new_directory('', ROOT_PARENT, b'new-root-id')
280
 
        transform.new_file('file', new_trans_id, [b'new-contents\n'],
281
 
                           b'new-file-id')
282
 
        transform.delete_contents(root)
283
 
        transform.unversion_file(root)
284
 
        transform.fixup_new_roots()
285
 
        transform.apply()
286
 
        self.assertEqual(b'new-root-id', self.wt.path2id(''))
287
 
        self.assertEqual(b'new-file-id', self.wt.path2id('file'))
288
 
        self.assertFileEqual(b'new-contents\n', self.wt.abspath('file'))
289
 
 
290
 
    def test_add_two_roots(self):
291
 
        transform, root = self.get_transform()
292
 
        transform.new_directory('', ROOT_PARENT, b'new-root-id')
293
 
        transform.new_directory('', ROOT_PARENT, b'alt-root-id')
294
 
        self.assertRaises(ValueError, transform.fixup_new_roots)
295
 
 
296
 
    def test_retain_existing_root(self):
297
 
        tt, root = self.get_transform()
298
 
        with tt:
299
 
            tt.new_directory('', ROOT_PARENT, b'new-root-id')
300
 
            tt.fixup_new_roots()
301
 
            self.assertNotEqual(b'new-root-id', tt.final_file_id(tt.root))
302
 
 
303
 
    def test_retain_existing_root_added_file(self):
304
 
        tt, root = self.get_transform()
305
 
        new_trans_id = tt.new_directory('', ROOT_PARENT, b'new-root-id')
306
 
        child = tt.new_directory('child', new_trans_id, b'child-id')
307
 
        tt.fixup_new_roots()
308
 
        self.assertEqual(tt.root, tt.final_parent(child))
309
 
 
310
 
    def test_add_unversioned_root(self):
311
 
        transform, root = self.get_transform()
312
 
        transform.new_directory('', ROOT_PARENT, None)
313
 
        transform.delete_contents(transform.root)
314
 
        transform.fixup_new_roots()
315
 
        self.assertNotIn(transform.root, transform._new_id)
316
 
 
317
 
    def test_remove_root_fixup(self):
318
 
        transform, root = self.get_transform()
319
 
        old_root_id = self.wt.path2id('')
320
 
        self.assertNotEqual(b'new-root-id', old_root_id)
321
 
        transform.delete_contents(root)
322
 
        transform.unversion_file(root)
323
 
        transform.fixup_new_roots()
324
 
        transform.apply()
325
 
        self.assertEqual(old_root_id, self.wt.path2id(''))
326
 
 
327
 
        transform, root = self.get_transform()
328
 
        transform.new_directory('', ROOT_PARENT, b'new-root-id')
329
 
        transform.new_directory('', ROOT_PARENT, b'alt-root-id')
330
 
        self.assertRaises(ValueError, transform.fixup_new_roots)
331
 
 
332
 
    def test_fixup_new_roots_permits_empty_tree(self):
333
 
        transform, root = self.get_transform()
334
 
        transform.delete_contents(root)
335
 
        transform.unversion_file(root)
336
 
        transform.fixup_new_roots()
337
 
        self.assertIs(None, transform.final_kind(root))
338
 
        self.assertIs(None, transform.final_file_id(root))
339
 
 
340
 
    def test_apply_retains_root_directory(self):
341
 
        # Do not attempt to delete the physical root directory, because that
342
 
        # is impossible.
343
 
        transform, root = self.get_transform()
344
 
        with transform:
345
 
            transform.delete_contents(root)
346
 
            e = self.assertRaises(AssertionError, self.assertRaises,
347
 
                                  errors.TransformRenameFailed,
348
 
                                  transform.apply)
349
 
        self.assertContainsRe('TransformRenameFailed not raised', str(e))
350
 
 
351
 
    def test_apply_retains_file_id(self):
352
 
        transform, root = self.get_transform()
353
 
        old_root_id = transform.tree_file_id(root)
354
 
        transform.unversion_file(root)
355
 
        transform.apply()
356
 
        self.assertEqual(old_root_id, self.wt.path2id(''))
357
 
 
358
 
    def test_hardlink(self):
359
 
        self.requireFeature(HardlinkFeature)
360
 
        transform, root = self.get_transform()
361
 
        transform.new_file('file1', root, [b'contents'])
362
 
        transform.apply()
363
 
        target = self.make_branch_and_tree('target')
364
 
        target_transform = TreeTransform(target)
365
 
        trans_id = target_transform.create_path('file1', target_transform.root)
366
 
        target_transform.create_hardlink(self.wt.abspath('file1'), trans_id)
367
 
        target_transform.apply()
368
 
        self.assertPathExists('target/file1')
369
 
        source_stat = os.stat(self.wt.abspath('file1'))
370
 
        target_stat = os.stat('target/file1')
371
 
        self.assertEqual(source_stat, target_stat)
372
 
 
373
 
    def test_convenience(self):
374
 
        transform, root = self.get_transform()
375
 
        self.wt.lock_tree_write()
376
 
        self.addCleanup(self.wt.unlock)
377
 
        transform.new_file('name', root, [b'contents'], b'my_pretties', True)
378
 
        oz = transform.new_directory('oz', root, b'oz-id')
379
 
        dorothy = transform.new_directory('dorothy', oz, b'dorothy-id')
380
 
        transform.new_file('toto', dorothy, [b'toto-contents'], b'toto-id',
381
 
                           False)
382
 
 
383
 
        self.assertEqual(len(transform.find_conflicts()), 0)
384
 
        transform.apply()
385
 
        self.assertRaises(ReusingTransform, transform.find_conflicts)
386
 
        with open(self.wt.abspath('name'), 'r') as f:
387
 
            self.assertEqual('contents', f.read())
388
 
        self.assertEqual(self.wt.path2id('name'), b'my_pretties')
389
 
        self.assertIs(self.wt.is_executable('name'), True)
390
 
        self.assertEqual(self.wt.path2id('oz'), b'oz-id')
391
 
        self.assertEqual(self.wt.path2id('oz/dorothy'), b'dorothy-id')
392
 
        self.assertEqual(self.wt.path2id('oz/dorothy/toto'), b'toto-id')
393
 
 
394
 
        self.assertEqual(b'toto-contents',
395
 
                         self.wt.get_file('oz/dorothy/toto').read())
396
 
        self.assertIs(self.wt.is_executable('oz/dorothy/toto'), False)
397
 
 
398
 
    def test_tree_reference(self):
399
 
        transform, root = self.get_transform()
400
 
        tree = transform._tree
401
 
        trans_id = transform.new_directory('reference', root, b'subtree-id')
402
 
        transform.set_tree_reference(b'subtree-revision', trans_id)
403
 
        transform.apply()
404
 
        tree.lock_read()
405
 
        self.addCleanup(tree.unlock)
406
 
        self.assertEqual(
407
 
            b'subtree-revision',
408
 
            tree.root_inventory.get_entry(b'subtree-id').reference_revision)
409
 
 
410
 
    def test_conflicts(self):
411
 
        transform, root = self.get_transform()
412
 
        trans_id = transform.new_file('name', root, [b'contents'],
413
 
                                      b'my_pretties')
414
 
        self.assertEqual(len(transform.find_conflicts()), 0)
415
 
        trans_id2 = transform.new_file('name', root, [b'Crontents'], b'toto')
416
 
        self.assertEqual(transform.find_conflicts(),
417
 
                         [('duplicate', trans_id, trans_id2, 'name')])
418
 
        self.assertRaises(MalformedTransform, transform.apply)
419
 
        transform.adjust_path('name', trans_id, trans_id2)
420
 
        self.assertEqual(transform.find_conflicts(),
421
 
                         [('non-directory parent', trans_id)])
422
 
        tinman_id = transform.trans_id_tree_path('tinman')
423
 
        transform.adjust_path('name', tinman_id, trans_id2)
424
 
        self.assertEqual(transform.find_conflicts(),
425
 
                         [('unversioned parent', tinman_id),
426
 
                          ('missing parent', tinman_id)])
427
 
        lion_id = transform.create_path('lion', root)
428
 
        self.assertEqual(transform.find_conflicts(),
429
 
                         [('unversioned parent', tinman_id),
430
 
                          ('missing parent', tinman_id)])
431
 
        transform.adjust_path('name', lion_id, trans_id2)
432
 
        self.assertEqual(transform.find_conflicts(),
433
 
                         [('unversioned parent', lion_id),
434
 
                          ('missing parent', lion_id)])
435
 
        transform.version_file(b"Courage", lion_id)
436
 
        self.assertEqual(transform.find_conflicts(),
437
 
                         [('missing parent', lion_id),
438
 
                          ('versioning no contents', lion_id)])
439
 
        transform.adjust_path('name2', root, trans_id2)
440
 
        self.assertEqual(transform.find_conflicts(),
441
 
                         [('versioning no contents', lion_id)])
442
 
        transform.create_file([b'Contents, okay?'], lion_id)
443
 
        transform.adjust_path('name2', trans_id2, trans_id2)
444
 
        self.assertEqual(transform.find_conflicts(),
445
 
                         [('parent loop', trans_id2),
446
 
                          ('non-directory parent', trans_id2)])
447
 
        transform.adjust_path('name2', root, trans_id2)
448
 
        oz_id = transform.new_directory('oz', root)
449
 
        transform.set_executability(True, oz_id)
450
 
        self.assertEqual(transform.find_conflicts(),
451
 
                         [('unversioned executability', oz_id)])
452
 
        transform.version_file(b'oz-id', oz_id)
453
 
        self.assertEqual(transform.find_conflicts(),
454
 
                         [('non-file executability', oz_id)])
455
 
        transform.set_executability(None, oz_id)
456
 
        tip_id = transform.new_file('tip', oz_id, [b'ozma'], b'tip-id')
457
 
        transform.apply()
458
 
        self.assertEqual(self.wt.path2id('name'), b'my_pretties')
459
 
        with open(self.wt.abspath('name'), 'rb') as f:
460
 
            self.assertEqual(b'contents', f.read())
461
 
        transform2, root = self.get_transform()
462
 
        oz_id = transform2.trans_id_tree_path('oz')
463
 
        newtip = transform2.new_file('tip', oz_id, [b'other'], b'tip-id')
464
 
        result = transform2.find_conflicts()
465
 
        fp = FinalPaths(transform2)
466
 
        self.assertTrue('oz/tip' in transform2._tree_path_ids)
467
 
        self.assertEqual(fp.get_path(newtip), pathjoin('oz', 'tip'))
468
 
        self.assertEqual(len(result), 2)
469
 
        self.assertEqual((result[0][0], result[0][1]),
470
 
                         ('duplicate', newtip))
471
 
        self.assertEqual((result[1][0], result[1][2]),
472
 
                         ('duplicate id', newtip))
473
 
        transform2.finalize()
474
 
        transform3 = TreeTransform(self.wt)
475
 
        self.addCleanup(transform3.finalize)
476
 
        oz_id = transform3.trans_id_tree_path('oz')
477
 
        transform3.delete_contents(oz_id)
478
 
        self.assertEqual(transform3.find_conflicts(),
479
 
                         [('missing parent', oz_id)])
480
 
        root_id = transform3.root
481
 
        tip_id = transform3.trans_id_tree_path('oz/tip')
482
 
        transform3.adjust_path('tip', root_id, tip_id)
483
 
        transform3.apply()
484
 
 
485
 
    def test_conflict_on_case_insensitive(self):
486
 
        tree = self.make_branch_and_tree('tree')
487
 
        # Don't try this at home, kids!
488
 
        # Force the tree to report that it is case sensitive, for conflict
489
 
        # resolution tests
490
 
        tree.case_sensitive = True
491
 
        transform = TreeTransform(tree)
492
 
        self.addCleanup(transform.finalize)
493
 
        transform.new_file('file', transform.root, [b'content'])
494
 
        transform.new_file('FiLe', transform.root, [b'content'])
495
 
        result = transform.find_conflicts()
496
 
        self.assertEqual([], result)
497
 
        transform.finalize()
498
 
        # Force the tree to report that it is case insensitive, for conflict
499
 
        # generation tests
500
 
        tree.case_sensitive = False
501
 
        transform = TreeTransform(tree)
502
 
        self.addCleanup(transform.finalize)
503
 
        transform.new_file('file', transform.root, [b'content'])
504
 
        transform.new_file('FiLe', transform.root, [b'content'])
505
 
        result = transform.find_conflicts()
506
 
        self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
507
 
 
508
 
    def test_conflict_on_case_insensitive_existing(self):
509
 
        tree = self.make_branch_and_tree('tree')
510
 
        self.build_tree(['tree/FiLe'])
511
 
        # Don't try this at home, kids!
512
 
        # Force the tree to report that it is case sensitive, for conflict
513
 
        # resolution tests
514
 
        tree.case_sensitive = True
515
 
        transform = TreeTransform(tree)
516
 
        self.addCleanup(transform.finalize)
517
 
        transform.new_file('file', transform.root, [b'content'])
518
 
        result = transform.find_conflicts()
519
 
        self.assertEqual([], result)
520
 
        transform.finalize()
521
 
        # Force the tree to report that it is case insensitive, for conflict
522
 
        # generation tests
523
 
        tree.case_sensitive = False
524
 
        transform = TreeTransform(tree)
525
 
        self.addCleanup(transform.finalize)
526
 
        transform.new_file('file', transform.root, [b'content'])
527
 
        result = transform.find_conflicts()
528
 
        self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
529
 
 
530
 
    def test_resolve_case_insensitive_conflict(self):
531
 
        tree = self.make_branch_and_tree('tree')
532
 
        # Don't try this at home, kids!
533
 
        # Force the tree to report that it is case insensitive, for conflict
534
 
        # resolution tests
535
 
        tree.case_sensitive = False
536
 
        transform = TreeTransform(tree)
537
 
        self.addCleanup(transform.finalize)
538
 
        transform.new_file('file', transform.root, [b'content'])
539
 
        transform.new_file('FiLe', transform.root, [b'content'])
540
 
        resolve_conflicts(transform)
541
 
        transform.apply()
542
 
        self.assertPathExists('tree/file')
543
 
        self.assertPathExists('tree/FiLe.moved')
544
 
 
545
 
    def test_resolve_checkout_case_conflict(self):
546
 
        tree = self.make_branch_and_tree('tree')
547
 
        # Don't try this at home, kids!
548
 
        # Force the tree to report that it is case insensitive, for conflict
549
 
        # resolution tests
550
 
        tree.case_sensitive = False
551
 
        transform = TreeTransform(tree)
552
 
        self.addCleanup(transform.finalize)
553
 
        transform.new_file('file', transform.root, [b'content'])
554
 
        transform.new_file('FiLe', transform.root, [b'content'])
555
 
        resolve_conflicts(transform,
556
 
                          pass_func=lambda t, c: resolve_checkout(t, c, []))
557
 
        transform.apply()
558
 
        self.assertPathExists('tree/file')
559
 
        self.assertPathExists('tree/FiLe.moved')
560
 
 
561
 
    def test_apply_case_conflict(self):
562
 
        """Ensure that a transform with case conflicts can always be applied"""
563
 
        tree = self.make_branch_and_tree('tree')
564
 
        transform = TreeTransform(tree)
565
 
        self.addCleanup(transform.finalize)
566
 
        transform.new_file('file', transform.root, [b'content'])
567
 
        transform.new_file('FiLe', transform.root, [b'content'])
568
 
        dir = transform.new_directory('dir', transform.root)
569
 
        transform.new_file('dirfile', dir, [b'content'])
570
 
        transform.new_file('dirFiLe', dir, [b'content'])
571
 
        resolve_conflicts(transform)
572
 
        transform.apply()
573
 
        self.assertPathExists('tree/file')
574
 
        if not os.path.exists('tree/FiLe.moved'):
575
 
            self.assertPathExists('tree/FiLe')
576
 
        self.assertPathExists('tree/dir/dirfile')
577
 
        if not os.path.exists('tree/dir/dirFiLe.moved'):
578
 
            self.assertPathExists('tree/dir/dirFiLe')
579
 
 
580
 
    def test_case_insensitive_limbo(self):
581
 
        tree = self.make_branch_and_tree('tree')
582
 
        # Don't try this at home, kids!
583
 
        # Force the tree to report that it is case insensitive
584
 
        tree.case_sensitive = False
585
 
        transform = TreeTransform(tree)
586
 
        self.addCleanup(transform.finalize)
587
 
        dir = transform.new_directory('dir', transform.root)
588
 
        first = transform.new_file('file', dir, [b'content'])
589
 
        second = transform.new_file('FiLe', dir, [b'content'])
590
 
        self.assertContainsRe(transform._limbo_name(first), 'new-1/file')
591
 
        self.assertNotContainsRe(transform._limbo_name(second), 'new-1/FiLe')
592
 
 
593
 
    def test_adjust_path_updates_child_limbo_names(self):
594
 
        tree = self.make_branch_and_tree('tree')
595
 
        transform = TreeTransform(tree)
596
 
        self.addCleanup(transform.finalize)
597
 
        foo_id = transform.new_directory('foo', transform.root)
598
 
        bar_id = transform.new_directory('bar', foo_id)
599
 
        baz_id = transform.new_directory('baz', bar_id)
600
 
        qux_id = transform.new_directory('qux', baz_id)
601
 
        transform.adjust_path('quxx', foo_id, bar_id)
602
 
        self.assertStartsWith(transform._limbo_name(qux_id),
603
 
                              transform._limbo_name(bar_id))
604
 
 
605
 
    def test_add_del(self):
606
 
        start, root = self.get_transform()
607
 
        start.new_directory('a', root, b'a')
608
 
        start.apply()
609
 
        transform, root = self.get_transform()
610
 
        transform.delete_versioned(transform.trans_id_tree_path('a'))
611
 
        transform.new_directory('a', root, b'a')
612
 
        transform.apply()
613
 
 
614
 
    def test_unversioning(self):
615
 
        create_tree, root = self.get_transform()
616
 
        parent_id = create_tree.new_directory('parent', root, b'parent-id')
617
 
        create_tree.new_file('child', parent_id, [b'child'], b'child-id')
618
 
        create_tree.apply()
619
 
        unversion = TreeTransform(self.wt)
620
 
        self.addCleanup(unversion.finalize)
621
 
        parent = unversion.trans_id_tree_path('parent')
622
 
        unversion.unversion_file(parent)
623
 
        self.assertEqual(unversion.find_conflicts(),
624
 
                         [('unversioned parent', parent_id)])
625
 
        file_id = unversion.trans_id_tree_path('parent/child')
626
 
        unversion.unversion_file(file_id)
627
 
        unversion.apply()
628
 
 
629
 
    def test_name_invariants(self):
630
 
        create_tree, root = self.get_transform()
631
 
        # prepare tree
632
 
        root = create_tree.root
633
 
        create_tree.new_file('name1', root, [b'hello1'], b'name1')
634
 
        create_tree.new_file('name2', root, [b'hello2'], b'name2')
635
 
        ddir = create_tree.new_directory('dying_directory', root, b'ddir')
636
 
        create_tree.new_file('dying_file', ddir, [b'goodbye1'], b'dfile')
637
 
        create_tree.new_file('moving_file', ddir, [b'later1'], b'mfile')
638
 
        create_tree.new_file('moving_file2', root, [b'later2'], b'mfile2')
639
 
        create_tree.apply()
640
 
 
641
 
        mangle_tree, root = self.get_transform()
642
 
        root = mangle_tree.root
643
 
        # swap names
644
 
        name1 = mangle_tree.trans_id_tree_path('name1')
645
 
        name2 = mangle_tree.trans_id_tree_path('name2')
646
 
        mangle_tree.adjust_path('name2', root, name1)
647
 
        mangle_tree.adjust_path('name1', root, name2)
648
 
 
649
 
        # tests for deleting parent directories
650
 
        ddir = mangle_tree.trans_id_tree_path('dying_directory')
651
 
        mangle_tree.delete_contents(ddir)
652
 
        dfile = mangle_tree.trans_id_tree_path('dying_directory/dying_file')
653
 
        mangle_tree.delete_versioned(dfile)
654
 
        mangle_tree.unversion_file(dfile)
655
 
        mfile = mangle_tree.trans_id_tree_path('dying_directory/moving_file')
656
 
        mangle_tree.adjust_path('mfile', root, mfile)
657
 
 
658
 
        # tests for adding parent directories
659
 
        newdir = mangle_tree.new_directory('new_directory', root, b'newdir')
660
 
        mfile2 = mangle_tree.trans_id_tree_path('moving_file2')
661
 
        mangle_tree.adjust_path('mfile2', newdir, mfile2)
662
 
        mangle_tree.new_file('newfile', newdir, [b'hello3'], b'dfile')
663
 
        self.assertEqual(mangle_tree.final_file_id(mfile2), b'mfile2')
664
 
        self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
665
 
        self.assertEqual(mangle_tree.final_file_id(mfile2), b'mfile2')
666
 
        mangle_tree.apply()
667
 
        with open(self.wt.abspath('name1'), 'r') as f:
668
 
            self.assertEqual(f.read(), 'hello2')
669
 
        with open(self.wt.abspath('name2'), 'r') as f:
670
 
            self.assertEqual(f.read(), 'hello1')
671
 
        mfile2_path = self.wt.abspath(pathjoin('new_directory', 'mfile2'))
672
 
        self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
673
 
        with open(mfile2_path, 'r') as f:
674
 
            self.assertEqual(f.read(), 'later2')
675
 
        self.assertEqual(self.wt.id2path(b'mfile2'), 'new_directory/mfile2')
676
 
        self.assertEqual(self.wt.path2id('new_directory/mfile2'), b'mfile2')
677
 
        newfile_path = self.wt.abspath(pathjoin('new_directory', 'newfile'))
678
 
        with open(newfile_path, 'r') as f:
679
 
            self.assertEqual(f.read(), 'hello3')
680
 
        self.assertEqual(self.wt.path2id('dying_directory'), b'ddir')
681
 
        self.assertIs(self.wt.path2id('dying_directory/dying_file'), None)
682
 
        mfile2_path = self.wt.abspath(pathjoin('new_directory', 'mfile2'))
683
 
 
684
 
    def test_both_rename(self):
685
 
        create_tree, root = self.get_transform()
686
 
        newdir = create_tree.new_directory('selftest', root, b'selftest-id')
687
 
        create_tree.new_file('blackbox.py', newdir, [
688
 
                             b'hello1'], b'blackbox-id')
689
 
        create_tree.apply()
690
 
        mangle_tree, root = self.get_transform()
691
 
        selftest = mangle_tree.trans_id_tree_path('selftest')
692
 
        blackbox = mangle_tree.trans_id_tree_path('selftest/blackbox.py')
693
 
        mangle_tree.adjust_path('test', root, selftest)
694
 
        mangle_tree.adjust_path('test_too_much', root, selftest)
695
 
        mangle_tree.set_executability(True, blackbox)
696
 
        mangle_tree.apply()
697
 
 
698
 
    def test_both_rename2(self):
699
 
        create_tree, root = self.get_transform()
700
 
        breezy = create_tree.new_directory('breezy', root, b'breezy-id')
701
 
        tests = create_tree.new_directory('tests', breezy, b'tests-id')
702
 
        blackbox = create_tree.new_directory('blackbox', tests, b'blackbox-id')
703
 
        create_tree.new_file('test_too_much.py', blackbox, [b'hello1'],
704
 
                             b'test_too_much-id')
705
 
        create_tree.apply()
706
 
        mangle_tree, root = self.get_transform()
707
 
        breezy = mangle_tree.trans_id_tree_path('breezy')
708
 
        tests = mangle_tree.trans_id_tree_path('breezy/tests')
709
 
        test_too_much = mangle_tree.trans_id_tree_path(
710
 
            'breezy/tests/blackbox/test_too_much.py')
711
 
        mangle_tree.adjust_path('selftest', breezy, tests)
712
 
        mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
713
 
        mangle_tree.set_executability(True, test_too_much)
714
 
        mangle_tree.apply()
715
 
 
716
 
    def test_both_rename3(self):
717
 
        create_tree, root = self.get_transform()
718
 
        tests = create_tree.new_directory('tests', root, b'tests-id')
719
 
        create_tree.new_file('test_too_much.py', tests, [b'hello1'],
720
 
                             b'test_too_much-id')
721
 
        create_tree.apply()
722
 
        mangle_tree, root = self.get_transform()
723
 
        tests = mangle_tree.trans_id_tree_path('tests')
724
 
        test_too_much = mangle_tree.trans_id_tree_path(
725
 
            'tests/test_too_much.py')
726
 
        mangle_tree.adjust_path('selftest', root, tests)
727
 
        mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
728
 
        mangle_tree.set_executability(True, test_too_much)
729
 
        mangle_tree.apply()
730
 
 
731
 
    def test_move_dangling_ie(self):
732
 
        create_tree, root = self.get_transform()
733
 
        # prepare tree
734
 
        root = create_tree.root
735
 
        create_tree.new_file('name1', root, [b'hello1'], b'name1')
736
 
        create_tree.apply()
737
 
        delete_contents, root = self.get_transform()
738
 
        file = delete_contents.trans_id_tree_path('name1')
739
 
        delete_contents.delete_contents(file)
740
 
        delete_contents.apply()
741
 
        move_id, root = self.get_transform()
742
 
        name1 = move_id.trans_id_tree_path('name1')
743
 
        newdir = move_id.new_directory('dir', root, b'newdir')
744
 
        move_id.adjust_path('name2', newdir, name1)
745
 
        move_id.apply()
746
 
 
747
 
    def test_replace_dangling_ie(self):
748
 
        create_tree, root = self.get_transform()
749
 
        # prepare tree
750
 
        root = create_tree.root
751
 
        create_tree.new_file('name1', root, [b'hello1'], b'name1')
752
 
        create_tree.apply()
753
 
        delete_contents = TreeTransform(self.wt)
754
 
        self.addCleanup(delete_contents.finalize)
755
 
        file = delete_contents.trans_id_tree_path('name1')
756
 
        delete_contents.delete_contents(file)
757
 
        delete_contents.apply()
758
 
        delete_contents.finalize()
759
 
        replace = TreeTransform(self.wt)
760
 
        self.addCleanup(replace.finalize)
761
 
        name2 = replace.new_file('name2', root, [b'hello2'], b'name1')
762
 
        conflicts = replace.find_conflicts()
763
 
        name1 = replace.trans_id_tree_path('name1')
764
 
        self.assertEqual(conflicts, [('duplicate id', name1, name2)])
765
 
        resolve_conflicts(replace)
766
 
        replace.apply()
767
 
 
768
 
    def _test_symlinks(self, link_name1, link_target1,
769
 
                       link_name2, link_target2):
770
 
 
771
 
        def ozpath(p):
772
 
            return 'oz/' + p
773
 
 
774
 
        self.requireFeature(SymlinkFeature)
775
 
        transform, root = self.get_transform()
776
 
        oz_id = transform.new_directory('oz', root, b'oz-id')
777
 
        transform.new_symlink(link_name1, oz_id, link_target1, b'wizard-id')
778
 
        wiz_id = transform.create_path(link_name2, oz_id)
779
 
        transform.create_symlink(link_target2, wiz_id)
780
 
        transform.version_file(b'wiz-id2', wiz_id)
781
 
        transform.set_executability(True, wiz_id)
782
 
        self.assertEqual(transform.find_conflicts(),
783
 
                         [('non-file executability', wiz_id)])
784
 
        transform.set_executability(None, wiz_id)
785
 
        transform.apply()
786
 
        self.assertEqual(self.wt.path2id(ozpath(link_name1)), b'wizard-id')
787
 
        self.assertEqual('symlink',
788
 
                         file_kind(self.wt.abspath(ozpath(link_name1))))
789
 
        self.assertEqual(link_target2,
790
 
                         osutils.readlink(self.wt.abspath(ozpath(link_name2))))
791
 
        self.assertEqual(link_target1,
792
 
                         osutils.readlink(self.wt.abspath(ozpath(link_name1))))
793
 
 
794
 
    def test_symlinks(self):
795
 
        self._test_symlinks('wizard', 'wizard-target',
796
 
                            'wizard2', 'behind_curtain')
797
 
 
798
 
    def test_symlinks_unicode(self):
799
 
        self.requireFeature(features.UnicodeFilenameFeature)
800
 
        self._test_symlinks(u'\N{Euro Sign}wizard',
801
 
                            u'wizard-targ\N{Euro Sign}t',
802
 
                            u'\N{Euro Sign}wizard2',
803
 
                            u'b\N{Euro Sign}hind_curtain')
804
 
 
805
 
    def test_unsupported_symlink_no_conflict(self):
806
 
        def tt_helper():
807
 
            wt = self.make_branch_and_tree('.')
808
 
            tt = TreeTransform(wt)
809
 
            self.addCleanup(tt.finalize)
810
 
            tt.new_symlink('foo', tt.root, 'bar')
811
 
            result = tt.find_conflicts()
812
 
            self.assertEqual([], result)
813
 
        os_symlink = getattr(os, 'symlink', None)
814
 
        os.symlink = None
815
 
        try:
816
 
            tt_helper()
817
 
        finally:
818
 
            if os_symlink:
819
 
                os.symlink = os_symlink
820
 
 
821
 
    def get_conflicted(self):
822
 
        create, root = self.get_transform()
823
 
        create.new_file('dorothy', root, [b'dorothy'], b'dorothy-id')
824
 
        oz = create.new_directory('oz', root, b'oz-id')
825
 
        create.new_directory('emeraldcity', oz, b'emerald-id')
826
 
        create.apply()
827
 
        conflicts, root = self.get_transform()
828
 
        # set up duplicate entry, duplicate id
829
 
        new_dorothy = conflicts.new_file('dorothy', root, [b'dorothy'],
830
 
                                         b'dorothy-id')
831
 
        old_dorothy = conflicts.trans_id_tree_path('dorothy')
832
 
        oz = conflicts.trans_id_tree_path('oz')
833
 
        # set up DeletedParent parent conflict
834
 
        conflicts.delete_versioned(oz)
835
 
        emerald = conflicts.trans_id_tree_path('oz/emeraldcity')
836
 
        # set up MissingParent conflict
837
 
        munchkincity = conflicts.trans_id_file_id(b'munchkincity-id')
838
 
        conflicts.adjust_path('munchkincity', root, munchkincity)
839
 
        conflicts.new_directory('auntem', munchkincity, b'auntem-id')
840
 
        # set up parent loop
841
 
        conflicts.adjust_path('emeraldcity', emerald, emerald)
842
 
        return conflicts, emerald, oz, old_dorothy, new_dorothy
843
 
 
844
 
    def test_conflict_resolution(self):
845
 
        conflicts, emerald, oz, old_dorothy, new_dorothy =\
846
 
            self.get_conflicted()
847
 
        resolve_conflicts(conflicts)
848
 
        self.assertEqual(conflicts.final_name(old_dorothy), 'dorothy.moved')
849
 
        self.assertIs(conflicts.final_file_id(old_dorothy), None)
850
 
        self.assertEqual(conflicts.final_name(new_dorothy), 'dorothy')
851
 
        self.assertEqual(conflicts.final_file_id(new_dorothy), b'dorothy-id')
852
 
        self.assertEqual(conflicts.final_parent(emerald), oz)
853
 
        conflicts.apply()
854
 
 
855
 
    def test_cook_conflicts(self):
856
 
        tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
857
 
        raw_conflicts = resolve_conflicts(tt)
858
 
        cooked_conflicts = cook_conflicts(raw_conflicts, tt)
859
 
        duplicate = DuplicateEntry('Moved existing file to', 'dorothy.moved',
860
 
                                   'dorothy', None, b'dorothy-id')
861
 
        self.assertEqual(cooked_conflicts[0], duplicate)
862
 
        duplicate_id = DuplicateID('Unversioned existing file',
863
 
                                   'dorothy.moved', 'dorothy', None,
864
 
                                   b'dorothy-id')
865
 
        self.assertEqual(cooked_conflicts[1], duplicate_id)
866
 
        missing_parent = MissingParent('Created directory', 'munchkincity',
867
 
                                       b'munchkincity-id')
868
 
        deleted_parent = DeletingParent('Not deleting', 'oz', b'oz-id')
869
 
        self.assertEqual(cooked_conflicts[2], missing_parent)
870
 
        unversioned_parent = UnversionedParent('Versioned directory',
871
 
                                               'munchkincity',
872
 
                                               b'munchkincity-id')
873
 
        unversioned_parent2 = UnversionedParent('Versioned directory', 'oz',
874
 
                                                b'oz-id')
875
 
        self.assertEqual(cooked_conflicts[3], unversioned_parent)
876
 
        parent_loop = ParentLoop(
877
 
            'Cancelled move', 'oz/emeraldcity',
878
 
            'oz/emeraldcity', b'emerald-id', b'emerald-id')
879
 
        self.assertEqual(cooked_conflicts[4], deleted_parent)
880
 
        self.assertEqual(cooked_conflicts[5], unversioned_parent2)
881
 
        self.assertEqual(cooked_conflicts[6], parent_loop)
882
 
        self.assertEqual(len(cooked_conflicts), 7)
883
 
        tt.finalize()
884
 
 
885
 
    def test_string_conflicts(self):
886
 
        tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
887
 
        raw_conflicts = resolve_conflicts(tt)
888
 
        cooked_conflicts = cook_conflicts(raw_conflicts, tt)
889
 
        tt.finalize()
890
 
        conflicts_s = [str(c) for c in cooked_conflicts]
891
 
        self.assertEqual(len(cooked_conflicts), len(conflicts_s))
892
 
        self.assertEqual(conflicts_s[0], 'Conflict adding file dorothy.  '
893
 
                                         'Moved existing file to '
894
 
                                         'dorothy.moved.')
895
 
        self.assertEqual(conflicts_s[1], 'Conflict adding id to dorothy.  '
896
 
                                         'Unversioned existing file '
897
 
                                         'dorothy.moved.')
898
 
        self.assertEqual(conflicts_s[2], 'Conflict adding files to'
899
 
                                         ' munchkincity.  Created directory.')
900
 
        self.assertEqual(conflicts_s[3], 'Conflict because munchkincity is not'
901
 
                                         ' versioned, but has versioned'
902
 
                                         ' children.  Versioned directory.')
903
 
        self.assertEqualDiff(
904
 
            conflicts_s[4], "Conflict: can't delete oz because it"
905
 
                            " is not empty.  Not deleting.")
906
 
        self.assertEqual(conflicts_s[5], 'Conflict because oz is not'
907
 
                                         ' versioned, but has versioned'
908
 
                                         ' children.  Versioned directory.')
909
 
        self.assertEqual(conflicts_s[6], 'Conflict moving oz/emeraldcity into'
910
 
                                         ' oz/emeraldcity. Cancelled move.')
911
 
 
912
 
    def prepare_wrong_parent_kind(self):
913
 
        tt, root = self.get_transform()
914
 
        tt.new_file('parent', root, [b'contents'], b'parent-id')
915
 
        tt.apply()
916
 
        tt, root = self.get_transform()
917
 
        parent_id = tt.trans_id_file_id(b'parent-id')
918
 
        tt.new_file('child,', parent_id, [b'contents2'], b'file-id')
919
 
        return tt
920
 
 
921
 
    def test_find_conflicts_wrong_parent_kind(self):
922
 
        tt = self.prepare_wrong_parent_kind()
923
 
        tt.find_conflicts()
924
 
 
925
 
    def test_resolve_conflicts_wrong_existing_parent_kind(self):
926
 
        tt = self.prepare_wrong_parent_kind()
927
 
        raw_conflicts = resolve_conflicts(tt)
928
 
        self.assertEqual({('non-directory parent', 'Created directory',
929
 
                           'new-3')}, raw_conflicts)
930
 
        cooked_conflicts = cook_conflicts(raw_conflicts, tt)
931
 
        self.assertEqual([NonDirectoryParent('Created directory', 'parent.new',
932
 
                                             b'parent-id')], cooked_conflicts)
933
 
        tt.apply()
934
 
        self.assertFalse(self.wt.is_versioned('parent'))
935
 
        self.assertEqual(b'parent-id', self.wt.path2id('parent.new'))
936
 
 
937
 
    def test_resolve_conflicts_wrong_new_parent_kind(self):
938
 
        tt, root = self.get_transform()
939
 
        parent_id = tt.new_directory('parent', root, b'parent-id')
940
 
        tt.new_file('child,', parent_id, [b'contents2'], b'file-id')
941
 
        tt.apply()
942
 
        tt, root = self.get_transform()
943
 
        parent_id = tt.trans_id_file_id(b'parent-id')
944
 
        tt.delete_contents(parent_id)
945
 
        tt.create_file([b'contents'], parent_id)
946
 
        raw_conflicts = resolve_conflicts(tt)
947
 
        self.assertEqual({('non-directory parent', 'Created directory',
948
 
                           'new-3')}, raw_conflicts)
949
 
        tt.apply()
950
 
        self.assertFalse(self.wt.is_versioned('parent'))
951
 
        self.assertEqual(b'parent-id', self.wt.path2id('parent.new'))
952
 
 
953
 
    def test_resolve_conflicts_wrong_parent_kind_unversioned(self):
954
 
        tt, root = self.get_transform()
955
 
        parent_id = tt.new_directory('parent', root)
956
 
        tt.new_file('child,', parent_id, [b'contents2'])
957
 
        tt.apply()
958
 
        tt, root = self.get_transform()
959
 
        parent_id = tt.trans_id_tree_path('parent')
960
 
        tt.delete_contents(parent_id)
961
 
        tt.create_file([b'contents'], parent_id)
962
 
        resolve_conflicts(tt)
963
 
        tt.apply()
964
 
        self.assertFalse(self.wt.is_versioned('parent'))
965
 
        self.assertFalse(self.wt.is_versioned('parent.new'))
966
 
 
967
 
    def test_resolve_conflicts_missing_parent(self):
968
 
        wt = self.make_branch_and_tree('.')
969
 
        tt = TreeTransform(wt)
970
 
        self.addCleanup(tt.finalize)
971
 
        parent = tt.trans_id_file_id(b'parent-id')
972
 
        tt.new_file('file', parent, [b'Contents'])
973
 
        raw_conflicts = resolve_conflicts(tt)
974
 
        # Since the directory doesn't exist it's seen as 'missing'.  So
975
 
        # 'resolve_conflicts' create a conflict asking for it to be created.
976
 
        self.assertLength(1, raw_conflicts)
977
 
        self.assertEqual(('missing parent', 'Created directory', 'new-1'),
978
 
                         raw_conflicts.pop())
979
 
        # apply fail since the missing directory doesn't exist
980
 
        self.assertRaises(errors.NoFinalPath, tt.apply)
981
 
 
982
 
    def test_moving_versioned_directories(self):
983
 
        create, root = self.get_transform()
984
 
        kansas = create.new_directory('kansas', root, b'kansas-id')
985
 
        create.new_directory('house', kansas, b'house-id')
986
 
        create.new_directory('oz', root, b'oz-id')
987
 
        create.apply()
988
 
        cyclone, root = self.get_transform()
989
 
        oz = cyclone.trans_id_tree_path('oz')
990
 
        house = cyclone.trans_id_tree_path('house')
991
 
        cyclone.adjust_path('house', oz, house)
992
 
        cyclone.apply()
993
 
 
994
 
    def test_moving_root(self):
995
 
        create, root = self.get_transform()
996
 
        fun = create.new_directory('fun', root, b'fun-id')
997
 
        create.new_directory('sun', root, b'sun-id')
998
 
        create.new_directory('moon', root, b'moon')
999
 
        create.apply()
1000
 
        transform, root = self.get_transform()
1001
 
        transform.adjust_root_path('oldroot', fun)
1002
 
        new_root = transform.trans_id_tree_path('')
1003
 
        transform.version_file(b'new-root', new_root)
1004
 
        transform.apply()
1005
 
 
1006
 
    def test_renames(self):
1007
 
        create, root = self.get_transform()
1008
 
        old = create.new_directory('old-parent', root, b'old-id')
1009
 
        intermediate = create.new_directory('intermediate', old, b'im-id')
1010
 
        myfile = create.new_file('myfile', intermediate, [b'myfile-text'],
1011
 
                                 b'myfile-id')
1012
 
        create.apply()
1013
 
        rename, root = self.get_transform()
1014
 
        old = rename.trans_id_file_id(b'old-id')
1015
 
        rename.adjust_path('new', root, old)
1016
 
        myfile = rename.trans_id_file_id(b'myfile-id')
1017
 
        rename.set_executability(True, myfile)
1018
 
        rename.apply()
1019
 
 
1020
 
    def test_rename_fails(self):
1021
 
        self.requireFeature(features.not_running_as_root)
1022
 
        # see https://bugs.launchpad.net/bzr/+bug/491763
1023
 
        create, root_id = self.get_transform()
1024
 
        create.new_directory('first-dir', root_id, b'first-id')
1025
 
        create.new_file('myfile', root_id, [b'myfile-text'], b'myfile-id')
1026
 
        create.apply()
1027
 
        if os.name == "posix" and sys.platform != "cygwin":
1028
 
            # posix filesystems fail on renaming if the readonly bit is set
1029
 
            osutils.make_readonly(self.wt.abspath('first-dir'))
1030
 
        elif os.name == "nt":
1031
 
            # windows filesystems fail on renaming open files
1032
 
            self.addCleanup(open(self.wt.abspath('myfile')).close)
1033
 
        else:
1034
 
            self.skipTest("Can't force a permissions error on rename")
1035
 
        # now transform to rename
1036
 
        rename_transform, root_id = self.get_transform()
1037
 
        file_trans_id = rename_transform.trans_id_file_id(b'myfile-id')
1038
 
        dir_id = rename_transform.trans_id_file_id(b'first-id')
1039
 
        rename_transform.adjust_path('newname', dir_id, file_trans_id)
1040
 
        e = self.assertRaises(errors.TransformRenameFailed,
1041
 
                              rename_transform.apply)
1042
 
        # On nix looks like:
1043
 
        # "Failed to rename .../work/.bzr/checkout/limbo/new-1
1044
 
        # to .../first-dir/newname: [Errno 13] Permission denied"
1045
 
        # On windows looks like:
1046
 
        # "Failed to rename .../work/myfile to
1047
 
        # .../work/.bzr/checkout/limbo/new-1: [Errno 13] Permission denied"
1048
 
        # This test isn't concerned with exactly what the error looks like,
1049
 
        # and the strerror will vary across OS and locales, but the assert
1050
 
        # that the exeception attributes are what we expect
1051
 
        self.assertEqual(e.errno, errno.EACCES)
1052
 
        if os.name == "posix":
1053
 
            self.assertEndsWith(e.to_path, "/first-dir/newname")
1054
 
        else:
1055
 
            self.assertEqual(os.path.basename(e.from_path), "myfile")
1056
 
 
1057
 
    def test_set_executability_order(self):
1058
 
        """Ensure that executability behaves the same, no matter what order.
1059
 
 
1060
 
        - create file and set executability simultaneously
1061
 
        - create file and set executability afterward
1062
 
        - unsetting the executability of a file whose executability has not
1063
 
          been
1064
 
        declared should throw an exception (this may happen when a
1065
 
        merge attempts to create a file with a duplicate ID)
1066
 
        """
1067
 
        transform, root = self.get_transform()
1068
 
        wt = transform._tree
1069
 
        wt.lock_read()
1070
 
        self.addCleanup(wt.unlock)
1071
 
        transform.new_file('set_on_creation', root, [b'Set on creation'],
1072
 
                           b'soc', True)
1073
 
        sac = transform.new_file('set_after_creation', root,
1074
 
                                 [b'Set after creation'], b'sac')
1075
 
        transform.set_executability(True, sac)
1076
 
        uws = transform.new_file('unset_without_set', root, [b'Unset badly'],
1077
 
                                 b'uws')
1078
 
        self.assertRaises(KeyError, transform.set_executability, None, uws)
1079
 
        transform.apply()
1080
 
        self.assertTrue(wt.is_executable('set_on_creation'))
1081
 
        self.assertTrue(wt.is_executable('set_after_creation'))
1082
 
 
1083
 
    def test_preserve_mode(self):
1084
 
        """File mode is preserved when replacing content"""
1085
 
        if sys.platform == 'win32':
1086
 
            raise TestSkipped('chmod has no effect on win32')
1087
 
        transform, root = self.get_transform()
1088
 
        transform.new_file('file1', root, [b'contents'], b'file1-id', True)
1089
 
        transform.apply()
1090
 
        self.wt.lock_write()
1091
 
        self.addCleanup(self.wt.unlock)
1092
 
        self.assertTrue(self.wt.is_executable('file1'))
1093
 
        transform, root = self.get_transform()
1094
 
        file1_id = transform.trans_id_tree_path('file1')
1095
 
        transform.delete_contents(file1_id)
1096
 
        transform.create_file([b'contents2'], file1_id)
1097
 
        transform.apply()
1098
 
        self.assertTrue(self.wt.is_executable('file1'))
1099
 
 
1100
 
    def test__set_mode_stats_correctly(self):
1101
 
        """_set_mode stats to determine file mode."""
1102
 
        if sys.platform == 'win32':
1103
 
            raise TestSkipped('chmod has no effect on win32')
1104
 
 
1105
 
        stat_paths = []
1106
 
        real_stat = os.stat
1107
 
 
1108
 
        def instrumented_stat(path):
1109
 
            stat_paths.append(path)
1110
 
            return real_stat(path)
1111
 
 
1112
 
        transform, root = self.get_transform()
1113
 
 
1114
 
        bar1_id = transform.new_file('bar', root, [b'bar contents 1\n'],
1115
 
                                     file_id=b'bar-id-1', executable=False)
1116
 
        transform.apply()
1117
 
 
1118
 
        transform, root = self.get_transform()
1119
 
        bar1_id = transform.trans_id_tree_path('bar')
1120
 
        bar2_id = transform.trans_id_tree_path('bar2')
1121
 
        try:
1122
 
            os.stat = instrumented_stat
1123
 
            transform.create_file([b'bar2 contents\n'],
1124
 
                                  bar2_id, mode_id=bar1_id)
1125
 
        finally:
1126
 
            os.stat = real_stat
1127
 
            transform.finalize()
1128
 
 
1129
 
        bar1_abspath = self.wt.abspath('bar')
1130
 
        self.assertEqual([bar1_abspath], stat_paths)
1131
 
 
1132
 
    def test_iter_changes(self):
1133
 
        self.wt.set_root_id(b'eert_toor')
1134
 
        transform, root = self.get_transform()
1135
 
        transform.new_file('old', root, [b'blah'], b'id-1', True)
1136
 
        transform.apply()
1137
 
        transform, root = self.get_transform()
1138
 
        try:
1139
 
            self.assertEqual([], list(transform.iter_changes()))
1140
 
            old = transform.trans_id_tree_path('old')
1141
 
            transform.unversion_file(old)
1142
 
            self.assertEqual([(b'id-1', ('old', None), False, (True, False),
1143
 
                               (b'eert_toor', b'eert_toor'),
1144
 
                               ('old', 'old'), ('file', 'file'),
1145
 
                               (True, True), False)],
1146
 
                             list(transform.iter_changes()))
1147
 
            transform.new_directory('new', root, b'id-1')
1148
 
            self.assertEqual([(b'id-1', ('old', 'new'), True, (True, True),
1149
 
                               (b'eert_toor', b'eert_toor'), ('old', 'new'),
1150
 
                               ('file', 'directory'),
1151
 
                               (True, False), False)],
1152
 
                             list(transform.iter_changes()))
1153
 
        finally:
1154
 
            transform.finalize()
1155
 
 
1156
 
    def test_iter_changes_new(self):
1157
 
        self.wt.set_root_id(b'eert_toor')
1158
 
        transform, root = self.get_transform()
1159
 
        transform.new_file('old', root, [b'blah'])
1160
 
        transform.apply()
1161
 
        transform, root = self.get_transform()
1162
 
        try:
1163
 
            old = transform.trans_id_tree_path('old')
1164
 
            transform.version_file(b'id-1', old)
1165
 
            self.assertEqual([(b'id-1', (None, 'old'), False, (False, True),
1166
 
                               (b'eert_toor', b'eert_toor'),
1167
 
                               ('old', 'old'), ('file', 'file'),
1168
 
                               (False, False), False)],
1169
 
                             list(transform.iter_changes()))
1170
 
        finally:
1171
 
            transform.finalize()
1172
 
 
1173
 
    def test_iter_changes_modifications(self):
1174
 
        self.wt.set_root_id(b'eert_toor')
1175
 
        transform, root = self.get_transform()
1176
 
        transform.new_file('old', root, [b'blah'], b'id-1')
1177
 
        transform.new_file('new', root, [b'blah'])
1178
 
        transform.new_directory('subdir', root, b'subdir-id')
1179
 
        transform.apply()
1180
 
        transform, root = self.get_transform()
1181
 
        try:
1182
 
            old = transform.trans_id_tree_path('old')
1183
 
            subdir = transform.trans_id_tree_path('subdir')
1184
 
            new = transform.trans_id_tree_path('new')
1185
 
            self.assertEqual([], list(transform.iter_changes()))
1186
 
 
1187
 
            # content deletion
1188
 
            transform.delete_contents(old)
1189
 
            self.assertEqual([(b'id-1', ('old', 'old'), True, (True, True),
1190
 
                               (b'eert_toor', b'eert_toor'),
1191
 
                               ('old', 'old'), ('file', None),
1192
 
                               (False, False), False)],
1193
 
                             list(transform.iter_changes()))
1194
 
 
1195
 
            # content change
1196
 
            transform.create_file([b'blah'], old)
1197
 
            self.assertEqual([(b'id-1', ('old', 'old'), True, (True, True),
1198
 
                               (b'eert_toor', b'eert_toor'),
1199
 
                               ('old', 'old'), ('file', 'file'),
1200
 
                               (False, False), False)],
1201
 
                             list(transform.iter_changes()))
1202
 
            transform.cancel_deletion(old)
1203
 
            self.assertEqual([(b'id-1', ('old', 'old'), True, (True, True),
1204
 
                               (b'eert_toor', b'eert_toor'),
1205
 
                               ('old', 'old'), ('file', 'file'),
1206
 
                               (False, False), False)],
1207
 
                             list(transform.iter_changes()))
1208
 
            transform.cancel_creation(old)
1209
 
 
1210
 
            # move file_id to a different file
1211
 
            self.assertEqual([], list(transform.iter_changes()))
1212
 
            transform.unversion_file(old)
1213
 
            transform.version_file(b'id-1', new)
1214
 
            transform.adjust_path('old', root, new)
1215
 
            self.assertEqual([(b'id-1', ('old', 'old'), True, (True, True),
1216
 
                               (b'eert_toor', b'eert_toor'),
1217
 
                               ('old', 'old'), ('file', 'file'),
1218
 
                               (False, False), False)],
1219
 
                             list(transform.iter_changes()))
1220
 
            transform.cancel_versioning(new)
1221
 
            transform._removed_id = set()
1222
 
 
1223
 
            # execute bit
1224
 
            self.assertEqual([], list(transform.iter_changes()))
1225
 
            transform.set_executability(True, old)
1226
 
            self.assertEqual([(b'id-1', ('old', 'old'), False, (True, True),
1227
 
                               (b'eert_toor', b'eert_toor'),
1228
 
                               ('old', 'old'), ('file', 'file'),
1229
 
                               (False, True), False)],
1230
 
                             list(transform.iter_changes()))
1231
 
            transform.set_executability(None, old)
1232
 
 
1233
 
            # filename
1234
 
            self.assertEqual([], list(transform.iter_changes()))
1235
 
            transform.adjust_path('new', root, old)
1236
 
            transform._new_parent = {}
1237
 
            self.assertEqual([(b'id-1', ('old', 'new'), False, (True, True),
1238
 
                               (b'eert_toor', b'eert_toor'),
1239
 
                               ('old', 'new'), ('file', 'file'),
1240
 
                               (False, False), False)],
1241
 
                             list(transform.iter_changes()))
1242
 
            transform._new_name = {}
1243
 
 
1244
 
            # parent directory
1245
 
            self.assertEqual([], list(transform.iter_changes()))
1246
 
            transform.adjust_path('new', subdir, old)
1247
 
            transform._new_name = {}
1248
 
            self.assertEqual([(b'id-1', ('old', 'subdir/old'), False,
1249
 
                               (True, True), (b'eert_toor',
1250
 
                                              b'subdir-id'), ('old', 'old'),
1251
 
                               ('file', 'file'), (False, False), False)],
1252
 
                             list(transform.iter_changes()))
1253
 
            transform._new_path = {}
1254
 
 
1255
 
        finally:
1256
 
            transform.finalize()
1257
 
 
1258
 
    def test_iter_changes_modified_bleed(self):
1259
 
        self.wt.set_root_id(b'eert_toor')
1260
 
        """Modified flag should not bleed from one change to another"""
1261
 
        # unfortunately, we have no guarantee that file1 (which is modified)
1262
 
        # will be applied before file2.  And if it's applied after file2, it
1263
 
        # obviously can't bleed into file2's change output.  But for now, it
1264
 
        # works.
1265
 
        transform, root = self.get_transform()
1266
 
        transform.new_file('file1', root, [b'blah'], b'id-1')
1267
 
        transform.new_file('file2', root, [b'blah'], b'id-2')
1268
 
        transform.apply()
1269
 
        transform, root = self.get_transform()
1270
 
        try:
1271
 
            transform.delete_contents(transform.trans_id_file_id(b'id-1'))
1272
 
            transform.set_executability(True,
1273
 
                                        transform.trans_id_file_id(b'id-2'))
1274
 
            self.assertEqual(
1275
 
                [(b'id-1', (u'file1', u'file1'), True, (True, True),
1276
 
                 (b'eert_toor', b'eert_toor'), ('file1', u'file1'),
1277
 
                 ('file', None), (False, False), False),
1278
 
                 (b'id-2', (u'file2', u'file2'), False, (True, True),
1279
 
                 (b'eert_toor', b'eert_toor'), ('file2', u'file2'),
1280
 
                 ('file', 'file'), (False, True), False)],
1281
 
                list(transform.iter_changes()))
1282
 
        finally:
1283
 
            transform.finalize()
1284
 
 
1285
 
    def test_iter_changes_move_missing(self):
1286
 
        """Test moving ids with no files around"""
1287
 
        self.wt.set_root_id(b'toor_eert')
1288
 
        # Need two steps because versioning a non-existant file is a conflict.
1289
 
        transform, root = self.get_transform()
1290
 
        transform.new_directory('floater', root, b'floater-id')
1291
 
        transform.apply()
1292
 
        transform, root = self.get_transform()
1293
 
        transform.delete_contents(transform.trans_id_tree_path('floater'))
1294
 
        transform.apply()
1295
 
        transform, root = self.get_transform()
1296
 
        floater = transform.trans_id_tree_path('floater')
1297
 
        try:
1298
 
            transform.adjust_path('flitter', root, floater)
1299
 
            self.assertEqual([(b'floater-id', ('floater', 'flitter'), False,
1300
 
                               (True, True),
1301
 
                               (b'toor_eert', b'toor_eert'),
1302
 
                               ('floater', 'flitter'),
1303
 
                               (None, None), (False, False), False)],
1304
 
                             list(transform.iter_changes()))
1305
 
        finally:
1306
 
            transform.finalize()
1307
 
 
1308
 
    def test_iter_changes_pointless(self):
1309
 
        """Ensure that no-ops are not treated as modifications"""
1310
 
        self.wt.set_root_id(b'eert_toor')
1311
 
        transform, root = self.get_transform()
1312
 
        transform.new_file('old', root, [b'blah'], b'id-1')
1313
 
        transform.new_directory('subdir', root, b'subdir-id')
1314
 
        transform.apply()
1315
 
        transform, root = self.get_transform()
1316
 
        try:
1317
 
            old = transform.trans_id_tree_path('old')
1318
 
            subdir = transform.trans_id_tree_path('subdir')
1319
 
            self.assertEqual([], list(transform.iter_changes()))
1320
 
            transform.delete_contents(subdir)
1321
 
            transform.create_directory(subdir)
1322
 
            transform.set_executability(False, old)
1323
 
            transform.unversion_file(old)
1324
 
            transform.version_file(b'id-1', old)
1325
 
            transform.adjust_path('old', root, old)
1326
 
            self.assertEqual([], list(transform.iter_changes()))
1327
 
        finally:
1328
 
            transform.finalize()
1329
 
 
1330
 
    def test_rename_count(self):
1331
 
        transform, root = self.get_transform()
1332
 
        transform.new_file('name1', root, [b'contents'])
1333
 
        self.assertEqual(transform.rename_count, 0)
1334
 
        transform.apply()
1335
 
        self.assertEqual(transform.rename_count, 1)
1336
 
        transform2, root = self.get_transform()
1337
 
        transform2.adjust_path('name2', root,
1338
 
                               transform2.trans_id_tree_path('name1'))
1339
 
        self.assertEqual(transform2.rename_count, 0)
1340
 
        transform2.apply()
1341
 
        self.assertEqual(transform2.rename_count, 2)
1342
 
 
1343
 
    def test_change_parent(self):
1344
 
        """Ensure that after we change a parent, the results are still right.
1345
 
 
1346
 
        Renames and parent changes on pending transforms can happen as part
1347
 
        of conflict resolution, and are explicitly permitted by the
1348
 
        TreeTransform API.
1349
 
 
1350
 
        This test ensures they work correctly with the rename-avoidance
1351
 
        optimization.
1352
 
        """
1353
 
        transform, root = self.get_transform()
1354
 
        parent1 = transform.new_directory('parent1', root)
1355
 
        child1 = transform.new_file('child1', parent1, [b'contents'])
1356
 
        parent2 = transform.new_directory('parent2', root)
1357
 
        transform.adjust_path('child1', parent2, child1)
1358
 
        transform.apply()
1359
 
        self.assertPathDoesNotExist(self.wt.abspath('parent1/child1'))
1360
 
        self.assertPathExists(self.wt.abspath('parent2/child1'))
1361
 
        # rename limbo/new-1 => parent1, rename limbo/new-3 => parent2
1362
 
        # no rename for child1 (counting only renames during apply)
1363
 
        self.assertEqual(2, transform.rename_count)
1364
 
 
1365
 
    def test_cancel_parent(self):
1366
 
        """Cancelling a parent doesn't cause deletion of a non-empty directory
1367
 
 
1368
 
        This is like the test_change_parent, except that we cancel the parent
1369
 
        before adjusting the path.  The transform must detect that the
1370
 
        directory is non-empty, and move children to safe locations.
1371
 
        """
1372
 
        transform, root = self.get_transform()
1373
 
        parent1 = transform.new_directory('parent1', root)
1374
 
        child1 = transform.new_file('child1', parent1, [b'contents'])
1375
 
        child2 = transform.new_file('child2', parent1, [b'contents'])
1376
 
        try:
1377
 
            transform.cancel_creation(parent1)
1378
 
        except OSError:
1379
 
            self.fail('Failed to move child1 before deleting parent1')
1380
 
        transform.cancel_creation(child2)
1381
 
        transform.create_directory(parent1)
1382
 
        try:
1383
 
            transform.cancel_creation(parent1)
1384
 
        # If the transform incorrectly believes that child2 is still in
1385
 
        # parent1's limbo directory, it will try to rename it and fail
1386
 
        # because was already moved by the first cancel_creation.
1387
 
        except OSError:
1388
 
            self.fail('Transform still thinks child2 is a child of parent1')
1389
 
        parent2 = transform.new_directory('parent2', root)
1390
 
        transform.adjust_path('child1', parent2, child1)
1391
 
        transform.apply()
1392
 
        self.assertPathDoesNotExist(self.wt.abspath('parent1'))
1393
 
        self.assertPathExists(self.wt.abspath('parent2/child1'))
1394
 
        # rename limbo/new-3 => parent2, rename limbo/new-2 => child1
1395
 
        self.assertEqual(2, transform.rename_count)
1396
 
 
1397
 
    def test_adjust_and_cancel(self):
1398
 
        """Make sure adjust_path keeps track of limbo children properly"""
1399
 
        transform, root = self.get_transform()
1400
 
        parent1 = transform.new_directory('parent1', root)
1401
 
        child1 = transform.new_file('child1', parent1, [b'contents'])
1402
 
        parent2 = transform.new_directory('parent2', root)
1403
 
        transform.adjust_path('child1', parent2, child1)
1404
 
        transform.cancel_creation(child1)
1405
 
        try:
1406
 
            transform.cancel_creation(parent1)
1407
 
        # if the transform thinks child1 is still in parent1's limbo
1408
 
        # directory, it will attempt to move it and fail.
1409
 
        except OSError:
1410
 
            self.fail('Transform still thinks child1 is a child of parent1')
1411
 
        transform.finalize()
1412
 
 
1413
 
    def test_noname_contents(self):
1414
 
        """TreeTransform should permit deferring naming files."""
1415
 
        transform, root = self.get_transform()
1416
 
        parent = transform.trans_id_file_id(b'parent-id')
1417
 
        try:
1418
 
            transform.create_directory(parent)
1419
 
        except KeyError:
1420
 
            self.fail("Can't handle contents with no name")
1421
 
        transform.finalize()
1422
 
 
1423
 
    def test_noname_contents_nested(self):
1424
 
        """TreeTransform should permit deferring naming files."""
1425
 
        transform, root = self.get_transform()
1426
 
        parent = transform.trans_id_file_id(b'parent-id')
1427
 
        try:
1428
 
            transform.create_directory(parent)
1429
 
        except KeyError:
1430
 
            self.fail("Can't handle contents with no name")
1431
 
        transform.new_directory('child', parent)
1432
 
        transform.adjust_path('parent', root, parent)
1433
 
        transform.apply()
1434
 
        self.assertPathExists(self.wt.abspath('parent/child'))
1435
 
        self.assertEqual(1, transform.rename_count)
1436
 
 
1437
 
    def test_reuse_name(self):
1438
 
        """Avoid reusing the same limbo name for different files"""
1439
 
        transform, root = self.get_transform()
1440
 
        parent = transform.new_directory('parent', root)
1441
 
        transform.new_directory('child', parent)
1442
 
        try:
1443
 
            child2 = transform.new_directory('child', parent)
1444
 
        except OSError:
1445
 
            self.fail('Tranform tried to use the same limbo name twice')
1446
 
        transform.adjust_path('child2', parent, child2)
1447
 
        transform.apply()
1448
 
        # limbo/new-1 => parent, limbo/new-3 => parent/child2
1449
 
        # child2 is put into top-level limbo because child1 has already
1450
 
        # claimed the direct limbo path when child2 is created.  There is no
1451
 
        # advantage in renaming files once they're in top-level limbo, except
1452
 
        # as part of apply.
1453
 
        self.assertEqual(2, transform.rename_count)
1454
 
 
1455
 
    def test_reuse_when_first_moved(self):
1456
 
        """Don't avoid direct paths when it is safe to use them"""
1457
 
        transform, root = self.get_transform()
1458
 
        parent = transform.new_directory('parent', root)
1459
 
        child1 = transform.new_directory('child', parent)
1460
 
        transform.adjust_path('child1', parent, child1)
1461
 
        transform.new_directory('child', parent)
1462
 
        transform.apply()
1463
 
        # limbo/new-1 => parent
1464
 
        self.assertEqual(1, transform.rename_count)
1465
 
 
1466
 
    def test_reuse_after_cancel(self):
1467
 
        """Don't avoid direct paths when it is safe to use them"""
1468
 
        transform, root = self.get_transform()
1469
 
        parent2 = transform.new_directory('parent2', root)
1470
 
        child1 = transform.new_directory('child1', parent2)
1471
 
        transform.cancel_creation(parent2)
1472
 
        transform.create_directory(parent2)
1473
 
        transform.new_directory('child1', parent2)
1474
 
        transform.adjust_path('child2', parent2, child1)
1475
 
        transform.apply()
1476
 
        # limbo/new-1 => parent2, limbo/new-2 => parent2/child1
1477
 
        self.assertEqual(2, transform.rename_count)
1478
 
 
1479
 
    def test_finalize_order(self):
1480
 
        """Finalize must be done in child-to-parent order"""
1481
 
        transform, root = self.get_transform()
1482
 
        parent = transform.new_directory('parent', root)
1483
 
        transform.new_directory('child', parent)
1484
 
        try:
1485
 
            transform.finalize()
1486
 
        except OSError:
1487
 
            self.fail('Tried to remove parent before child1')
1488
 
 
1489
 
    def test_cancel_with_cancelled_child_should_succeed(self):
1490
 
        transform, root = self.get_transform()
1491
 
        parent = transform.new_directory('parent', root)
1492
 
        child = transform.new_directory('child', parent)
1493
 
        transform.cancel_creation(child)
1494
 
        transform.cancel_creation(parent)
1495
 
        transform.finalize()
1496
 
 
1497
 
    def test_rollback_on_directory_clash(self):
1498
 
        def tt_helper():
1499
 
            wt = self.make_branch_and_tree('.')
1500
 
            tt = TreeTransform(wt)  # TreeTransform obtains write lock
1501
 
            try:
1502
 
                foo = tt.new_directory('foo', tt.root)
1503
 
                tt.new_file('bar', foo, [b'foobar'])
1504
 
                baz = tt.new_directory('baz', tt.root)
1505
 
                tt.new_file('qux', baz, [b'quux'])
1506
 
                # Ask for a rename 'foo' -> 'baz'
1507
 
                tt.adjust_path('baz', tt.root, foo)
1508
 
                # Lie to tt that we've already resolved all conflicts.
1509
 
                tt.apply(no_conflicts=True)
1510
 
            except BaseException:
1511
 
                wt.unlock()
1512
 
                raise
1513
 
        # The rename will fail because the target directory is not empty (but
1514
 
        # raises FileExists anyway).
1515
 
        err = self.assertRaises(errors.FileExists, tt_helper)
1516
 
        self.assertEndsWith(err.path, "/baz")
1517
 
 
1518
 
    def test_two_directories_clash(self):
1519
 
        def tt_helper():
1520
 
            wt = self.make_branch_and_tree('.')
1521
 
            tt = TreeTransform(wt)  # TreeTransform obtains write lock
1522
 
            try:
1523
 
                foo_1 = tt.new_directory('foo', tt.root)
1524
 
                tt.new_directory('bar', foo_1)
1525
 
                # Adding the same directory with a different content
1526
 
                foo_2 = tt.new_directory('foo', tt.root)
1527
 
                tt.new_directory('baz', foo_2)
1528
 
                # Lie to tt that we've already resolved all conflicts.
1529
 
                tt.apply(no_conflicts=True)
1530
 
            except BaseException:
1531
 
                wt.unlock()
1532
 
                raise
1533
 
        err = self.assertRaises(errors.FileExists, tt_helper)
1534
 
        self.assertEndsWith(err.path, "/foo")
1535
 
 
1536
 
    def test_two_directories_clash_finalize(self):
1537
 
        def tt_helper():
1538
 
            wt = self.make_branch_and_tree('.')
1539
 
            tt = TreeTransform(wt)  # TreeTransform obtains write lock
1540
 
            try:
1541
 
                foo_1 = tt.new_directory('foo', tt.root)
1542
 
                tt.new_directory('bar', foo_1)
1543
 
                # Adding the same directory with a different content
1544
 
                foo_2 = tt.new_directory('foo', tt.root)
1545
 
                tt.new_directory('baz', foo_2)
1546
 
                # Lie to tt that we've already resolved all conflicts.
1547
 
                tt.apply(no_conflicts=True)
1548
 
            except BaseException:
1549
 
                tt.finalize()
1550
 
                raise
1551
 
        err = self.assertRaises(errors.FileExists, tt_helper)
1552
 
        self.assertEndsWith(err.path, "/foo")
1553
 
 
1554
 
    def test_file_to_directory(self):
1555
 
        wt = self.make_branch_and_tree('.')
1556
 
        self.build_tree(['foo'])
1557
 
        wt.add(['foo'])
1558
 
        wt.commit("one")
1559
 
        tt = TreeTransform(wt)
1560
 
        self.addCleanup(tt.finalize)
1561
 
        foo_trans_id = tt.trans_id_tree_path("foo")
1562
 
        tt.delete_contents(foo_trans_id)
1563
 
        tt.create_directory(foo_trans_id)
1564
 
        bar_trans_id = tt.trans_id_tree_path("foo/bar")
1565
 
        tt.create_file([b"aa\n"], bar_trans_id)
1566
 
        tt.version_file(b"bar-1", bar_trans_id)
1567
 
        tt.apply()
1568
 
        self.assertPathExists("foo/bar")
1569
 
        wt.lock_read()
1570
 
        try:
1571
 
            self.assertEqual(wt.kind("foo"), "directory")
1572
 
        finally:
1573
 
            wt.unlock()
1574
 
        wt.commit("two")
1575
 
        changes = wt.changes_from(wt.basis_tree())
1576
 
        self.assertFalse(changes.has_changed(), changes)
1577
 
 
1578
 
    def test_file_to_symlink(self):
1579
 
        self.requireFeature(SymlinkFeature)
1580
 
        wt = self.make_branch_and_tree('.')
1581
 
        self.build_tree(['foo'])
1582
 
        wt.add(['foo'])
1583
 
        wt.commit("one")
1584
 
        tt = TreeTransform(wt)
1585
 
        self.addCleanup(tt.finalize)
1586
 
        foo_trans_id = tt.trans_id_tree_path("foo")
1587
 
        tt.delete_contents(foo_trans_id)
1588
 
        tt.create_symlink("bar", foo_trans_id)
1589
 
        tt.apply()
1590
 
        self.assertPathExists("foo")
1591
 
        wt.lock_read()
1592
 
        self.addCleanup(wt.unlock)
1593
 
        self.assertEqual(wt.kind("foo"), "symlink")
1594
 
 
1595
 
    def test_file_to_symlink_unsupported(self):
1596
 
        wt = self.make_branch_and_tree('.')
1597
 
        self.build_tree(['foo'])
1598
 
        wt.add(['foo'])
1599
 
        wt.commit("one")
1600
 
        self.overrideAttr(osutils, 'supports_symlinks', lambda p: False)
1601
 
        tt = TreeTransform(wt)
1602
 
        self.addCleanup(tt.finalize)
1603
 
        foo_trans_id = tt.trans_id_tree_path("foo")
1604
 
        tt.delete_contents(foo_trans_id)
1605
 
        log = BytesIO()
1606
 
        trace.push_log_file(log)
1607
 
        tt.create_symlink("bar", foo_trans_id)
1608
 
        tt.apply()
1609
 
        self.assertContainsRe(
1610
 
            log.getvalue(),
1611
 
            b'Unable to create symlink "foo" on this filesystem')
1612
 
 
1613
 
    def test_dir_to_file(self):
1614
 
        wt = self.make_branch_and_tree('.')
1615
 
        self.build_tree(['foo/', 'foo/bar'])
1616
 
        wt.add(['foo', 'foo/bar'])
1617
 
        wt.commit("one")
1618
 
        tt = TreeTransform(wt)
1619
 
        self.addCleanup(tt.finalize)
1620
 
        foo_trans_id = tt.trans_id_tree_path("foo")
1621
 
        bar_trans_id = tt.trans_id_tree_path("foo/bar")
1622
 
        tt.delete_contents(foo_trans_id)
1623
 
        tt.delete_versioned(bar_trans_id)
1624
 
        tt.create_file([b"aa\n"], foo_trans_id)
1625
 
        tt.apply()
1626
 
        self.assertPathExists("foo")
1627
 
        wt.lock_read()
1628
 
        self.addCleanup(wt.unlock)
1629
 
        self.assertEqual(wt.kind("foo"), "file")
1630
 
 
1631
 
    def test_dir_to_hardlink(self):
1632
 
        self.requireFeature(HardlinkFeature)
1633
 
        wt = self.make_branch_and_tree('.')
1634
 
        self.build_tree(['foo/', 'foo/bar'])
1635
 
        wt.add(['foo', 'foo/bar'])
1636
 
        wt.commit("one")
1637
 
        tt = TreeTransform(wt)
1638
 
        self.addCleanup(tt.finalize)
1639
 
        foo_trans_id = tt.trans_id_tree_path("foo")
1640
 
        bar_trans_id = tt.trans_id_tree_path("foo/bar")
1641
 
        tt.delete_contents(foo_trans_id)
1642
 
        tt.delete_versioned(bar_trans_id)
1643
 
        self.build_tree(['baz'])
1644
 
        tt.create_hardlink("baz", foo_trans_id)
1645
 
        tt.apply()
1646
 
        self.assertPathExists("foo")
1647
 
        self.assertPathExists("baz")
1648
 
        wt.lock_read()
1649
 
        self.addCleanup(wt.unlock)
1650
 
        self.assertEqual(wt.kind("foo"), "file")
1651
 
 
1652
 
    def test_no_final_path(self):
1653
 
        transform, root = self.get_transform()
1654
 
        trans_id = transform.trans_id_file_id(b'foo')
1655
 
        transform.create_file([b'bar'], trans_id)
1656
 
        transform.cancel_creation(trans_id)
1657
 
        transform.apply()
1658
 
 
1659
 
    def test_create_from_tree(self):
1660
 
        tree1 = self.make_branch_and_tree('tree1')
1661
 
        self.build_tree_contents([('tree1/foo/',), ('tree1/bar', b'baz')])
1662
 
        tree1.add(['foo', 'bar'], [b'foo-id', b'bar-id'])
1663
 
        tree2 = self.make_branch_and_tree('tree2')
1664
 
        tt = TreeTransform(tree2)
1665
 
        foo_trans_id = tt.create_path('foo', tt.root)
1666
 
        create_from_tree(tt, foo_trans_id, tree1, 'foo')
1667
 
        bar_trans_id = tt.create_path('bar', tt.root)
1668
 
        create_from_tree(tt, bar_trans_id, tree1, 'bar')
1669
 
        tt.apply()
1670
 
        self.assertEqual('directory', osutils.file_kind('tree2/foo'))
1671
 
        self.assertFileEqual(b'baz', 'tree2/bar')
1672
 
 
1673
 
    def test_create_from_tree_bytes(self):
1674
 
        """Provided lines are used instead of tree content."""
1675
 
        tree1 = self.make_branch_and_tree('tree1')
1676
 
        self.build_tree_contents([('tree1/foo', b'bar'), ])
1677
 
        tree1.add('foo', b'foo-id')
1678
 
        tree2 = self.make_branch_and_tree('tree2')
1679
 
        tt = TreeTransform(tree2)
1680
 
        foo_trans_id = tt.create_path('foo', tt.root)
1681
 
        create_from_tree(tt, foo_trans_id, tree1, 'foo', chunks=[b'qux'])
1682
 
        tt.apply()
1683
 
        self.assertFileEqual(b'qux', 'tree2/foo')
1684
 
 
1685
 
    def test_create_from_tree_symlink(self):
1686
 
        self.requireFeature(SymlinkFeature)
1687
 
        tree1 = self.make_branch_and_tree('tree1')
1688
 
        os.symlink('bar', 'tree1/foo')
1689
 
        tree1.add('foo', b'foo-id')
1690
 
        tt = TreeTransform(self.make_branch_and_tree('tree2'))
1691
 
        foo_trans_id = tt.create_path('foo', tt.root)
1692
 
        create_from_tree(tt, foo_trans_id, tree1, 'foo')
1693
 
        tt.apply()
1694
 
        self.assertEqual('bar', os.readlink('tree2/foo'))
1695
 
 
1696
 
 
1697
89
class TransformGroup(object):
1698
90
 
1699
91
    def __init__(self, dirname, root_id):
1702
94
        self.wt = ControlDir.create_standalone_workingtree(dirname)
1703
95
        self.wt.set_root_id(root_id)
1704
96
        self.b = self.wt.branch
1705
 
        self.tt = TreeTransform(self.wt)
 
97
        self.tt = self.wt.transform()
1706
98
        self.root = self.tt.trans_id_tree_path('')
1707
99
 
1708
100
 
1711
103
    return template % (b'<' * 7, tree, b'=' * 7, merge, b'>' * 7)
1712
104
 
1713
105
 
1714
 
class TestInventoryAltered(tests.TestCaseWithTransport):
1715
 
 
1716
 
    def test_inventory_altered_unchanged(self):
1717
 
        tree = self.make_branch_and_tree('tree')
1718
 
        self.build_tree(['tree/foo'])
1719
 
        tree.add('foo', b'foo-id')
1720
 
        with TransformPreview(tree) as tt:
1721
 
            self.assertEqual([], tt._inventory_altered())
1722
 
 
1723
 
    def test_inventory_altered_changed_parent_id(self):
1724
 
        tree = self.make_branch_and_tree('tree')
1725
 
        self.build_tree(['tree/foo'])
1726
 
        tree.add('foo', b'foo-id')
1727
 
        with TransformPreview(tree) as tt:
1728
 
            tt.unversion_file(tt.root)
1729
 
            tt.version_file(b'new-id', tt.root)
1730
 
            foo_trans_id = tt.trans_id_tree_path('foo')
1731
 
            foo_tuple = ('foo', foo_trans_id)
1732
 
            root_tuple = ('', tt.root)
1733
 
            self.assertEqual([root_tuple, foo_tuple], tt._inventory_altered())
1734
 
 
1735
 
    def test_inventory_altered_noop_changed_parent_id(self):
1736
 
        tree = self.make_branch_and_tree('tree')
1737
 
        self.build_tree(['tree/foo'])
1738
 
        tree.add('foo', b'foo-id')
1739
 
        with TransformPreview(tree) as tt:
1740
 
            tt.unversion_file(tt.root)
1741
 
            tt.version_file(tree.path2id(''), tt.root)
1742
 
            tt.trans_id_tree_path('foo')
1743
 
            self.assertEqual([], tt._inventory_altered())
1744
 
 
1745
 
 
1746
106
class TestTransformMerge(TestCaseInTempDir):
1747
107
 
1748
108
    def test_text_merge(self):
2219
579
    def test_build_tree_hardlinks_preserve_execute(self):
2220
580
        self.requireFeature(HardlinkFeature)
2221
581
        source = self.create_ab_tree()
2222
 
        tt = TreeTransform(source)
 
582
        tt = source.transform()
2223
583
        trans_id = tt.trans_id_tree_path('file1')
2224
584
        tt.set_executability(True, trans_id)
2225
585
        tt.apply()
2349
709
 
2350
710
    def get_branch_and_transform(self):
2351
711
        branch = self.get_branch()
2352
 
        tt = TransformPreview(branch.basis_tree())
 
712
        tt = branch.basis_tree().preview_transform()
2353
713
        self.addCleanup(tt.finalize)
2354
714
        return branch, tt
2355
715
 
2357
717
        branch = self.get_branch()
2358
718
        basis = branch.repository.revision_tree(
2359
719
            _mod_revision.NULL_REVISION)
2360
 
        tt = TransformPreview(basis)
 
720
        tt = basis.preview_transform()
2361
721
        self.addCleanup(tt.finalize)
2362
722
        e = self.assertRaises(ValueError, tt.commit, branch, '')
2363
723
        self.assertEqual('TreeTransform not based on branch basis: null:',
2380
740
        branch = self.make_branch('branch')
2381
741
        branch.lock_write()
2382
742
        self.addCleanup(branch.unlock)
2383
 
        tt = TransformPreview(branch.basis_tree())
 
743
        tt = branch.basis_tree().preview_transform()
2384
744
        self.addCleanup(tt.finalize)
2385
745
        tt.new_directory('', ROOT_PARENT, b'TREE_ROOT')
2386
746
        tt.commit(branch, 'my message')
2392
752
        branch = self.make_branch('branch')
2393
753
        branch.lock_write()
2394
754
        self.addCleanup(branch.unlock)
2395
 
        tt = TransformPreview(branch.basis_tree())
 
755
        tt = branch.basis_tree().preview_transform()
2396
756
        self.addCleanup(tt.finalize)
2397
757
        e = self.assertRaises(ValueError, tt.commit, branch,
2398
758
                              'my message', [b'rev1b-id'])
2425
785
        branch, tt = self.get_branch_and_transform()
2426
786
        tt.new_file('file', tt.root, [b'contents'], b'file-id')
2427
787
        tt.commit(branch, 'message', strict=True)
2428
 
        tt = TransformPreview(branch.basis_tree())
 
788
        tt = branch.basis_tree().preview_transform()
2429
789
        self.addCleanup(tt.finalize)
2430
790
        trans_id = tt.trans_id_file_id(b'file-id')
2431
791
        tt.delete_contents(trans_id)
2440
800
        branch, tt = self.get_branch_and_transform()
2441
801
        parent_id = tt.trans_id_file_id(b'parent-id')
2442
802
        tt.new_file('file', parent_id, [b'contents'], b'file-id')
2443
 
        self.assertRaises(errors.MalformedTransform, tt.commit, branch,
 
803
        self.assertRaises(MalformedTransform, tt.commit, branch,
2444
804
                          'message')
2445
805
 
2446
806
    def test_commit_rich_revision_data(self):
2552
912
    def test_rollback_rename(self):
2553
913
        tree = self.make_branch_and_tree('.')
2554
914
        self.build_tree(['a/', 'a/b'])
2555
 
        tt = TreeTransform(tree)
 
915
        tt = tree.transform()
2556
916
        self.addCleanup(tt.finalize)
2557
917
        a_id = tt.trans_id_tree_path('a')
2558
918
        tt.adjust_path('c', tt.root, a_id)
2568
928
    def test_rollback_rename_into_place(self):
2569
929
        tree = self.make_branch_and_tree('.')
2570
930
        self.build_tree(['a/', 'a/b'])
2571
 
        tt = TreeTransform(tree)
 
931
        tt = tree.transform()
2572
932
        self.addCleanup(tt.finalize)
2573
933
        a_id = tt.trans_id_tree_path('a')
2574
934
        tt.adjust_path('c', tt.root, a_id)
2584
944
    def test_rollback_deletion(self):
2585
945
        tree = self.make_branch_and_tree('.')
2586
946
        self.build_tree(['a/', 'a/b'])
2587
 
        tt = TreeTransform(tree)
 
947
        tt = tree.transform()
2588
948
        self.addCleanup(tt.finalize)
2589
949
        a_id = tt.trans_id_tree_path('a')
2590
950
        tt.delete_contents(a_id)
2624
984
    def create_transform_and_root_trans_id(self):
2625
985
        """Setup a transform creating a file in limbo"""
2626
986
        tree = self.make_branch_and_tree('.')
2627
 
        tt = TreeTransform(tree)
 
987
        tt = tree.transform()
2628
988
        return tt, tt.create_path("a", tt.root)
2629
989
 
2630
990
    def create_transform_and_subdir_trans_id(self):
2631
991
        """Setup a transform creating a directory containing a file in limbo"""
2632
992
        tree = self.make_branch_and_tree('.')
2633
 
        tt = TreeTransform(tree)
 
993
        tt = tree.transform()
2634
994
        d_trans_id = tt.create_path("d", tt.root)
2635
995
        tt.create_directory(d_trans_id)
2636
996
        f_trans_id = tt.create_path("a", d_trans_id)
2730
1090
        self.build_tree(['dir/', ])
2731
1091
        wt.add(['dir'], [b'dir-id'])
2732
1092
        wt.commit('Create dir')
2733
 
        tt = TreeTransform(wt)
 
1093
        tt = wt.transform()
2734
1094
        self.addCleanup(tt.finalize)
2735
1095
        return wt, tt
2736
1096
 
2758
1118
                         conflicts.pop())
2759
1119
 
2760
1120
 
2761
 
A_ENTRY = (b'a-id', ('a', 'a'), True, (True, True),
2762
 
           (b'TREE_ROOT', b'TREE_ROOT'), ('a', 'a'), ('file', 'file'),
2763
 
           (False, False), False)
2764
 
ROOT_ENTRY = (b'TREE_ROOT', ('', ''), False, (True, True), (None, None),
2765
 
              ('', ''), ('directory', 'directory'), (False, False), False)
2766
 
 
2767
 
 
2768
 
class TestTransformPreview(tests.TestCaseWithTransport):
2769
 
 
2770
 
    def create_tree(self):
2771
 
        tree = self.make_branch_and_tree('.')
2772
 
        self.build_tree_contents([('a', b'content 1')])
2773
 
        tree.set_root_id(b'TREE_ROOT')
2774
 
        tree.add('a', b'a-id')
2775
 
        tree.commit('rev1', rev_id=b'rev1')
2776
 
        return tree.branch.repository.revision_tree(b'rev1')
2777
 
 
2778
 
    def get_empty_preview(self):
2779
 
        repository = self.make_repository('repo')
2780
 
        tree = repository.revision_tree(_mod_revision.NULL_REVISION)
2781
 
        preview = TransformPreview(tree)
2782
 
        self.addCleanup(preview.finalize)
2783
 
        return preview
2784
 
 
2785
 
    def test_transform_preview(self):
2786
 
        revision_tree = self.create_tree()
2787
 
        preview = TransformPreview(revision_tree)
2788
 
        self.addCleanup(preview.finalize)
2789
 
 
2790
 
    def test_transform_preview_tree(self):
2791
 
        revision_tree = self.create_tree()
2792
 
        preview = TransformPreview(revision_tree)
2793
 
        self.addCleanup(preview.finalize)
2794
 
        preview.get_preview_tree()
2795
 
 
2796
 
    def test_transform_new_file(self):
2797
 
        revision_tree = self.create_tree()
2798
 
        preview = TransformPreview(revision_tree)
2799
 
        self.addCleanup(preview.finalize)
2800
 
        preview.new_file('file2', preview.root, [b'content B\n'], b'file2-id')
2801
 
        preview_tree = preview.get_preview_tree()
2802
 
        self.assertEqual(preview_tree.kind('file2'), 'file')
2803
 
        with preview_tree.get_file('file2') as f:
2804
 
            self.assertEqual(f.read(), b'content B\n')
2805
 
 
2806
 
    def test_diff_preview_tree(self):
2807
 
        revision_tree = self.create_tree()
2808
 
        preview = TransformPreview(revision_tree)
2809
 
        self.addCleanup(preview.finalize)
2810
 
        preview.new_file('file2', preview.root, [b'content B\n'], b'file2-id')
2811
 
        preview_tree = preview.get_preview_tree()
2812
 
        out = BytesIO()
2813
 
        show_diff_trees(revision_tree, preview_tree, out)
2814
 
        lines = out.getvalue().splitlines()
2815
 
        self.assertEqual(lines[0], b"=== added file 'file2'")
2816
 
        # 3 lines of diff administrivia
2817
 
        self.assertEqual(lines[4], b"+content B")
2818
 
 
2819
 
    def test_unsupported_symlink_diff(self):
2820
 
        self.requireFeature(SymlinkFeature)
2821
 
        tree = self.make_branch_and_tree('.')
2822
 
        self.build_tree_contents([('a', 'content 1')])
2823
 
        tree.set_root_id(b'TREE_ROOT')
2824
 
        tree.add('a', b'a-id')
2825
 
        os.symlink('a', 'foo')
2826
 
        tree.add('foo', b'foo-id')
2827
 
        tree.commit('rev1', rev_id=b'rev1')
2828
 
        revision_tree = tree.branch.repository.revision_tree(b'rev1')
2829
 
        preview = TransformPreview(revision_tree)
2830
 
        self.addCleanup(preview.finalize)
2831
 
        preview.delete_versioned(preview.trans_id_tree_path('foo'))
2832
 
        preview_tree = preview.get_preview_tree()
2833
 
        out = BytesIO()
2834
 
        log = BytesIO()
2835
 
        trace.push_log_file(log)
2836
 
        os_symlink = getattr(os, 'symlink', None)
2837
 
        os.symlink = None
2838
 
        try:
2839
 
            show_diff_trees(revision_tree, preview_tree, out)
2840
 
            lines = out.getvalue().splitlines()
2841
 
        finally:
2842
 
            os.symlink = os_symlink
2843
 
        self.assertContainsRe(
2844
 
            log.getvalue(),
2845
 
            b'Ignoring "foo" as symlinks are not supported on this filesystem')
2846
 
 
2847
 
    def test_transform_conflicts(self):
2848
 
        revision_tree = self.create_tree()
2849
 
        preview = TransformPreview(revision_tree)
2850
 
        self.addCleanup(preview.finalize)
2851
 
        preview.new_file('a', preview.root, [b'content 2'])
2852
 
        resolve_conflicts(preview)
2853
 
        trans_id = preview.trans_id_file_id(b'a-id')
2854
 
        self.assertEqual('a.moved', preview.final_name(trans_id))
2855
 
 
2856
 
    def get_tree_and_preview_tree(self):
2857
 
        revision_tree = self.create_tree()
2858
 
        preview = TransformPreview(revision_tree)
2859
 
        self.addCleanup(preview.finalize)
2860
 
        a_trans_id = preview.trans_id_file_id(b'a-id')
2861
 
        preview.delete_contents(a_trans_id)
2862
 
        preview.create_file([b'b content'], a_trans_id)
2863
 
        preview_tree = preview.get_preview_tree()
2864
 
        return revision_tree, preview_tree
2865
 
 
2866
 
    def test_iter_changes(self):
2867
 
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
2868
 
        root = revision_tree.path2id('')
2869
 
        self.assertEqual([(b'a-id', ('a', 'a'), True, (True, True),
2870
 
                           (root, root), ('a', 'a'), ('file', 'file'),
2871
 
                           (False, False), False)],
2872
 
                         list(preview_tree.iter_changes(revision_tree)))
2873
 
 
2874
 
    def test_include_unchanged_succeeds(self):
2875
 
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
2876
 
        changes = preview_tree.iter_changes(revision_tree,
2877
 
                                            include_unchanged=True)
2878
 
        self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
2879
 
 
2880
 
    def test_specific_files(self):
2881
 
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
2882
 
        changes = preview_tree.iter_changes(revision_tree,
2883
 
                                            specific_files=[''])
2884
 
        self.assertEqual([A_ENTRY], list(changes))
2885
 
 
2886
 
    def test_want_unversioned(self):
2887
 
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
2888
 
        changes = preview_tree.iter_changes(revision_tree,
2889
 
                                            want_unversioned=True)
2890
 
        self.assertEqual([A_ENTRY], list(changes))
2891
 
 
2892
 
    def test_ignore_extra_trees_no_specific_files(self):
2893
 
        # extra_trees is harmless without specific_files, so we'll silently
2894
 
        # accept it, even though we won't use it.
2895
 
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
2896
 
        preview_tree.iter_changes(revision_tree, extra_trees=[preview_tree])
2897
 
 
2898
 
    def test_ignore_require_versioned_no_specific_files(self):
2899
 
        # require_versioned is meaningless without specific_files.
2900
 
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
2901
 
        preview_tree.iter_changes(revision_tree, require_versioned=False)
2902
 
 
2903
 
    def test_ignore_pb(self):
2904
 
        # pb could be supported, but TT.iter_changes doesn't support it.
2905
 
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
2906
 
        preview_tree.iter_changes(revision_tree)
2907
 
 
2908
 
    def test_kind(self):
2909
 
        revision_tree = self.create_tree()
2910
 
        preview = TransformPreview(revision_tree)
2911
 
        self.addCleanup(preview.finalize)
2912
 
        preview.new_file('file', preview.root, [b'contents'], b'file-id')
2913
 
        preview.new_directory('directory', preview.root, b'dir-id')
2914
 
        preview_tree = preview.get_preview_tree()
2915
 
        self.assertEqual('file', preview_tree.kind('file'))
2916
 
        self.assertEqual('directory', preview_tree.kind('directory'))
2917
 
 
2918
 
    def test_get_file_mtime(self):
2919
 
        preview = self.get_empty_preview()
2920
 
        file_trans_id = preview.new_file('file', preview.root, [b'contents'],
2921
 
                                         b'file-id')
2922
 
        limbo_path = preview._limbo_name(file_trans_id)
2923
 
        preview_tree = preview.get_preview_tree()
2924
 
        self.assertEqual(os.stat(limbo_path).st_mtime,
2925
 
                         preview_tree.get_file_mtime('file'))
2926
 
 
2927
 
    def test_get_file_mtime_renamed(self):
2928
 
        work_tree = self.make_branch_and_tree('tree')
2929
 
        self.build_tree(['tree/file'])
2930
 
        work_tree.add('file', b'file-id')
2931
 
        preview = TransformPreview(work_tree)
2932
 
        self.addCleanup(preview.finalize)
2933
 
        file_trans_id = preview.trans_id_tree_path('file')
2934
 
        preview.adjust_path('renamed', preview.root, file_trans_id)
2935
 
        preview_tree = preview.get_preview_tree()
2936
 
        preview_mtime = preview_tree.get_file_mtime('renamed')
2937
 
        work_mtime = work_tree.get_file_mtime('file')
2938
 
 
2939
 
    def test_get_file_size(self):
2940
 
        work_tree = self.make_branch_and_tree('tree')
2941
 
        self.build_tree_contents([('tree/old', b'old')])
2942
 
        work_tree.add('old', b'old-id')
2943
 
        preview = TransformPreview(work_tree)
2944
 
        self.addCleanup(preview.finalize)
2945
 
        preview.new_file('name', preview.root, [b'contents'], b'new-id',
2946
 
                         'executable')
2947
 
        tree = preview.get_preview_tree()
2948
 
        self.assertEqual(len('old'), tree.get_file_size('old'))
2949
 
        self.assertEqual(len('contents'), tree.get_file_size('name'))
2950
 
 
2951
 
    def test_get_file(self):
2952
 
        preview = self.get_empty_preview()
2953
 
        preview.new_file('file', preview.root, [b'contents'], b'file-id')
2954
 
        preview_tree = preview.get_preview_tree()
2955
 
        with preview_tree.get_file('file') as tree_file:
2956
 
            self.assertEqual(b'contents', tree_file.read())
2957
 
 
2958
 
    def test_get_symlink_target(self):
2959
 
        self.requireFeature(SymlinkFeature)
2960
 
        preview = self.get_empty_preview()
2961
 
        preview.new_symlink('symlink', preview.root, 'target', b'symlink-id')
2962
 
        preview_tree = preview.get_preview_tree()
2963
 
        self.assertEqual('target',
2964
 
                         preview_tree.get_symlink_target('symlink'))
2965
 
 
2966
 
    def test_all_file_ids(self):
2967
 
        tree = self.make_branch_and_tree('tree')
2968
 
        self.build_tree(['tree/a', 'tree/b', 'tree/c'])
2969
 
        tree.add(['a', 'b', 'c'], [b'a-id', b'b-id', b'c-id'])
2970
 
        preview = TransformPreview(tree)
2971
 
        self.addCleanup(preview.finalize)
2972
 
        preview.unversion_file(preview.trans_id_file_id(b'b-id'))
2973
 
        c_trans_id = preview.trans_id_file_id(b'c-id')
2974
 
        preview.unversion_file(c_trans_id)
2975
 
        preview.version_file(b'c-id', c_trans_id)
2976
 
        preview_tree = preview.get_preview_tree()
2977
 
        self.assertEqual({b'a-id', b'c-id', tree.path2id('')},
2978
 
                         preview_tree.all_file_ids())
2979
 
 
2980
 
    def test_path2id_deleted_unchanged(self):
2981
 
        tree = self.make_branch_and_tree('tree')
2982
 
        self.build_tree(['tree/unchanged', 'tree/deleted'])
2983
 
        tree.add(['unchanged', 'deleted'], [b'unchanged-id', b'deleted-id'])
2984
 
        preview = TransformPreview(tree)
2985
 
        self.addCleanup(preview.finalize)
2986
 
        preview.unversion_file(preview.trans_id_file_id(b'deleted-id'))
2987
 
        preview_tree = preview.get_preview_tree()
2988
 
        self.assertEqual(b'unchanged-id', preview_tree.path2id('unchanged'))
2989
 
        self.assertFalse(preview_tree.is_versioned('deleted'))
2990
 
 
2991
 
    def test_path2id_created(self):
2992
 
        tree = self.make_branch_and_tree('tree')
2993
 
        self.build_tree(['tree/unchanged'])
2994
 
        tree.add(['unchanged'], [b'unchanged-id'])
2995
 
        preview = TransformPreview(tree)
2996
 
        self.addCleanup(preview.finalize)
2997
 
        preview.new_file('new', preview.trans_id_file_id(b'unchanged-id'),
2998
 
                         [b'contents'], b'new-id')
2999
 
        preview_tree = preview.get_preview_tree()
3000
 
        self.assertEqual(b'new-id', preview_tree.path2id('unchanged/new'))
3001
 
 
3002
 
    def test_path2id_moved(self):
3003
 
        tree = self.make_branch_and_tree('tree')
3004
 
        self.build_tree(['tree/old_parent/', 'tree/old_parent/child'])
3005
 
        tree.add(['old_parent', 'old_parent/child'],
3006
 
                 [b'old_parent-id', b'child-id'])
3007
 
        preview = TransformPreview(tree)
3008
 
        self.addCleanup(preview.finalize)
3009
 
        new_parent = preview.new_directory('new_parent', preview.root,
3010
 
                                           b'new_parent-id')
3011
 
        preview.adjust_path('child', new_parent,
3012
 
                            preview.trans_id_file_id(b'child-id'))
3013
 
        preview_tree = preview.get_preview_tree()
3014
 
        self.assertFalse(preview_tree.is_versioned('old_parent/child'))
3015
 
        self.assertEqual(b'child-id', preview_tree.path2id('new_parent/child'))
3016
 
 
3017
 
    def test_path2id_renamed_parent(self):
3018
 
        tree = self.make_branch_and_tree('tree')
3019
 
        self.build_tree(['tree/old_name/', 'tree/old_name/child'])
3020
 
        tree.add(['old_name', 'old_name/child'],
3021
 
                 [b'parent-id', b'child-id'])
3022
 
        preview = TransformPreview(tree)
3023
 
        self.addCleanup(preview.finalize)
3024
 
        preview.adjust_path('new_name', preview.root,
3025
 
                            preview.trans_id_file_id(b'parent-id'))
3026
 
        preview_tree = preview.get_preview_tree()
3027
 
        self.assertFalse(preview_tree.is_versioned('old_name/child'))
3028
 
        self.assertEqual(b'child-id', preview_tree.path2id('new_name/child'))
3029
 
 
3030
 
    def assertMatchingIterEntries(self, tt, specific_files=None):
3031
 
        preview_tree = tt.get_preview_tree()
3032
 
        preview_result = list(preview_tree.iter_entries_by_dir(
3033
 
                              specific_files=specific_files))
3034
 
        tree = tt._tree
3035
 
        tt.apply()
3036
 
        actual_result = list(tree.iter_entries_by_dir(
3037
 
            specific_files=specific_files))
3038
 
        self.assertEqual(actual_result, preview_result)
3039
 
 
3040
 
    def test_iter_entries_by_dir_new(self):
3041
 
        tree = self.make_branch_and_tree('tree')
3042
 
        tt = TreeTransform(tree)
3043
 
        tt.new_file('new', tt.root, [b'contents'], b'new-id')
3044
 
        self.assertMatchingIterEntries(tt)
3045
 
 
3046
 
    def test_iter_entries_by_dir_deleted(self):
3047
 
        tree = self.make_branch_and_tree('tree')
3048
 
        self.build_tree(['tree/deleted'])
3049
 
        tree.add('deleted', b'deleted-id')
3050
 
        tt = TreeTransform(tree)
3051
 
        tt.delete_contents(tt.trans_id_file_id(b'deleted-id'))
3052
 
        self.assertMatchingIterEntries(tt)
3053
 
 
3054
 
    def test_iter_entries_by_dir_unversioned(self):
3055
 
        tree = self.make_branch_and_tree('tree')
3056
 
        self.build_tree(['tree/removed'])
3057
 
        tree.add('removed', b'removed-id')
3058
 
        tt = TreeTransform(tree)
3059
 
        tt.unversion_file(tt.trans_id_file_id(b'removed-id'))
3060
 
        self.assertMatchingIterEntries(tt)
3061
 
 
3062
 
    def test_iter_entries_by_dir_moved(self):
3063
 
        tree = self.make_branch_and_tree('tree')
3064
 
        self.build_tree(['tree/moved', 'tree/new_parent/'])
3065
 
        tree.add(['moved', 'new_parent'], [b'moved-id', b'new_parent-id'])
3066
 
        tt = TreeTransform(tree)
3067
 
        tt.adjust_path('moved', tt.trans_id_file_id(b'new_parent-id'),
3068
 
                       tt.trans_id_file_id(b'moved-id'))
3069
 
        self.assertMatchingIterEntries(tt)
3070
 
 
3071
 
    def test_iter_entries_by_dir_specific_files(self):
3072
 
        tree = self.make_branch_and_tree('tree')
3073
 
        tree.set_root_id(b'tree-root-id')
3074
 
        self.build_tree(['tree/parent/', 'tree/parent/child'])
3075
 
        tree.add(['parent', 'parent/child'], [b'parent-id', b'child-id'])
3076
 
        tt = TreeTransform(tree)
3077
 
        self.assertMatchingIterEntries(tt, ['', 'parent/child'])
3078
 
 
3079
 
    def test_symlink_content_summary(self):
3080
 
        self.requireFeature(SymlinkFeature)
3081
 
        preview = self.get_empty_preview()
3082
 
        preview.new_symlink('path', preview.root, 'target', b'path-id')
3083
 
        summary = preview.get_preview_tree().path_content_summary('path')
3084
 
        self.assertEqual(('symlink', None, None, 'target'), summary)
3085
 
 
3086
 
    def test_missing_content_summary(self):
3087
 
        preview = self.get_empty_preview()
3088
 
        summary = preview.get_preview_tree().path_content_summary('path')
3089
 
        self.assertEqual(('missing', None, None, None), summary)
3090
 
 
3091
 
    def test_deleted_content_summary(self):
3092
 
        tree = self.make_branch_and_tree('tree')
3093
 
        self.build_tree(['tree/path/'])
3094
 
        tree.add('path')
3095
 
        preview = TransformPreview(tree)
3096
 
        self.addCleanup(preview.finalize)
3097
 
        preview.delete_contents(preview.trans_id_tree_path('path'))
3098
 
        summary = preview.get_preview_tree().path_content_summary('path')
3099
 
        self.assertEqual(('missing', None, None, None), summary)
3100
 
 
3101
 
    def test_file_content_summary_executable(self):
3102
 
        preview = self.get_empty_preview()
3103
 
        path_id = preview.new_file('path', preview.root, [
3104
 
                                   b'contents'], b'path-id')
3105
 
        preview.set_executability(True, path_id)
3106
 
        summary = preview.get_preview_tree().path_content_summary('path')
3107
 
        self.assertEqual(4, len(summary))
3108
 
        self.assertEqual('file', summary[0])
3109
 
        # size must be known
3110
 
        self.assertEqual(len('contents'), summary[1])
3111
 
        # executable
3112
 
        self.assertEqual(True, summary[2])
3113
 
        # will not have hash (not cheap to determine)
3114
 
        self.assertIs(None, summary[3])
3115
 
 
3116
 
    def test_change_executability(self):
3117
 
        tree = self.make_branch_and_tree('tree')
3118
 
        self.build_tree(['tree/path'])
3119
 
        tree.add('path')
3120
 
        preview = TransformPreview(tree)
3121
 
        self.addCleanup(preview.finalize)
3122
 
        path_id = preview.trans_id_tree_path('path')
3123
 
        preview.set_executability(True, path_id)
3124
 
        summary = preview.get_preview_tree().path_content_summary('path')
3125
 
        self.assertEqual(True, summary[2])
3126
 
 
3127
 
    def test_file_content_summary_non_exec(self):
3128
 
        preview = self.get_empty_preview()
3129
 
        preview.new_file('path', preview.root, [b'contents'], b'path-id')
3130
 
        summary = preview.get_preview_tree().path_content_summary('path')
3131
 
        self.assertEqual(4, len(summary))
3132
 
        self.assertEqual('file', summary[0])
3133
 
        # size must be known
3134
 
        self.assertEqual(len('contents'), summary[1])
3135
 
        # not executable
3136
 
        self.assertEqual(False, summary[2])
3137
 
        # will not have hash (not cheap to determine)
3138
 
        self.assertIs(None, summary[3])
3139
 
 
3140
 
    def test_dir_content_summary(self):
3141
 
        preview = self.get_empty_preview()
3142
 
        preview.new_directory('path', preview.root, b'path-id')
3143
 
        summary = preview.get_preview_tree().path_content_summary('path')
3144
 
        self.assertEqual(('directory', None, None, None), summary)
3145
 
 
3146
 
    def test_tree_content_summary(self):
3147
 
        preview = self.get_empty_preview()
3148
 
        path = preview.new_directory('path', preview.root, b'path-id')
3149
 
        preview.set_tree_reference(b'rev-1', path)
3150
 
        summary = preview.get_preview_tree().path_content_summary('path')
3151
 
        self.assertEqual(4, len(summary))
3152
 
        self.assertEqual('tree-reference', summary[0])
3153
 
 
3154
 
    def test_annotate(self):
3155
 
        tree = self.make_branch_and_tree('tree')
3156
 
        self.build_tree_contents([('tree/file', b'a\n')])
3157
 
        tree.add('file', b'file-id')
3158
 
        tree.commit('a', rev_id=b'one')
3159
 
        self.build_tree_contents([('tree/file', b'a\nb\n')])
3160
 
        preview = TransformPreview(tree)
3161
 
        self.addCleanup(preview.finalize)
3162
 
        file_trans_id = preview.trans_id_file_id(b'file-id')
3163
 
        preview.delete_contents(file_trans_id)
3164
 
        preview.create_file([b'a\nb\nc\n'], file_trans_id)
3165
 
        preview_tree = preview.get_preview_tree()
3166
 
        expected = [
3167
 
            (b'one', b'a\n'),
3168
 
            (b'me:', b'b\n'),
3169
 
            (b'me:', b'c\n'),
3170
 
        ]
3171
 
        annotation = preview_tree.annotate_iter(
3172
 
            'file', default_revision=b'me:')
3173
 
        self.assertEqual(expected, annotation)
3174
 
 
3175
 
    def test_annotate_missing(self):
3176
 
        preview = self.get_empty_preview()
3177
 
        preview.new_file('file', preview.root, [b'a\nb\nc\n'], b'file-id')
3178
 
        preview_tree = preview.get_preview_tree()
3179
 
        expected = [
3180
 
            (b'me:', b'a\n'),
3181
 
            (b'me:', b'b\n'),
3182
 
            (b'me:', b'c\n'),
3183
 
            ]
3184
 
        annotation = preview_tree.annotate_iter(
3185
 
            'file', default_revision=b'me:')
3186
 
        self.assertEqual(expected, annotation)
3187
 
 
3188
 
    def test_annotate_rename(self):
3189
 
        tree = self.make_branch_and_tree('tree')
3190
 
        self.build_tree_contents([('tree/file', b'a\n')])
3191
 
        tree.add('file', b'file-id')
3192
 
        tree.commit('a', rev_id=b'one')
3193
 
        preview = TransformPreview(tree)
3194
 
        self.addCleanup(preview.finalize)
3195
 
        file_trans_id = preview.trans_id_file_id(b'file-id')
3196
 
        preview.adjust_path('newname', preview.root, file_trans_id)
3197
 
        preview_tree = preview.get_preview_tree()
3198
 
        expected = [
3199
 
            (b'one', b'a\n'),
3200
 
        ]
3201
 
        annotation = preview_tree.annotate_iter(
3202
 
            'file', default_revision=b'me:')
3203
 
        self.assertEqual(expected, annotation)
3204
 
 
3205
 
    def test_annotate_deleted(self):
3206
 
        tree = self.make_branch_and_tree('tree')
3207
 
        self.build_tree_contents([('tree/file', b'a\n')])
3208
 
        tree.add('file', b'file-id')
3209
 
        tree.commit('a', rev_id=b'one')
3210
 
        self.build_tree_contents([('tree/file', b'a\nb\n')])
3211
 
        preview = TransformPreview(tree)
3212
 
        self.addCleanup(preview.finalize)
3213
 
        file_trans_id = preview.trans_id_file_id(b'file-id')
3214
 
        preview.delete_contents(file_trans_id)
3215
 
        preview_tree = preview.get_preview_tree()
3216
 
        annotation = preview_tree.annotate_iter(
3217
 
            'file', default_revision=b'me:')
3218
 
        self.assertIs(None, annotation)
3219
 
 
3220
 
    def test_stored_kind(self):
3221
 
        preview = self.get_empty_preview()
3222
 
        preview.new_file('file', preview.root, [b'a\nb\nc\n'], b'file-id')
3223
 
        preview_tree = preview.get_preview_tree()
3224
 
        self.assertEqual('file', preview_tree.stored_kind('file'))
3225
 
 
3226
 
    def test_is_executable(self):
3227
 
        preview = self.get_empty_preview()
3228
 
        preview.new_file('file', preview.root, [b'a\nb\nc\n'], b'file-id')
3229
 
        preview.set_executability(True, preview.trans_id_file_id(b'file-id'))
3230
 
        preview_tree = preview.get_preview_tree()
3231
 
        self.assertEqual(True, preview_tree.is_executable('file'))
3232
 
 
3233
 
    def test_get_set_parent_ids(self):
3234
 
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
3235
 
        self.assertEqual([], preview_tree.get_parent_ids())
3236
 
        preview_tree.set_parent_ids([b'rev-1'])
3237
 
        self.assertEqual([b'rev-1'], preview_tree.get_parent_ids())
3238
 
 
3239
 
    def test_plan_file_merge(self):
3240
 
        work_a = self.make_branch_and_tree('wta')
3241
 
        self.build_tree_contents([('wta/file', b'a\nb\nc\nd\n')])
3242
 
        work_a.add('file', b'file-id')
3243
 
        base_id = work_a.commit('base version')
3244
 
        tree_b = work_a.controldir.sprout('wtb').open_workingtree()
3245
 
        preview = TransformPreview(work_a)
3246
 
        self.addCleanup(preview.finalize)
3247
 
        trans_id = preview.trans_id_file_id(b'file-id')
3248
 
        preview.delete_contents(trans_id)
3249
 
        preview.create_file([b'b\nc\nd\ne\n'], trans_id)
3250
 
        self.build_tree_contents([('wtb/file', b'a\nc\nd\nf\n')])
3251
 
        tree_a = preview.get_preview_tree()
3252
 
        tree_a.set_parent_ids([base_id])
3253
 
        self.assertEqual([
3254
 
            ('killed-a', b'a\n'),
3255
 
            ('killed-b', b'b\n'),
3256
 
            ('unchanged', b'c\n'),
3257
 
            ('unchanged', b'd\n'),
3258
 
            ('new-a', b'e\n'),
3259
 
            ('new-b', b'f\n'),
3260
 
        ], list(tree_a.plan_file_merge('file', tree_b)))
3261
 
 
3262
 
    def test_plan_file_merge_revision_tree(self):
3263
 
        work_a = self.make_branch_and_tree('wta')
3264
 
        self.build_tree_contents([('wta/file', b'a\nb\nc\nd\n')])
3265
 
        work_a.add('file', b'file-id')
3266
 
        base_id = work_a.commit('base version')
3267
 
        tree_b = work_a.controldir.sprout('wtb').open_workingtree()
3268
 
        preview = TransformPreview(work_a.basis_tree())
3269
 
        self.addCleanup(preview.finalize)
3270
 
        trans_id = preview.trans_id_file_id(b'file-id')
3271
 
        preview.delete_contents(trans_id)
3272
 
        preview.create_file([b'b\nc\nd\ne\n'], trans_id)
3273
 
        self.build_tree_contents([('wtb/file', b'a\nc\nd\nf\n')])
3274
 
        tree_a = preview.get_preview_tree()
3275
 
        tree_a.set_parent_ids([base_id])
3276
 
        self.assertEqual([
3277
 
            ('killed-a', b'a\n'),
3278
 
            ('killed-b', b'b\n'),
3279
 
            ('unchanged', b'c\n'),
3280
 
            ('unchanged', b'd\n'),
3281
 
            ('new-a', b'e\n'),
3282
 
            ('new-b', b'f\n'),
3283
 
        ], list(tree_a.plan_file_merge('file', tree_b)))
3284
 
 
3285
 
    def test_walkdirs(self):
3286
 
        preview = self.get_empty_preview()
3287
 
        preview.new_directory('', ROOT_PARENT, b'tree-root')
3288
 
        # FIXME: new_directory should mark root.
3289
 
        preview.fixup_new_roots()
3290
 
        preview_tree = preview.get_preview_tree()
3291
 
        preview.new_file('a', preview.root, [b'contents'], b'a-id')
3292
 
        expected = [(('', b'tree-root'),
3293
 
                     [('a', 'a', 'file', None, b'a-id', 'file')])]
3294
 
        self.assertEqual(expected, list(preview_tree.walkdirs()))
3295
 
 
3296
 
    def test_extras(self):
3297
 
        work_tree = self.make_branch_and_tree('tree')
3298
 
        self.build_tree(['tree/removed-file', 'tree/existing-file',
3299
 
                         'tree/not-removed-file'])
3300
 
        work_tree.add(['removed-file', 'not-removed-file'])
3301
 
        preview = TransformPreview(work_tree)
3302
 
        self.addCleanup(preview.finalize)
3303
 
        preview.new_file('new-file', preview.root, [b'contents'])
3304
 
        preview.new_file('new-versioned-file', preview.root, [b'contents'],
3305
 
                         b'new-versioned-id')
3306
 
        tree = preview.get_preview_tree()
3307
 
        preview.unversion_file(preview.trans_id_tree_path('removed-file'))
3308
 
        self.assertEqual({'new-file', 'removed-file', 'existing-file'},
3309
 
                         set(tree.extras()))
3310
 
 
3311
 
    def test_merge_into_preview(self):
3312
 
        work_tree = self.make_branch_and_tree('tree')
3313
 
        self.build_tree_contents([('tree/file', b'b\n')])
3314
 
        work_tree.add('file', b'file-id')
3315
 
        work_tree.commit('first commit')
3316
 
        child_tree = work_tree.controldir.sprout('child').open_workingtree()
3317
 
        self.build_tree_contents([('child/file', b'b\nc\n')])
3318
 
        child_tree.commit('child commit')
3319
 
        child_tree.lock_write()
3320
 
        self.addCleanup(child_tree.unlock)
3321
 
        work_tree.lock_write()
3322
 
        self.addCleanup(work_tree.unlock)
3323
 
        preview = TransformPreview(work_tree)
3324
 
        self.addCleanup(preview.finalize)
3325
 
        file_trans_id = preview.trans_id_file_id(b'file-id')
3326
 
        preview.delete_contents(file_trans_id)
3327
 
        preview.create_file([b'a\nb\n'], file_trans_id)
3328
 
        preview_tree = preview.get_preview_tree()
3329
 
        merger = Merger.from_revision_ids(preview_tree,
3330
 
                                          child_tree.branch.last_revision(),
3331
 
                                          other_branch=child_tree.branch,
3332
 
                                          tree_branch=work_tree.branch)
3333
 
        merger.merge_type = Merge3Merger
3334
 
        tt = merger.make_merger().make_preview_transform()
3335
 
        self.addCleanup(tt.finalize)
3336
 
        final_tree = tt.get_preview_tree()
3337
 
        self.assertEqual(
3338
 
            b'a\nb\nc\n',
3339
 
            final_tree.get_file_text(final_tree.id2path(b'file-id')))
3340
 
 
3341
 
    def test_merge_preview_into_workingtree(self):
3342
 
        tree = self.make_branch_and_tree('tree')
3343
 
        tree.set_root_id(b'TREE_ROOT')
3344
 
        tt = TransformPreview(tree)
3345
 
        self.addCleanup(tt.finalize)
3346
 
        tt.new_file('name', tt.root, [b'content'], b'file-id')
3347
 
        tree2 = self.make_branch_and_tree('tree2')
3348
 
        tree2.set_root_id(b'TREE_ROOT')
3349
 
        merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
3350
 
                                         tree.basis_tree())
3351
 
        merger.merge_type = Merge3Merger
3352
 
        merger.do_merge()
3353
 
 
3354
 
    def test_merge_preview_into_workingtree_handles_conflicts(self):
3355
 
        tree = self.make_branch_and_tree('tree')
3356
 
        self.build_tree_contents([('tree/foo', b'bar')])
3357
 
        tree.add('foo', b'foo-id')
3358
 
        tree.commit('foo')
3359
 
        tt = TransformPreview(tree)
3360
 
        self.addCleanup(tt.finalize)
3361
 
        trans_id = tt.trans_id_file_id(b'foo-id')
3362
 
        tt.delete_contents(trans_id)
3363
 
        tt.create_file([b'baz'], trans_id)
3364
 
        tree2 = tree.controldir.sprout('tree2').open_workingtree()
3365
 
        self.build_tree_contents([('tree2/foo', b'qux')])
3366
 
        merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
3367
 
                                         tree.basis_tree())
3368
 
        merger.merge_type = Merge3Merger
3369
 
        merger.do_merge()
3370
 
 
3371
 
    def test_has_filename(self):
3372
 
        wt = self.make_branch_and_tree('tree')
3373
 
        self.build_tree(['tree/unmodified', 'tree/removed', 'tree/modified'])
3374
 
        tt = TransformPreview(wt)
3375
 
        removed_id = tt.trans_id_tree_path('removed')
3376
 
        tt.delete_contents(removed_id)
3377
 
        tt.new_file('new', tt.root, [b'contents'])
3378
 
        modified_id = tt.trans_id_tree_path('modified')
3379
 
        tt.delete_contents(modified_id)
3380
 
        tt.create_file([b'modified-contents'], modified_id)
3381
 
        self.addCleanup(tt.finalize)
3382
 
        tree = tt.get_preview_tree()
3383
 
        self.assertTrue(tree.has_filename('unmodified'))
3384
 
        self.assertFalse(tree.has_filename('not-present'))
3385
 
        self.assertFalse(tree.has_filename('removed'))
3386
 
        self.assertTrue(tree.has_filename('new'))
3387
 
        self.assertTrue(tree.has_filename('modified'))
3388
 
 
3389
 
    def test_is_executable(self):
3390
 
        tree = self.make_branch_and_tree('tree')
3391
 
        preview = TransformPreview(tree)
3392
 
        self.addCleanup(preview.finalize)
3393
 
        preview.new_file('foo', preview.root, [b'bar'], b'baz-id')
3394
 
        preview_tree = preview.get_preview_tree()
3395
 
        self.assertEqual(False, preview_tree.is_executable('tree/foo'))
3396
 
 
3397
 
    def test_commit_preview_tree(self):
3398
 
        tree = self.make_branch_and_tree('tree')
3399
 
        rev_id = tree.commit('rev1')
3400
 
        tree.branch.lock_write()
3401
 
        self.addCleanup(tree.branch.unlock)
3402
 
        tt = TransformPreview(tree)
3403
 
        tt.new_file('file', tt.root, [b'contents'], b'file_id')
3404
 
        self.addCleanup(tt.finalize)
3405
 
        preview = tt.get_preview_tree()
3406
 
        preview.set_parent_ids([rev_id])
3407
 
        builder = tree.branch.get_commit_builder([rev_id])
3408
 
        list(builder.record_iter_changes(preview, rev_id, tt.iter_changes()))
3409
 
        builder.finish_inventory()
3410
 
        rev2_id = builder.commit('rev2')
3411
 
        rev2_tree = tree.branch.repository.revision_tree(rev2_id)
3412
 
        self.assertEqual(b'contents', rev2_tree.get_file_text('file'))
3413
 
 
3414
 
    def test_ascii_limbo_paths(self):
3415
 
        self.requireFeature(features.UnicodeFilenameFeature)
3416
 
        branch = self.make_branch('any')
3417
 
        tree = branch.repository.revision_tree(_mod_revision.NULL_REVISION)
3418
 
        tt = TransformPreview(tree)
3419
 
        self.addCleanup(tt.finalize)
3420
 
        foo_id = tt.new_directory('', ROOT_PARENT)
3421
 
        bar_id = tt.new_file(u'\u1234bar', foo_id, [b'contents'])
3422
 
        limbo_path = tt._limbo_name(bar_id)
3423
 
        self.assertEqual(limbo_path, limbo_path)
3424
 
 
3425
 
 
3426
1121
class FakeSerializer(object):
3427
1122
    """Serializer implementation that simply returns the input.
3428
1123
 
3440
1135
    def get_preview(self, tree=None):
3441
1136
        if tree is None:
3442
1137
            tree = self.make_branch_and_tree('tree')
3443
 
        tt = TransformPreview(tree)
 
1138
        tt = tree.preview_transform()
3444
1139
        self.addCleanup(tt.finalize)
3445
1140
        return tt
3446
1141
 
3500
1195
        self.assertEqual({'new-1': True}, tt._new_executability)
3501
1196
        self.assertEqual({'new-1': 'file',
3502
1197
                          'new-2': 'directory'}, tt._new_contents)
3503
 
        foo_limbo = open(tt._limbo_name('new-1'), 'rb')
3504
 
        try:
 
1198
        with open(tt._limbo_name('new-1'), 'rb') as foo_limbo:
3505
1199
            foo_content = foo_limbo.read()
3506
 
        finally:
3507
 
            foo_limbo.close()
3508
1200
        self.assertEqual(b'bar', foo_content)
3509
1201
 
3510
1202
    def symlink_creation_records(self):
3699
1391
 
3700
1392
    def test_no_orphan_for_transform_preview(self):
3701
1393
        tree = self.make_branch_and_tree('tree')
3702
 
        tt = transform.TransformPreview(tree)
 
1394
        tt = tree.preview_transform()
3703
1395
        self.addCleanup(tt.finalize)
3704
1396
        self.assertRaises(NotImplementedError, tt.new_orphan, 'foo', 'bar')
3705
1397
 
3711
1403
        self.build_tree(['dir/', 'dir/file', 'dir/foo'])
3712
1404
        wt.add(['dir', 'dir/file'], [b'dir-id', b'file-id'])
3713
1405
        wt.commit('add dir and file ignoring foo')
3714
 
        tt = transform.TreeTransform(wt)
 
1406
        tt = wt.transform()
3715
1407
        self.addCleanup(tt.finalize)
3716
1408
        # dir and bar are deleted
3717
1409
        dir_tid = tt.trans_id_tree_path('dir')
3795
1487
        self.wt = self.make_branch_and_tree('.')
3796
1488
        os.chdir('..')
3797
1489
 
3798
 
    def get_transform(self):
3799
 
        transform = TreeTransform(self.wt)
 
1490
    def transform(self):
 
1491
        transform = self.wt.transform()
3800
1492
        self.addCleanup(transform.finalize)
3801
1493
        return transform, transform.root
3802
1494
 
3807
1499
            calls.append((tree, tt))
3808
1500
        MutableTree.hooks.install_named_hook(
3809
1501
            'pre_transform', record_pre_transform, "Pre transform")
3810
 
        transform, root = self.get_transform()
 
1502
        transform, root = self.transform()
3811
1503
        old_root_id = transform.tree_file_id(root)
3812
1504
        transform.apply()
3813
1505
        self.assertEqual(old_root_id, self.wt.path2id(''))
3820
1512
            calls.append((tree, tt))
3821
1513
        MutableTree.hooks.install_named_hook(
3822
1514
            'post_transform', record_post_transform, "Post transform")
3823
 
        transform, root = self.get_transform()
 
1515
        transform, root = self.transform()
3824
1516
        old_root_id = transform.tree_file_id(root)
3825
1517
        transform.apply()
3826
1518
        self.assertEqual(old_root_id, self.wt.path2id(''))
3855
1547
 
3856
1548
    def test_link_fails_if_execute_bit_changed(self):
3857
1549
        """If the file to be linked has modified execute bit, don't link."""
3858
 
        tt = TreeTransform(self.child_tree)
 
1550
        tt = self.child_tree.transform()
3859
1551
        try:
3860
1552
            trans_id = tt.trans_id_tree_path('foo')
3861
1553
            tt.set_executability(True, trans_id)
3869
1561
        """If the file to be linked is unmodified, link"""
3870
1562
        transform.link_tree(self.child_tree, self.parent_tree)
3871
1563
        self.assertTrue(self.hardlinked())
 
1564
 
 
1565
 
 
1566
class ErrorTests(tests.TestCase):
 
1567
 
 
1568
    def test_transform_rename_failed(self):
 
1569
        e = TransformRenameFailed(u"from", u"to", "readonly file", 2)
 
1570
        self.assertEqual(
 
1571
            u"Failed to rename from to to: readonly file",
 
1572
            str(e))