~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_transform.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2006-07-12 12:36:57 UTC
  • mfrom: (1732.3.4 bzr.revnoX)
  • Revision ID: pqm@pqm.ubuntu.com-20060712123657-365eeb32b69308bf
(matthieu) revno:x:url revision spec

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006-2010 Canonical Ltd
2
 
#
 
1
# Copyright (C) 2006 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
17
import os
18
 
from StringIO import StringIO
19
 
import sys
20
 
import time
21
18
 
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
 
    )
36
19
from bzrlib.bzrdir import BzrDir
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):
 
20
from bzrlib.conflicts import (DuplicateEntry, DuplicateID, MissingParent,
 
21
                              UnversionedParent, ParentLoop)
 
22
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
 
23
                           ReusingTransform, CantMoveRoot, 
 
24
                           PathsNotVersionedError, ExistingLimbo,
 
25
                           ImmortalLimbo, LockError)
 
26
from bzrlib.osutils import file_kind, has_symlinks, pathjoin
 
27
from bzrlib.merge import Merge3Merger
 
28
from bzrlib.tests import TestCaseInTempDir, TestSkipped, TestCase
 
29
from bzrlib.transform import (TreeTransform, ROOT_PARENT, FinalPaths, 
 
30
                              resolve_conflicts, cook_conflicts, 
 
31
                              find_interesting, build_tree, get_backup_name)
 
32
import bzrlib.urlutils as urlutils
 
33
 
 
34
class TestTreeTransform(TestCaseInTempDir):
87
35
 
88
36
    def setUp(self):
89
37
        super(TestTreeTransform, self).setUp()
90
 
        self.wt = self.make_branch_and_tree('.', format='dirstate-with-subtree')
 
38
        self.wt = BzrDir.create_standalone_workingtree('.')
91
39
        os.chdir('..')
92
40
 
93
41
    def get_transform(self):
94
42
        transform = TreeTransform(self.wt)
95
 
        self.addCleanup(transform.finalize)
96
 
        return transform, transform.root
 
43
        #self.addCleanup(transform.finalize)
 
44
        return transform, transform.trans_id_tree_file_id(self.wt.get_root_id())
97
45
 
98
46
    def test_existing_limbo(self):
 
47
        limbo_name = urlutils.local_path_from_url(
 
48
            self.wt._control_files.controlfilename('limbo'))
99
49
        transform, root = self.get_transform()
100
 
        limbo_name = transform._limbodir
101
 
        deletion_path = transform._deletiondir
102
50
        os.mkdir(pathjoin(limbo_name, 'hehe'))
103
51
        self.assertRaises(ImmortalLimbo, transform.apply)
104
52
        self.assertRaises(LockError, self.wt.unlock)
106
54
        self.assertRaises(LockError, self.wt.unlock)
107
55
        os.rmdir(pathjoin(limbo_name, 'hehe'))
108
56
        os.rmdir(limbo_name)
109
 
        os.rmdir(deletion_path)
110
57
        transform, root = self.get_transform()
111
58
        transform.apply()
112
59
 
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
 
 
122
60
    def test_build(self):
123
 
        transform, root = self.get_transform()
124
 
        self.wt.lock_tree_write()
125
 
        self.addCleanup(self.wt.unlock)
 
61
        transform, root = self.get_transform() 
126
62
        self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
127
63
        imaginary_id = transform.trans_id_tree_path('imaginary')
128
64
        imaginary_id2 = transform.trans_id_tree_path('imaginary/')
129
65
        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))
 
66
        self.assertEqual(transform.get_tree_parent(imaginary_id), root)
 
67
        self.assertEqual(transform.final_kind(root), 'directory')
 
68
        self.assertEqual(transform.final_file_id(root), self.wt.get_root_id())
133
69
        trans_id = transform.create_path('name', root)
134
70
        self.assertIs(transform.final_file_id(trans_id), None)
135
 
        self.assertIs(None, transform.final_kind(trans_id))
 
71
        self.assertRaises(NoSuchFile, transform.final_kind, trans_id)
136
72
        transform.create_file('contents', trans_id)
137
73
        transform.set_executability(True, trans_id)
138
74
        transform.version_file('my_pretties', trans_id)
156
92
        self.assertIs(self.wt.is_executable('my_pretties2'), False)
157
93
        self.assertEqual('directory', file_kind(self.wt.abspath('oz')))
158
94
        self.assertEqual(len(modified_paths), 3)
159
 
        tree_mod_paths = [self.wt.id2abspath(f) for f in
 
95
        tree_mod_paths = [self.wt.id2abspath(f) for f in 
160
96
                          ('ozzie', 'my_pretties', 'my_pretties2')]
161
97
        self.assertSubset(tree_mod_paths, modified_paths)
162
98
        # is it safe to finalize repeatedly?
163
99
        transform.finalize()
164
100
        transform.finalize()
165
101
 
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
 
 
234
102
    def test_convenience(self):
235
103
        transform, root = self.get_transform()
236
 
        self.wt.lock_tree_write()
237
 
        self.addCleanup(self.wt.unlock)
238
 
        trans_id = transform.new_file('name', root, 'contents',
 
104
        trans_id = transform.new_file('name', root, 'contents', 
239
105
                                      'my_pretties', True)
240
106
        oz = transform.new_directory('oz', root, 'oz-id')
241
107
        dorothy = transform.new_directory('dorothy', oz, 'dorothy-id')
242
 
        toto = transform.new_file('toto', dorothy, 'toto-contents',
 
108
        toto = transform.new_file('toto', dorothy, 'toto-contents', 
243
109
                                  'toto-id', False)
244
110
 
245
111
        self.assertEqual(len(transform.find_conflicts()), 0)
252
118
        self.assertEqual(self.wt.path2id('oz/dorothy'), 'dorothy-id')
253
119
        self.assertEqual(self.wt.path2id('oz/dorothy/toto'), 'toto-id')
254
120
 
255
 
        self.assertEqual('toto-contents',
 
121
        self.assertEqual('toto-contents', 
256
122
                         self.wt.get_file_byname('oz/dorothy/toto').read())
257
123
        self.assertIs(self.wt.is_executable('toto-id'), False)
258
124
 
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
 
 
270
125
    def test_conflicts(self):
271
126
        transform, root = self.get_transform()
272
 
        trans_id = transform.new_file('name', root, 'contents',
 
127
        trans_id = transform.new_file('name', root, 'contents', 
273
128
                                      'my_pretties')
274
129
        self.assertEqual(len(transform.find_conflicts()), 0)
275
130
        trans_id2 = transform.new_file('name', root, 'Crontents', 'toto')
276
 
        self.assertEqual(transform.find_conflicts(),
 
131
        self.assertEqual(transform.find_conflicts(), 
277
132
                         [('duplicate', trans_id, trans_id2, 'name')])
278
133
        self.assertRaises(MalformedTransform, transform.apply)
279
134
        transform.adjust_path('name', trans_id, trans_id2)
280
 
        self.assertEqual(transform.find_conflicts(),
 
135
        self.assertEqual(transform.find_conflicts(), 
281
136
                         [('non-directory parent', trans_id)])
282
137
        tinman_id = transform.trans_id_tree_path('tinman')
283
138
        transform.adjust_path('name', tinman_id, trans_id2)
284
 
        self.assertEqual(transform.find_conflicts(),
285
 
                         [('unversioned parent', tinman_id),
 
139
        self.assertEqual(transform.find_conflicts(), 
 
140
                         [('unversioned parent', tinman_id), 
286
141
                          ('missing parent', tinman_id)])
287
142
        lion_id = transform.create_path('lion', root)
288
 
        self.assertEqual(transform.find_conflicts(),
289
 
                         [('unversioned parent', tinman_id),
 
143
        self.assertEqual(transform.find_conflicts(), 
 
144
                         [('unversioned parent', tinman_id), 
290
145
                          ('missing parent', tinman_id)])
291
146
        transform.adjust_path('name', lion_id, trans_id2)
292
 
        self.assertEqual(transform.find_conflicts(),
 
147
        self.assertEqual(transform.find_conflicts(), 
293
148
                         [('unversioned parent', lion_id),
294
149
                          ('missing parent', lion_id)])
295
150
        transform.version_file("Courage", lion_id)
296
 
        self.assertEqual(transform.find_conflicts(),
297
 
                         [('missing parent', lion_id),
 
151
        self.assertEqual(transform.find_conflicts(), 
 
152
                         [('missing parent', lion_id), 
298
153
                          ('versioning no contents', lion_id)])
299
154
        transform.adjust_path('name2', root, trans_id2)
300
 
        self.assertEqual(transform.find_conflicts(),
 
155
        self.assertEqual(transform.find_conflicts(), 
301
156
                         [('versioning no contents', lion_id)])
302
157
        transform.create_file('Contents, okay?', lion_id)
303
158
        transform.adjust_path('name2', trans_id2, trans_id2)
304
 
        self.assertEqual(transform.find_conflicts(),
305
 
                         [('parent loop', trans_id2),
 
159
        self.assertEqual(transform.find_conflicts(), 
 
160
                         [('parent loop', trans_id2), 
306
161
                          ('non-directory parent', trans_id2)])
307
162
        transform.adjust_path('name2', root, trans_id2)
308
163
        oz_id = transform.new_directory('oz', root)
309
164
        transform.set_executability(True, oz_id)
310
 
        self.assertEqual(transform.find_conflicts(),
 
165
        self.assertEqual(transform.find_conflicts(), 
311
166
                         [('unversioned executability', oz_id)])
312
167
        transform.version_file('oz-id', oz_id)
313
 
        self.assertEqual(transform.find_conflicts(),
 
168
        self.assertEqual(transform.find_conflicts(), 
314
169
                         [('non-file executability', oz_id)])
315
170
        transform.set_executability(None, oz_id)
316
171
        tip_id = transform.new_file('tip', oz_id, 'ozma', 'tip-id')
325
180
        self.assert_('oz/tip' in transform2._tree_path_ids)
326
181
        self.assertEqual(fp.get_path(newtip), pathjoin('oz', 'tip'))
327
182
        self.assertEqual(len(result), 2)
328
 
        self.assertEqual((result[0][0], result[0][1]),
 
183
        self.assertEqual((result[0][0], result[0][1]), 
329
184
                         ('duplicate', newtip))
330
 
        self.assertEqual((result[1][0], result[1][2]),
 
185
        self.assertEqual((result[1][0], result[1][2]), 
331
186
                         ('duplicate id', newtip))
332
187
        transform2.finalize()
333
188
        transform3 = TreeTransform(self.wt)
334
189
        self.addCleanup(transform3.finalize)
335
190
        oz_id = transform3.trans_id_tree_file_id('oz-id')
336
191
        transform3.delete_contents(oz_id)
337
 
        self.assertEqual(transform3.find_conflicts(),
 
192
        self.assertEqual(transform3.find_conflicts(), 
338
193
                         [('missing parent', oz_id)])
339
 
        root_id = transform3.root
 
194
        root_id = transform3.trans_id_tree_file_id('TREE_ROOT')
340
195
        tip_id = transform3.trans_id_tree_file_id('tip-id')
341
196
        transform3.adjust_path('tip', root_id, tip_id)
342
197
        transform3.apply()
343
198
 
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
 
 
464
199
    def test_add_del(self):
465
200
        start, root = self.get_transform()
466
201
        start.new_directory('a', root, 'a')
479
214
        self.addCleanup(unversion.finalize)
480
215
        parent = unversion.trans_id_tree_path('parent')
481
216
        unversion.unversion_file(parent)
482
 
        self.assertEqual(unversion.find_conflicts(),
 
217
        self.assertEqual(unversion.find_conflicts(), 
483
218
                         [('unversioned parent', parent_id)])
484
219
        file_id = unversion.trans_id_tree_file_id('child-id')
485
220
        unversion.unversion_file(file_id)
488
223
    def test_name_invariants(self):
489
224
        create_tree, root = self.get_transform()
490
225
        # prepare tree
491
 
        root = create_tree.root
 
226
        root = create_tree.trans_id_tree_file_id('TREE_ROOT')
492
227
        create_tree.new_file('name1', root, 'hello1', 'name1')
493
228
        create_tree.new_file('name2', root, 'hello2', 'name2')
494
229
        ddir = create_tree.new_directory('dying_directory', root, 'ddir')
498
233
        create_tree.apply()
499
234
 
500
235
        mangle_tree,root = self.get_transform()
501
 
        root = mangle_tree.root
 
236
        root = mangle_tree.trans_id_tree_file_id('TREE_ROOT')
502
237
        #swap names
503
238
        name1 = mangle_tree.trans_id_tree_file_id('name1')
504
239
        name2 = mangle_tree.trans_id_tree_file_id('name2')
505
240
        mangle_tree.adjust_path('name2', root, name1)
506
241
        mangle_tree.adjust_path('name1', root, name2)
507
242
 
508
 
        #tests for deleting parent directories
 
243
        #tests for deleting parent directories 
509
244
        ddir = mangle_tree.trans_id_tree_file_id('ddir')
510
245
        mangle_tree.delete_contents(ddir)
511
246
        dfile = mangle_tree.trans_id_tree_file_id('dfile')
540
275
        create_tree,root = self.get_transform()
541
276
        newdir = create_tree.new_directory('selftest', root, 'selftest-id')
542
277
        create_tree.new_file('blackbox.py', newdir, 'hello1', 'blackbox-id')
543
 
        create_tree.apply()
 
278
        create_tree.apply()        
544
279
        mangle_tree,root = self.get_transform()
545
280
        selftest = mangle_tree.trans_id_tree_file_id('selftest-id')
546
281
        blackbox = mangle_tree.trans_id_tree_file_id('blackbox-id')
554
289
        bzrlib = create_tree.new_directory('bzrlib', root, 'bzrlib-id')
555
290
        tests = create_tree.new_directory('tests', bzrlib, 'tests-id')
556
291
        blackbox = create_tree.new_directory('blackbox', tests, 'blackbox-id')
557
 
        create_tree.new_file('test_too_much.py', blackbox, 'hello1',
 
292
        create_tree.new_file('test_too_much.py', blackbox, 'hello1', 
558
293
                             'test_too_much-id')
559
 
        create_tree.apply()
 
294
        create_tree.apply()        
560
295
        mangle_tree,root = self.get_transform()
561
296
        bzrlib = mangle_tree.trans_id_tree_file_id('bzrlib-id')
562
297
        tests = mangle_tree.trans_id_tree_file_id('tests-id')
563
298
        test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
564
299
        mangle_tree.adjust_path('selftest', bzrlib, tests)
565
 
        mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
 
300
        mangle_tree.adjust_path('blackbox.py', tests, test_too_much) 
566
301
        mangle_tree.set_executability(True, test_too_much)
567
302
        mangle_tree.apply()
568
303
 
569
304
    def test_both_rename3(self):
570
305
        create_tree,root = self.get_transform()
571
306
        tests = create_tree.new_directory('tests', root, 'tests-id')
572
 
        create_tree.new_file('test_too_much.py', tests, 'hello1',
 
307
        create_tree.new_file('test_too_much.py', tests, 'hello1', 
573
308
                             'test_too_much-id')
574
 
        create_tree.apply()
 
309
        create_tree.apply()        
575
310
        mangle_tree,root = self.get_transform()
576
311
        tests = mangle_tree.trans_id_tree_file_id('tests-id')
577
312
        test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
578
313
        mangle_tree.adjust_path('selftest', root, tests)
579
 
        mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
 
314
        mangle_tree.adjust_path('blackbox.py', tests, test_too_much) 
580
315
        mangle_tree.set_executability(True, test_too_much)
581
316
        mangle_tree.apply()
582
317
 
583
318
    def test_move_dangling_ie(self):
584
319
        create_tree, root = self.get_transform()
585
320
        # prepare tree
586
 
        root = create_tree.root
 
321
        root = create_tree.trans_id_tree_file_id('TREE_ROOT')
587
322
        create_tree.new_file('name1', root, 'hello1', 'name1')
588
323
        create_tree.apply()
589
324
        delete_contents, root = self.get_transform()
595
330
        newdir = move_id.new_directory('dir', root, 'newdir')
596
331
        move_id.adjust_path('name2', newdir, name1)
597
332
        move_id.apply()
598
 
 
 
333
        
599
334
    def test_replace_dangling_ie(self):
600
335
        create_tree, root = self.get_transform()
601
336
        # prepare tree
602
 
        root = create_tree.root
 
337
        root = create_tree.trans_id_tree_file_id('TREE_ROOT')
603
338
        create_tree.new_file('name1', root, 'hello1', 'name1')
604
339
        create_tree.apply()
605
340
        delete_contents = TreeTransform(self.wt)
617
352
        resolve_conflicts(replace)
618
353
        replace.apply()
619
354
 
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()
 
355
    def test_symlinks(self):
 
356
        if not has_symlinks():
 
357
            raise TestSkipped('Symlinks are not supported on this platform')
 
358
        transform,root = self.get_transform()
627
359
        oz_id = transform.new_directory('oz', root, 'oz-id')
628
 
        wizard = transform.new_symlink(link_name1, oz_id, link_target1,
 
360
        wizard = transform.new_symlink('wizard', oz_id, 'wizard-target', 
629
361
                                       'wizard-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)
 
362
        wiz_id = transform.create_path('wizard2', oz_id)
 
363
        transform.create_symlink('behind_curtain', wiz_id)
 
364
        transform.version_file('wiz-id2', wiz_id)            
633
365
        transform.set_executability(True, wiz_id)
634
 
        self.assertEqual(transform.find_conflicts(),
 
366
        self.assertEqual(transform.find_conflicts(), 
635
367
                         [('non-file executability', wiz_id)])
636
368
        transform.set_executability(None, wiz_id)
637
369
        transform.apply()
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
 
370
        self.assertEqual(self.wt.path2id('oz/wizard'), 'wizard-id')
 
371
        self.assertEqual(file_kind(self.wt.abspath('oz/wizard')), 'symlink')
 
372
        self.assertEqual(os.readlink(self.wt.abspath('oz/wizard2')), 
 
373
                         'behind_curtain')
 
374
        self.assertEqual(os.readlink(self.wt.abspath('oz/wizard')),
 
375
                         'wizard-target')
 
376
 
676
377
 
677
378
    def get_conflicted(self):
678
379
        create,root = self.get_transform()
682
383
        create.apply()
683
384
        conflicts,root = self.get_transform()
684
385
        # set up duplicate entry, duplicate id
685
 
        new_dorothy = conflicts.new_file('dorothy', root, 'dorothy',
 
386
        new_dorothy = conflicts.new_file('dorothy', root, 'dorothy', 
686
387
                                         'dorothy-id')
687
388
        old_dorothy = conflicts.trans_id_tree_file_id('dorothy-id')
688
389
        oz = conflicts.trans_id_tree_file_id('oz-id')
689
 
        # set up DeletedParent parent conflict
 
390
        # set up missing, unversioned parent
690
391
        conflicts.delete_versioned(oz)
691
392
        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')
696
393
        # set up parent loop
697
394
        conflicts.adjust_path('emeraldcity', emerald, emerald)
698
395
        return conflicts, emerald, oz, old_dorothy, new_dorothy
712
409
        tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
713
410
        raw_conflicts = resolve_conflicts(tt)
714
411
        cooked_conflicts = cook_conflicts(raw_conflicts, tt)
715
 
        duplicate = DuplicateEntry('Moved existing file to', 'dorothy.moved',
 
412
        duplicate = DuplicateEntry('Moved existing file to', 'dorothy.moved', 
716
413
                                   'dorothy', None, 'dorothy-id')
717
414
        self.assertEqual(cooked_conflicts[0], duplicate)
718
 
        duplicate_id = DuplicateID('Unversioned existing file',
 
415
        duplicate_id = DuplicateID('Unversioned existing file', 
719
416
                                   'dorothy.moved', 'dorothy', None,
720
417
                                   'dorothy-id')
721
418
        self.assertEqual(cooked_conflicts[1], duplicate_id)
722
 
        missing_parent = MissingParent('Created directory', 'munchkincity',
723
 
                                       'munchkincity-id')
724
 
        deleted_parent = DeletingParent('Not deleting', 'oz', 'oz-id')
 
419
        missing_parent = MissingParent('Not deleting', 'oz', 'oz-id')
725
420
        self.assertEqual(cooked_conflicts[2], missing_parent)
726
 
        unversioned_parent = UnversionedParent('Versioned directory',
727
 
                                               'munchkincity',
728
 
                                               'munchkincity-id')
729
 
        unversioned_parent2 = UnversionedParent('Versioned directory', 'oz',
 
421
        unversioned_parent = UnversionedParent('Versioned directory', 'oz',
730
422
                                               'oz-id')
731
423
        self.assertEqual(cooked_conflicts[3], unversioned_parent)
732
 
        parent_loop = ParentLoop('Cancelled move', 'oz/emeraldcity',
 
424
        parent_loop = ParentLoop('Cancelled move', 'oz/emeraldcity', 
733
425
                                 'oz/emeraldcity', 'emerald-id', 'emerald-id')
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)
 
426
        self.assertEqual(cooked_conflicts[4], parent_loop)
 
427
        self.assertEqual(len(cooked_conflicts), 5)
738
428
        tt.finalize()
739
429
 
740
430
    def test_string_conflicts(self):
750
440
        self.assertEqual(conflicts_s[1], 'Conflict adding id to dorothy.  '
751
441
                                         'Unversioned existing file '
752
442
                                         'dorothy.moved.')
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
        self.assertEqual(conflicts_s[2], 'Conflict adding files to oz.  '
 
444
                                         'Not deleting.')
 
445
        self.assertEqual(conflicts_s[3], 'Conflict adding versioned files to '
 
446
                                         'oz.  Versioned directory.')
 
447
        self.assertEqual(conflicts_s[4], 'Conflict moving oz/emeraldcity into'
 
448
                                         ' oz/emeraldcity.  Cancelled move.')
835
449
 
836
450
    def test_moving_versioned_directories(self):
837
451
        create, root = self.get_transform()
853
467
        create.apply()
854
468
        transform, root = self.get_transform()
855
469
        transform.adjust_root_path('oldroot', fun)
856
 
        new_root = transform.trans_id_tree_path('')
 
470
        new_root=transform.trans_id_tree_path('')
857
471
        transform.version_file('new-root', new_root)
858
472
        transform.apply()
859
473
 
871
485
        rename.set_executability(True, myfile)
872
486
        rename.apply()
873
487
 
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')
 
488
    def test_find_interesting(self):
 
489
        create, root = self.get_transform()
 
490
        wt = create._tree
 
491
        create.new_file('vfile', root, 'myfile-text', 'myfile-id')
 
492
        create.new_file('uvfile', root, 'othertext')
881
493
        create.apply()
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)")
 
494
        self.assertEqual(find_interesting(wt, wt, ['vfile']),
 
495
                         set(['myfile-id']))
 
496
        self.assertRaises(PathsNotVersionedError, find_interesting, wt, wt,
 
497
                          ['uvfile'])
906
498
 
907
499
    def test_set_executability_order(self):
908
500
        """Ensure that executability behaves the same, no matter what order.
909
 
 
 
501
        
910
502
        - create file and set executability simultaneously
911
503
        - create file and set executability afterward
912
504
        - unsetting the executability of a file whose executability has not been
915
507
        """
916
508
        transform, root = self.get_transform()
917
509
        wt = transform._tree
918
 
        wt.lock_read()
919
 
        self.addCleanup(wt.unlock)
920
510
        transform.new_file('set_on_creation', root, 'Set on creation', 'soc',
921
511
                           True)
922
 
        sac = transform.new_file('set_after_creation', root,
923
 
                                 'Set after creation', 'sac')
 
512
        sac = transform.new_file('set_after_creation', root, 'Set after creation', 'sac')
924
513
        transform.set_executability(True, sac)
925
 
        uws = transform.new_file('unset_without_set', root, 'Unset badly',
926
 
                                 'uws')
 
514
        uws = transform.new_file('unset_without_set', root, 'Unset badly', 'uws')
927
515
        self.assertRaises(KeyError, transform.set_executability, None, uws)
928
516
        transform.apply()
929
517
        self.assertTrue(wt.is_executable('soc'))
930
518
        self.assertTrue(wt.is_executable('sac'))
931
519
 
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'))
1509
 
 
1510
520
 
1511
521
class TransformGroup(object):
1512
 
 
1513
 
    def __init__(self, dirname, root_id):
 
522
    def __init__(self, dirname):
1514
523
        self.name = dirname
1515
524
        os.mkdir(dirname)
1516
525
        self.wt = BzrDir.create_standalone_workingtree(dirname)
1517
 
        self.wt.set_root_id(root_id)
1518
526
        self.b = self.wt.branch
1519
527
        self.tt = TreeTransform(self.wt)
1520
528
        self.root = self.tt.trans_id_tree_file_id(self.wt.get_root_id())
1521
529
 
1522
 
 
1523
530
def conflict_text(tree, merge):
1524
531
    template = '%s TREE\n%s%s\n%s%s MERGE-SOURCE\n'
1525
532
    return template % ('<' * 7, tree, '=' * 7, merge, '>' * 7)
1526
533
 
1527
534
 
1528
535
class TestTransformMerge(TestCaseInTempDir):
1529
 
 
1530
536
    def test_text_merge(self):
1531
 
        root_id = generate_ids.gen_root_id()
1532
 
        base = TransformGroup("base", root_id)
 
537
        base = TransformGroup("base")
1533
538
        base.tt.new_file('a', base.root, 'a\nb\nc\nd\be\n', 'a')
1534
539
        base.tt.new_file('b', base.root, 'b1', 'b')
1535
540
        base.tt.new_file('c', base.root, 'c', 'c')
1539
544
        base.tt.new_directory('g', base.root, 'g')
1540
545
        base.tt.new_directory('h', base.root, 'h')
1541
546
        base.tt.apply()
1542
 
        other = TransformGroup("other", root_id)
 
547
        other = TransformGroup("other")
1543
548
        other.tt.new_file('a', other.root, 'y\nb\nc\nd\be\n', 'a')
1544
549
        other.tt.new_file('b', other.root, 'b2', 'b')
1545
550
        other.tt.new_file('c', other.root, 'c2', 'c')
1550
555
        other.tt.new_file('h', other.root, 'h\ni\nj\nk\n', 'h')
1551
556
        other.tt.new_file('i', other.root, 'h\ni\nj\nk\n', 'i')
1552
557
        other.tt.apply()
1553
 
        this = TransformGroup("this", root_id)
 
558
        this = TransformGroup("this")
1554
559
        this.tt.new_file('a', this.root, 'a\nb\nc\nd\bz\n', 'a')
1555
560
        this.tt.new_file('b', this.root, 'b', 'b')
1556
561
        this.tt.new_file('c', this.root, 'c', 'c')
1562
567
        this.tt.new_file('i', this.root, '1\n2\n3\n4\n', 'i')
1563
568
        this.tt.apply()
1564
569
        Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1565
 
 
1566
570
        # textual merge
1567
571
        self.assertEqual(this.wt.get_file('a').read(), 'y\nb\nc\nd\bz\n')
1568
572
        # three-way text conflict
1569
 
        self.assertEqual(this.wt.get_file('b').read(),
 
573
        self.assertEqual(this.wt.get_file('b').read(), 
1570
574
                         conflict_text('b', 'b2'))
1571
575
        # OTHER wins
1572
576
        self.assertEqual(this.wt.get_file('c').read(), 'c2')
1576
580
        self.assertEqual(this.wt.get_file('e').read(), 'e2')
1577
581
        # No change
1578
582
        self.assertEqual(this.wt.get_file('f').read(), 'f')
1579
 
        # Correct correct results when THIS == OTHER
 
583
        # Correct correct results when THIS == OTHER 
1580
584
        self.assertEqual(this.wt.get_file('g').read(), 'g')
1581
585
        # Text conflict when THIS & OTHER are text and BASE is dir
1582
 
        self.assertEqual(this.wt.get_file('h').read(),
 
586
        self.assertEqual(this.wt.get_file('h').read(), 
1583
587
                         conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1584
588
        self.assertEqual(this.wt.get_file_byname('h.THIS').read(),
1585
589
                         '1\n2\n3\n4\n')
1586
590
        self.assertEqual(this.wt.get_file_byname('h.OTHER').read(),
1587
591
                         'h\ni\nj\nk\n')
1588
592
        self.assertEqual(file_kind(this.wt.abspath('h.BASE')), 'directory')
1589
 
        self.assertEqual(this.wt.get_file('i').read(),
 
593
        self.assertEqual(this.wt.get_file('i').read(), 
1590
594
                         conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1591
595
        self.assertEqual(this.wt.get_file_byname('i.THIS').read(),
1592
596
                         '1\n2\n3\n4\n')
1603
607
        self.assertSubset(merge_modified, modified)
1604
608
        self.assertEqual(len(merge_modified), len(modified))
1605
609
        this.wt.remove('b')
1606
 
        this.wt.revert()
 
610
        this.wt.revert([])
1607
611
 
1608
612
    def test_file_merge(self):
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)
 
613
        if not has_symlinks():
 
614
            raise TestSkipped('Symlinks are not supported on this platform')
 
615
        base = TransformGroup("BASE")
 
616
        this = TransformGroup("THIS")
 
617
        other = TransformGroup("OTHER")
1614
618
        for tg in this, base, other:
1615
619
            tg.tt.new_directory('a', tg.root, 'a')
1616
620
            tg.tt.new_symlink('b', tg.root, 'b', 'b')
1617
621
            tg.tt.new_file('c', tg.root, 'c', 'c')
1618
622
            tg.tt.new_symlink('d', tg.root, tg.name, 'd')
1619
 
        targets = ((base, 'base-e', 'base-f', None, None),
1620
 
                   (this, 'other-e', 'this-f', 'other-g', 'this-h'),
 
623
        targets = ((base, 'base-e', 'base-f', None, None), 
 
624
                   (this, 'other-e', 'this-f', 'other-g', 'this-h'), 
1621
625
                   (other, 'other-e', None, 'other-g', 'other-h'))
1622
626
        for tg, e_target, f_target, g_target, h_target in targets:
1623
 
            for link, target in (('e', e_target), ('f', f_target),
 
627
            for link, target in (('e', e_target), ('f', f_target), 
1624
628
                                 ('g', g_target), ('h', h_target)):
1625
629
                if target is not None:
1626
630
                    tg.tt.new_symlink(link, tg.root, target, link)
1648
652
        self.assertIs(os.path.lexists(this.wt.abspath('h.OTHER')), True)
1649
653
 
1650
654
    def test_filename_merge(self):
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')
 
655
        base = TransformGroup("BASE")
 
656
        this = TransformGroup("THIS")
 
657
        other = TransformGroup("OTHER")
 
658
        base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a') 
1656
659
                                   for t in [base, this, other]]
1657
 
        base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
 
660
        base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b') 
1658
661
                                   for t in [base, this, other]]
1659
662
        base.tt.new_directory('c', base_a, 'c')
1660
663
        this.tt.new_directory('c1', this_a, 'c')
1681
684
        self.assertEqual(this.wt.id2path('f'), pathjoin('b/f1'))
1682
685
 
1683
686
    def test_filename_merge_conflicts(self):
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')
 
687
        base = TransformGroup("BASE")
 
688
        this = TransformGroup("THIS")
 
689
        other = TransformGroup("OTHER")
 
690
        base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a') 
1689
691
                                   for t in [base, this, other]]
1690
 
        base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
 
692
        base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b') 
1691
693
                                   for t in [base, this, other]]
1692
694
 
1693
695
        base.tt.new_file('g', base_a, 'g', 'g')
1711
713
        self.assertIs(os.path.lexists(this.wt.abspath('b/h1.OTHER')), False)
1712
714
        self.assertEqual(this.wt.id2path('i'), pathjoin('b/i1.OTHER'))
1713
715
 
1714
 
 
1715
 
class TestBuildTree(tests.TestCaseWithTransport):
1716
 
 
1717
 
    def test_build_tree_with_symlinks(self):
1718
 
        self.requireFeature(SymlinkFeature)
 
716
class TestBuildTree(TestCaseInTempDir):
 
717
    def test_build_tree(self):
 
718
        if not has_symlinks():
 
719
            raise TestSkipped('Test requires symlink support')
1719
720
        os.mkdir('a')
1720
721
        a = BzrDir.create_standalone_workingtree('a')
1721
722
        os.mkdir('a/foo')
1724
725
        a.add(['foo', 'foo/bar', 'foo/baz'])
1725
726
        a.commit('initial commit')
1726
727
        b = BzrDir.create_standalone_workingtree('b')
1727
 
        basis = a.basis_tree()
1728
 
        basis.lock_read()
1729
 
        self.addCleanup(basis.unlock)
1730
 
        build_tree(basis, b)
 
728
        build_tree(a.basis_tree(), b)
1731
729
        self.assertIs(os.path.isdir('b/foo'), True)
1732
730
        self.assertEqual(file('b/foo/bar', 'rb').read(), "contents")
1733
731
        self.assertEqual(os.readlink('b/foo/baz'), 'a/foo/bar')
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
 
 
 
732
        
 
733
class MockTransform(object):
 
734
 
 
735
    def has_named_child(self, by_parent, parent_id, name):
 
736
        for child_id in by_parent[parent_id]:
 
737
            if child_id == '0':
 
738
                if name == "name~":
 
739
                    return True
 
740
            elif name == "name.~%s~" % child_id:
 
741
                return True
 
742
        return False
 
743
 
 
744
class MockEntry(object):
 
745
    def __init__(self):
 
746
        object.__init__(self)
 
747
        self.name = "name"
 
748
 
 
749
class TestGetBackupName(TestCase):
 
750
    def test_get_backup_name(self):
2215
751
        tt = MockTransform()
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, None))
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([ROOT_ENTRY, 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([ROOT_ENTRY, 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/foo'])
3276
 
        wt.add(['dir'], ['dir-id'])
3277
 
        wt.commit('add dir')
3278
 
        tt = transform.TreeTransform(wt)
3279
 
        self.addCleanup(tt.finalize)
3280
 
        dir_tid = tt.trans_id_tree_path('dir')
3281
 
        orphan_tid = tt.trans_id_tree_path('dir/foo')
3282
 
        tt.delete_contents(dir_tid)
3283
 
        tt.unversion_file(dir_tid)
3284
 
        raw_conflicts = tt.find_conflicts()
3285
 
        self.assertLength(1, raw_conflicts)
3286
 
        self.assertEqual(('missing parent', 'new-1'), raw_conflicts[0])
3287
 
        return tt, orphan_tid
3288
 
 
3289
 
    def test_new_orphan_created(self):
3290
 
        wt = self.make_branch_and_tree('.')
3291
 
        self._set_orphan_policy(wt, 'move')
3292
 
        tt, orphan_tid = self._prepare_orphan(wt)
3293
 
        remaining_conflicts = resolve_conflicts(tt)
3294
 
        # Yeah for resolved conflicts !
3295
 
        self.assertLength(0, remaining_conflicts)
3296
 
        # We have a new orphan
3297
 
        self.assertEquals('foo.~1~', tt.final_name(orphan_tid))
3298
 
        self.assertEquals('bzr-orphans',
3299
 
                          tt.final_name(tt.final_parent(orphan_tid)))
3300
 
 
3301
 
    def test_never_orphan(self):
3302
 
        wt = self.make_branch_and_tree('.')
3303
 
        self._set_orphan_policy(wt, 'conflict')
3304
 
        tt, orphan_tid = self._prepare_orphan(wt)
3305
 
        remaining_conflicts = resolve_conflicts(tt)
3306
 
        self.assertLength(1, remaining_conflicts)
3307
 
        self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
3308
 
                         remaining_conflicts.pop())
3309
 
 
3310
 
    def test_orphan_error(self):
3311
 
        def bogus_orphan(tt, orphan_id, parent_id):
3312
 
            raise transform.OrphaningError(tt.final_name(orphan_id),
3313
 
                                           tt.final_name(parent_id))
3314
 
        transform.orphaning_registry.register('bogus', bogus_orphan,
3315
 
                                              'Raise an error when orphaning')
3316
 
        wt = self.make_branch_and_tree('.')
3317
 
        self._set_orphan_policy(wt, 'bogus')
3318
 
        tt, orphan_tid = self._prepare_orphan(wt)
3319
 
        remaining_conflicts = resolve_conflicts(tt)
3320
 
        self.assertLength(1, remaining_conflicts)
3321
 
        self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
3322
 
                         remaining_conflicts.pop())
3323
 
 
3324
 
    def test_unknown_orphan_policy(self):
3325
 
        wt = self.make_branch_and_tree('.')
3326
 
        # Set a fictional policy nobody ever implemented
3327
 
        self._set_orphan_policy(wt, 'donttouchmypreciouuus')
3328
 
        tt, orphan_tid = self._prepare_orphan(wt)
3329
 
        warnings = []
3330
 
        def warning(*args):
3331
 
            warnings.append(args[0] % args[1:])
3332
 
        self.overrideAttr(trace, 'warning', warning)
3333
 
        remaining_conflicts = resolve_conflicts(tt)
3334
 
        # We fallback to the default policy which create a conflict
3335
 
        self.assertLength(1, remaining_conflicts)
3336
 
        self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
3337
 
                         remaining_conflicts.pop())
3338
 
        self.assertLength(1, warnings)
3339
 
        self.assertStartsWith(warnings[0], 'donttouchmypreciouuus')
 
752
        name = get_backup_name(MockEntry(), {'a':[]}, 'a', tt)
 
753
        self.assertEqual(name, 'name.~1~')
 
754
        name = get_backup_name(MockEntry(), {'a':['1']}, 'a', tt)
 
755
        self.assertEqual(name, 'name.~2~')
 
756
        name = get_backup_name(MockEntry(), {'a':['2']}, 'a', tt)
 
757
        self.assertEqual(name, 'name.~1~')
 
758
        name = get_backup_name(MockEntry(), {'a':['2'], 'b':[]}, 'b', tt)
 
759
        self.assertEqual(name, 'name.~1~')
 
760
        name = get_backup_name(MockEntry(), {'a':['1', '2', '3']}, 'a', tt)
 
761
        self.assertEqual(name, 'name.~4~')