/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_transform.py

  • Committer: Jelmer Vernooij
  • Date: 2010-12-20 11:57:14 UTC
  • mto: This revision was merged to the branch mainline in revision 5577.
  • Revision ID: jelmer@samba.org-20101220115714-2ru3hfappjweeg7q
Don't use no-plugins.

Show diffs side-by-side

added added

removed removed

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