/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: Martin
  • Date: 2018-11-16 19:10:17 UTC
  • mto: This revision was merged to the branch mainline in revision 7177.
  • Revision ID: gzlist@googlemail.com-20181116191017-kyedz1qck0ovon3h
Remove lazy_regexp reset in bt.test_source

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