/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/per_workingtree/test_transform.py

  • Committer: Jelmer Vernooij
  • Date: 2020-07-18 23:14:00 UTC
  • mfrom: (7490.40.62 work)
  • mto: This revision was merged to the branch mainline in revision 7519.
  • Revision ID: jelmer@jelmer.uk-20200718231400-jaes9qltn8oi8xss
Merge lp:brz/3.1.

Show diffs side-by-side

added added

removed removed

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