/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-06-01 21:57:00 UTC
  • mfrom: (7490.39.3 move-launchpad)
  • Revision ID: breezy.the.bot@gmail.com-20200601215700-joxuzo6w172gq74v
Move launchpad hoster support to the launchpad plugin.

Merged from https://code.launchpad.net/~jelmer/brz/move-launchpad/+merge/384931

Show diffs side-by-side

added added

removed removed

Lines of Context:
36
36
from ..bzr import (
37
37
    generate_ids,
38
38
    )
39
 
from ..bzr.conflicts import (
 
39
from ..conflicts import (
40
40
    DeletingParent,
41
41
    DuplicateEntry,
42
42
    DuplicateID,
51
51
    DuplicateKey,
52
52
    ExistingLimbo,
53
53
    ExistingPendingDeletion,
 
54
    ImmortalLimbo,
54
55
    ImmortalPendingDeletion,
55
56
    LockError,
 
57
    MalformedTransform,
 
58
    ReusingTransform,
56
59
)
57
60
from ..osutils import (
58
61
    file_kind,
70
73
    SymlinkFeature,
71
74
    )
72
75
from ..transform import (
 
76
    build_tree,
73
77
    create_from_tree,
 
78
    cook_conflicts,
74
79
    _FileMover,
75
80
    FinalPaths,
76
81
    resolve_conflicts,
 
82
    resolve_checkout,
77
83
    ROOT_PARENT,
78
 
    ImmortalLimbo,
79
 
    MalformedTransform,
80
 
    NoFinalPath,
81
 
    ReusingTransform,
82
 
    TransformRenameFailed,
 
84
    TransformPreview,
 
85
    TreeTransform,
83
86
)
84
87
 
85
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
 
86
1697
class TransformGroup(object):
87
1698
 
88
1699
    def __init__(self, dirname, root_id):
91
1702
        self.wt = ControlDir.create_standalone_workingtree(dirname)
92
1703
        self.wt.set_root_id(root_id)
93
1704
        self.b = self.wt.branch
94
 
        self.tt = self.wt.transform()
 
1705
        self.tt = TreeTransform(self.wt)
95
1706
        self.root = self.tt.trans_id_tree_path('')
96
1707
 
97
1708
 
100
1711
    return template % (b'<' * 7, tree, b'=' * 7, merge, b'>' * 7)
101
1712
 
102
1713
 
 
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
 
103
1746
class TestTransformMerge(TestCaseInTempDir):
104
1747
 
105
1748
    def test_text_merge(self):
291
1934
        self.assertEqual(this.wt.id2path(b'i'), pathjoin('b/i1.OTHER'))
292
1935
 
293
1936
 
 
1937
class TestBuildTree(tests.TestCaseWithTransport):
 
1938
 
 
1939
    def test_build_tree_with_symlinks(self):
 
1940
        self.requireFeature(SymlinkFeature)
 
1941
        os.mkdir('a')
 
1942
        a = ControlDir.create_standalone_workingtree('a')
 
1943
        os.mkdir('a/foo')
 
1944
        with open('a/foo/bar', 'wb') as f:
 
1945
            f.write(b'contents')
 
1946
        os.symlink('a/foo/bar', 'a/foo/baz')
 
1947
        a.add(['foo', 'foo/bar', 'foo/baz'])
 
1948
        a.commit('initial commit')
 
1949
        b = ControlDir.create_standalone_workingtree('b')
 
1950
        basis = a.basis_tree()
 
1951
        basis.lock_read()
 
1952
        self.addCleanup(basis.unlock)
 
1953
        build_tree(basis, b)
 
1954
        self.assertIs(os.path.isdir('b/foo'), True)
 
1955
        with open('b/foo/bar', 'rb') as f:
 
1956
            self.assertEqual(f.read(), b"contents")
 
1957
        self.assertEqual(os.readlink('b/foo/baz'), 'a/foo/bar')
 
1958
 
 
1959
    def test_build_with_references(self):
 
1960
        tree = self.make_branch_and_tree('source',
 
1961
                                         format='development-subtree')
 
1962
        subtree = self.make_branch_and_tree('source/subtree',
 
1963
                                            format='development-subtree')
 
1964
        tree.add_reference(subtree)
 
1965
        tree.commit('a revision')
 
1966
        tree.branch.create_checkout('target')
 
1967
        self.assertPathExists('target')
 
1968
        self.assertPathExists('target/subtree')
 
1969
 
 
1970
    def test_file_conflict_handling(self):
 
1971
        """Ensure that when building trees, conflict handling is done"""
 
1972
        source = self.make_branch_and_tree('source')
 
1973
        target = self.make_branch_and_tree('target')
 
1974
        self.build_tree(['source/file', 'target/file'])
 
1975
        source.add('file', b'new-file')
 
1976
        source.commit('added file')
 
1977
        build_tree(source.basis_tree(), target)
 
1978
        self.assertEqual(
 
1979
            [DuplicateEntry('Moved existing file to', 'file.moved',
 
1980
                            'file', None, 'new-file')],
 
1981
            target.conflicts())
 
1982
        target2 = self.make_branch_and_tree('target2')
 
1983
        with open('target2/file', 'wb') as target_file, \
 
1984
                open('source/file', 'rb') as source_file:
 
1985
            target_file.write(source_file.read())
 
1986
        build_tree(source.basis_tree(), target2)
 
1987
        self.assertEqual([], target2.conflicts())
 
1988
 
 
1989
    def test_symlink_conflict_handling(self):
 
1990
        """Ensure that when building trees, conflict handling is done"""
 
1991
        self.requireFeature(SymlinkFeature)
 
1992
        source = self.make_branch_and_tree('source')
 
1993
        os.symlink('foo', 'source/symlink')
 
1994
        source.add('symlink', b'new-symlink')
 
1995
        source.commit('added file')
 
1996
        target = self.make_branch_and_tree('target')
 
1997
        os.symlink('bar', 'target/symlink')
 
1998
        build_tree(source.basis_tree(), target)
 
1999
        self.assertEqual(
 
2000
            [DuplicateEntry('Moved existing file to', 'symlink.moved',
 
2001
                            'symlink', None, 'new-symlink')],
 
2002
            target.conflicts())
 
2003
        target = self.make_branch_and_tree('target2')
 
2004
        os.symlink('foo', 'target2/symlink')
 
2005
        build_tree(source.basis_tree(), target)
 
2006
        self.assertEqual([], target.conflicts())
 
2007
 
 
2008
    def test_directory_conflict_handling(self):
 
2009
        """Ensure that when building trees, conflict handling is done"""
 
2010
        source = self.make_branch_and_tree('source')
 
2011
        target = self.make_branch_and_tree('target')
 
2012
        self.build_tree(['source/dir1/', 'source/dir1/file', 'target/dir1/'])
 
2013
        source.add(['dir1', 'dir1/file'], [b'new-dir1', b'new-file'])
 
2014
        source.commit('added file')
 
2015
        build_tree(source.basis_tree(), target)
 
2016
        self.assertEqual([], target.conflicts())
 
2017
        self.assertPathExists('target/dir1/file')
 
2018
 
 
2019
        # Ensure contents are merged
 
2020
        target = self.make_branch_and_tree('target2')
 
2021
        self.build_tree(['target2/dir1/', 'target2/dir1/file2'])
 
2022
        build_tree(source.basis_tree(), target)
 
2023
        self.assertEqual([], target.conflicts())
 
2024
        self.assertPathExists('target2/dir1/file2')
 
2025
        self.assertPathExists('target2/dir1/file')
 
2026
 
 
2027
        # Ensure new contents are suppressed for existing branches
 
2028
        target = self.make_branch_and_tree('target3')
 
2029
        self.make_branch('target3/dir1')
 
2030
        self.build_tree(['target3/dir1/file2'])
 
2031
        build_tree(source.basis_tree(), target)
 
2032
        self.assertPathDoesNotExist('target3/dir1/file')
 
2033
        self.assertPathExists('target3/dir1/file2')
 
2034
        self.assertPathExists('target3/dir1.diverted/file')
 
2035
        self.assertEqual(
 
2036
            [DuplicateEntry('Diverted to', 'dir1.diverted',
 
2037
                            'dir1', 'new-dir1', None)],
 
2038
            target.conflicts())
 
2039
 
 
2040
        target = self.make_branch_and_tree('target4')
 
2041
        self.build_tree(['target4/dir1/'])
 
2042
        self.make_branch('target4/dir1/file')
 
2043
        build_tree(source.basis_tree(), target)
 
2044
        self.assertPathExists('target4/dir1/file')
 
2045
        self.assertEqual('directory', file_kind('target4/dir1/file'))
 
2046
        self.assertPathExists('target4/dir1/file.diverted')
 
2047
        self.assertEqual(
 
2048
            [DuplicateEntry('Diverted to', 'dir1/file.diverted',
 
2049
                            'dir1/file', 'new-file', None)],
 
2050
            target.conflicts())
 
2051
 
 
2052
    def test_mixed_conflict_handling(self):
 
2053
        """Ensure that when building trees, conflict handling is done"""
 
2054
        source = self.make_branch_and_tree('source')
 
2055
        target = self.make_branch_and_tree('target')
 
2056
        self.build_tree(['source/name', 'target/name/'])
 
2057
        source.add('name', b'new-name')
 
2058
        source.commit('added file')
 
2059
        build_tree(source.basis_tree(), target)
 
2060
        self.assertEqual(
 
2061
            [DuplicateEntry('Moved existing file to',
 
2062
                            'name.moved', 'name', None, 'new-name')],
 
2063
            target.conflicts())
 
2064
 
 
2065
    def test_raises_in_populated(self):
 
2066
        source = self.make_branch_and_tree('source')
 
2067
        self.build_tree(['source/name'])
 
2068
        source.add('name')
 
2069
        source.commit('added name')
 
2070
        target = self.make_branch_and_tree('target')
 
2071
        self.build_tree(['target/name'])
 
2072
        target.add('name')
 
2073
        self.assertRaises(errors.WorkingTreeAlreadyPopulated,
 
2074
                          build_tree, source.basis_tree(), target)
 
2075
 
 
2076
    def test_build_tree_rename_count(self):
 
2077
        source = self.make_branch_and_tree('source')
 
2078
        self.build_tree(['source/file1', 'source/dir1/'])
 
2079
        source.add(['file1', 'dir1'])
 
2080
        source.commit('add1')
 
2081
        target1 = self.make_branch_and_tree('target1')
 
2082
        transform_result = build_tree(source.basis_tree(), target1)
 
2083
        self.assertEqual(2, transform_result.rename_count)
 
2084
 
 
2085
        self.build_tree(['source/dir1/file2'])
 
2086
        source.add(['dir1/file2'])
 
2087
        source.commit('add3')
 
2088
        target2 = self.make_branch_and_tree('target2')
 
2089
        transform_result = build_tree(source.basis_tree(), target2)
 
2090
        # children of non-root directories should not be renamed
 
2091
        self.assertEqual(2, transform_result.rename_count)
 
2092
 
 
2093
    def create_ab_tree(self):
 
2094
        """Create a committed test tree with two files"""
 
2095
        source = self.make_branch_and_tree('source')
 
2096
        self.build_tree_contents([('source/file1', b'A')])
 
2097
        self.build_tree_contents([('source/file2', b'B')])
 
2098
        source.add(['file1', 'file2'], [b'file1-id', b'file2-id'])
 
2099
        source.commit('commit files')
 
2100
        source.lock_write()
 
2101
        self.addCleanup(source.unlock)
 
2102
        return source
 
2103
 
 
2104
    def test_build_tree_accelerator_tree(self):
 
2105
        source = self.create_ab_tree()
 
2106
        self.build_tree_contents([('source/file2', b'C')])
 
2107
        calls = []
 
2108
        real_source_get_file = source.get_file
 
2109
 
 
2110
        def get_file(path):
 
2111
            calls.append(path)
 
2112
            return real_source_get_file(path)
 
2113
        source.get_file = get_file
 
2114
        target = self.make_branch_and_tree('target')
 
2115
        revision_tree = source.basis_tree()
 
2116
        revision_tree.lock_read()
 
2117
        self.addCleanup(revision_tree.unlock)
 
2118
        build_tree(revision_tree, target, source)
 
2119
        self.assertEqual(['file1'], calls)
 
2120
        target.lock_read()
 
2121
        self.addCleanup(target.unlock)
 
2122
        self.assertEqual([], list(target.iter_changes(revision_tree)))
 
2123
 
 
2124
    def test_build_tree_accelerator_tree_observes_sha1(self):
 
2125
        source = self.create_ab_tree()
 
2126
        sha1 = osutils.sha_string(b'A')
 
2127
        target = self.make_branch_and_tree('target')
 
2128
        target.lock_write()
 
2129
        self.addCleanup(target.unlock)
 
2130
        state = target.current_dirstate()
 
2131
        state._cutoff_time = time.time() + 60
 
2132
        build_tree(source.basis_tree(), target, source)
 
2133
        entry = state._get_entry(0, path_utf8=b'file1')
 
2134
        self.assertEqual(sha1, entry[1][0][1])
 
2135
 
 
2136
    def test_build_tree_accelerator_tree_missing_file(self):
 
2137
        source = self.create_ab_tree()
 
2138
        os.unlink('source/file1')
 
2139
        source.remove(['file2'])
 
2140
        target = self.make_branch_and_tree('target')
 
2141
        revision_tree = source.basis_tree()
 
2142
        revision_tree.lock_read()
 
2143
        self.addCleanup(revision_tree.unlock)
 
2144
        build_tree(revision_tree, target, source)
 
2145
        target.lock_read()
 
2146
        self.addCleanup(target.unlock)
 
2147
        self.assertEqual([], list(target.iter_changes(revision_tree)))
 
2148
 
 
2149
    def test_build_tree_accelerator_wrong_kind(self):
 
2150
        self.requireFeature(SymlinkFeature)
 
2151
        source = self.make_branch_and_tree('source')
 
2152
        self.build_tree_contents([('source/file1', b'')])
 
2153
        self.build_tree_contents([('source/file2', b'')])
 
2154
        source.add(['file1', 'file2'], [b'file1-id', b'file2-id'])
 
2155
        source.commit('commit files')
 
2156
        os.unlink('source/file2')
 
2157
        self.build_tree_contents([('source/file2/', b'C')])
 
2158
        os.unlink('source/file1')
 
2159
        os.symlink('file2', 'source/file1')
 
2160
        calls = []
 
2161
        real_source_get_file = source.get_file
 
2162
 
 
2163
        def get_file(path):
 
2164
            calls.append(path)
 
2165
            return real_source_get_file(path)
 
2166
        source.get_file = get_file
 
2167
        target = self.make_branch_and_tree('target')
 
2168
        revision_tree = source.basis_tree()
 
2169
        revision_tree.lock_read()
 
2170
        self.addCleanup(revision_tree.unlock)
 
2171
        build_tree(revision_tree, target, source)
 
2172
        self.assertEqual([], calls)
 
2173
        target.lock_read()
 
2174
        self.addCleanup(target.unlock)
 
2175
        self.assertEqual([], list(target.iter_changes(revision_tree)))
 
2176
 
 
2177
    def test_build_tree_hardlink(self):
 
2178
        self.requireFeature(HardlinkFeature)
 
2179
        source = self.create_ab_tree()
 
2180
        target = self.make_branch_and_tree('target')
 
2181
        revision_tree = source.basis_tree()
 
2182
        revision_tree.lock_read()
 
2183
        self.addCleanup(revision_tree.unlock)
 
2184
        build_tree(revision_tree, target, source, hardlink=True)
 
2185
        target.lock_read()
 
2186
        self.addCleanup(target.unlock)
 
2187
        self.assertEqual([], list(target.iter_changes(revision_tree)))
 
2188
        source_stat = os.stat('source/file1')
 
2189
        target_stat = os.stat('target/file1')
 
2190
        self.assertEqual(source_stat, target_stat)
 
2191
 
 
2192
        # Explicitly disallowing hardlinks should prevent them.
 
2193
        target2 = self.make_branch_and_tree('target2')
 
2194
        build_tree(revision_tree, target2, source, hardlink=False)
 
2195
        target2.lock_read()
 
2196
        self.addCleanup(target2.unlock)
 
2197
        self.assertEqual([], list(target2.iter_changes(revision_tree)))
 
2198
        source_stat = os.stat('source/file1')
 
2199
        target2_stat = os.stat('target2/file1')
 
2200
        self.assertNotEqual(source_stat, target2_stat)
 
2201
 
 
2202
    def test_build_tree_accelerator_tree_moved(self):
 
2203
        source = self.make_branch_and_tree('source')
 
2204
        self.build_tree_contents([('source/file1', b'A')])
 
2205
        source.add(['file1'], [b'file1-id'])
 
2206
        source.commit('commit files')
 
2207
        source.rename_one('file1', 'file2')
 
2208
        source.lock_read()
 
2209
        self.addCleanup(source.unlock)
 
2210
        target = self.make_branch_and_tree('target')
 
2211
        revision_tree = source.basis_tree()
 
2212
        revision_tree.lock_read()
 
2213
        self.addCleanup(revision_tree.unlock)
 
2214
        build_tree(revision_tree, target, source)
 
2215
        target.lock_read()
 
2216
        self.addCleanup(target.unlock)
 
2217
        self.assertEqual([], list(target.iter_changes(revision_tree)))
 
2218
 
 
2219
    def test_build_tree_hardlinks_preserve_execute(self):
 
2220
        self.requireFeature(HardlinkFeature)
 
2221
        source = self.create_ab_tree()
 
2222
        tt = TreeTransform(source)
 
2223
        trans_id = tt.trans_id_tree_path('file1')
 
2224
        tt.set_executability(True, trans_id)
 
2225
        tt.apply()
 
2226
        self.assertTrue(source.is_executable('file1'))
 
2227
        target = self.make_branch_and_tree('target')
 
2228
        revision_tree = source.basis_tree()
 
2229
        revision_tree.lock_read()
 
2230
        self.addCleanup(revision_tree.unlock)
 
2231
        build_tree(revision_tree, target, source, hardlink=True)
 
2232
        target.lock_read()
 
2233
        self.addCleanup(target.unlock)
 
2234
        self.assertEqual([], list(target.iter_changes(revision_tree)))
 
2235
        self.assertTrue(source.is_executable('file1'))
 
2236
 
 
2237
    def install_rot13_content_filter(self, pattern):
 
2238
        # We could use
 
2239
        # self.addCleanup(filters._reset_registry, filters._reset_registry())
 
2240
        # below, but that looks a bit... hard to read even if it's exactly
 
2241
        # the same thing.
 
2242
        original_registry = filters._reset_registry()
 
2243
 
 
2244
        def restore_registry():
 
2245
            filters._reset_registry(original_registry)
 
2246
        self.addCleanup(restore_registry)
 
2247
 
 
2248
        def rot13(chunks, context=None):
 
2249
            return [
 
2250
                codecs.encode(chunk.decode('ascii'), 'rot13').encode('ascii')
 
2251
                for chunk in chunks]
 
2252
        rot13filter = filters.ContentFilter(rot13, rot13)
 
2253
        filters.filter_stacks_registry.register(
 
2254
            'rot13', {'yes': [rot13filter]}.get)
 
2255
        os.mkdir(self.test_home_dir + '/.bazaar')
 
2256
        rules_filename = self.test_home_dir + '/.bazaar/rules'
 
2257
        with open(rules_filename, 'wb') as f:
 
2258
            f.write(b'[name %s]\nrot13=yes\n' % (pattern,))
 
2259
 
 
2260
        def uninstall_rules():
 
2261
            os.remove(rules_filename)
 
2262
            rules.reset_rules()
 
2263
        self.addCleanup(uninstall_rules)
 
2264
        rules.reset_rules()
 
2265
 
 
2266
    def test_build_tree_content_filtered_files_are_not_hardlinked(self):
 
2267
        """build_tree will not hardlink files that have content filtering rules
 
2268
        applied to them (but will still hardlink other files from the same tree
 
2269
        if it can).
 
2270
        """
 
2271
        self.requireFeature(HardlinkFeature)
 
2272
        self.install_rot13_content_filter(b'file1')
 
2273
        source = self.create_ab_tree()
 
2274
        target = self.make_branch_and_tree('target')
 
2275
        revision_tree = source.basis_tree()
 
2276
        revision_tree.lock_read()
 
2277
        self.addCleanup(revision_tree.unlock)
 
2278
        build_tree(revision_tree, target, source, hardlink=True)
 
2279
        target.lock_read()
 
2280
        self.addCleanup(target.unlock)
 
2281
        self.assertEqual([], list(target.iter_changes(revision_tree)))
 
2282
        source_stat = os.stat('source/file1')
 
2283
        target_stat = os.stat('target/file1')
 
2284
        self.assertNotEqual(source_stat, target_stat)
 
2285
        source_stat = os.stat('source/file2')
 
2286
        target_stat = os.stat('target/file2')
 
2287
        self.assertEqualStat(source_stat, target_stat)
 
2288
 
 
2289
    def test_case_insensitive_build_tree_inventory(self):
 
2290
        if (features.CaseInsensitiveFilesystemFeature.available()
 
2291
                or features.CaseInsCasePresFilenameFeature.available()):
 
2292
            raise tests.UnavailableFeature('Fully case sensitive filesystem')
 
2293
        source = self.make_branch_and_tree('source')
 
2294
        self.build_tree(['source/file', 'source/FILE'])
 
2295
        source.add(['file', 'FILE'], [b'lower-id', b'upper-id'])
 
2296
        source.commit('added files')
 
2297
        # Don't try this at home, kids!
 
2298
        # Force the tree to report that it is case insensitive
 
2299
        target = self.make_branch_and_tree('target')
 
2300
        target.case_sensitive = False
 
2301
        build_tree(source.basis_tree(), target, source, delta_from_tree=True)
 
2302
        self.assertEqual('file.moved', target.id2path(b'lower-id'))
 
2303
        self.assertEqual('FILE', target.id2path(b'upper-id'))
 
2304
 
 
2305
    def test_build_tree_observes_sha(self):
 
2306
        source = self.make_branch_and_tree('source')
 
2307
        self.build_tree(['source/file1', 'source/dir/', 'source/dir/file2'])
 
2308
        source.add(['file1', 'dir', 'dir/file2'],
 
2309
                   [b'file1-id', b'dir-id', b'file2-id'])
 
2310
        source.commit('new files')
 
2311
        target = self.make_branch_and_tree('target')
 
2312
        target.lock_write()
 
2313
        self.addCleanup(target.unlock)
 
2314
        # We make use of the fact that DirState caches its cutoff time. So we
 
2315
        # set the 'safe' time to one minute in the future.
 
2316
        state = target.current_dirstate()
 
2317
        state._cutoff_time = time.time() + 60
 
2318
        build_tree(source.basis_tree(), target)
 
2319
        entry1_sha = osutils.sha_file_by_name('source/file1')
 
2320
        entry2_sha = osutils.sha_file_by_name('source/dir/file2')
 
2321
        # entry[1] is the state information, entry[1][0] is the state of the
 
2322
        # working tree, entry[1][0][1] is the sha value for the current working
 
2323
        # tree
 
2324
        entry1 = state._get_entry(0, path_utf8=b'file1')
 
2325
        self.assertEqual(entry1_sha, entry1[1][0][1])
 
2326
        # The 'size' field must also be set.
 
2327
        self.assertEqual(25, entry1[1][0][2])
 
2328
        entry1_state = entry1[1][0]
 
2329
        entry2 = state._get_entry(0, path_utf8=b'dir/file2')
 
2330
        self.assertEqual(entry2_sha, entry2[1][0][1])
 
2331
        self.assertEqual(29, entry2[1][0][2])
 
2332
        entry2_state = entry2[1][0]
 
2333
        # Now, make sure that we don't have to re-read the content. The
 
2334
        # packed_stat should match exactly.
 
2335
        self.assertEqual(entry1_sha, target.get_file_sha1('file1'))
 
2336
        self.assertEqual(entry2_sha, target.get_file_sha1('dir/file2'))
 
2337
        self.assertEqual(entry1_state, entry1[1][0])
 
2338
        self.assertEqual(entry2_state, entry2[1][0])
 
2339
 
 
2340
 
294
2341
class TestCommitTransform(tests.TestCaseWithTransport):
295
2342
 
296
2343
    def get_branch(self):
302
2349
 
303
2350
    def get_branch_and_transform(self):
304
2351
        branch = self.get_branch()
305
 
        tt = branch.basis_tree().preview_transform()
 
2352
        tt = TransformPreview(branch.basis_tree())
306
2353
        self.addCleanup(tt.finalize)
307
2354
        return branch, tt
308
2355
 
310
2357
        branch = self.get_branch()
311
2358
        basis = branch.repository.revision_tree(
312
2359
            _mod_revision.NULL_REVISION)
313
 
        tt = basis.preview_transform()
 
2360
        tt = TransformPreview(basis)
314
2361
        self.addCleanup(tt.finalize)
315
2362
        e = self.assertRaises(ValueError, tt.commit, branch, '')
316
2363
        self.assertEqual('TreeTransform not based on branch basis: null:',
333
2380
        branch = self.make_branch('branch')
334
2381
        branch.lock_write()
335
2382
        self.addCleanup(branch.unlock)
336
 
        tt = branch.basis_tree().preview_transform()
 
2383
        tt = TransformPreview(branch.basis_tree())
337
2384
        self.addCleanup(tt.finalize)
338
2385
        tt.new_directory('', ROOT_PARENT, b'TREE_ROOT')
339
2386
        tt.commit(branch, 'my message')
345
2392
        branch = self.make_branch('branch')
346
2393
        branch.lock_write()
347
2394
        self.addCleanup(branch.unlock)
348
 
        tt = branch.basis_tree().preview_transform()
 
2395
        tt = TransformPreview(branch.basis_tree())
349
2396
        self.addCleanup(tt.finalize)
350
2397
        e = self.assertRaises(ValueError, tt.commit, branch,
351
2398
                              'my message', [b'rev1b-id'])
378
2425
        branch, tt = self.get_branch_and_transform()
379
2426
        tt.new_file('file', tt.root, [b'contents'], b'file-id')
380
2427
        tt.commit(branch, 'message', strict=True)
381
 
        tt = branch.basis_tree().preview_transform()
 
2428
        tt = TransformPreview(branch.basis_tree())
382
2429
        self.addCleanup(tt.finalize)
383
2430
        trans_id = tt.trans_id_file_id(b'file-id')
384
2431
        tt.delete_contents(trans_id)
393
2440
        branch, tt = self.get_branch_and_transform()
394
2441
        parent_id = tt.trans_id_file_id(b'parent-id')
395
2442
        tt.new_file('file', parent_id, [b'contents'], b'file-id')
396
 
        self.assertRaises(MalformedTransform, tt.commit, branch,
 
2443
        self.assertRaises(errors.MalformedTransform, tt.commit, branch,
397
2444
                          'message')
398
2445
 
399
2446
    def test_commit_rich_revision_data(self):
505
2552
    def test_rollback_rename(self):
506
2553
        tree = self.make_branch_and_tree('.')
507
2554
        self.build_tree(['a/', 'a/b'])
508
 
        tt = tree.transform()
 
2555
        tt = TreeTransform(tree)
509
2556
        self.addCleanup(tt.finalize)
510
2557
        a_id = tt.trans_id_tree_path('a')
511
2558
        tt.adjust_path('c', tt.root, a_id)
521
2568
    def test_rollback_rename_into_place(self):
522
2569
        tree = self.make_branch_and_tree('.')
523
2570
        self.build_tree(['a/', 'a/b'])
524
 
        tt = tree.transform()
 
2571
        tt = TreeTransform(tree)
525
2572
        self.addCleanup(tt.finalize)
526
2573
        a_id = tt.trans_id_tree_path('a')
527
2574
        tt.adjust_path('c', tt.root, a_id)
537
2584
    def test_rollback_deletion(self):
538
2585
        tree = self.make_branch_and_tree('.')
539
2586
        self.build_tree(['a/', 'a/b'])
540
 
        tt = tree.transform()
 
2587
        tt = TreeTransform(tree)
541
2588
        self.addCleanup(tt.finalize)
542
2589
        a_id = tt.trans_id_tree_path('a')
543
2590
        tt.delete_contents(a_id)
577
2624
    def create_transform_and_root_trans_id(self):
578
2625
        """Setup a transform creating a file in limbo"""
579
2626
        tree = self.make_branch_and_tree('.')
580
 
        tt = tree.transform()
 
2627
        tt = TreeTransform(tree)
581
2628
        return tt, tt.create_path("a", tt.root)
582
2629
 
583
2630
    def create_transform_and_subdir_trans_id(self):
584
2631
        """Setup a transform creating a directory containing a file in limbo"""
585
2632
        tree = self.make_branch_and_tree('.')
586
 
        tt = tree.transform()
 
2633
        tt = TreeTransform(tree)
587
2634
        d_trans_id = tt.create_path("d", tt.root)
588
2635
        tt.create_directory(d_trans_id)
589
2636
        f_trans_id = tt.create_path("a", d_trans_id)
683
2730
        self.build_tree(['dir/', ])
684
2731
        wt.add(['dir'], [b'dir-id'])
685
2732
        wt.commit('Create dir')
686
 
        tt = wt.transform()
 
2733
        tt = TreeTransform(wt)
687
2734
        self.addCleanup(tt.finalize)
688
2735
        return wt, tt
689
2736
 
711
2758
                         conflicts.pop())
712
2759
 
713
2760
 
 
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
 
714
3426
class FakeSerializer(object):
715
3427
    """Serializer implementation that simply returns the input.
716
3428
 
728
3440
    def get_preview(self, tree=None):
729
3441
        if tree is None:
730
3442
            tree = self.make_branch_and_tree('tree')
731
 
        tt = tree.preview_transform()
 
3443
        tt = TransformPreview(tree)
732
3444
        self.addCleanup(tt.finalize)
733
3445
        return tt
734
3446
 
788
3500
        self.assertEqual({'new-1': True}, tt._new_executability)
789
3501
        self.assertEqual({'new-1': 'file',
790
3502
                          'new-2': 'directory'}, tt._new_contents)
791
 
        with open(tt._limbo_name('new-1'), 'rb') as foo_limbo:
 
3503
        foo_limbo = open(tt._limbo_name('new-1'), 'rb')
 
3504
        try:
792
3505
            foo_content = foo_limbo.read()
 
3506
        finally:
 
3507
            foo_limbo.close()
793
3508
        self.assertEqual(b'bar', foo_content)
794
3509
 
795
3510
    def symlink_creation_records(self):
984
3699
 
985
3700
    def test_no_orphan_for_transform_preview(self):
986
3701
        tree = self.make_branch_and_tree('tree')
987
 
        tt = tree.preview_transform()
 
3702
        tt = transform.TransformPreview(tree)
988
3703
        self.addCleanup(tt.finalize)
989
3704
        self.assertRaises(NotImplementedError, tt.new_orphan, 'foo', 'bar')
990
3705
 
996
3711
        self.build_tree(['dir/', 'dir/file', 'dir/foo'])
997
3712
        wt.add(['dir', 'dir/file'], [b'dir-id', b'file-id'])
998
3713
        wt.commit('add dir and file ignoring foo')
999
 
        tt = wt.transform()
 
3714
        tt = transform.TreeTransform(wt)
1000
3715
        self.addCleanup(tt.finalize)
1001
3716
        # dir and bar are deleted
1002
3717
        dir_tid = tt.trans_id_tree_path('dir')
1007
3722
        tt.delete_contents(dir_tid)
1008
3723
        tt.unversion_file(dir_tid)
1009
3724
        # There should be a conflict because dir still contain foo
1010
 
        raw_conflicts = tt.find_raw_conflicts()
 
3725
        raw_conflicts = tt.find_conflicts()
1011
3726
        self.assertLength(1, raw_conflicts)
1012
3727
        self.assertEqual(('missing parent', 'new-1'), raw_conflicts[0])
1013
3728
        return tt, orphan_tid
1080
3795
        self.wt = self.make_branch_and_tree('.')
1081
3796
        os.chdir('..')
1082
3797
 
1083
 
    def transform(self):
1084
 
        transform = self.wt.transform()
 
3798
    def get_transform(self):
 
3799
        transform = TreeTransform(self.wt)
1085
3800
        self.addCleanup(transform.finalize)
1086
3801
        return transform, transform.root
1087
3802
 
1092
3807
            calls.append((tree, tt))
1093
3808
        MutableTree.hooks.install_named_hook(
1094
3809
            'pre_transform', record_pre_transform, "Pre transform")
1095
 
        transform, root = self.transform()
 
3810
        transform, root = self.get_transform()
1096
3811
        old_root_id = transform.tree_file_id(root)
1097
3812
        transform.apply()
1098
3813
        self.assertEqual(old_root_id, self.wt.path2id(''))
1105
3820
            calls.append((tree, tt))
1106
3821
        MutableTree.hooks.install_named_hook(
1107
3822
            'post_transform', record_post_transform, "Post transform")
1108
 
        transform, root = self.transform()
 
3823
        transform, root = self.get_transform()
1109
3824
        old_root_id = transform.tree_file_id(root)
1110
3825
        transform.apply()
1111
3826
        self.assertEqual(old_root_id, self.wt.path2id(''))
1140
3855
 
1141
3856
    def test_link_fails_if_execute_bit_changed(self):
1142
3857
        """If the file to be linked has modified execute bit, don't link."""
1143
 
        tt = self.child_tree.transform()
 
3858
        tt = TreeTransform(self.child_tree)
1144
3859
        try:
1145
3860
            trans_id = tt.trans_id_tree_path('foo')
1146
3861
            tt.set_executability(True, trans_id)
1154
3869
        """If the file to be linked is unmodified, link"""
1155
3870
        transform.link_tree(self.child_tree, self.parent_tree)
1156
3871
        self.assertTrue(self.hardlinked())
1157
 
 
1158
 
 
1159
 
class ErrorTests(tests.TestCase):
1160
 
 
1161
 
    def test_transform_rename_failed(self):
1162
 
        e = TransformRenameFailed(u"from", u"to", "readonly file", 2)
1163
 
        self.assertEqual(
1164
 
            u"Failed to rename from to to: readonly file",
1165
 
            str(e))