~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_transform.py

  • Committer: Andrew Bennetts
  • Date: 2008-02-07 07:05:13 UTC
  • mto: This revision was merged to the branch mainline in revision 3398.
  • Revision ID: andrew.bennetts@canonical.com-20080207070513-u7tvul100g1yn6n7
Add a comment to the new CSS.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2006 Canonical Ltd
 
2
#
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
#
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
#
 
13
# You should have received a copy of the GNU General Public License
 
14
# along with this program; if not, write to the Free Software
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
16
 
 
17
import os
 
18
import stat
 
19
from StringIO import StringIO
 
20
import sys
 
21
 
 
22
from bzrlib import (
 
23
    errors,
 
24
    generate_ids,
 
25
    osutils,
 
26
    progress,
 
27
    revision as _mod_revision,
 
28
    symbol_versioning,
 
29
    tests,
 
30
    urlutils,
 
31
    )
 
32
from bzrlib.bzrdir import BzrDir
 
33
from bzrlib.conflicts import (DuplicateEntry, DuplicateID, MissingParent,
 
34
                              UnversionedParent, ParentLoop, DeletingParent,
 
35
                              NonDirectoryParent)
 
36
from bzrlib.diff import show_diff_trees
 
37
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
 
38
                           ReusingTransform, CantMoveRoot, 
 
39
                           PathsNotVersionedError, ExistingLimbo,
 
40
                           ExistingPendingDeletion, ImmortalLimbo,
 
41
                           ImmortalPendingDeletion, LockError)
 
42
from bzrlib.osutils import file_kind, pathjoin
 
43
from bzrlib.merge import Merge3Merger
 
44
from bzrlib.tests import (
 
45
    CaseInsensitiveFilesystemFeature,
 
46
    SymlinkFeature,
 
47
    TestCase,
 
48
    TestCaseInTempDir,
 
49
    TestSkipped,
 
50
    )
 
51
from bzrlib.transform import (TreeTransform, ROOT_PARENT, FinalPaths, 
 
52
                              resolve_conflicts, cook_conflicts, 
 
53
                              find_interesting, build_tree, get_backup_name,
 
54
                              change_entry, _FileMover, resolve_checkout,
 
55
                              TransformPreview)
 
56
 
 
57
class TestTreeTransform(tests.TestCaseWithTransport):
 
58
 
 
59
    def setUp(self):
 
60
        super(TestTreeTransform, self).setUp()
 
61
        self.wt = self.make_branch_and_tree('.', format='dirstate-with-subtree')
 
62
        os.chdir('..')
 
63
 
 
64
    def get_transform(self):
 
65
        transform = TreeTransform(self.wt)
 
66
        #self.addCleanup(transform.finalize)
 
67
        return transform, transform.root
 
68
 
 
69
    def test_existing_limbo(self):
 
70
        transform, root = self.get_transform()
 
71
        limbo_name = transform._limbodir
 
72
        deletion_path = transform._deletiondir
 
73
        os.mkdir(pathjoin(limbo_name, 'hehe'))
 
74
        self.assertRaises(ImmortalLimbo, transform.apply)
 
75
        self.assertRaises(LockError, self.wt.unlock)
 
76
        self.assertRaises(ExistingLimbo, self.get_transform)
 
77
        self.assertRaises(LockError, self.wt.unlock)
 
78
        os.rmdir(pathjoin(limbo_name, 'hehe'))
 
79
        os.rmdir(limbo_name)
 
80
        os.rmdir(deletion_path)
 
81
        transform, root = self.get_transform()
 
82
        transform.apply()
 
83
 
 
84
    def test_existing_pending_deletion(self):
 
85
        transform, root = self.get_transform()
 
86
        deletion_path = self._limbodir = urlutils.local_path_from_url(
 
87
            transform._tree._control_files.controlfilename('pending-deletion'))
 
88
        os.mkdir(pathjoin(deletion_path, 'blocking-directory'))
 
89
        self.assertRaises(ImmortalPendingDeletion, transform.apply)
 
90
        self.assertRaises(LockError, self.wt.unlock)
 
91
        self.assertRaises(ExistingPendingDeletion, self.get_transform)
 
92
 
 
93
    def test_build(self):
 
94
        transform, root = self.get_transform()
 
95
        self.wt.lock_tree_write()
 
96
        self.addCleanup(self.wt.unlock)
 
97
        self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
 
98
        imaginary_id = transform.trans_id_tree_path('imaginary')
 
99
        imaginary_id2 = transform.trans_id_tree_path('imaginary/')
 
100
        self.assertEqual(imaginary_id, imaginary_id2)
 
101
        self.assertEqual(transform.get_tree_parent(imaginary_id), root)
 
102
        self.assertEqual(transform.final_kind(root), 'directory')
 
103
        self.assertEqual(transform.final_file_id(root), self.wt.get_root_id())
 
104
        trans_id = transform.create_path('name', root)
 
105
        self.assertIs(transform.final_file_id(trans_id), None)
 
106
        self.assertRaises(NoSuchFile, transform.final_kind, trans_id)
 
107
        transform.create_file('contents', trans_id)
 
108
        transform.set_executability(True, trans_id)
 
109
        transform.version_file('my_pretties', trans_id)
 
110
        self.assertRaises(DuplicateKey, transform.version_file,
 
111
                          'my_pretties', trans_id)
 
112
        self.assertEqual(transform.final_file_id(trans_id), 'my_pretties')
 
113
        self.assertEqual(transform.final_parent(trans_id), root)
 
114
        self.assertIs(transform.final_parent(root), ROOT_PARENT)
 
115
        self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
 
116
        oz_id = transform.create_path('oz', root)
 
117
        transform.create_directory(oz_id)
 
118
        transform.version_file('ozzie', oz_id)
 
119
        trans_id2 = transform.create_path('name2', root)
 
120
        transform.create_file('contents', trans_id2)
 
121
        transform.set_executability(False, trans_id2)
 
122
        transform.version_file('my_pretties2', trans_id2)
 
123
        modified_paths = transform.apply().modified_paths
 
124
        self.assertEqual('contents', self.wt.get_file_byname('name').read())
 
125
        self.assertEqual(self.wt.path2id('name'), 'my_pretties')
 
126
        self.assertIs(self.wt.is_executable('my_pretties'), True)
 
127
        self.assertIs(self.wt.is_executable('my_pretties2'), False)
 
128
        self.assertEqual('directory', file_kind(self.wt.abspath('oz')))
 
129
        self.assertEqual(len(modified_paths), 3)
 
130
        tree_mod_paths = [self.wt.id2abspath(f) for f in 
 
131
                          ('ozzie', 'my_pretties', 'my_pretties2')]
 
132
        self.assertSubset(tree_mod_paths, modified_paths)
 
133
        # is it safe to finalize repeatedly?
 
134
        transform.finalize()
 
135
        transform.finalize()
 
136
 
 
137
    def test_convenience(self):
 
138
        transform, root = self.get_transform()
 
139
        self.wt.lock_tree_write()
 
140
        self.addCleanup(self.wt.unlock)
 
141
        trans_id = transform.new_file('name', root, 'contents', 
 
142
                                      'my_pretties', True)
 
143
        oz = transform.new_directory('oz', root, 'oz-id')
 
144
        dorothy = transform.new_directory('dorothy', oz, 'dorothy-id')
 
145
        toto = transform.new_file('toto', dorothy, 'toto-contents', 
 
146
                                  'toto-id', False)
 
147
 
 
148
        self.assertEqual(len(transform.find_conflicts()), 0)
 
149
        transform.apply()
 
150
        self.assertRaises(ReusingTransform, transform.find_conflicts)
 
151
        self.assertEqual('contents', file(self.wt.abspath('name')).read())
 
152
        self.assertEqual(self.wt.path2id('name'), 'my_pretties')
 
153
        self.assertIs(self.wt.is_executable('my_pretties'), True)
 
154
        self.assertEqual(self.wt.path2id('oz'), 'oz-id')
 
155
        self.assertEqual(self.wt.path2id('oz/dorothy'), 'dorothy-id')
 
156
        self.assertEqual(self.wt.path2id('oz/dorothy/toto'), 'toto-id')
 
157
 
 
158
        self.assertEqual('toto-contents',
 
159
                         self.wt.get_file_byname('oz/dorothy/toto').read())
 
160
        self.assertIs(self.wt.is_executable('toto-id'), False)
 
161
 
 
162
    def test_tree_reference(self):
 
163
        transform, root = self.get_transform()
 
164
        tree = transform._tree
 
165
        trans_id = transform.new_directory('reference', root, 'subtree-id')
 
166
        transform.set_tree_reference('subtree-revision', trans_id)
 
167
        transform.apply()
 
168
        tree.lock_read()
 
169
        self.addCleanup(tree.unlock)
 
170
        self.assertEqual('subtree-revision',
 
171
                         tree.inventory['subtree-id'].reference_revision)
 
172
 
 
173
    def test_conflicts(self):
 
174
        transform, root = self.get_transform()
 
175
        trans_id = transform.new_file('name', root, 'contents', 
 
176
                                      'my_pretties')
 
177
        self.assertEqual(len(transform.find_conflicts()), 0)
 
178
        trans_id2 = transform.new_file('name', root, 'Crontents', 'toto')
 
179
        self.assertEqual(transform.find_conflicts(), 
 
180
                         [('duplicate', trans_id, trans_id2, 'name')])
 
181
        self.assertRaises(MalformedTransform, transform.apply)
 
182
        transform.adjust_path('name', trans_id, trans_id2)
 
183
        self.assertEqual(transform.find_conflicts(), 
 
184
                         [('non-directory parent', trans_id)])
 
185
        tinman_id = transform.trans_id_tree_path('tinman')
 
186
        transform.adjust_path('name', tinman_id, trans_id2)
 
187
        self.assertEqual(transform.find_conflicts(), 
 
188
                         [('unversioned parent', tinman_id), 
 
189
                          ('missing parent', tinman_id)])
 
190
        lion_id = transform.create_path('lion', root)
 
191
        self.assertEqual(transform.find_conflicts(), 
 
192
                         [('unversioned parent', tinman_id), 
 
193
                          ('missing parent', tinman_id)])
 
194
        transform.adjust_path('name', lion_id, trans_id2)
 
195
        self.assertEqual(transform.find_conflicts(), 
 
196
                         [('unversioned parent', lion_id),
 
197
                          ('missing parent', lion_id)])
 
198
        transform.version_file("Courage", lion_id)
 
199
        self.assertEqual(transform.find_conflicts(), 
 
200
                         [('missing parent', lion_id), 
 
201
                          ('versioning no contents', lion_id)])
 
202
        transform.adjust_path('name2', root, trans_id2)
 
203
        self.assertEqual(transform.find_conflicts(), 
 
204
                         [('versioning no contents', lion_id)])
 
205
        transform.create_file('Contents, okay?', lion_id)
 
206
        transform.adjust_path('name2', trans_id2, trans_id2)
 
207
        self.assertEqual(transform.find_conflicts(), 
 
208
                         [('parent loop', trans_id2), 
 
209
                          ('non-directory parent', trans_id2)])
 
210
        transform.adjust_path('name2', root, trans_id2)
 
211
        oz_id = transform.new_directory('oz', root)
 
212
        transform.set_executability(True, oz_id)
 
213
        self.assertEqual(transform.find_conflicts(), 
 
214
                         [('unversioned executability', oz_id)])
 
215
        transform.version_file('oz-id', oz_id)
 
216
        self.assertEqual(transform.find_conflicts(), 
 
217
                         [('non-file executability', oz_id)])
 
218
        transform.set_executability(None, oz_id)
 
219
        tip_id = transform.new_file('tip', oz_id, 'ozma', 'tip-id')
 
220
        transform.apply()
 
221
        self.assertEqual(self.wt.path2id('name'), 'my_pretties')
 
222
        self.assertEqual('contents', file(self.wt.abspath('name')).read())
 
223
        transform2, root = self.get_transform()
 
224
        oz_id = transform2.trans_id_tree_file_id('oz-id')
 
225
        newtip = transform2.new_file('tip', oz_id, 'other', 'tip-id')
 
226
        result = transform2.find_conflicts()
 
227
        fp = FinalPaths(transform2)
 
228
        self.assert_('oz/tip' in transform2._tree_path_ids)
 
229
        self.assertEqual(fp.get_path(newtip), pathjoin('oz', 'tip'))
 
230
        self.assertEqual(len(result), 2)
 
231
        self.assertEqual((result[0][0], result[0][1]), 
 
232
                         ('duplicate', newtip))
 
233
        self.assertEqual((result[1][0], result[1][2]), 
 
234
                         ('duplicate id', newtip))
 
235
        transform2.finalize()
 
236
        transform3 = TreeTransform(self.wt)
 
237
        self.addCleanup(transform3.finalize)
 
238
        oz_id = transform3.trans_id_tree_file_id('oz-id')
 
239
        transform3.delete_contents(oz_id)
 
240
        self.assertEqual(transform3.find_conflicts(), 
 
241
                         [('missing parent', oz_id)])
 
242
        root_id = transform3.root
 
243
        tip_id = transform3.trans_id_tree_file_id('tip-id')
 
244
        transform3.adjust_path('tip', root_id, tip_id)
 
245
        transform3.apply()
 
246
 
 
247
    def test_conflict_on_case_insensitive(self):
 
248
        tree = self.make_branch_and_tree('tree')
 
249
        # Don't try this at home, kids!
 
250
        # Force the tree to report that it is case sensitive, for conflict
 
251
        # resolution tests
 
252
        tree.case_sensitive = True
 
253
        transform = TreeTransform(tree)
 
254
        self.addCleanup(transform.finalize)
 
255
        transform.new_file('file', transform.root, 'content')
 
256
        transform.new_file('FiLe', transform.root, 'content')
 
257
        result = transform.find_conflicts()
 
258
        self.assertEqual([], result)
 
259
        transform.finalize()
 
260
        # Force the tree to report that it is case insensitive, for conflict
 
261
        # generation tests
 
262
        tree.case_sensitive = False
 
263
        transform = TreeTransform(tree)
 
264
        self.addCleanup(transform.finalize)
 
265
        transform.new_file('file', transform.root, 'content')
 
266
        transform.new_file('FiLe', transform.root, 'content')
 
267
        result = transform.find_conflicts()
 
268
        self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
 
269
 
 
270
    def test_conflict_on_case_insensitive_existing(self):
 
271
        tree = self.make_branch_and_tree('tree')
 
272
        self.build_tree(['tree/FiLe'])
 
273
        # Don't try this at home, kids!
 
274
        # Force the tree to report that it is case sensitive, for conflict
 
275
        # resolution tests
 
276
        tree.case_sensitive = True
 
277
        transform = TreeTransform(tree)
 
278
        self.addCleanup(transform.finalize)
 
279
        transform.new_file('file', transform.root, 'content')
 
280
        result = transform.find_conflicts()
 
281
        self.assertEqual([], result)
 
282
        transform.finalize()
 
283
        # Force the tree to report that it is case insensitive, for conflict
 
284
        # generation tests
 
285
        tree.case_sensitive = False
 
286
        transform = TreeTransform(tree)
 
287
        self.addCleanup(transform.finalize)
 
288
        transform.new_file('file', transform.root, 'content')
 
289
        result = transform.find_conflicts()
 
290
        self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
 
291
 
 
292
    def test_resolve_case_insensitive_conflict(self):
 
293
        tree = self.make_branch_and_tree('tree')
 
294
        # Don't try this at home, kids!
 
295
        # Force the tree to report that it is case insensitive, for conflict
 
296
        # resolution tests
 
297
        tree.case_sensitive = False
 
298
        transform = TreeTransform(tree)
 
299
        self.addCleanup(transform.finalize)
 
300
        transform.new_file('file', transform.root, 'content')
 
301
        transform.new_file('FiLe', transform.root, 'content')
 
302
        resolve_conflicts(transform)
 
303
        transform.apply()
 
304
        self.failUnlessExists('tree/file')
 
305
        self.failUnlessExists('tree/FiLe.moved')
 
306
 
 
307
    def test_resolve_checkout_case_conflict(self):
 
308
        tree = self.make_branch_and_tree('tree')
 
309
        # Don't try this at home, kids!
 
310
        # Force the tree to report that it is case insensitive, for conflict
 
311
        # resolution tests
 
312
        tree.case_sensitive = False
 
313
        transform = TreeTransform(tree)
 
314
        self.addCleanup(transform.finalize)
 
315
        transform.new_file('file', transform.root, 'content')
 
316
        transform.new_file('FiLe', transform.root, 'content')
 
317
        resolve_conflicts(transform,
 
318
                          pass_func=lambda t, c: resolve_checkout(t, c, []))
 
319
        transform.apply()
 
320
        self.failUnlessExists('tree/file')
 
321
        self.failUnlessExists('tree/FiLe.moved')
 
322
 
 
323
    def test_apply_case_conflict(self):
 
324
        """Ensure that a transform with case conflicts can always be applied"""
 
325
        tree = self.make_branch_and_tree('tree')
 
326
        transform = TreeTransform(tree)
 
327
        self.addCleanup(transform.finalize)
 
328
        transform.new_file('file', transform.root, 'content')
 
329
        transform.new_file('FiLe', transform.root, 'content')
 
330
        dir = transform.new_directory('dir', transform.root)
 
331
        transform.new_file('dirfile', dir, 'content')
 
332
        transform.new_file('dirFiLe', dir, 'content')
 
333
        resolve_conflicts(transform)
 
334
        transform.apply()
 
335
        self.failUnlessExists('tree/file')
 
336
        if not os.path.exists('tree/FiLe.moved'):
 
337
            self.failUnlessExists('tree/FiLe')
 
338
        self.failUnlessExists('tree/dir/dirfile')
 
339
        if not os.path.exists('tree/dir/dirFiLe.moved'):
 
340
            self.failUnlessExists('tree/dir/dirFiLe')
 
341
 
 
342
    def test_case_insensitive_limbo(self):
 
343
        tree = self.make_branch_and_tree('tree')
 
344
        # Don't try this at home, kids!
 
345
        # Force the tree to report that it is case insensitive
 
346
        tree.case_sensitive = False
 
347
        transform = TreeTransform(tree)
 
348
        self.addCleanup(transform.finalize)
 
349
        dir = transform.new_directory('dir', transform.root)
 
350
        first = transform.new_file('file', dir, 'content')
 
351
        second = transform.new_file('FiLe', dir, 'content')
 
352
        self.assertContainsRe(transform._limbo_name(first), 'new-1/file')
 
353
        self.assertNotContainsRe(transform._limbo_name(second), 'new-1/FiLe')
 
354
 
 
355
    def test_add_del(self):
 
356
        start, root = self.get_transform()
 
357
        start.new_directory('a', root, 'a')
 
358
        start.apply()
 
359
        transform, root = self.get_transform()
 
360
        transform.delete_versioned(transform.trans_id_tree_file_id('a'))
 
361
        transform.new_directory('a', root, 'a')
 
362
        transform.apply()
 
363
 
 
364
    def test_unversioning(self):
 
365
        create_tree, root = self.get_transform()
 
366
        parent_id = create_tree.new_directory('parent', root, 'parent-id')
 
367
        create_tree.new_file('child', parent_id, 'child', 'child-id')
 
368
        create_tree.apply()
 
369
        unversion = TreeTransform(self.wt)
 
370
        self.addCleanup(unversion.finalize)
 
371
        parent = unversion.trans_id_tree_path('parent')
 
372
        unversion.unversion_file(parent)
 
373
        self.assertEqual(unversion.find_conflicts(), 
 
374
                         [('unversioned parent', parent_id)])
 
375
        file_id = unversion.trans_id_tree_file_id('child-id')
 
376
        unversion.unversion_file(file_id)
 
377
        unversion.apply()
 
378
 
 
379
    def test_name_invariants(self):
 
380
        create_tree, root = self.get_transform()
 
381
        # prepare tree
 
382
        root = create_tree.root
 
383
        create_tree.new_file('name1', root, 'hello1', 'name1')
 
384
        create_tree.new_file('name2', root, 'hello2', 'name2')
 
385
        ddir = create_tree.new_directory('dying_directory', root, 'ddir')
 
386
        create_tree.new_file('dying_file', ddir, 'goodbye1', 'dfile')
 
387
        create_tree.new_file('moving_file', ddir, 'later1', 'mfile')
 
388
        create_tree.new_file('moving_file2', root, 'later2', 'mfile2')
 
389
        create_tree.apply()
 
390
 
 
391
        mangle_tree,root = self.get_transform()
 
392
        root = mangle_tree.root
 
393
        #swap names
 
394
        name1 = mangle_tree.trans_id_tree_file_id('name1')
 
395
        name2 = mangle_tree.trans_id_tree_file_id('name2')
 
396
        mangle_tree.adjust_path('name2', root, name1)
 
397
        mangle_tree.adjust_path('name1', root, name2)
 
398
 
 
399
        #tests for deleting parent directories 
 
400
        ddir = mangle_tree.trans_id_tree_file_id('ddir')
 
401
        mangle_tree.delete_contents(ddir)
 
402
        dfile = mangle_tree.trans_id_tree_file_id('dfile')
 
403
        mangle_tree.delete_versioned(dfile)
 
404
        mangle_tree.unversion_file(dfile)
 
405
        mfile = mangle_tree.trans_id_tree_file_id('mfile')
 
406
        mangle_tree.adjust_path('mfile', root, mfile)
 
407
 
 
408
        #tests for adding parent directories
 
409
        newdir = mangle_tree.new_directory('new_directory', root, 'newdir')
 
410
        mfile2 = mangle_tree.trans_id_tree_file_id('mfile2')
 
411
        mangle_tree.adjust_path('mfile2', newdir, mfile2)
 
412
        mangle_tree.new_file('newfile', newdir, 'hello3', 'dfile')
 
413
        self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
 
414
        self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
 
415
        self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
 
416
        mangle_tree.apply()
 
417
        self.assertEqual(file(self.wt.abspath('name1')).read(), 'hello2')
 
418
        self.assertEqual(file(self.wt.abspath('name2')).read(), 'hello1')
 
419
        mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
 
420
        self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
 
421
        self.assertEqual(file(mfile2_path).read(), 'later2')
 
422
        self.assertEqual(self.wt.id2path('mfile2'), 'new_directory/mfile2')
 
423
        self.assertEqual(self.wt.path2id('new_directory/mfile2'), 'mfile2')
 
424
        newfile_path = self.wt.abspath(pathjoin('new_directory','newfile'))
 
425
        self.assertEqual(file(newfile_path).read(), 'hello3')
 
426
        self.assertEqual(self.wt.path2id('dying_directory'), 'ddir')
 
427
        self.assertIs(self.wt.path2id('dying_directory/dying_file'), None)
 
428
        mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
 
429
 
 
430
    def test_both_rename(self):
 
431
        create_tree,root = self.get_transform()
 
432
        newdir = create_tree.new_directory('selftest', root, 'selftest-id')
 
433
        create_tree.new_file('blackbox.py', newdir, 'hello1', 'blackbox-id')
 
434
        create_tree.apply()        
 
435
        mangle_tree,root = self.get_transform()
 
436
        selftest = mangle_tree.trans_id_tree_file_id('selftest-id')
 
437
        blackbox = mangle_tree.trans_id_tree_file_id('blackbox-id')
 
438
        mangle_tree.adjust_path('test', root, selftest)
 
439
        mangle_tree.adjust_path('test_too_much', root, selftest)
 
440
        mangle_tree.set_executability(True, blackbox)
 
441
        mangle_tree.apply()
 
442
 
 
443
    def test_both_rename2(self):
 
444
        create_tree,root = self.get_transform()
 
445
        bzrlib = create_tree.new_directory('bzrlib', root, 'bzrlib-id')
 
446
        tests = create_tree.new_directory('tests', bzrlib, 'tests-id')
 
447
        blackbox = create_tree.new_directory('blackbox', tests, 'blackbox-id')
 
448
        create_tree.new_file('test_too_much.py', blackbox, 'hello1', 
 
449
                             'test_too_much-id')
 
450
        create_tree.apply()        
 
451
        mangle_tree,root = self.get_transform()
 
452
        bzrlib = mangle_tree.trans_id_tree_file_id('bzrlib-id')
 
453
        tests = mangle_tree.trans_id_tree_file_id('tests-id')
 
454
        test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
 
455
        mangle_tree.adjust_path('selftest', bzrlib, tests)
 
456
        mangle_tree.adjust_path('blackbox.py', tests, test_too_much) 
 
457
        mangle_tree.set_executability(True, test_too_much)
 
458
        mangle_tree.apply()
 
459
 
 
460
    def test_both_rename3(self):
 
461
        create_tree,root = self.get_transform()
 
462
        tests = create_tree.new_directory('tests', root, 'tests-id')
 
463
        create_tree.new_file('test_too_much.py', tests, 'hello1', 
 
464
                             'test_too_much-id')
 
465
        create_tree.apply()        
 
466
        mangle_tree,root = self.get_transform()
 
467
        tests = mangle_tree.trans_id_tree_file_id('tests-id')
 
468
        test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
 
469
        mangle_tree.adjust_path('selftest', root, tests)
 
470
        mangle_tree.adjust_path('blackbox.py', tests, test_too_much) 
 
471
        mangle_tree.set_executability(True, test_too_much)
 
472
        mangle_tree.apply()
 
473
 
 
474
    def test_move_dangling_ie(self):
 
475
        create_tree, root = self.get_transform()
 
476
        # prepare tree
 
477
        root = create_tree.root
 
478
        create_tree.new_file('name1', root, 'hello1', 'name1')
 
479
        create_tree.apply()
 
480
        delete_contents, root = self.get_transform()
 
481
        file = delete_contents.trans_id_tree_file_id('name1')
 
482
        delete_contents.delete_contents(file)
 
483
        delete_contents.apply()
 
484
        move_id, root = self.get_transform()
 
485
        name1 = move_id.trans_id_tree_file_id('name1')
 
486
        newdir = move_id.new_directory('dir', root, 'newdir')
 
487
        move_id.adjust_path('name2', newdir, name1)
 
488
        move_id.apply()
 
489
        
 
490
    def test_replace_dangling_ie(self):
 
491
        create_tree, root = self.get_transform()
 
492
        # prepare tree
 
493
        root = create_tree.root
 
494
        create_tree.new_file('name1', root, 'hello1', 'name1')
 
495
        create_tree.apply()
 
496
        delete_contents = TreeTransform(self.wt)
 
497
        self.addCleanup(delete_contents.finalize)
 
498
        file = delete_contents.trans_id_tree_file_id('name1')
 
499
        delete_contents.delete_contents(file)
 
500
        delete_contents.apply()
 
501
        delete_contents.finalize()
 
502
        replace = TreeTransform(self.wt)
 
503
        self.addCleanup(replace.finalize)
 
504
        name2 = replace.new_file('name2', root, 'hello2', 'name1')
 
505
        conflicts = replace.find_conflicts()
 
506
        name1 = replace.trans_id_tree_file_id('name1')
 
507
        self.assertEqual(conflicts, [('duplicate id', name1, name2)])
 
508
        resolve_conflicts(replace)
 
509
        replace.apply()
 
510
 
 
511
    def test_symlinks(self):
 
512
        self.requireFeature(SymlinkFeature)
 
513
        transform,root = self.get_transform()
 
514
        oz_id = transform.new_directory('oz', root, 'oz-id')
 
515
        wizard = transform.new_symlink('wizard', oz_id, 'wizard-target', 
 
516
                                       'wizard-id')
 
517
        wiz_id = transform.create_path('wizard2', oz_id)
 
518
        transform.create_symlink('behind_curtain', wiz_id)
 
519
        transform.version_file('wiz-id2', wiz_id)            
 
520
        transform.set_executability(True, wiz_id)
 
521
        self.assertEqual(transform.find_conflicts(), 
 
522
                         [('non-file executability', wiz_id)])
 
523
        transform.set_executability(None, wiz_id)
 
524
        transform.apply()
 
525
        self.assertEqual(self.wt.path2id('oz/wizard'), 'wizard-id')
 
526
        self.assertEqual(file_kind(self.wt.abspath('oz/wizard')), 'symlink')
 
527
        self.assertEqual(os.readlink(self.wt.abspath('oz/wizard2')), 
 
528
                         'behind_curtain')
 
529
        self.assertEqual(os.readlink(self.wt.abspath('oz/wizard')),
 
530
                         'wizard-target')
 
531
 
 
532
    def test_unable_create_symlink(self):
 
533
        def tt_helper():
 
534
            wt = self.make_branch_and_tree('.')
 
535
            tt = TreeTransform(wt)  # TreeTransform obtains write lock
 
536
            try:
 
537
                tt.new_symlink('foo', tt.root, 'bar')
 
538
                tt.apply()
 
539
            finally:
 
540
                wt.unlock()
 
541
        os_symlink = getattr(os, 'symlink', None)
 
542
        os.symlink = None
 
543
        try:
 
544
            err = self.assertRaises(errors.UnableCreateSymlink, tt_helper)
 
545
            self.assertEquals(
 
546
                "Unable to create symlink 'foo' on this platform",
 
547
                str(err))
 
548
        finally:
 
549
            if os_symlink:
 
550
                os.symlink = os_symlink
 
551
 
 
552
    def get_conflicted(self):
 
553
        create,root = self.get_transform()
 
554
        create.new_file('dorothy', root, 'dorothy', 'dorothy-id')
 
555
        oz = create.new_directory('oz', root, 'oz-id')
 
556
        create.new_directory('emeraldcity', oz, 'emerald-id')
 
557
        create.apply()
 
558
        conflicts,root = self.get_transform()
 
559
        # set up duplicate entry, duplicate id
 
560
        new_dorothy = conflicts.new_file('dorothy', root, 'dorothy', 
 
561
                                         'dorothy-id')
 
562
        old_dorothy = conflicts.trans_id_tree_file_id('dorothy-id')
 
563
        oz = conflicts.trans_id_tree_file_id('oz-id')
 
564
        # set up DeletedParent parent conflict
 
565
        conflicts.delete_versioned(oz)
 
566
        emerald = conflicts.trans_id_tree_file_id('emerald-id')
 
567
        # set up MissingParent conflict
 
568
        munchkincity = conflicts.trans_id_file_id('munchkincity-id')
 
569
        conflicts.adjust_path('munchkincity', root, munchkincity)
 
570
        conflicts.new_directory('auntem', munchkincity, 'auntem-id')
 
571
        # set up parent loop
 
572
        conflicts.adjust_path('emeraldcity', emerald, emerald)
 
573
        return conflicts, emerald, oz, old_dorothy, new_dorothy
 
574
 
 
575
    def test_conflict_resolution(self):
 
576
        conflicts, emerald, oz, old_dorothy, new_dorothy =\
 
577
            self.get_conflicted()
 
578
        resolve_conflicts(conflicts)
 
579
        self.assertEqual(conflicts.final_name(old_dorothy), 'dorothy.moved')
 
580
        self.assertIs(conflicts.final_file_id(old_dorothy), None)
 
581
        self.assertEqual(conflicts.final_name(new_dorothy), 'dorothy')
 
582
        self.assertEqual(conflicts.final_file_id(new_dorothy), 'dorothy-id')
 
583
        self.assertEqual(conflicts.final_parent(emerald), oz)
 
584
        conflicts.apply()
 
585
 
 
586
    def test_cook_conflicts(self):
 
587
        tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
 
588
        raw_conflicts = resolve_conflicts(tt)
 
589
        cooked_conflicts = cook_conflicts(raw_conflicts, tt)
 
590
        duplicate = DuplicateEntry('Moved existing file to', 'dorothy.moved', 
 
591
                                   'dorothy', None, 'dorothy-id')
 
592
        self.assertEqual(cooked_conflicts[0], duplicate)
 
593
        duplicate_id = DuplicateID('Unversioned existing file', 
 
594
                                   'dorothy.moved', 'dorothy', None,
 
595
                                   'dorothy-id')
 
596
        self.assertEqual(cooked_conflicts[1], duplicate_id)
 
597
        missing_parent = MissingParent('Created directory', 'munchkincity',
 
598
                                       'munchkincity-id')
 
599
        deleted_parent = DeletingParent('Not deleting', 'oz', 'oz-id')
 
600
        self.assertEqual(cooked_conflicts[2], missing_parent)
 
601
        unversioned_parent = UnversionedParent('Versioned directory',
 
602
                                               'munchkincity',
 
603
                                               'munchkincity-id')
 
604
        unversioned_parent2 = UnversionedParent('Versioned directory', 'oz',
 
605
                                               'oz-id')
 
606
        self.assertEqual(cooked_conflicts[3], unversioned_parent)
 
607
        parent_loop = ParentLoop('Cancelled move', 'oz/emeraldcity', 
 
608
                                 'oz/emeraldcity', 'emerald-id', 'emerald-id')
 
609
        self.assertEqual(cooked_conflicts[4], deleted_parent)
 
610
        self.assertEqual(cooked_conflicts[5], unversioned_parent2)
 
611
        self.assertEqual(cooked_conflicts[6], parent_loop)
 
612
        self.assertEqual(len(cooked_conflicts), 7)
 
613
        tt.finalize()
 
614
 
 
615
    def test_string_conflicts(self):
 
616
        tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
 
617
        raw_conflicts = resolve_conflicts(tt)
 
618
        cooked_conflicts = cook_conflicts(raw_conflicts, tt)
 
619
        tt.finalize()
 
620
        conflicts_s = [str(c) for c in cooked_conflicts]
 
621
        self.assertEqual(len(cooked_conflicts), len(conflicts_s))
 
622
        self.assertEqual(conflicts_s[0], 'Conflict adding file dorothy.  '
 
623
                                         'Moved existing file to '
 
624
                                         'dorothy.moved.')
 
625
        self.assertEqual(conflicts_s[1], 'Conflict adding id to dorothy.  '
 
626
                                         'Unversioned existing file '
 
627
                                         'dorothy.moved.')
 
628
        self.assertEqual(conflicts_s[2], 'Conflict adding files to'
 
629
                                         ' munchkincity.  Created directory.')
 
630
        self.assertEqual(conflicts_s[3], 'Conflict because munchkincity is not'
 
631
                                         ' versioned, but has versioned'
 
632
                                         ' children.  Versioned directory.')
 
633
        self.assertEqualDiff(conflicts_s[4], "Conflict: can't delete oz because it"
 
634
                                         " is not empty.  Not deleting.")
 
635
        self.assertEqual(conflicts_s[5], 'Conflict because oz is not'
 
636
                                         ' versioned, but has versioned'
 
637
                                         ' children.  Versioned directory.')
 
638
        self.assertEqual(conflicts_s[6], 'Conflict moving oz/emeraldcity into'
 
639
                                         ' oz/emeraldcity.  Cancelled move.')
 
640
 
 
641
    def prepare_wrong_parent_kind(self):
 
642
        tt, root = self.get_transform()
 
643
        tt.new_file('parent', root, 'contents', 'parent-id')
 
644
        tt.apply()
 
645
        tt, root = self.get_transform()
 
646
        parent_id = tt.trans_id_file_id('parent-id')
 
647
        tt.new_file('child,', parent_id, 'contents2', 'file-id')
 
648
        return tt
 
649
 
 
650
    def test_find_conflicts_wrong_parent_kind(self):
 
651
        tt = self.prepare_wrong_parent_kind()
 
652
        tt.find_conflicts()
 
653
 
 
654
    def test_resolve_conflicts_wrong_existing_parent_kind(self):
 
655
        tt = self.prepare_wrong_parent_kind()
 
656
        raw_conflicts = resolve_conflicts(tt)
 
657
        self.assertEqual(set([('non-directory parent', 'Created directory',
 
658
                         'new-3')]), raw_conflicts)
 
659
        cooked_conflicts = cook_conflicts(raw_conflicts, tt)
 
660
        self.assertEqual([NonDirectoryParent('Created directory', 'parent.new',
 
661
        'parent-id')], cooked_conflicts)
 
662
        tt.apply()
 
663
        self.assertEqual(None, self.wt.path2id('parent'))
 
664
        self.assertEqual('parent-id', self.wt.path2id('parent.new'))
 
665
 
 
666
    def test_resolve_conflicts_wrong_new_parent_kind(self):
 
667
        tt, root = self.get_transform()
 
668
        parent_id = tt.new_directory('parent', root, 'parent-id')
 
669
        tt.new_file('child,', parent_id, 'contents2', 'file-id')
 
670
        tt.apply()
 
671
        tt, root = self.get_transform()
 
672
        parent_id = tt.trans_id_file_id('parent-id')
 
673
        tt.delete_contents(parent_id)
 
674
        tt.create_file('contents', parent_id)
 
675
        raw_conflicts = resolve_conflicts(tt)
 
676
        self.assertEqual(set([('non-directory parent', 'Created directory',
 
677
                         'new-3')]), raw_conflicts)
 
678
        tt.apply()
 
679
        self.assertEqual(None, self.wt.path2id('parent'))
 
680
        self.assertEqual('parent-id', self.wt.path2id('parent.new'))
 
681
 
 
682
    def test_resolve_conflicts_wrong_parent_kind_unversioned(self):
 
683
        tt, root = self.get_transform()
 
684
        parent_id = tt.new_directory('parent', root)
 
685
        tt.new_file('child,', parent_id, 'contents2')
 
686
        tt.apply()
 
687
        tt, root = self.get_transform()
 
688
        parent_id = tt.trans_id_tree_path('parent')
 
689
        tt.delete_contents(parent_id)
 
690
        tt.create_file('contents', parent_id)
 
691
        resolve_conflicts(tt)
 
692
        tt.apply()
 
693
        self.assertIs(None, self.wt.path2id('parent'))
 
694
        self.assertIs(None, self.wt.path2id('parent.new'))
 
695
 
 
696
    def test_moving_versioned_directories(self):
 
697
        create, root = self.get_transform()
 
698
        kansas = create.new_directory('kansas', root, 'kansas-id')
 
699
        create.new_directory('house', kansas, 'house-id')
 
700
        create.new_directory('oz', root, 'oz-id')
 
701
        create.apply()
 
702
        cyclone, root = self.get_transform()
 
703
        oz = cyclone.trans_id_tree_file_id('oz-id')
 
704
        house = cyclone.trans_id_tree_file_id('house-id')
 
705
        cyclone.adjust_path('house', oz, house)
 
706
        cyclone.apply()
 
707
 
 
708
    def test_moving_root(self):
 
709
        create, root = self.get_transform()
 
710
        fun = create.new_directory('fun', root, 'fun-id')
 
711
        create.new_directory('sun', root, 'sun-id')
 
712
        create.new_directory('moon', root, 'moon')
 
713
        create.apply()
 
714
        transform, root = self.get_transform()
 
715
        transform.adjust_root_path('oldroot', fun)
 
716
        new_root=transform.trans_id_tree_path('')
 
717
        transform.version_file('new-root', new_root)
 
718
        transform.apply()
 
719
 
 
720
    def test_renames(self):
 
721
        create, root = self.get_transform()
 
722
        old = create.new_directory('old-parent', root, 'old-id')
 
723
        intermediate = create.new_directory('intermediate', old, 'im-id')
 
724
        myfile = create.new_file('myfile', intermediate, 'myfile-text',
 
725
                                 'myfile-id')
 
726
        create.apply()
 
727
        rename, root = self.get_transform()
 
728
        old = rename.trans_id_file_id('old-id')
 
729
        rename.adjust_path('new', root, old)
 
730
        myfile = rename.trans_id_file_id('myfile-id')
 
731
        rename.set_executability(True, myfile)
 
732
        rename.apply()
 
733
 
 
734
    def test_find_interesting(self):
 
735
        create, root = self.get_transform()
 
736
        wt = create._tree
 
737
        create.new_file('vfile', root, 'myfile-text', 'myfile-id')
 
738
        create.new_file('uvfile', root, 'othertext')
 
739
        create.apply()
 
740
        result = self.applyDeprecated(symbol_versioning.zero_fifteen,
 
741
            find_interesting, wt, wt, ['vfile'])
 
742
        self.assertEqual(result, set(['myfile-id']))
 
743
 
 
744
    def test_set_executability_order(self):
 
745
        """Ensure that executability behaves the same, no matter what order.
 
746
        
 
747
        - create file and set executability simultaneously
 
748
        - create file and set executability afterward
 
749
        - unsetting the executability of a file whose executability has not been
 
750
        declared should throw an exception (this may happen when a
 
751
        merge attempts to create a file with a duplicate ID)
 
752
        """
 
753
        transform, root = self.get_transform()
 
754
        wt = transform._tree
 
755
        wt.lock_read()
 
756
        self.addCleanup(wt.unlock)
 
757
        transform.new_file('set_on_creation', root, 'Set on creation', 'soc',
 
758
                           True)
 
759
        sac = transform.new_file('set_after_creation', root,
 
760
                                 'Set after creation', 'sac')
 
761
        transform.set_executability(True, sac)
 
762
        uws = transform.new_file('unset_without_set', root, 'Unset badly',
 
763
                                 'uws')
 
764
        self.assertRaises(KeyError, transform.set_executability, None, uws)
 
765
        transform.apply()
 
766
        self.assertTrue(wt.is_executable('soc'))
 
767
        self.assertTrue(wt.is_executable('sac'))
 
768
 
 
769
    def test_preserve_mode(self):
 
770
        """File mode is preserved when replacing content"""
 
771
        if sys.platform == 'win32':
 
772
            raise TestSkipped('chmod has no effect on win32')
 
773
        transform, root = self.get_transform()
 
774
        transform.new_file('file1', root, 'contents', 'file1-id', True)
 
775
        transform.apply()
 
776
        self.wt.lock_write()
 
777
        self.addCleanup(self.wt.unlock)
 
778
        self.assertTrue(self.wt.is_executable('file1-id'))
 
779
        transform, root = self.get_transform()
 
780
        file1_id = transform.trans_id_tree_file_id('file1-id')
 
781
        transform.delete_contents(file1_id)
 
782
        transform.create_file('contents2', file1_id)
 
783
        transform.apply()
 
784
        self.assertTrue(self.wt.is_executable('file1-id'))
 
785
 
 
786
    def test__set_mode_stats_correctly(self):
 
787
        """_set_mode stats to determine file mode."""
 
788
        if sys.platform == 'win32':
 
789
            raise TestSkipped('chmod has no effect on win32')
 
790
 
 
791
        stat_paths = []
 
792
        real_stat = os.stat
 
793
        def instrumented_stat(path):
 
794
            stat_paths.append(path)
 
795
            return real_stat(path)
 
796
 
 
797
        transform, root = self.get_transform()
 
798
 
 
799
        bar1_id = transform.new_file('bar', root, 'bar contents 1\n',
 
800
                                     file_id='bar-id-1', executable=False)
 
801
        transform.apply()
 
802
 
 
803
        transform, root = self.get_transform()
 
804
        bar1_id = transform.trans_id_tree_path('bar')
 
805
        bar2_id = transform.trans_id_tree_path('bar2')
 
806
        try:
 
807
            os.stat = instrumented_stat
 
808
            transform.create_file('bar2 contents\n', bar2_id, mode_id=bar1_id)
 
809
        finally:
 
810
            os.stat = real_stat
 
811
            transform.finalize()
 
812
 
 
813
        bar1_abspath = self.wt.abspath('bar')
 
814
        self.assertEqual([bar1_abspath], stat_paths)
 
815
 
 
816
    def test_iter_changes(self):
 
817
        self.wt.set_root_id('eert_toor')
 
818
        transform, root = self.get_transform()
 
819
        transform.new_file('old', root, 'blah', 'id-1', True)
 
820
        transform.apply()
 
821
        transform, root = self.get_transform()
 
822
        try:
 
823
            self.assertEqual([], list(transform._iter_changes()))
 
824
            old = transform.trans_id_tree_file_id('id-1')
 
825
            transform.unversion_file(old)
 
826
            self.assertEqual([('id-1', ('old', None), False, (True, False),
 
827
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
 
828
                (True, True))], list(transform._iter_changes()))
 
829
            transform.new_directory('new', root, 'id-1')
 
830
            self.assertEqual([('id-1', ('old', 'new'), True, (True, True),
 
831
                ('eert_toor', 'eert_toor'), ('old', 'new'),
 
832
                ('file', 'directory'),
 
833
                (True, False))], list(transform._iter_changes()))
 
834
        finally:
 
835
            transform.finalize()
 
836
 
 
837
    def test_iter_changes_new(self):
 
838
        self.wt.set_root_id('eert_toor')
 
839
        transform, root = self.get_transform()
 
840
        transform.new_file('old', root, 'blah')
 
841
        transform.apply()
 
842
        transform, root = self.get_transform()
 
843
        try:
 
844
            old = transform.trans_id_tree_path('old')
 
845
            transform.version_file('id-1', old)
 
846
            self.assertEqual([('id-1', (None, 'old'), False, (False, True),
 
847
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
 
848
                (False, False))], list(transform._iter_changes()))
 
849
        finally:
 
850
            transform.finalize()
 
851
 
 
852
    def test_iter_changes_modifications(self):
 
853
        self.wt.set_root_id('eert_toor')
 
854
        transform, root = self.get_transform()
 
855
        transform.new_file('old', root, 'blah', 'id-1')
 
856
        transform.new_file('new', root, 'blah')
 
857
        transform.new_directory('subdir', root, 'subdir-id')
 
858
        transform.apply()
 
859
        transform, root = self.get_transform()
 
860
        try:
 
861
            old = transform.trans_id_tree_path('old')
 
862
            subdir = transform.trans_id_tree_file_id('subdir-id')
 
863
            new = transform.trans_id_tree_path('new')
 
864
            self.assertEqual([], list(transform._iter_changes()))
 
865
 
 
866
            #content deletion
 
867
            transform.delete_contents(old)
 
868
            self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
 
869
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', None),
 
870
                (False, False))], list(transform._iter_changes()))
 
871
 
 
872
            #content change
 
873
            transform.create_file('blah', old)
 
874
            self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
 
875
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
 
876
                (False, False))], list(transform._iter_changes()))
 
877
            transform.cancel_deletion(old)
 
878
            self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
 
879
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
 
880
                (False, False))], list(transform._iter_changes()))
 
881
            transform.cancel_creation(old)
 
882
 
 
883
            # move file_id to a different file
 
884
            self.assertEqual([], list(transform._iter_changes()))
 
885
            transform.unversion_file(old)
 
886
            transform.version_file('id-1', new)
 
887
            transform.adjust_path('old', root, new)
 
888
            self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
 
889
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
 
890
                (False, False))], list(transform._iter_changes()))
 
891
            transform.cancel_versioning(new)
 
892
            transform._removed_id = set()
 
893
 
 
894
            #execute bit
 
895
            self.assertEqual([], list(transform._iter_changes()))
 
896
            transform.set_executability(True, old)
 
897
            self.assertEqual([('id-1', ('old', 'old'), False, (True, True),
 
898
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
 
899
                (False, True))], list(transform._iter_changes()))
 
900
            transform.set_executability(None, old)
 
901
 
 
902
            # filename
 
903
            self.assertEqual([], list(transform._iter_changes()))
 
904
            transform.adjust_path('new', root, old)
 
905
            transform._new_parent = {}
 
906
            self.assertEqual([('id-1', ('old', 'new'), False, (True, True),
 
907
                ('eert_toor', 'eert_toor'), ('old', 'new'), ('file', 'file'),
 
908
                (False, False))], list(transform._iter_changes()))
 
909
            transform._new_name = {}
 
910
 
 
911
            # parent directory
 
912
            self.assertEqual([], list(transform._iter_changes()))
 
913
            transform.adjust_path('new', subdir, old)
 
914
            transform._new_name = {}
 
915
            self.assertEqual([('id-1', ('old', 'subdir/old'), False,
 
916
                (True, True), ('eert_toor', 'subdir-id'), ('old', 'old'),
 
917
                ('file', 'file'), (False, False))],
 
918
                list(transform._iter_changes()))
 
919
            transform._new_path = {}
 
920
 
 
921
        finally:
 
922
            transform.finalize()
 
923
 
 
924
    def test_iter_changes_modified_bleed(self):
 
925
        self.wt.set_root_id('eert_toor')
 
926
        """Modified flag should not bleed from one change to another"""
 
927
        # unfortunately, we have no guarantee that file1 (which is modified)
 
928
        # will be applied before file2.  And if it's applied after file2, it
 
929
        # obviously can't bleed into file2's change output.  But for now, it
 
930
        # works.
 
931
        transform, root = self.get_transform()
 
932
        transform.new_file('file1', root, 'blah', 'id-1')
 
933
        transform.new_file('file2', root, 'blah', 'id-2')
 
934
        transform.apply()
 
935
        transform, root = self.get_transform()
 
936
        try:
 
937
            transform.delete_contents(transform.trans_id_file_id('id-1'))
 
938
            transform.set_executability(True,
 
939
            transform.trans_id_file_id('id-2'))
 
940
            self.assertEqual([('id-1', (u'file1', u'file1'), True, (True, True),
 
941
                ('eert_toor', 'eert_toor'), ('file1', u'file1'),
 
942
                ('file', None), (False, False)),
 
943
                ('id-2', (u'file2', u'file2'), False, (True, True),
 
944
                ('eert_toor', 'eert_toor'), ('file2', u'file2'),
 
945
                ('file', 'file'), (False, True))],
 
946
                list(transform._iter_changes()))
 
947
        finally:
 
948
            transform.finalize()
 
949
 
 
950
    def test_iter_changes_move_missing(self):
 
951
        """Test moving ids with no files around"""
 
952
        self.wt.set_root_id('toor_eert')
 
953
        # Need two steps because versioning a non-existant file is a conflict.
 
954
        transform, root = self.get_transform()
 
955
        transform.new_directory('floater', root, 'floater-id')
 
956
        transform.apply()
 
957
        transform, root = self.get_transform()
 
958
        transform.delete_contents(transform.trans_id_tree_path('floater'))
 
959
        transform.apply()
 
960
        transform, root = self.get_transform()
 
961
        floater = transform.trans_id_tree_path('floater')
 
962
        try:
 
963
            transform.adjust_path('flitter', root, floater)
 
964
            self.assertEqual([('floater-id', ('floater', 'flitter'), False,
 
965
            (True, True), ('toor_eert', 'toor_eert'), ('floater', 'flitter'),
 
966
            (None, None), (False, False))], list(transform._iter_changes()))
 
967
        finally:
 
968
            transform.finalize()
 
969
 
 
970
    def test_iter_changes_pointless(self):
 
971
        """Ensure that no-ops are not treated as modifications"""
 
972
        self.wt.set_root_id('eert_toor')
 
973
        transform, root = self.get_transform()
 
974
        transform.new_file('old', root, 'blah', 'id-1')
 
975
        transform.new_directory('subdir', root, 'subdir-id')
 
976
        transform.apply()
 
977
        transform, root = self.get_transform()
 
978
        try:
 
979
            old = transform.trans_id_tree_path('old')
 
980
            subdir = transform.trans_id_tree_file_id('subdir-id')
 
981
            self.assertEqual([], list(transform._iter_changes()))
 
982
            transform.delete_contents(subdir)
 
983
            transform.create_directory(subdir)
 
984
            transform.set_executability(False, old)
 
985
            transform.unversion_file(old)
 
986
            transform.version_file('id-1', old)
 
987
            transform.adjust_path('old', root, old)
 
988
            self.assertEqual([], list(transform._iter_changes()))
 
989
        finally:
 
990
            transform.finalize()
 
991
 
 
992
    def test_rename_count(self):
 
993
        transform, root = self.get_transform()
 
994
        transform.new_file('name1', root, 'contents')
 
995
        self.assertEqual(transform.rename_count, 0)
 
996
        transform.apply()
 
997
        self.assertEqual(transform.rename_count, 1)
 
998
        transform2, root = self.get_transform()
 
999
        transform2.adjust_path('name2', root,
 
1000
                               transform2.trans_id_tree_path('name1'))
 
1001
        self.assertEqual(transform2.rename_count, 0)
 
1002
        transform2.apply()
 
1003
        self.assertEqual(transform2.rename_count, 2)
 
1004
 
 
1005
    def test_change_parent(self):
 
1006
        """Ensure that after we change a parent, the results are still right.
 
1007
 
 
1008
        Renames and parent changes on pending transforms can happen as part
 
1009
        of conflict resolution, and are explicitly permitted by the
 
1010
        TreeTransform API.
 
1011
 
 
1012
        This test ensures they work correctly with the rename-avoidance
 
1013
        optimization.
 
1014
        """
 
1015
        transform, root = self.get_transform()
 
1016
        parent1 = transform.new_directory('parent1', root)
 
1017
        child1 = transform.new_file('child1', parent1, 'contents')
 
1018
        parent2 = transform.new_directory('parent2', root)
 
1019
        transform.adjust_path('child1', parent2, child1)
 
1020
        transform.apply()
 
1021
        self.failIfExists(self.wt.abspath('parent1/child1'))
 
1022
        self.failUnlessExists(self.wt.abspath('parent2/child1'))
 
1023
        # rename limbo/new-1 => parent1, rename limbo/new-3 => parent2
 
1024
        # no rename for child1 (counting only renames during apply)
 
1025
        self.failUnlessEqual(2, transform.rename_count)
 
1026
 
 
1027
    def test_cancel_parent(self):
 
1028
        """Cancelling a parent doesn't cause deletion of a non-empty directory
 
1029
 
 
1030
        This is like the test_change_parent, except that we cancel the parent
 
1031
        before adjusting the path.  The transform must detect that the
 
1032
        directory is non-empty, and move children to safe locations.
 
1033
        """
 
1034
        transform, root = self.get_transform()
 
1035
        parent1 = transform.new_directory('parent1', root)
 
1036
        child1 = transform.new_file('child1', parent1, 'contents')
 
1037
        child2 = transform.new_file('child2', parent1, 'contents')
 
1038
        try:
 
1039
            transform.cancel_creation(parent1)
 
1040
        except OSError:
 
1041
            self.fail('Failed to move child1 before deleting parent1')
 
1042
        transform.cancel_creation(child2)
 
1043
        transform.create_directory(parent1)
 
1044
        try:
 
1045
            transform.cancel_creation(parent1)
 
1046
        # If the transform incorrectly believes that child2 is still in
 
1047
        # parent1's limbo directory, it will try to rename it and fail
 
1048
        # because was already moved by the first cancel_creation.
 
1049
        except OSError:
 
1050
            self.fail('Transform still thinks child2 is a child of parent1')
 
1051
        parent2 = transform.new_directory('parent2', root)
 
1052
        transform.adjust_path('child1', parent2, child1)
 
1053
        transform.apply()
 
1054
        self.failIfExists(self.wt.abspath('parent1'))
 
1055
        self.failUnlessExists(self.wt.abspath('parent2/child1'))
 
1056
        # rename limbo/new-3 => parent2, rename limbo/new-2 => child1
 
1057
        self.failUnlessEqual(2, transform.rename_count)
 
1058
 
 
1059
    def test_adjust_and_cancel(self):
 
1060
        """Make sure adjust_path keeps track of limbo children properly"""
 
1061
        transform, root = self.get_transform()
 
1062
        parent1 = transform.new_directory('parent1', root)
 
1063
        child1 = transform.new_file('child1', parent1, 'contents')
 
1064
        parent2 = transform.new_directory('parent2', root)
 
1065
        transform.adjust_path('child1', parent2, child1)
 
1066
        transform.cancel_creation(child1)
 
1067
        try:
 
1068
            transform.cancel_creation(parent1)
 
1069
        # if the transform thinks child1 is still in parent1's limbo
 
1070
        # directory, it will attempt to move it and fail.
 
1071
        except OSError:
 
1072
            self.fail('Transform still thinks child1 is a child of parent1')
 
1073
        transform.finalize()
 
1074
 
 
1075
    def test_noname_contents(self):
 
1076
        """TreeTransform should permit deferring naming files."""
 
1077
        transform, root = self.get_transform()
 
1078
        parent = transform.trans_id_file_id('parent-id')
 
1079
        try:
 
1080
            transform.create_directory(parent)
 
1081
        except KeyError:
 
1082
            self.fail("Can't handle contents with no name")
 
1083
        transform.finalize()
 
1084
 
 
1085
    def test_noname_contents_nested(self):
 
1086
        """TreeTransform should permit deferring naming files."""
 
1087
        transform, root = self.get_transform()
 
1088
        parent = transform.trans_id_file_id('parent-id')
 
1089
        try:
 
1090
            transform.create_directory(parent)
 
1091
        except KeyError:
 
1092
            self.fail("Can't handle contents with no name")
 
1093
        child = transform.new_directory('child', parent)
 
1094
        transform.adjust_path('parent', root, parent)
 
1095
        transform.apply()
 
1096
        self.failUnlessExists(self.wt.abspath('parent/child'))
 
1097
        self.assertEqual(1, transform.rename_count)
 
1098
 
 
1099
    def test_reuse_name(self):
 
1100
        """Avoid reusing the same limbo name for different files"""
 
1101
        transform, root = self.get_transform()
 
1102
        parent = transform.new_directory('parent', root)
 
1103
        child1 = transform.new_directory('child', parent)
 
1104
        try:
 
1105
            child2 = transform.new_directory('child', parent)
 
1106
        except OSError:
 
1107
            self.fail('Tranform tried to use the same limbo name twice')
 
1108
        transform.adjust_path('child2', parent, child2)
 
1109
        transform.apply()
 
1110
        # limbo/new-1 => parent, limbo/new-3 => parent/child2
 
1111
        # child2 is put into top-level limbo because child1 has already
 
1112
        # claimed the direct limbo path when child2 is created.  There is no
 
1113
        # advantage in renaming files once they're in top-level limbo, except
 
1114
        # as part of apply.
 
1115
        self.assertEqual(2, transform.rename_count)
 
1116
 
 
1117
    def test_reuse_when_first_moved(self):
 
1118
        """Don't avoid direct paths when it is safe to use them"""
 
1119
        transform, root = self.get_transform()
 
1120
        parent = transform.new_directory('parent', root)
 
1121
        child1 = transform.new_directory('child', parent)
 
1122
        transform.adjust_path('child1', parent, child1)
 
1123
        child2 = transform.new_directory('child', parent)
 
1124
        transform.apply()
 
1125
        # limbo/new-1 => parent
 
1126
        self.assertEqual(1, transform.rename_count)
 
1127
 
 
1128
    def test_reuse_after_cancel(self):
 
1129
        """Don't avoid direct paths when it is safe to use them"""
 
1130
        transform, root = self.get_transform()
 
1131
        parent2 = transform.new_directory('parent2', root)
 
1132
        child1 = transform.new_directory('child1', parent2)
 
1133
        transform.cancel_creation(parent2)
 
1134
        transform.create_directory(parent2)
 
1135
        child2 = transform.new_directory('child1', parent2)
 
1136
        transform.adjust_path('child2', parent2, child1)
 
1137
        transform.apply()
 
1138
        # limbo/new-1 => parent2, limbo/new-2 => parent2/child1
 
1139
        self.assertEqual(2, transform.rename_count)
 
1140
 
 
1141
    def test_finalize_order(self):
 
1142
        """Finalize must be done in child-to-parent order"""
 
1143
        transform, root = self.get_transform()
 
1144
        parent = transform.new_directory('parent', root)
 
1145
        child = transform.new_directory('child', parent)
 
1146
        try:
 
1147
            transform.finalize()
 
1148
        except OSError:
 
1149
            self.fail('Tried to remove parent before child1')
 
1150
 
 
1151
    def test_cancel_with_cancelled_child_should_succeed(self):
 
1152
        transform, root = self.get_transform()
 
1153
        parent = transform.new_directory('parent', root)
 
1154
        child = transform.new_directory('child', parent)
 
1155
        transform.cancel_creation(child)
 
1156
        transform.cancel_creation(parent)
 
1157
        transform.finalize()
 
1158
 
 
1159
    def test_change_entry(self):
 
1160
        txt = 'bzrlib.transform.change_entry was deprecated in version 0.90.'
 
1161
        self.callDeprecated([txt], change_entry, None, None, None, None, None,
 
1162
            None, None, None)
 
1163
 
 
1164
    def test_case_insensitive_clash(self):
 
1165
        self.requireFeature(CaseInsensitiveFilesystemFeature)
 
1166
        def tt_helper():
 
1167
            wt = self.make_branch_and_tree('.')
 
1168
            tt = TreeTransform(wt)  # TreeTransform obtains write lock
 
1169
            try:
 
1170
                tt.new_file('foo', tt.root, 'bar')
 
1171
                tt.new_file('Foo', tt.root, 'spam')
 
1172
                # Lie to tt that we've already resolved all conflicts.
 
1173
                tt.apply(no_conflicts=True)
 
1174
            except:
 
1175
                wt.unlock()
 
1176
                raise
 
1177
        err = self.assertRaises(errors.FileExists, tt_helper)
 
1178
        self.assertContainsRe(str(err),
 
1179
            "^File exists: .+/foo")
 
1180
 
 
1181
    def test_two_directories_clash(self):
 
1182
        def tt_helper():
 
1183
            wt = self.make_branch_and_tree('.')
 
1184
            tt = TreeTransform(wt)  # TreeTransform obtains write lock
 
1185
            try:
 
1186
                foo_1 = tt.new_directory('foo', tt.root)
 
1187
                tt.new_directory('bar', foo_1)
 
1188
                foo_2 = tt.new_directory('foo', tt.root)
 
1189
                tt.new_directory('baz', foo_2)
 
1190
                # Lie to tt that we've already resolved all conflicts.
 
1191
                tt.apply(no_conflicts=True)
 
1192
            except:
 
1193
                wt.unlock()
 
1194
                raise
 
1195
        err = self.assertRaises(errors.FileExists, tt_helper)
 
1196
        self.assertContainsRe(str(err),
 
1197
            "^File exists: .+/foo")
 
1198
 
 
1199
    def test_two_directories_clash_finalize(self):
 
1200
        def tt_helper():
 
1201
            wt = self.make_branch_and_tree('.')
 
1202
            tt = TreeTransform(wt)  # TreeTransform obtains write lock
 
1203
            try:
 
1204
                foo_1 = tt.new_directory('foo', tt.root)
 
1205
                tt.new_directory('bar', foo_1)
 
1206
                foo_2 = tt.new_directory('foo', tt.root)
 
1207
                tt.new_directory('baz', foo_2)
 
1208
                # Lie to tt that we've already resolved all conflicts.
 
1209
                tt.apply(no_conflicts=True)
 
1210
            except:
 
1211
                tt.finalize()
 
1212
                raise
 
1213
        err = self.assertRaises(errors.FileExists, tt_helper)
 
1214
        self.assertContainsRe(str(err),
 
1215
            "^File exists: .+/foo")
 
1216
 
 
1217
 
 
1218
class TransformGroup(object):
 
1219
 
 
1220
    def __init__(self, dirname, root_id):
 
1221
        self.name = dirname
 
1222
        os.mkdir(dirname)
 
1223
        self.wt = BzrDir.create_standalone_workingtree(dirname)
 
1224
        self.wt.set_root_id(root_id)
 
1225
        self.b = self.wt.branch
 
1226
        self.tt = TreeTransform(self.wt)
 
1227
        self.root = self.tt.trans_id_tree_file_id(self.wt.get_root_id())
 
1228
 
 
1229
 
 
1230
def conflict_text(tree, merge):
 
1231
    template = '%s TREE\n%s%s\n%s%s MERGE-SOURCE\n'
 
1232
    return template % ('<' * 7, tree, '=' * 7, merge, '>' * 7)
 
1233
 
 
1234
 
 
1235
class TestTransformMerge(TestCaseInTempDir):
 
1236
 
 
1237
    def test_text_merge(self):
 
1238
        root_id = generate_ids.gen_root_id()
 
1239
        base = TransformGroup("base", root_id)
 
1240
        base.tt.new_file('a', base.root, 'a\nb\nc\nd\be\n', 'a')
 
1241
        base.tt.new_file('b', base.root, 'b1', 'b')
 
1242
        base.tt.new_file('c', base.root, 'c', 'c')
 
1243
        base.tt.new_file('d', base.root, 'd', 'd')
 
1244
        base.tt.new_file('e', base.root, 'e', 'e')
 
1245
        base.tt.new_file('f', base.root, 'f', 'f')
 
1246
        base.tt.new_directory('g', base.root, 'g')
 
1247
        base.tt.new_directory('h', base.root, 'h')
 
1248
        base.tt.apply()
 
1249
        other = TransformGroup("other", root_id)
 
1250
        other.tt.new_file('a', other.root, 'y\nb\nc\nd\be\n', 'a')
 
1251
        other.tt.new_file('b', other.root, 'b2', 'b')
 
1252
        other.tt.new_file('c', other.root, 'c2', 'c')
 
1253
        other.tt.new_file('d', other.root, 'd', 'd')
 
1254
        other.tt.new_file('e', other.root, 'e2', 'e')
 
1255
        other.tt.new_file('f', other.root, 'f', 'f')
 
1256
        other.tt.new_file('g', other.root, 'g', 'g')
 
1257
        other.tt.new_file('h', other.root, 'h\ni\nj\nk\n', 'h')
 
1258
        other.tt.new_file('i', other.root, 'h\ni\nj\nk\n', 'i')
 
1259
        other.tt.apply()
 
1260
        this = TransformGroup("this", root_id)
 
1261
        this.tt.new_file('a', this.root, 'a\nb\nc\nd\bz\n', 'a')
 
1262
        this.tt.new_file('b', this.root, 'b', 'b')
 
1263
        this.tt.new_file('c', this.root, 'c', 'c')
 
1264
        this.tt.new_file('d', this.root, 'd2', 'd')
 
1265
        this.tt.new_file('e', this.root, 'e2', 'e')
 
1266
        this.tt.new_file('f', this.root, 'f', 'f')
 
1267
        this.tt.new_file('g', this.root, 'g', 'g')
 
1268
        this.tt.new_file('h', this.root, '1\n2\n3\n4\n', 'h')
 
1269
        this.tt.new_file('i', this.root, '1\n2\n3\n4\n', 'i')
 
1270
        this.tt.apply()
 
1271
        Merge3Merger(this.wt, this.wt, base.wt, other.wt)
 
1272
 
 
1273
        # textual merge
 
1274
        self.assertEqual(this.wt.get_file('a').read(), 'y\nb\nc\nd\bz\n')
 
1275
        # three-way text conflict
 
1276
        self.assertEqual(this.wt.get_file('b').read(), 
 
1277
                         conflict_text('b', 'b2'))
 
1278
        # OTHER wins
 
1279
        self.assertEqual(this.wt.get_file('c').read(), 'c2')
 
1280
        # THIS wins
 
1281
        self.assertEqual(this.wt.get_file('d').read(), 'd2')
 
1282
        # Ambigious clean merge
 
1283
        self.assertEqual(this.wt.get_file('e').read(), 'e2')
 
1284
        # No change
 
1285
        self.assertEqual(this.wt.get_file('f').read(), 'f')
 
1286
        # Correct correct results when THIS == OTHER 
 
1287
        self.assertEqual(this.wt.get_file('g').read(), 'g')
 
1288
        # Text conflict when THIS & OTHER are text and BASE is dir
 
1289
        self.assertEqual(this.wt.get_file('h').read(), 
 
1290
                         conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
 
1291
        self.assertEqual(this.wt.get_file_byname('h.THIS').read(),
 
1292
                         '1\n2\n3\n4\n')
 
1293
        self.assertEqual(this.wt.get_file_byname('h.OTHER').read(),
 
1294
                         'h\ni\nj\nk\n')
 
1295
        self.assertEqual(file_kind(this.wt.abspath('h.BASE')), 'directory')
 
1296
        self.assertEqual(this.wt.get_file('i').read(), 
 
1297
                         conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
 
1298
        self.assertEqual(this.wt.get_file_byname('i.THIS').read(),
 
1299
                         '1\n2\n3\n4\n')
 
1300
        self.assertEqual(this.wt.get_file_byname('i.OTHER').read(),
 
1301
                         'h\ni\nj\nk\n')
 
1302
        self.assertEqual(os.path.exists(this.wt.abspath('i.BASE')), False)
 
1303
        modified = ['a', 'b', 'c', 'h', 'i']
 
1304
        merge_modified = this.wt.merge_modified()
 
1305
        self.assertSubset(merge_modified, modified)
 
1306
        self.assertEqual(len(merge_modified), len(modified))
 
1307
        file(this.wt.id2abspath('a'), 'wb').write('booga')
 
1308
        modified.pop(0)
 
1309
        merge_modified = this.wt.merge_modified()
 
1310
        self.assertSubset(merge_modified, modified)
 
1311
        self.assertEqual(len(merge_modified), len(modified))
 
1312
        this.wt.remove('b')
 
1313
        this.wt.revert()
 
1314
 
 
1315
    def test_file_merge(self):
 
1316
        self.requireFeature(SymlinkFeature)
 
1317
        root_id = generate_ids.gen_root_id()
 
1318
        base = TransformGroup("BASE", root_id)
 
1319
        this = TransformGroup("THIS", root_id)
 
1320
        other = TransformGroup("OTHER", root_id)
 
1321
        for tg in this, base, other:
 
1322
            tg.tt.new_directory('a', tg.root, 'a')
 
1323
            tg.tt.new_symlink('b', tg.root, 'b', 'b')
 
1324
            tg.tt.new_file('c', tg.root, 'c', 'c')
 
1325
            tg.tt.new_symlink('d', tg.root, tg.name, 'd')
 
1326
        targets = ((base, 'base-e', 'base-f', None, None), 
 
1327
                   (this, 'other-e', 'this-f', 'other-g', 'this-h'), 
 
1328
                   (other, 'other-e', None, 'other-g', 'other-h'))
 
1329
        for tg, e_target, f_target, g_target, h_target in targets:
 
1330
            for link, target in (('e', e_target), ('f', f_target), 
 
1331
                                 ('g', g_target), ('h', h_target)):
 
1332
                if target is not None:
 
1333
                    tg.tt.new_symlink(link, tg.root, target, link)
 
1334
 
 
1335
        for tg in this, base, other:
 
1336
            tg.tt.apply()
 
1337
        Merge3Merger(this.wt, this.wt, base.wt, other.wt)
 
1338
        self.assertIs(os.path.isdir(this.wt.abspath('a')), True)
 
1339
        self.assertIs(os.path.islink(this.wt.abspath('b')), True)
 
1340
        self.assertIs(os.path.isfile(this.wt.abspath('c')), True)
 
1341
        for suffix in ('THIS', 'BASE', 'OTHER'):
 
1342
            self.assertEqual(os.readlink(this.wt.abspath('d.'+suffix)), suffix)
 
1343
        self.assertIs(os.path.lexists(this.wt.abspath('d')), False)
 
1344
        self.assertEqual(this.wt.id2path('d'), 'd.OTHER')
 
1345
        self.assertEqual(this.wt.id2path('f'), 'f.THIS')
 
1346
        self.assertEqual(os.readlink(this.wt.abspath('e')), 'other-e')
 
1347
        self.assertIs(os.path.lexists(this.wt.abspath('e.THIS')), False)
 
1348
        self.assertIs(os.path.lexists(this.wt.abspath('e.OTHER')), False)
 
1349
        self.assertIs(os.path.lexists(this.wt.abspath('e.BASE')), False)
 
1350
        self.assertIs(os.path.lexists(this.wt.abspath('g')), True)
 
1351
        self.assertIs(os.path.lexists(this.wt.abspath('g.BASE')), False)
 
1352
        self.assertIs(os.path.lexists(this.wt.abspath('h')), False)
 
1353
        self.assertIs(os.path.lexists(this.wt.abspath('h.BASE')), False)
 
1354
        self.assertIs(os.path.lexists(this.wt.abspath('h.THIS')), True)
 
1355
        self.assertIs(os.path.lexists(this.wt.abspath('h.OTHER')), True)
 
1356
 
 
1357
    def test_filename_merge(self):
 
1358
        root_id = generate_ids.gen_root_id()
 
1359
        base = TransformGroup("BASE", root_id)
 
1360
        this = TransformGroup("THIS", root_id)
 
1361
        other = TransformGroup("OTHER", root_id)
 
1362
        base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a') 
 
1363
                                   for t in [base, this, other]]
 
1364
        base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b') 
 
1365
                                   for t in [base, this, other]]
 
1366
        base.tt.new_directory('c', base_a, 'c')
 
1367
        this.tt.new_directory('c1', this_a, 'c')
 
1368
        other.tt.new_directory('c', other_b, 'c')
 
1369
 
 
1370
        base.tt.new_directory('d', base_a, 'd')
 
1371
        this.tt.new_directory('d1', this_b, 'd')
 
1372
        other.tt.new_directory('d', other_a, 'd')
 
1373
 
 
1374
        base.tt.new_directory('e', base_a, 'e')
 
1375
        this.tt.new_directory('e', this_a, 'e')
 
1376
        other.tt.new_directory('e1', other_b, 'e')
 
1377
 
 
1378
        base.tt.new_directory('f', base_a, 'f')
 
1379
        this.tt.new_directory('f1', this_b, 'f')
 
1380
        other.tt.new_directory('f1', other_b, 'f')
 
1381
 
 
1382
        for tg in [this, base, other]:
 
1383
            tg.tt.apply()
 
1384
        Merge3Merger(this.wt, this.wt, base.wt, other.wt)
 
1385
        self.assertEqual(this.wt.id2path('c'), pathjoin('b/c1'))
 
1386
        self.assertEqual(this.wt.id2path('d'), pathjoin('b/d1'))
 
1387
        self.assertEqual(this.wt.id2path('e'), pathjoin('b/e1'))
 
1388
        self.assertEqual(this.wt.id2path('f'), pathjoin('b/f1'))
 
1389
 
 
1390
    def test_filename_merge_conflicts(self):
 
1391
        root_id = generate_ids.gen_root_id()
 
1392
        base = TransformGroup("BASE", root_id)
 
1393
        this = TransformGroup("THIS", root_id)
 
1394
        other = TransformGroup("OTHER", root_id)
 
1395
        base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a') 
 
1396
                                   for t in [base, this, other]]
 
1397
        base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b') 
 
1398
                                   for t in [base, this, other]]
 
1399
 
 
1400
        base.tt.new_file('g', base_a, 'g', 'g')
 
1401
        other.tt.new_file('g1', other_b, 'g1', 'g')
 
1402
 
 
1403
        base.tt.new_file('h', base_a, 'h', 'h')
 
1404
        this.tt.new_file('h1', this_b, 'h1', 'h')
 
1405
 
 
1406
        base.tt.new_file('i', base.root, 'i', 'i')
 
1407
        other.tt.new_directory('i1', this_b, 'i')
 
1408
 
 
1409
        for tg in [this, base, other]:
 
1410
            tg.tt.apply()
 
1411
        Merge3Merger(this.wt, this.wt, base.wt, other.wt)
 
1412
 
 
1413
        self.assertEqual(this.wt.id2path('g'), pathjoin('b/g1.OTHER'))
 
1414
        self.assertIs(os.path.lexists(this.wt.abspath('b/g1.BASE')), True)
 
1415
        self.assertIs(os.path.lexists(this.wt.abspath('b/g1.THIS')), False)
 
1416
        self.assertEqual(this.wt.id2path('h'), pathjoin('b/h1.THIS'))
 
1417
        self.assertIs(os.path.lexists(this.wt.abspath('b/h1.BASE')), True)
 
1418
        self.assertIs(os.path.lexists(this.wt.abspath('b/h1.OTHER')), False)
 
1419
        self.assertEqual(this.wt.id2path('i'), pathjoin('b/i1.OTHER'))
 
1420
 
 
1421
 
 
1422
class TestBuildTree(tests.TestCaseWithTransport):
 
1423
 
 
1424
    def test_build_tree_with_symlinks(self):
 
1425
        self.requireFeature(SymlinkFeature)
 
1426
        os.mkdir('a')
 
1427
        a = BzrDir.create_standalone_workingtree('a')
 
1428
        os.mkdir('a/foo')
 
1429
        file('a/foo/bar', 'wb').write('contents')
 
1430
        os.symlink('a/foo/bar', 'a/foo/baz')
 
1431
        a.add(['foo', 'foo/bar', 'foo/baz'])
 
1432
        a.commit('initial commit')
 
1433
        b = BzrDir.create_standalone_workingtree('b')
 
1434
        basis = a.basis_tree()
 
1435
        basis.lock_read()
 
1436
        self.addCleanup(basis.unlock)
 
1437
        build_tree(basis, b)
 
1438
        self.assertIs(os.path.isdir('b/foo'), True)
 
1439
        self.assertEqual(file('b/foo/bar', 'rb').read(), "contents")
 
1440
        self.assertEqual(os.readlink('b/foo/baz'), 'a/foo/bar')
 
1441
 
 
1442
    def test_build_with_references(self):
 
1443
        tree = self.make_branch_and_tree('source',
 
1444
            format='dirstate-with-subtree')
 
1445
        subtree = self.make_branch_and_tree('source/subtree',
 
1446
            format='dirstate-with-subtree')
 
1447
        tree.add_reference(subtree)
 
1448
        tree.commit('a revision')
 
1449
        tree.branch.create_checkout('target')
 
1450
        self.failUnlessExists('target')
 
1451
        self.failUnlessExists('target/subtree')
 
1452
 
 
1453
    def test_file_conflict_handling(self):
 
1454
        """Ensure that when building trees, conflict handling is done"""
 
1455
        source = self.make_branch_and_tree('source')
 
1456
        target = self.make_branch_and_tree('target')
 
1457
        self.build_tree(['source/file', 'target/file'])
 
1458
        source.add('file', 'new-file')
 
1459
        source.commit('added file')
 
1460
        build_tree(source.basis_tree(), target)
 
1461
        self.assertEqual([DuplicateEntry('Moved existing file to',
 
1462
                          'file.moved', 'file', None, 'new-file')],
 
1463
                         target.conflicts())
 
1464
        target2 = self.make_branch_and_tree('target2')
 
1465
        target_file = file('target2/file', 'wb')
 
1466
        try:
 
1467
            source_file = file('source/file', 'rb')
 
1468
            try:
 
1469
                target_file.write(source_file.read())
 
1470
            finally:
 
1471
                source_file.close()
 
1472
        finally:
 
1473
            target_file.close()
 
1474
        build_tree(source.basis_tree(), target2)
 
1475
        self.assertEqual([], target2.conflicts())
 
1476
 
 
1477
    def test_symlink_conflict_handling(self):
 
1478
        """Ensure that when building trees, conflict handling is done"""
 
1479
        self.requireFeature(SymlinkFeature)
 
1480
        source = self.make_branch_and_tree('source')
 
1481
        os.symlink('foo', 'source/symlink')
 
1482
        source.add('symlink', 'new-symlink')
 
1483
        source.commit('added file')
 
1484
        target = self.make_branch_and_tree('target')
 
1485
        os.symlink('bar', 'target/symlink')
 
1486
        build_tree(source.basis_tree(), target)
 
1487
        self.assertEqual([DuplicateEntry('Moved existing file to',
 
1488
            'symlink.moved', 'symlink', None, 'new-symlink')],
 
1489
            target.conflicts())
 
1490
        target = self.make_branch_and_tree('target2')
 
1491
        os.symlink('foo', 'target2/symlink')
 
1492
        build_tree(source.basis_tree(), target)
 
1493
        self.assertEqual([], target.conflicts())
 
1494
        
 
1495
    def test_directory_conflict_handling(self):
 
1496
        """Ensure that when building trees, conflict handling is done"""
 
1497
        source = self.make_branch_and_tree('source')
 
1498
        target = self.make_branch_and_tree('target')
 
1499
        self.build_tree(['source/dir1/', 'source/dir1/file', 'target/dir1/'])
 
1500
        source.add(['dir1', 'dir1/file'], ['new-dir1', 'new-file'])
 
1501
        source.commit('added file')
 
1502
        build_tree(source.basis_tree(), target)
 
1503
        self.assertEqual([], target.conflicts())
 
1504
        self.failUnlessExists('target/dir1/file')
 
1505
 
 
1506
        # Ensure contents are merged
 
1507
        target = self.make_branch_and_tree('target2')
 
1508
        self.build_tree(['target2/dir1/', 'target2/dir1/file2'])
 
1509
        build_tree(source.basis_tree(), target)
 
1510
        self.assertEqual([], target.conflicts())
 
1511
        self.failUnlessExists('target2/dir1/file2')
 
1512
        self.failUnlessExists('target2/dir1/file')
 
1513
 
 
1514
        # Ensure new contents are suppressed for existing branches
 
1515
        target = self.make_branch_and_tree('target3')
 
1516
        self.make_branch('target3/dir1')
 
1517
        self.build_tree(['target3/dir1/file2'])
 
1518
        build_tree(source.basis_tree(), target)
 
1519
        self.failIfExists('target3/dir1/file')
 
1520
        self.failUnlessExists('target3/dir1/file2')
 
1521
        self.failUnlessExists('target3/dir1.diverted/file')
 
1522
        self.assertEqual([DuplicateEntry('Diverted to',
 
1523
            'dir1.diverted', 'dir1', 'new-dir1', None)],
 
1524
            target.conflicts())
 
1525
 
 
1526
        target = self.make_branch_and_tree('target4')
 
1527
        self.build_tree(['target4/dir1/'])
 
1528
        self.make_branch('target4/dir1/file')
 
1529
        build_tree(source.basis_tree(), target)
 
1530
        self.failUnlessExists('target4/dir1/file')
 
1531
        self.assertEqual('directory', file_kind('target4/dir1/file'))
 
1532
        self.failUnlessExists('target4/dir1/file.diverted')
 
1533
        self.assertEqual([DuplicateEntry('Diverted to',
 
1534
            'dir1/file.diverted', 'dir1/file', 'new-file', None)],
 
1535
            target.conflicts())
 
1536
 
 
1537
    def test_mixed_conflict_handling(self):
 
1538
        """Ensure that when building trees, conflict handling is done"""
 
1539
        source = self.make_branch_and_tree('source')
 
1540
        target = self.make_branch_and_tree('target')
 
1541
        self.build_tree(['source/name', 'target/name/'])
 
1542
        source.add('name', 'new-name')
 
1543
        source.commit('added file')
 
1544
        build_tree(source.basis_tree(), target)
 
1545
        self.assertEqual([DuplicateEntry('Moved existing file to',
 
1546
            'name.moved', 'name', None, 'new-name')], target.conflicts())
 
1547
 
 
1548
    def test_raises_in_populated(self):
 
1549
        source = self.make_branch_and_tree('source')
 
1550
        self.build_tree(['source/name'])
 
1551
        source.add('name')
 
1552
        source.commit('added name')
 
1553
        target = self.make_branch_and_tree('target')
 
1554
        self.build_tree(['target/name'])
 
1555
        target.add('name')
 
1556
        self.assertRaises(errors.WorkingTreeAlreadyPopulated, 
 
1557
            build_tree, source.basis_tree(), target)
 
1558
 
 
1559
    def test_build_tree_rename_count(self):
 
1560
        source = self.make_branch_and_tree('source')
 
1561
        self.build_tree(['source/file1', 'source/dir1/'])
 
1562
        source.add(['file1', 'dir1'])
 
1563
        source.commit('add1')
 
1564
        target1 = self.make_branch_and_tree('target1')
 
1565
        transform_result = build_tree(source.basis_tree(), target1)
 
1566
        self.assertEqual(2, transform_result.rename_count)
 
1567
 
 
1568
        self.build_tree(['source/dir1/file2'])
 
1569
        source.add(['dir1/file2'])
 
1570
        source.commit('add3')
 
1571
        target2 = self.make_branch_and_tree('target2')
 
1572
        transform_result = build_tree(source.basis_tree(), target2)
 
1573
        # children of non-root directories should not be renamed
 
1574
        self.assertEqual(2, transform_result.rename_count)
 
1575
 
 
1576
    def test_build_tree_accelerator_tree(self):
 
1577
        source = self.make_branch_and_tree('source')
 
1578
        self.build_tree_contents([('source/file1', 'A')])
 
1579
        self.build_tree_contents([('source/file2', 'B')])
 
1580
        source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
 
1581
        source.commit('commit files')
 
1582
        self.build_tree_contents([('source/file2', 'C')])
 
1583
        calls = []
 
1584
        real_source_get_file = source.get_file
 
1585
        def get_file(file_id, path=None):
 
1586
            calls.append(file_id)
 
1587
            return real_source_get_file(file_id, path)
 
1588
        source.get_file = get_file
 
1589
        source.lock_read()
 
1590
        self.addCleanup(source.unlock)
 
1591
        target = self.make_branch_and_tree('target')
 
1592
        revision_tree = source.basis_tree()
 
1593
        revision_tree.lock_read()
 
1594
        self.addCleanup(revision_tree.unlock)
 
1595
        build_tree(revision_tree, target, source)
 
1596
        self.assertEqual(['file1-id'], calls)
 
1597
        target.lock_read()
 
1598
        self.addCleanup(target.unlock)
 
1599
        self.assertEqual([], list(target._iter_changes(revision_tree)))
 
1600
 
 
1601
    def test_build_tree_accelerator_tree_missing_file(self):
 
1602
        source = self.make_branch_and_tree('source')
 
1603
        self.build_tree_contents([('source/file1', 'A')])
 
1604
        self.build_tree_contents([('source/file2', 'B')])
 
1605
        source.add(['file1', 'file2'])
 
1606
        source.commit('commit files')
 
1607
        os.unlink('source/file1')
 
1608
        source.remove(['file2'])
 
1609
        target = self.make_branch_and_tree('target')
 
1610
        revision_tree = source.basis_tree()
 
1611
        revision_tree.lock_read()
 
1612
        self.addCleanup(revision_tree.unlock)
 
1613
        build_tree(revision_tree, target, source)
 
1614
        target.lock_read()
 
1615
        self.addCleanup(target.unlock)
 
1616
        self.assertEqual([], list(target._iter_changes(revision_tree)))
 
1617
 
 
1618
    def test_build_tree_accelerator_wrong_kind(self):
 
1619
        self.requireFeature(SymlinkFeature)
 
1620
        source = self.make_branch_and_tree('source')
 
1621
        self.build_tree_contents([('source/file1', '')])
 
1622
        self.build_tree_contents([('source/file2', '')])
 
1623
        source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
 
1624
        source.commit('commit files')
 
1625
        os.unlink('source/file2')
 
1626
        self.build_tree_contents([('source/file2/', 'C')])
 
1627
        os.unlink('source/file1')
 
1628
        os.symlink('file2', 'source/file1')
 
1629
        calls = []
 
1630
        real_source_get_file = source.get_file
 
1631
        def get_file(file_id, path=None):
 
1632
            calls.append(file_id)
 
1633
            return real_source_get_file(file_id, path)
 
1634
        source.get_file = get_file
 
1635
        source.lock_read()
 
1636
        self.addCleanup(source.unlock)
 
1637
        target = self.make_branch_and_tree('target')
 
1638
        revision_tree = source.basis_tree()
 
1639
        revision_tree.lock_read()
 
1640
        self.addCleanup(revision_tree.unlock)
 
1641
        build_tree(revision_tree, target, source)
 
1642
        self.assertEqual([], calls)
 
1643
        target.lock_read()
 
1644
        self.addCleanup(target.unlock)
 
1645
        self.assertEqual([], list(target._iter_changes(revision_tree)))
 
1646
 
 
1647
    def test_build_tree_accelerator_tree_moved(self):
 
1648
        source = self.make_branch_and_tree('source')
 
1649
        self.build_tree_contents([('source/file1', 'A')])
 
1650
        source.add(['file1'], ['file1-id'])
 
1651
        source.commit('commit files')
 
1652
        source.rename_one('file1', 'file2')
 
1653
        source.lock_read()
 
1654
        self.addCleanup(source.unlock)
 
1655
        target = self.make_branch_and_tree('target')
 
1656
        revision_tree = source.basis_tree()
 
1657
        revision_tree.lock_read()
 
1658
        self.addCleanup(revision_tree.unlock)
 
1659
        build_tree(revision_tree, target, source)
 
1660
        target.lock_read()
 
1661
        self.addCleanup(target.unlock)
 
1662
        self.assertEqual([], list(target._iter_changes(revision_tree)))
 
1663
 
 
1664
 
 
1665
class MockTransform(object):
 
1666
 
 
1667
    def has_named_child(self, by_parent, parent_id, name):
 
1668
        for child_id in by_parent[parent_id]:
 
1669
            if child_id == '0':
 
1670
                if name == "name~":
 
1671
                    return True
 
1672
            elif name == "name.~%s~" % child_id:
 
1673
                return True
 
1674
        return False
 
1675
 
 
1676
 
 
1677
class MockEntry(object):
 
1678
    def __init__(self):
 
1679
        object.__init__(self)
 
1680
        self.name = "name"
 
1681
 
 
1682
 
 
1683
class TestGetBackupName(TestCase):
 
1684
    def test_get_backup_name(self):
 
1685
        tt = MockTransform()
 
1686
        name = get_backup_name(MockEntry(), {'a':[]}, 'a', tt)
 
1687
        self.assertEqual(name, 'name.~1~')
 
1688
        name = get_backup_name(MockEntry(), {'a':['1']}, 'a', tt)
 
1689
        self.assertEqual(name, 'name.~2~')
 
1690
        name = get_backup_name(MockEntry(), {'a':['2']}, 'a', tt)
 
1691
        self.assertEqual(name, 'name.~1~')
 
1692
        name = get_backup_name(MockEntry(), {'a':['2'], 'b':[]}, 'b', tt)
 
1693
        self.assertEqual(name, 'name.~1~')
 
1694
        name = get_backup_name(MockEntry(), {'a':['1', '2', '3']}, 'a', tt)
 
1695
        self.assertEqual(name, 'name.~4~')
 
1696
 
 
1697
 
 
1698
class TestFileMover(tests.TestCaseWithTransport):
 
1699
 
 
1700
    def test_file_mover(self):
 
1701
        self.build_tree(['a/', 'a/b', 'c/', 'c/d'])
 
1702
        mover = _FileMover()
 
1703
        mover.rename('a', 'q')
 
1704
        self.failUnlessExists('q')
 
1705
        self.failIfExists('a')
 
1706
        self.failUnlessExists('q/b')
 
1707
        self.failUnlessExists('c')
 
1708
        self.failUnlessExists('c/d')
 
1709
 
 
1710
    def test_pre_delete_rollback(self):
 
1711
        self.build_tree(['a/'])
 
1712
        mover = _FileMover()
 
1713
        mover.pre_delete('a', 'q')
 
1714
        self.failUnlessExists('q')
 
1715
        self.failIfExists('a')
 
1716
        mover.rollback()
 
1717
        self.failIfExists('q')
 
1718
        self.failUnlessExists('a')
 
1719
 
 
1720
    def test_apply_deletions(self):
 
1721
        self.build_tree(['a/', 'b/'])
 
1722
        mover = _FileMover()
 
1723
        mover.pre_delete('a', 'q')
 
1724
        mover.pre_delete('b', 'r')
 
1725
        self.failUnlessExists('q')
 
1726
        self.failUnlessExists('r')
 
1727
        self.failIfExists('a')
 
1728
        self.failIfExists('b')
 
1729
        mover.apply_deletions()
 
1730
        self.failIfExists('q')
 
1731
        self.failIfExists('r')
 
1732
        self.failIfExists('a')
 
1733
        self.failIfExists('b')
 
1734
 
 
1735
    def test_file_mover_rollback(self):
 
1736
        self.build_tree(['a/', 'a/b', 'c/', 'c/d/', 'c/e/'])
 
1737
        mover = _FileMover()
 
1738
        mover.rename('c/d', 'c/f')
 
1739
        mover.rename('c/e', 'c/d')
 
1740
        try:
 
1741
            mover.rename('a', 'c')
 
1742
        except errors.FileExists, e:
 
1743
            mover.rollback()
 
1744
        self.failUnlessExists('a')
 
1745
        self.failUnlessExists('c/d')
 
1746
 
 
1747
 
 
1748
class Bogus(Exception):
 
1749
    pass
 
1750
 
 
1751
 
 
1752
class TestTransformRollback(tests.TestCaseWithTransport):
 
1753
 
 
1754
    class ExceptionFileMover(_FileMover):
 
1755
 
 
1756
        def __init__(self, bad_source=None, bad_target=None):
 
1757
            _FileMover.__init__(self)
 
1758
            self.bad_source = bad_source
 
1759
            self.bad_target = bad_target
 
1760
 
 
1761
        def rename(self, source, target):
 
1762
            if (self.bad_source is not None and
 
1763
                source.endswith(self.bad_source)):
 
1764
                raise Bogus
 
1765
            elif (self.bad_target is not None and
 
1766
                target.endswith(self.bad_target)):
 
1767
                raise Bogus
 
1768
            else:
 
1769
                _FileMover.rename(self, source, target)
 
1770
 
 
1771
    def test_rollback_rename(self):
 
1772
        tree = self.make_branch_and_tree('.')
 
1773
        self.build_tree(['a/', 'a/b'])
 
1774
        tt = TreeTransform(tree)
 
1775
        self.addCleanup(tt.finalize)
 
1776
        a_id = tt.trans_id_tree_path('a')
 
1777
        tt.adjust_path('c', tt.root, a_id)
 
1778
        tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
 
1779
        self.assertRaises(Bogus, tt.apply,
 
1780
                          _mover=self.ExceptionFileMover(bad_source='a'))
 
1781
        self.failUnlessExists('a')
 
1782
        self.failUnlessExists('a/b')
 
1783
        tt.apply()
 
1784
        self.failUnlessExists('c')
 
1785
        self.failUnlessExists('c/d')
 
1786
 
 
1787
    def test_rollback_rename_into_place(self):
 
1788
        tree = self.make_branch_and_tree('.')
 
1789
        self.build_tree(['a/', 'a/b'])
 
1790
        tt = TreeTransform(tree)
 
1791
        self.addCleanup(tt.finalize)
 
1792
        a_id = tt.trans_id_tree_path('a')
 
1793
        tt.adjust_path('c', tt.root, a_id)
 
1794
        tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
 
1795
        self.assertRaises(Bogus, tt.apply,
 
1796
                          _mover=self.ExceptionFileMover(bad_target='c/d'))
 
1797
        self.failUnlessExists('a')
 
1798
        self.failUnlessExists('a/b')
 
1799
        tt.apply()
 
1800
        self.failUnlessExists('c')
 
1801
        self.failUnlessExists('c/d')
 
1802
 
 
1803
    def test_rollback_deletion(self):
 
1804
        tree = self.make_branch_and_tree('.')
 
1805
        self.build_tree(['a/', 'a/b'])
 
1806
        tt = TreeTransform(tree)
 
1807
        self.addCleanup(tt.finalize)
 
1808
        a_id = tt.trans_id_tree_path('a')
 
1809
        tt.delete_contents(a_id)
 
1810
        tt.adjust_path('d', tt.root, tt.trans_id_tree_path('a/b'))
 
1811
        self.assertRaises(Bogus, tt.apply,
 
1812
                          _mover=self.ExceptionFileMover(bad_target='d'))
 
1813
        self.failUnlessExists('a')
 
1814
        self.failUnlessExists('a/b')
 
1815
 
 
1816
    def test_resolve_no_parent(self):
 
1817
        wt = self.make_branch_and_tree('.')
 
1818
        tt = TreeTransform(wt)
 
1819
        self.addCleanup(tt.finalize)
 
1820
        parent = tt.trans_id_file_id('parent-id')
 
1821
        tt.new_file('file', parent, 'Contents')
 
1822
        resolve_conflicts(tt)
 
1823
 
 
1824
 
 
1825
class TestTransformPreview(tests.TestCaseWithTransport):
 
1826
 
 
1827
    def create_tree(self):
 
1828
        tree = self.make_branch_and_tree('.')
 
1829
        self.build_tree_contents([('a', 'content 1')])
 
1830
        tree.add('a', 'a-id')
 
1831
        tree.commit('rev1', rev_id='rev1')
 
1832
        return tree.branch.repository.revision_tree('rev1')
 
1833
 
 
1834
    def get_empty_preview(self):
 
1835
        repository = self.make_repository('repo')
 
1836
        tree = repository.revision_tree(_mod_revision.NULL_REVISION)
 
1837
        preview = TransformPreview(tree)
 
1838
        self.addCleanup(preview.finalize)
 
1839
        return preview
 
1840
 
 
1841
    def test_transform_preview(self):
 
1842
        revision_tree = self.create_tree()
 
1843
        preview = TransformPreview(revision_tree)
 
1844
        self.addCleanup(preview.finalize)
 
1845
 
 
1846
    def test_transform_preview_tree(self):
 
1847
        revision_tree = self.create_tree()
 
1848
        preview = TransformPreview(revision_tree)
 
1849
        self.addCleanup(preview.finalize)
 
1850
        preview.get_preview_tree()
 
1851
 
 
1852
    def test_transform_new_file(self):
 
1853
        revision_tree = self.create_tree()
 
1854
        preview = TransformPreview(revision_tree)
 
1855
        self.addCleanup(preview.finalize)
 
1856
        preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
 
1857
        preview_tree = preview.get_preview_tree()
 
1858
        self.assertEqual(preview_tree.kind('file2-id'), 'file')
 
1859
        self.assertEqual(
 
1860
            preview_tree.get_file('file2-id').read(), 'content B\n')
 
1861
 
 
1862
    def test_diff_preview_tree(self):
 
1863
        revision_tree = self.create_tree()
 
1864
        preview = TransformPreview(revision_tree)
 
1865
        self.addCleanup(preview.finalize)
 
1866
        preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
 
1867
        preview_tree = preview.get_preview_tree()
 
1868
        out = StringIO()
 
1869
        show_diff_trees(revision_tree, preview_tree, out)
 
1870
        lines = out.getvalue().splitlines()
 
1871
        self.assertEqual(lines[0], "=== added file 'file2'")
 
1872
        # 3 lines of diff administrivia
 
1873
        self.assertEqual(lines[4], "+content B")
 
1874
 
 
1875
    def test_transform_conflicts(self):
 
1876
        revision_tree = self.create_tree()
 
1877
        preview = TransformPreview(revision_tree)
 
1878
        self.addCleanup(preview.finalize)
 
1879
        preview.new_file('a', preview.root, 'content 2')
 
1880
        resolve_conflicts(preview)
 
1881
        trans_id = preview.trans_id_file_id('a-id')
 
1882
        self.assertEqual('a.moved', preview.final_name(trans_id))
 
1883
 
 
1884
    def get_tree_and_preview_tree(self):
 
1885
        revision_tree = self.create_tree()
 
1886
        preview = TransformPreview(revision_tree)
 
1887
        self.addCleanup(preview.finalize)
 
1888
        a_trans_id = preview.trans_id_file_id('a-id')
 
1889
        preview.delete_contents(a_trans_id)
 
1890
        preview.create_file('b content', a_trans_id)
 
1891
        preview_tree = preview.get_preview_tree()
 
1892
        return revision_tree, preview_tree
 
1893
 
 
1894
    def test_iter_changes(self):
 
1895
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
 
1896
        root = revision_tree.inventory.root.file_id
 
1897
        self.assertEqual([('a-id', ('a', 'a'), True, (True, True),
 
1898
                          (root, root), ('a', 'a'), ('file', 'file'),
 
1899
                          (False, False))],
 
1900
                          list(preview_tree._iter_changes(revision_tree)))
 
1901
 
 
1902
    def test_wrong_tree_value_error(self):
 
1903
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
 
1904
        e = self.assertRaises(ValueError, preview_tree._iter_changes,
 
1905
                              preview_tree)
 
1906
        self.assertEqual('from_tree must be transform source tree.', str(e))
 
1907
 
 
1908
    def test_include_unchanged_value_error(self):
 
1909
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
 
1910
        e = self.assertRaises(ValueError, preview_tree._iter_changes,
 
1911
                              revision_tree, include_unchanged=True)
 
1912
        self.assertEqual('include_unchanged is not supported', str(e))
 
1913
 
 
1914
    def test_specific_files(self):
 
1915
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
 
1916
        e = self.assertRaises(ValueError, preview_tree._iter_changes,
 
1917
                              revision_tree, specific_files=['pete'])
 
1918
        self.assertEqual('specific_files is not supported', str(e))
 
1919
 
 
1920
    def test_want_unversioned_value_error(self):
 
1921
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
 
1922
        e = self.assertRaises(ValueError, preview_tree._iter_changes,
 
1923
                              revision_tree, want_unversioned=True)
 
1924
        self.assertEqual('want_unversioned is not supported', str(e))
 
1925
 
 
1926
    def test_ignore_extra_trees_no_specific_files(self):
 
1927
        # extra_trees is harmless without specific_files, so we'll silently
 
1928
        # accept it, even though we won't use it.
 
1929
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
 
1930
        preview_tree._iter_changes(revision_tree, extra_trees=[preview_tree])
 
1931
 
 
1932
    def test_ignore_require_versioned_no_specific_files(self):
 
1933
        # require_versioned is meaningless without specific_files.
 
1934
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
 
1935
        preview_tree._iter_changes(revision_tree, require_versioned=False)
 
1936
 
 
1937
    def test_ignore_pb(self):
 
1938
        # pb could be supported, but TT.iter_changes doesn't support it.
 
1939
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
 
1940
        preview_tree._iter_changes(revision_tree, pb=progress.DummyProgress())
 
1941
 
 
1942
    def test_kind(self):
 
1943
        revision_tree = self.create_tree()
 
1944
        preview = TransformPreview(revision_tree)
 
1945
        self.addCleanup(preview.finalize)
 
1946
        preview.new_file('file', preview.root, 'contents', 'file-id')
 
1947
        preview.new_directory('directory', preview.root, 'dir-id')
 
1948
        preview_tree = preview.get_preview_tree()
 
1949
        self.assertEqual('file', preview_tree.kind('file-id'))
 
1950
        self.assertEqual('directory', preview_tree.kind('dir-id'))
 
1951
 
 
1952
    def test_get_file_mtime(self):
 
1953
        preview = self.get_empty_preview()
 
1954
        file_trans_id = preview.new_file('file', preview.root, 'contents',
 
1955
                                         'file-id')
 
1956
        limbo_path = preview._limbo_name(file_trans_id)
 
1957
        preview_tree = preview.get_preview_tree()
 
1958
        self.assertEqual(os.stat(limbo_path).st_mtime,
 
1959
                         preview_tree.get_file_mtime('file-id'))
 
1960
 
 
1961
    def test_get_file(self):
 
1962
        preview = self.get_empty_preview()
 
1963
        preview.new_file('file', preview.root, 'contents', 'file-id')
 
1964
        preview_tree = preview.get_preview_tree()
 
1965
        tree_file = preview_tree.get_file('file-id')
 
1966
        try:
 
1967
            self.assertEqual('contents', tree_file.read())
 
1968
        finally:
 
1969
            tree_file.close()