/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

Add bzrlib.tests.per_repository_vf.

Show diffs side-by-side

added added

removed removed

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