~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-03-06 11:20:10 UTC
  • mfrom: (1593 +trunk)
  • mto: This revision was merged to the branch mainline in revision 1611.
  • Revision ID: mbp@sourcefrog.net-20060306112010-17c0170dde5d1eea
[merge] large merge to sync with bzr.dev

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