~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_transform.py

(mbp) merge bzr.dev to 0.8, prepare for release

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~')