~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_transform.py

  • Committer: Martin Pool
  • Date: 2006-06-15 05:36:34 UTC
  • mto: This revision was merged to the branch mainline in revision 1797.
  • Revision ID: mbp@sourcefrog.net-20060615053634-4fd52ba691855659
Clean up many exception classes.

Errors indicating a user error are now shown with is_user_error on the
exception; use this rather than hardcoding a list of exceptions that should be
handled this way.

Exceptions now inherit from BzrNewException where possible to use consistent
formatting method.

Remove rather obsolete docstring test on Branch.missing_revisions.

Remove dead code from find_merge_base.


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
 
 
19
from bzrlib.bzrdir import BzrDir
 
20
from bzrlib.conflicts import (DuplicateEntry, DuplicateID, MissingParent,
 
21
                              UnversionedParent, ParentLoop)
 
22
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
 
23
                           ReusingTransform, CantMoveRoot, NotVersionedError,
 
24
                           ExistingLimbo, ImmortalLimbo, LockError)
 
25
from bzrlib.osutils import file_kind, has_symlinks, pathjoin
 
26
from bzrlib.merge import Merge3Merger
 
27
from bzrlib.tests import TestCaseInTempDir, TestSkipped, TestCase
 
28
from bzrlib.transform import (TreeTransform, ROOT_PARENT, FinalPaths, 
 
29
                              resolve_conflicts, cook_conflicts, 
 
30
                              find_interesting, build_tree, get_backup_name)
 
31
 
 
32
class TestTreeTransform(TestCaseInTempDir):
 
33
    def setUp(self):
 
34
        super(TestTreeTransform, self).setUp()
 
35
        self.wt = BzrDir.create_standalone_workingtree('.')
 
36
        os.chdir('..')
 
37
 
 
38
    def get_transform(self):
 
39
        transform = TreeTransform(self.wt)
 
40
        #self.addCleanup(transform.finalize)
 
41
        return transform, transform.trans_id_tree_file_id(self.wt.get_root_id())
 
42
 
 
43
    def test_existing_limbo(self):
 
44
        limbo_name = self.wt._control_files.controlfilename('limbo')
 
45
        transform, root = self.get_transform()
 
46
        os.mkdir(pathjoin(limbo_name, 'hehe'))
 
47
        self.assertRaises(ImmortalLimbo, transform.apply)
 
48
        self.assertRaises(LockError, self.wt.unlock)
 
49
        self.assertRaises(ExistingLimbo, self.get_transform)
 
50
        self.assertRaises(LockError, self.wt.unlock)
 
51
        os.rmdir(pathjoin(limbo_name, 'hehe'))
 
52
        os.rmdir(limbo_name)
 
53
        transform, root = self.get_transform()
 
54
        transform.apply()
 
55
 
 
56
    def test_build(self):
 
57
        transform, root = self.get_transform() 
 
58
        self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
 
59
        imaginary_id = transform.trans_id_tree_path('imaginary')
 
60
        imaginary_id2 = transform.trans_id_tree_path('imaginary/')
 
61
        self.assertEqual(imaginary_id, imaginary_id2)
 
62
        self.assertEqual(transform.get_tree_parent(imaginary_id), root)
 
63
        self.assertEqual(transform.final_kind(root), 'directory')
 
64
        self.assertEqual(transform.final_file_id(root), self.wt.get_root_id())
 
65
        trans_id = transform.create_path('name', root)
 
66
        self.assertIs(transform.final_file_id(trans_id), None)
 
67
        self.assertRaises(NoSuchFile, transform.final_kind, trans_id)
 
68
        transform.create_file('contents', trans_id)
 
69
        transform.set_executability(True, trans_id)
 
70
        transform.version_file('my_pretties', trans_id)
 
71
        self.assertRaises(DuplicateKey, transform.version_file,
 
72
                          'my_pretties', trans_id)
 
73
        self.assertEqual(transform.final_file_id(trans_id), 'my_pretties')
 
74
        self.assertEqual(transform.final_parent(trans_id), root)
 
75
        self.assertIs(transform.final_parent(root), ROOT_PARENT)
 
76
        self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
 
77
        oz_id = transform.create_path('oz', root)
 
78
        transform.create_directory(oz_id)
 
79
        transform.version_file('ozzie', oz_id)
 
80
        trans_id2 = transform.create_path('name2', root)
 
81
        transform.create_file('contents', trans_id2)
 
82
        transform.set_executability(False, trans_id2)
 
83
        transform.version_file('my_pretties2', trans_id2)
 
84
        modified_paths = transform.apply().modified_paths
 
85
        self.assertEqual('contents', self.wt.get_file_byname('name').read())
 
86
        self.assertEqual(self.wt.path2id('name'), 'my_pretties')
 
87
        self.assertIs(self.wt.is_executable('my_pretties'), True)
 
88
        self.assertIs(self.wt.is_executable('my_pretties2'), False)
 
89
        self.assertEqual('directory', file_kind(self.wt.abspath('oz')))
 
90
        self.assertEqual(len(modified_paths), 3)
 
91
        tree_mod_paths = [self.wt.id2abspath(f) for f in 
 
92
                          ('ozzie', 'my_pretties', 'my_pretties2')]
 
93
        self.assertSubset(tree_mod_paths, modified_paths)
 
94
        # is it safe to finalize repeatedly?
 
95
        transform.finalize()
 
96
        transform.finalize()
 
97
 
 
98
    def test_convenience(self):
 
99
        transform, root = self.get_transform()
 
100
        trans_id = transform.new_file('name', root, 'contents', 
 
101
                                      'my_pretties', True)
 
102
        oz = transform.new_directory('oz', root, 'oz-id')
 
103
        dorothy = transform.new_directory('dorothy', oz, 'dorothy-id')
 
104
        toto = transform.new_file('toto', dorothy, 'toto-contents', 
 
105
                                  'toto-id', False)
 
106
 
 
107
        self.assertEqual(len(transform.find_conflicts()), 0)
 
108
        transform.apply()
 
109
        self.assertRaises(ReusingTransform, transform.find_conflicts)
 
110
        self.assertEqual('contents', file(self.wt.abspath('name')).read())
 
111
        self.assertEqual(self.wt.path2id('name'), 'my_pretties')
 
112
        self.assertIs(self.wt.is_executable('my_pretties'), True)
 
113
        self.assertEqual(self.wt.path2id('oz'), 'oz-id')
 
114
        self.assertEqual(self.wt.path2id('oz/dorothy'), 'dorothy-id')
 
115
        self.assertEqual(self.wt.path2id('oz/dorothy/toto'), 'toto-id')
 
116
 
 
117
        self.assertEqual('toto-contents', 
 
118
                         self.wt.get_file_byname('oz/dorothy/toto').read())
 
119
        self.assertIs(self.wt.is_executable('toto-id'), False)
 
120
 
 
121
    def test_conflicts(self):
 
122
        transform, root = self.get_transform()
 
123
        trans_id = transform.new_file('name', root, 'contents', 
 
124
                                      'my_pretties')
 
125
        self.assertEqual(len(transform.find_conflicts()), 0)
 
126
        trans_id2 = transform.new_file('name', root, 'Crontents', 'toto')
 
127
        self.assertEqual(transform.find_conflicts(), 
 
128
                         [('duplicate', trans_id, trans_id2, 'name')])
 
129
        self.assertRaises(MalformedTransform, transform.apply)
 
130
        transform.adjust_path('name', trans_id, trans_id2)
 
131
        self.assertEqual(transform.find_conflicts(), 
 
132
                         [('non-directory parent', trans_id)])
 
133
        tinman_id = transform.trans_id_tree_path('tinman')
 
134
        transform.adjust_path('name', tinman_id, trans_id2)
 
135
        self.assertEqual(transform.find_conflicts(), 
 
136
                         [('unversioned parent', tinman_id), 
 
137
                          ('missing parent', tinman_id)])
 
138
        lion_id = transform.create_path('lion', root)
 
139
        self.assertEqual(transform.find_conflicts(), 
 
140
                         [('unversioned parent', tinman_id), 
 
141
                          ('missing parent', tinman_id)])
 
142
        transform.adjust_path('name', lion_id, trans_id2)
 
143
        self.assertEqual(transform.find_conflicts(), 
 
144
                         [('unversioned parent', lion_id),
 
145
                          ('missing parent', lion_id)])
 
146
        transform.version_file("Courage", lion_id)
 
147
        self.assertEqual(transform.find_conflicts(), 
 
148
                         [('missing parent', lion_id), 
 
149
                          ('versioning no contents', lion_id)])
 
150
        transform.adjust_path('name2', root, trans_id2)
 
151
        self.assertEqual(transform.find_conflicts(), 
 
152
                         [('versioning no contents', lion_id)])
 
153
        transform.create_file('Contents, okay?', lion_id)
 
154
        transform.adjust_path('name2', trans_id2, trans_id2)
 
155
        self.assertEqual(transform.find_conflicts(), 
 
156
                         [('parent loop', trans_id2), 
 
157
                          ('non-directory parent', trans_id2)])
 
158
        transform.adjust_path('name2', root, trans_id2)
 
159
        oz_id = transform.new_directory('oz', root)
 
160
        transform.set_executability(True, oz_id)
 
161
        self.assertEqual(transform.find_conflicts(), 
 
162
                         [('unversioned executability', oz_id)])
 
163
        transform.version_file('oz-id', oz_id)
 
164
        self.assertEqual(transform.find_conflicts(), 
 
165
                         [('non-file executability', oz_id)])
 
166
        transform.set_executability(None, oz_id)
 
167
        tip_id = transform.new_file('tip', oz_id, 'ozma', 'tip-id')
 
168
        transform.apply()
 
169
        self.assertEqual(self.wt.path2id('name'), 'my_pretties')
 
170
        self.assertEqual('contents', file(self.wt.abspath('name')).read())
 
171
        transform2, root = self.get_transform()
 
172
        oz_id = transform2.trans_id_tree_file_id('oz-id')
 
173
        newtip = transform2.new_file('tip', oz_id, 'other', 'tip-id')
 
174
        result = transform2.find_conflicts()
 
175
        fp = FinalPaths(transform2)
 
176
        self.assert_('oz/tip' in transform2._tree_path_ids)
 
177
        self.assertEqual(fp.get_path(newtip), pathjoin('oz', 'tip'))
 
178
        self.assertEqual(len(result), 2)
 
179
        self.assertEqual((result[0][0], result[0][1]), 
 
180
                         ('duplicate', newtip))
 
181
        self.assertEqual((result[1][0], result[1][2]), 
 
182
                         ('duplicate id', newtip))
 
183
        transform2.finalize()
 
184
        transform3 = TreeTransform(self.wt)
 
185
        self.addCleanup(transform3.finalize)
 
186
        oz_id = transform3.trans_id_tree_file_id('oz-id')
 
187
        transform3.delete_contents(oz_id)
 
188
        self.assertEqual(transform3.find_conflicts(), 
 
189
                         [('missing parent', oz_id)])
 
190
        root_id = transform3.trans_id_tree_file_id('TREE_ROOT')
 
191
        tip_id = transform3.trans_id_tree_file_id('tip-id')
 
192
        transform3.adjust_path('tip', root_id, tip_id)
 
193
        transform3.apply()
 
194
 
 
195
    def test_add_del(self):
 
196
        start, root = self.get_transform()
 
197
        start.new_directory('a', root, 'a')
 
198
        start.apply()
 
199
        transform, root = self.get_transform()
 
200
        transform.delete_versioned(transform.trans_id_tree_file_id('a'))
 
201
        transform.new_directory('a', root, 'a')
 
202
        transform.apply()
 
203
 
 
204
    def test_unversioning(self):
 
205
        create_tree, root = self.get_transform()
 
206
        parent_id = create_tree.new_directory('parent', root, 'parent-id')
 
207
        create_tree.new_file('child', parent_id, 'child', 'child-id')
 
208
        create_tree.apply()
 
209
        unversion = TreeTransform(self.wt)
 
210
        self.addCleanup(unversion.finalize)
 
211
        parent = unversion.trans_id_tree_path('parent')
 
212
        unversion.unversion_file(parent)
 
213
        self.assertEqual(unversion.find_conflicts(), 
 
214
                         [('unversioned parent', parent_id)])
 
215
        file_id = unversion.trans_id_tree_file_id('child-id')
 
216
        unversion.unversion_file(file_id)
 
217
        unversion.apply()
 
218
 
 
219
    def test_name_invariants(self):
 
220
        create_tree, root = self.get_transform()
 
221
        # prepare tree
 
222
        root = create_tree.trans_id_tree_file_id('TREE_ROOT')
 
223
        create_tree.new_file('name1', root, 'hello1', 'name1')
 
224
        create_tree.new_file('name2', root, 'hello2', 'name2')
 
225
        ddir = create_tree.new_directory('dying_directory', root, 'ddir')
 
226
        create_tree.new_file('dying_file', ddir, 'goodbye1', 'dfile')
 
227
        create_tree.new_file('moving_file', ddir, 'later1', 'mfile')
 
228
        create_tree.new_file('moving_file2', root, 'later2', 'mfile2')
 
229
        create_tree.apply()
 
230
 
 
231
        mangle_tree,root = self.get_transform()
 
232
        root = mangle_tree.trans_id_tree_file_id('TREE_ROOT')
 
233
        #swap names
 
234
        name1 = mangle_tree.trans_id_tree_file_id('name1')
 
235
        name2 = mangle_tree.trans_id_tree_file_id('name2')
 
236
        mangle_tree.adjust_path('name2', root, name1)
 
237
        mangle_tree.adjust_path('name1', root, name2)
 
238
 
 
239
        #tests for deleting parent directories 
 
240
        ddir = mangle_tree.trans_id_tree_file_id('ddir')
 
241
        mangle_tree.delete_contents(ddir)
 
242
        dfile = mangle_tree.trans_id_tree_file_id('dfile')
 
243
        mangle_tree.delete_versioned(dfile)
 
244
        mangle_tree.unversion_file(dfile)
 
245
        mfile = mangle_tree.trans_id_tree_file_id('mfile')
 
246
        mangle_tree.adjust_path('mfile', root, mfile)
 
247
 
 
248
        #tests for adding parent directories
 
249
        newdir = mangle_tree.new_directory('new_directory', root, 'newdir')
 
250
        mfile2 = mangle_tree.trans_id_tree_file_id('mfile2')
 
251
        mangle_tree.adjust_path('mfile2', newdir, mfile2)
 
252
        mangle_tree.new_file('newfile', newdir, 'hello3', 'dfile')
 
253
        self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
 
254
        self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
 
255
        self.assertEqual(mangle_tree.final_file_id(mfile2), 'mfile2')
 
256
        mangle_tree.apply()
 
257
        self.assertEqual(file(self.wt.abspath('name1')).read(), 'hello2')
 
258
        self.assertEqual(file(self.wt.abspath('name2')).read(), 'hello1')
 
259
        mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
 
260
        self.assertEqual(mangle_tree.final_parent(mfile2), newdir)
 
261
        self.assertEqual(file(mfile2_path).read(), 'later2')
 
262
        self.assertEqual(self.wt.id2path('mfile2'), 'new_directory/mfile2')
 
263
        self.assertEqual(self.wt.path2id('new_directory/mfile2'), 'mfile2')
 
264
        newfile_path = self.wt.abspath(pathjoin('new_directory','newfile'))
 
265
        self.assertEqual(file(newfile_path).read(), 'hello3')
 
266
        self.assertEqual(self.wt.path2id('dying_directory'), 'ddir')
 
267
        self.assertIs(self.wt.path2id('dying_directory/dying_file'), None)
 
268
        mfile2_path = self.wt.abspath(pathjoin('new_directory','mfile2'))
 
269
 
 
270
    def test_both_rename(self):
 
271
        create_tree,root = self.get_transform()
 
272
        newdir = create_tree.new_directory('selftest', root, 'selftest-id')
 
273
        create_tree.new_file('blackbox.py', newdir, 'hello1', 'blackbox-id')
 
274
        create_tree.apply()        
 
275
        mangle_tree,root = self.get_transform()
 
276
        selftest = mangle_tree.trans_id_tree_file_id('selftest-id')
 
277
        blackbox = mangle_tree.trans_id_tree_file_id('blackbox-id')
 
278
        mangle_tree.adjust_path('test', root, selftest)
 
279
        mangle_tree.adjust_path('test_too_much', root, selftest)
 
280
        mangle_tree.set_executability(True, blackbox)
 
281
        mangle_tree.apply()
 
282
 
 
283
    def test_both_rename2(self):
 
284
        create_tree,root = self.get_transform()
 
285
        bzrlib = create_tree.new_directory('bzrlib', root, 'bzrlib-id')
 
286
        tests = create_tree.new_directory('tests', bzrlib, 'tests-id')
 
287
        blackbox = create_tree.new_directory('blackbox', tests, 'blackbox-id')
 
288
        create_tree.new_file('test_too_much.py', blackbox, 'hello1', 
 
289
                             'test_too_much-id')
 
290
        create_tree.apply()        
 
291
        mangle_tree,root = self.get_transform()
 
292
        bzrlib = mangle_tree.trans_id_tree_file_id('bzrlib-id')
 
293
        tests = mangle_tree.trans_id_tree_file_id('tests-id')
 
294
        test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
 
295
        mangle_tree.adjust_path('selftest', bzrlib, tests)
 
296
        mangle_tree.adjust_path('blackbox.py', tests, test_too_much) 
 
297
        mangle_tree.set_executability(True, test_too_much)
 
298
        mangle_tree.apply()
 
299
 
 
300
    def test_both_rename3(self):
 
301
        create_tree,root = self.get_transform()
 
302
        tests = create_tree.new_directory('tests', root, 'tests-id')
 
303
        create_tree.new_file('test_too_much.py', tests, 'hello1', 
 
304
                             'test_too_much-id')
 
305
        create_tree.apply()        
 
306
        mangle_tree,root = self.get_transform()
 
307
        tests = mangle_tree.trans_id_tree_file_id('tests-id')
 
308
        test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
 
309
        mangle_tree.adjust_path('selftest', root, tests)
 
310
        mangle_tree.adjust_path('blackbox.py', tests, test_too_much) 
 
311
        mangle_tree.set_executability(True, test_too_much)
 
312
        mangle_tree.apply()
 
313
 
 
314
    def test_move_dangling_ie(self):
 
315
        create_tree, root = self.get_transform()
 
316
        # prepare tree
 
317
        root = create_tree.trans_id_tree_file_id('TREE_ROOT')
 
318
        create_tree.new_file('name1', root, 'hello1', 'name1')
 
319
        create_tree.apply()
 
320
        delete_contents, root = self.get_transform()
 
321
        file = delete_contents.trans_id_tree_file_id('name1')
 
322
        delete_contents.delete_contents(file)
 
323
        delete_contents.apply()
 
324
        move_id, root = self.get_transform()
 
325
        name1 = move_id.trans_id_tree_file_id('name1')
 
326
        newdir = move_id.new_directory('dir', root, 'newdir')
 
327
        move_id.adjust_path('name2', newdir, name1)
 
328
        move_id.apply()
 
329
        
 
330
    def test_replace_dangling_ie(self):
 
331
        create_tree, root = self.get_transform()
 
332
        # prepare tree
 
333
        root = create_tree.trans_id_tree_file_id('TREE_ROOT')
 
334
        create_tree.new_file('name1', root, 'hello1', 'name1')
 
335
        create_tree.apply()
 
336
        delete_contents = TreeTransform(self.wt)
 
337
        self.addCleanup(delete_contents.finalize)
 
338
        file = delete_contents.trans_id_tree_file_id('name1')
 
339
        delete_contents.delete_contents(file)
 
340
        delete_contents.apply()
 
341
        delete_contents.finalize()
 
342
        replace = TreeTransform(self.wt)
 
343
        self.addCleanup(replace.finalize)
 
344
        name2 = replace.new_file('name2', root, 'hello2', 'name1')
 
345
        conflicts = replace.find_conflicts()
 
346
        name1 = replace.trans_id_tree_file_id('name1')
 
347
        self.assertEqual(conflicts, [('duplicate id', name1, name2)])
 
348
        resolve_conflicts(replace)
 
349
        replace.apply()
 
350
 
 
351
    def test_symlinks(self):
 
352
        if not has_symlinks():
 
353
            raise TestSkipped('Symlinks are not supported on this platform')
 
354
        transform,root = self.get_transform()
 
355
        oz_id = transform.new_directory('oz', root, 'oz-id')
 
356
        wizard = transform.new_symlink('wizard', oz_id, 'wizard-target', 
 
357
                                       'wizard-id')
 
358
        wiz_id = transform.create_path('wizard2', oz_id)
 
359
        transform.create_symlink('behind_curtain', wiz_id)
 
360
        transform.version_file('wiz-id2', wiz_id)            
 
361
        transform.set_executability(True, wiz_id)
 
362
        self.assertEqual(transform.find_conflicts(), 
 
363
                         [('non-file executability', wiz_id)])
 
364
        transform.set_executability(None, wiz_id)
 
365
        transform.apply()
 
366
        self.assertEqual(self.wt.path2id('oz/wizard'), 'wizard-id')
 
367
        self.assertEqual(file_kind(self.wt.abspath('oz/wizard')), 'symlink')
 
368
        self.assertEqual(os.readlink(self.wt.abspath('oz/wizard2')), 
 
369
                         'behind_curtain')
 
370
        self.assertEqual(os.readlink(self.wt.abspath('oz/wizard')),
 
371
                         'wizard-target')
 
372
 
 
373
 
 
374
    def get_conflicted(self):
 
375
        create,root = self.get_transform()
 
376
        create.new_file('dorothy', root, 'dorothy', 'dorothy-id')
 
377
        oz = create.new_directory('oz', root, 'oz-id')
 
378
        create.new_directory('emeraldcity', oz, 'emerald-id')
 
379
        create.apply()
 
380
        conflicts,root = self.get_transform()
 
381
        # set up duplicate entry, duplicate id
 
382
        new_dorothy = conflicts.new_file('dorothy', root, 'dorothy', 
 
383
                                         'dorothy-id')
 
384
        old_dorothy = conflicts.trans_id_tree_file_id('dorothy-id')
 
385
        oz = conflicts.trans_id_tree_file_id('oz-id')
 
386
        # set up missing, unversioned parent
 
387
        conflicts.delete_versioned(oz)
 
388
        emerald = conflicts.trans_id_tree_file_id('emerald-id')
 
389
        # set up parent loop
 
390
        conflicts.adjust_path('emeraldcity', emerald, emerald)
 
391
        return conflicts, emerald, oz, old_dorothy, new_dorothy
 
392
 
 
393
    def test_conflict_resolution(self):
 
394
        conflicts, emerald, oz, old_dorothy, new_dorothy =\
 
395
            self.get_conflicted()
 
396
        resolve_conflicts(conflicts)
 
397
        self.assertEqual(conflicts.final_name(old_dorothy), 'dorothy.moved')
 
398
        self.assertIs(conflicts.final_file_id(old_dorothy), None)
 
399
        self.assertEqual(conflicts.final_name(new_dorothy), 'dorothy')
 
400
        self.assertEqual(conflicts.final_file_id(new_dorothy), 'dorothy-id')
 
401
        self.assertEqual(conflicts.final_parent(emerald), oz)
 
402
        conflicts.apply()
 
403
 
 
404
    def test_cook_conflicts(self):
 
405
        tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
 
406
        raw_conflicts = resolve_conflicts(tt)
 
407
        cooked_conflicts = cook_conflicts(raw_conflicts, tt)
 
408
        duplicate = DuplicateEntry('Moved existing file to', 'dorothy.moved', 
 
409
                                   'dorothy', None, 'dorothy-id')
 
410
        self.assertEqual(cooked_conflicts[0], duplicate)
 
411
        duplicate_id = DuplicateID('Unversioned existing file', 
 
412
                                   'dorothy.moved', 'dorothy', None,
 
413
                                   'dorothy-id')
 
414
        self.assertEqual(cooked_conflicts[1], duplicate_id)
 
415
        missing_parent = MissingParent('Not deleting', 'oz', 'oz-id')
 
416
        self.assertEqual(cooked_conflicts[2], missing_parent)
 
417
        unversioned_parent = UnversionedParent('Versioned directory', 'oz',
 
418
                                               'oz-id')
 
419
        self.assertEqual(cooked_conflicts[3], unversioned_parent)
 
420
        parent_loop = ParentLoop('Cancelled move', 'oz/emeraldcity', 
 
421
                                 'oz/emeraldcity', 'emerald-id', 'emerald-id')
 
422
        self.assertEqual(cooked_conflicts[4], parent_loop)
 
423
        self.assertEqual(len(cooked_conflicts), 5)
 
424
        tt.finalize()
 
425
 
 
426
    def test_string_conflicts(self):
 
427
        tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
 
428
        raw_conflicts = resolve_conflicts(tt)
 
429
        cooked_conflicts = cook_conflicts(raw_conflicts, tt)
 
430
        tt.finalize()
 
431
        conflicts_s = [str(c) for c in cooked_conflicts]
 
432
        self.assertEqual(len(cooked_conflicts), len(conflicts_s))
 
433
        self.assertEqual(conflicts_s[0], 'Conflict adding file dorothy.  '
 
434
                                         'Moved existing file to '
 
435
                                         'dorothy.moved.')
 
436
        self.assertEqual(conflicts_s[1], 'Conflict adding id to dorothy.  '
 
437
                                         'Unversioned existing file '
 
438
                                         'dorothy.moved.')
 
439
        self.assertEqual(conflicts_s[2], 'Conflict adding files to oz.  '
 
440
                                         'Not deleting.')
 
441
        self.assertEqual(conflicts_s[3], 'Conflict adding versioned files to '
 
442
                                         'oz.  Versioned directory.')
 
443
        self.assertEqual(conflicts_s[4], 'Conflict moving oz/emeraldcity into'
 
444
                                         ' oz/emeraldcity.  Cancelled move.')
 
445
 
 
446
    def test_moving_versioned_directories(self):
 
447
        create, root = self.get_transform()
 
448
        kansas = create.new_directory('kansas', root, 'kansas-id')
 
449
        create.new_directory('house', kansas, 'house-id')
 
450
        create.new_directory('oz', root, 'oz-id')
 
451
        create.apply()
 
452
        cyclone, root = self.get_transform()
 
453
        oz = cyclone.trans_id_tree_file_id('oz-id')
 
454
        house = cyclone.trans_id_tree_file_id('house-id')
 
455
        cyclone.adjust_path('house', oz, house)
 
456
        cyclone.apply()
 
457
 
 
458
    def test_moving_root(self):
 
459
        create, root = self.get_transform()
 
460
        fun = create.new_directory('fun', root, 'fun-id')
 
461
        create.new_directory('sun', root, 'sun-id')
 
462
        create.new_directory('moon', root, 'moon')
 
463
        create.apply()
 
464
        transform, root = self.get_transform()
 
465
        transform.adjust_root_path('oldroot', fun)
 
466
        new_root=transform.trans_id_tree_path('')
 
467
        transform.version_file('new-root', new_root)
 
468
        transform.apply()
 
469
 
 
470
    def test_renames(self):
 
471
        create, root = self.get_transform()
 
472
        old = create.new_directory('old-parent', root, 'old-id')
 
473
        intermediate = create.new_directory('intermediate', old, 'im-id')
 
474
        myfile = create.new_file('myfile', intermediate, 'myfile-text',
 
475
                                 'myfile-id')
 
476
        create.apply()
 
477
        rename, root = self.get_transform()
 
478
        old = rename.trans_id_file_id('old-id')
 
479
        rename.adjust_path('new', root, old)
 
480
        myfile = rename.trans_id_file_id('myfile-id')
 
481
        rename.set_executability(True, myfile)
 
482
        rename.apply()
 
483
 
 
484
    def test_find_interesting(self):
 
485
        create, root = self.get_transform()
 
486
        wt = create._tree
 
487
        create.new_file('vfile', root, 'myfile-text', 'myfile-id')
 
488
        create.new_file('uvfile', root, 'othertext')
 
489
        create.apply()
 
490
        self.assertEqual(find_interesting(wt, wt, ['vfile']),
 
491
                         set(['myfile-id']))
 
492
        self.assertRaises(NotVersionedError, find_interesting, wt, wt,
 
493
                          ['uvfile'])
 
494
 
 
495
 
 
496
class TransformGroup(object):
 
497
    def __init__(self, dirname):
 
498
        self.name = dirname
 
499
        os.mkdir(dirname)
 
500
        self.wt = BzrDir.create_standalone_workingtree(dirname)
 
501
        self.b = self.wt.branch
 
502
        self.tt = TreeTransform(self.wt)
 
503
        self.root = self.tt.trans_id_tree_file_id(self.wt.get_root_id())
 
504
 
 
505
def conflict_text(tree, merge):
 
506
    template = '%s TREE\n%s%s\n%s%s MERGE-SOURCE\n'
 
507
    return template % ('<' * 7, tree, '=' * 7, merge, '>' * 7)
 
508
 
 
509
 
 
510
class TestTransformMerge(TestCaseInTempDir):
 
511
    def test_text_merge(self):
 
512
        base = TransformGroup("base")
 
513
        base.tt.new_file('a', base.root, 'a\nb\nc\nd\be\n', 'a')
 
514
        base.tt.new_file('b', base.root, 'b1', 'b')
 
515
        base.tt.new_file('c', base.root, 'c', 'c')
 
516
        base.tt.new_file('d', base.root, 'd', 'd')
 
517
        base.tt.new_file('e', base.root, 'e', 'e')
 
518
        base.tt.new_file('f', base.root, 'f', 'f')
 
519
        base.tt.new_directory('g', base.root, 'g')
 
520
        base.tt.new_directory('h', base.root, 'h')
 
521
        base.tt.apply()
 
522
        other = TransformGroup("other")
 
523
        other.tt.new_file('a', other.root, 'y\nb\nc\nd\be\n', 'a')
 
524
        other.tt.new_file('b', other.root, 'b2', 'b')
 
525
        other.tt.new_file('c', other.root, 'c2', 'c')
 
526
        other.tt.new_file('d', other.root, 'd', 'd')
 
527
        other.tt.new_file('e', other.root, 'e2', 'e')
 
528
        other.tt.new_file('f', other.root, 'f', 'f')
 
529
        other.tt.new_file('g', other.root, 'g', 'g')
 
530
        other.tt.new_file('h', other.root, 'h\ni\nj\nk\n', 'h')
 
531
        other.tt.new_file('i', other.root, 'h\ni\nj\nk\n', 'i')
 
532
        other.tt.apply()
 
533
        this = TransformGroup("this")
 
534
        this.tt.new_file('a', this.root, 'a\nb\nc\nd\bz\n', 'a')
 
535
        this.tt.new_file('b', this.root, 'b', 'b')
 
536
        this.tt.new_file('c', this.root, 'c', 'c')
 
537
        this.tt.new_file('d', this.root, 'd2', 'd')
 
538
        this.tt.new_file('e', this.root, 'e2', 'e')
 
539
        this.tt.new_file('f', this.root, 'f', 'f')
 
540
        this.tt.new_file('g', this.root, 'g', 'g')
 
541
        this.tt.new_file('h', this.root, '1\n2\n3\n4\n', 'h')
 
542
        this.tt.new_file('i', this.root, '1\n2\n3\n4\n', 'i')
 
543
        this.tt.apply()
 
544
        Merge3Merger(this.wt, this.wt, base.wt, other.wt)
 
545
        # textual merge
 
546
        self.assertEqual(this.wt.get_file('a').read(), 'y\nb\nc\nd\bz\n')
 
547
        # three-way text conflict
 
548
        self.assertEqual(this.wt.get_file('b').read(), 
 
549
                         conflict_text('b', 'b2'))
 
550
        # OTHER wins
 
551
        self.assertEqual(this.wt.get_file('c').read(), 'c2')
 
552
        # THIS wins
 
553
        self.assertEqual(this.wt.get_file('d').read(), 'd2')
 
554
        # Ambigious clean merge
 
555
        self.assertEqual(this.wt.get_file('e').read(), 'e2')
 
556
        # No change
 
557
        self.assertEqual(this.wt.get_file('f').read(), 'f')
 
558
        # Correct correct results when THIS == OTHER 
 
559
        self.assertEqual(this.wt.get_file('g').read(), 'g')
 
560
        # Text conflict when THIS & OTHER are text and BASE is dir
 
561
        self.assertEqual(this.wt.get_file('h').read(), 
 
562
                         conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
 
563
        self.assertEqual(this.wt.get_file_byname('h.THIS').read(),
 
564
                         '1\n2\n3\n4\n')
 
565
        self.assertEqual(this.wt.get_file_byname('h.OTHER').read(),
 
566
                         'h\ni\nj\nk\n')
 
567
        self.assertEqual(file_kind(this.wt.abspath('h.BASE')), 'directory')
 
568
        self.assertEqual(this.wt.get_file('i').read(), 
 
569
                         conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
 
570
        self.assertEqual(this.wt.get_file_byname('i.THIS').read(),
 
571
                         '1\n2\n3\n4\n')
 
572
        self.assertEqual(this.wt.get_file_byname('i.OTHER').read(),
 
573
                         'h\ni\nj\nk\n')
 
574
        self.assertEqual(os.path.exists(this.wt.abspath('i.BASE')), False)
 
575
        modified = ['a', 'b', 'c', 'h', 'i']
 
576
        merge_modified = this.wt.merge_modified()
 
577
        self.assertSubset(merge_modified, modified)
 
578
        self.assertEqual(len(merge_modified), len(modified))
 
579
        file(this.wt.id2abspath('a'), 'wb').write('booga')
 
580
        modified.pop(0)
 
581
        merge_modified = this.wt.merge_modified()
 
582
        self.assertSubset(merge_modified, modified)
 
583
        self.assertEqual(len(merge_modified), len(modified))
 
584
        this.wt.remove('b')
 
585
        this.wt.revert([])
 
586
 
 
587
    def test_file_merge(self):
 
588
        if not has_symlinks():
 
589
            raise TestSkipped('Symlinks are not supported on this platform')
 
590
        base = TransformGroup("BASE")
 
591
        this = TransformGroup("THIS")
 
592
        other = TransformGroup("OTHER")
 
593
        for tg in this, base, other:
 
594
            tg.tt.new_directory('a', tg.root, 'a')
 
595
            tg.tt.new_symlink('b', tg.root, 'b', 'b')
 
596
            tg.tt.new_file('c', tg.root, 'c', 'c')
 
597
            tg.tt.new_symlink('d', tg.root, tg.name, 'd')
 
598
        targets = ((base, 'base-e', 'base-f', None, None), 
 
599
                   (this, 'other-e', 'this-f', 'other-g', 'this-h'), 
 
600
                   (other, 'other-e', None, 'other-g', 'other-h'))
 
601
        for tg, e_target, f_target, g_target, h_target in targets:
 
602
            for link, target in (('e', e_target), ('f', f_target), 
 
603
                                 ('g', g_target), ('h', h_target)):
 
604
                if target is not None:
 
605
                    tg.tt.new_symlink(link, tg.root, target, link)
 
606
 
 
607
        for tg in this, base, other:
 
608
            tg.tt.apply()
 
609
        Merge3Merger(this.wt, this.wt, base.wt, other.wt)
 
610
        self.assertIs(os.path.isdir(this.wt.abspath('a')), True)
 
611
        self.assertIs(os.path.islink(this.wt.abspath('b')), True)
 
612
        self.assertIs(os.path.isfile(this.wt.abspath('c')), True)
 
613
        for suffix in ('THIS', 'BASE', 'OTHER'):
 
614
            self.assertEqual(os.readlink(this.wt.abspath('d.'+suffix)), suffix)
 
615
        self.assertIs(os.path.lexists(this.wt.abspath('d')), False)
 
616
        self.assertEqual(this.wt.id2path('d'), 'd.OTHER')
 
617
        self.assertEqual(this.wt.id2path('f'), 'f.THIS')
 
618
        self.assertEqual(os.readlink(this.wt.abspath('e')), 'other-e')
 
619
        self.assertIs(os.path.lexists(this.wt.abspath('e.THIS')), False)
 
620
        self.assertIs(os.path.lexists(this.wt.abspath('e.OTHER')), False)
 
621
        self.assertIs(os.path.lexists(this.wt.abspath('e.BASE')), False)
 
622
        self.assertIs(os.path.lexists(this.wt.abspath('g')), True)
 
623
        self.assertIs(os.path.lexists(this.wt.abspath('g.BASE')), False)
 
624
        self.assertIs(os.path.lexists(this.wt.abspath('h')), False)
 
625
        self.assertIs(os.path.lexists(this.wt.abspath('h.BASE')), False)
 
626
        self.assertIs(os.path.lexists(this.wt.abspath('h.THIS')), True)
 
627
        self.assertIs(os.path.lexists(this.wt.abspath('h.OTHER')), True)
 
628
 
 
629
    def test_filename_merge(self):
 
630
        base = TransformGroup("BASE")
 
631
        this = TransformGroup("THIS")
 
632
        other = TransformGroup("OTHER")
 
633
        base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a') 
 
634
                                   for t in [base, this, other]]
 
635
        base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b') 
 
636
                                   for t in [base, this, other]]
 
637
        base.tt.new_directory('c', base_a, 'c')
 
638
        this.tt.new_directory('c1', this_a, 'c')
 
639
        other.tt.new_directory('c', other_b, 'c')
 
640
 
 
641
        base.tt.new_directory('d', base_a, 'd')
 
642
        this.tt.new_directory('d1', this_b, 'd')
 
643
        other.tt.new_directory('d', other_a, 'd')
 
644
 
 
645
        base.tt.new_directory('e', base_a, 'e')
 
646
        this.tt.new_directory('e', this_a, 'e')
 
647
        other.tt.new_directory('e1', other_b, 'e')
 
648
 
 
649
        base.tt.new_directory('f', base_a, 'f')
 
650
        this.tt.new_directory('f1', this_b, 'f')
 
651
        other.tt.new_directory('f1', other_b, 'f')
 
652
 
 
653
        for tg in [this, base, other]:
 
654
            tg.tt.apply()
 
655
        Merge3Merger(this.wt, this.wt, base.wt, other.wt)
 
656
        self.assertEqual(this.wt.id2path('c'), pathjoin('b/c1'))
 
657
        self.assertEqual(this.wt.id2path('d'), pathjoin('b/d1'))
 
658
        self.assertEqual(this.wt.id2path('e'), pathjoin('b/e1'))
 
659
        self.assertEqual(this.wt.id2path('f'), pathjoin('b/f1'))
 
660
 
 
661
    def test_filename_merge_conflicts(self):
 
662
        base = TransformGroup("BASE")
 
663
        this = TransformGroup("THIS")
 
664
        other = TransformGroup("OTHER")
 
665
        base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a') 
 
666
                                   for t in [base, this, other]]
 
667
        base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b') 
 
668
                                   for t in [base, this, other]]
 
669
 
 
670
        base.tt.new_file('g', base_a, 'g', 'g')
 
671
        other.tt.new_file('g1', other_b, 'g1', 'g')
 
672
 
 
673
        base.tt.new_file('h', base_a, 'h', 'h')
 
674
        this.tt.new_file('h1', this_b, 'h1', 'h')
 
675
 
 
676
        base.tt.new_file('i', base.root, 'i', 'i')
 
677
        other.tt.new_directory('i1', this_b, 'i')
 
678
 
 
679
        for tg in [this, base, other]:
 
680
            tg.tt.apply()
 
681
        Merge3Merger(this.wt, this.wt, base.wt, other.wt)
 
682
 
 
683
        self.assertEqual(this.wt.id2path('g'), pathjoin('b/g1.OTHER'))
 
684
        self.assertIs(os.path.lexists(this.wt.abspath('b/g1.BASE')), True)
 
685
        self.assertIs(os.path.lexists(this.wt.abspath('b/g1.THIS')), False)
 
686
        self.assertEqual(this.wt.id2path('h'), pathjoin('b/h1.THIS'))
 
687
        self.assertIs(os.path.lexists(this.wt.abspath('b/h1.BASE')), True)
 
688
        self.assertIs(os.path.lexists(this.wt.abspath('b/h1.OTHER')), False)
 
689
        self.assertEqual(this.wt.id2path('i'), pathjoin('b/i1.OTHER'))
 
690
 
 
691
class TestBuildTree(TestCaseInTempDir):
 
692
    def test_build_tree(self):
 
693
        if not has_symlinks():
 
694
            raise TestSkipped('Test requires symlink support')
 
695
        os.mkdir('a')
 
696
        a = BzrDir.create_standalone_workingtree('a')
 
697
        os.mkdir('a/foo')
 
698
        file('a/foo/bar', 'wb').write('contents')
 
699
        os.symlink('a/foo/bar', 'a/foo/baz')
 
700
        a.add(['foo', 'foo/bar', 'foo/baz'])
 
701
        a.commit('initial commit')
 
702
        b = BzrDir.create_standalone_workingtree('b')
 
703
        build_tree(a.basis_tree(), b)
 
704
        self.assertIs(os.path.isdir('b/foo'), True)
 
705
        self.assertEqual(file('b/foo/bar', 'rb').read(), "contents")
 
706
        self.assertEqual(os.readlink('b/foo/baz'), 'a/foo/bar')
 
707
        
 
708
class MockTransform(object):
 
709
 
 
710
    def has_named_child(self, by_parent, parent_id, name):
 
711
        for child_id in by_parent[parent_id]:
 
712
            if child_id == '0':
 
713
                if name == "name~":
 
714
                    return True
 
715
            elif name == "name.~%s~" % child_id:
 
716
                return True
 
717
        return False
 
718
 
 
719
class MockEntry(object):
 
720
    def __init__(self):
 
721
        object.__init__(self)
 
722
        self.name = "name"
 
723
 
 
724
class TestGetBackupName(TestCase):
 
725
    def test_get_backup_name(self):
 
726
        tt = MockTransform()
 
727
        name = get_backup_name(MockEntry(), {'a':[]}, 'a', tt)
 
728
        self.assertEqual(name, 'name.~1~')
 
729
        name = get_backup_name(MockEntry(), {'a':['1']}, 'a', tt)
 
730
        self.assertEqual(name, 'name.~2~')
 
731
        name = get_backup_name(MockEntry(), {'a':['2']}, 'a', tt)
 
732
        self.assertEqual(name, 'name.~1~')
 
733
        name = get_backup_name(MockEntry(), {'a':['2'], 'b':[]}, 'b', tt)
 
734
        self.assertEqual(name, 'name.~1~')
 
735
        name = get_backup_name(MockEntry(), {'a':['1', '2', '3']}, 'a', tt)
 
736
        self.assertEqual(name, 'name.~4~')