~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_transform.py

  • Committer: Martin Pool
  • Date: 2005-05-03 08:00:27 UTC
  • Revision ID: mbp@sourcefrog.net-20050503080027-908edb5b39982198
doc

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