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

  • Committer: Robert Collins
  • Date: 2010-05-11 08:36:16 UTC
  • mto: This revision was merged to the branch mainline in revision 5223.
  • Revision ID: robertc@robertcollins.net-20100511083616-b8fjb19zomwupid0
Make all lock methods return Result objects, rather than lock_read returning self, as per John's review.

Show diffs side-by-side

added added

removed removed

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