~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-08-12 15:41:44 UTC
  • Revision ID: mbp@sourcefrog.net-20050812154144-bc98570a78b8f633
- merge in deferred revfile work

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
 
 
583
 
    def test_file_merge(self):
584
 
        if not has_symlinks():
585
 
            raise TestSkipped('Symlinks are not supported on this platform')
586
 
        base = TransformGroup("BASE")
587
 
        this = TransformGroup("THIS")
588
 
        other = TransformGroup("OTHER")
589
 
        for tg in this, base, other:
590
 
            tg.tt.new_directory('a', tg.root, 'a')
591
 
            tg.tt.new_symlink('b', tg.root, 'b', 'b')
592
 
            tg.tt.new_file('c', tg.root, 'c', 'c')
593
 
            tg.tt.new_symlink('d', tg.root, tg.name, 'd')
594
 
        targets = ((base, 'base-e', 'base-f', None, None), 
595
 
                   (this, 'other-e', 'this-f', 'other-g', 'this-h'), 
596
 
                   (other, 'other-e', None, 'other-g', 'other-h'))
597
 
        for tg, e_target, f_target, g_target, h_target in targets:
598
 
            for link, target in (('e', e_target), ('f', f_target), 
599
 
                                 ('g', g_target), ('h', h_target)):
600
 
                if target is not None:
601
 
                    tg.tt.new_symlink(link, tg.root, target, link)
602
 
 
603
 
        for tg in this, base, other:
604
 
            tg.tt.apply()
605
 
        Merge3Merger(this.wt, this.wt, base.wt, other.wt)
606
 
        self.assertIs(os.path.isdir(this.wt.abspath('a')), True)
607
 
        self.assertIs(os.path.islink(this.wt.abspath('b')), True)
608
 
        self.assertIs(os.path.isfile(this.wt.abspath('c')), True)
609
 
        for suffix in ('THIS', 'BASE', 'OTHER'):
610
 
            self.assertEqual(os.readlink(this.wt.abspath('d.'+suffix)), suffix)
611
 
        self.assertIs(os.path.lexists(this.wt.abspath('d')), False)
612
 
        self.assertEqual(this.wt.id2path('d'), 'd.OTHER')
613
 
        self.assertEqual(this.wt.id2path('f'), 'f.THIS')
614
 
        self.assertEqual(os.readlink(this.wt.abspath('e')), 'other-e')
615
 
        self.assertIs(os.path.lexists(this.wt.abspath('e.THIS')), False)
616
 
        self.assertIs(os.path.lexists(this.wt.abspath('e.OTHER')), False)
617
 
        self.assertIs(os.path.lexists(this.wt.abspath('e.BASE')), False)
618
 
        self.assertIs(os.path.lexists(this.wt.abspath('g')), True)
619
 
        self.assertIs(os.path.lexists(this.wt.abspath('g.BASE')), False)
620
 
        self.assertIs(os.path.lexists(this.wt.abspath('h')), False)
621
 
        self.assertIs(os.path.lexists(this.wt.abspath('h.BASE')), False)
622
 
        self.assertIs(os.path.lexists(this.wt.abspath('h.THIS')), True)
623
 
        self.assertIs(os.path.lexists(this.wt.abspath('h.OTHER')), True)
624
 
 
625
 
    def test_filename_merge(self):
626
 
        base = TransformGroup("BASE")
627
 
        this = TransformGroup("THIS")
628
 
        other = TransformGroup("OTHER")
629
 
        base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a') 
630
 
                                   for t in [base, this, other]]
631
 
        base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b') 
632
 
                                   for t in [base, this, other]]
633
 
        base.tt.new_directory('c', base_a, 'c')
634
 
        this.tt.new_directory('c1', this_a, 'c')
635
 
        other.tt.new_directory('c', other_b, 'c')
636
 
 
637
 
        base.tt.new_directory('d', base_a, 'd')
638
 
        this.tt.new_directory('d1', this_b, 'd')
639
 
        other.tt.new_directory('d', other_a, 'd')
640
 
 
641
 
        base.tt.new_directory('e', base_a, 'e')
642
 
        this.tt.new_directory('e', this_a, 'e')
643
 
        other.tt.new_directory('e1', other_b, 'e')
644
 
 
645
 
        base.tt.new_directory('f', base_a, 'f')
646
 
        this.tt.new_directory('f1', this_b, 'f')
647
 
        other.tt.new_directory('f1', other_b, 'f')
648
 
 
649
 
        for tg in [this, base, other]:
650
 
            tg.tt.apply()
651
 
        Merge3Merger(this.wt, this.wt, base.wt, other.wt)
652
 
        self.assertEqual(this.wt.id2path('c'), pathjoin('b/c1'))
653
 
        self.assertEqual(this.wt.id2path('d'), pathjoin('b/d1'))
654
 
        self.assertEqual(this.wt.id2path('e'), pathjoin('b/e1'))
655
 
        self.assertEqual(this.wt.id2path('f'), pathjoin('b/f1'))
656
 
 
657
 
    def test_filename_merge_conflicts(self):
658
 
        base = TransformGroup("BASE")
659
 
        this = TransformGroup("THIS")
660
 
        other = TransformGroup("OTHER")
661
 
        base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a') 
662
 
                                   for t in [base, this, other]]
663
 
        base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b') 
664
 
                                   for t in [base, this, other]]
665
 
 
666
 
        base.tt.new_file('g', base_a, 'g', 'g')
667
 
        other.tt.new_file('g1', other_b, 'g1', 'g')
668
 
 
669
 
        base.tt.new_file('h', base_a, 'h', 'h')
670
 
        this.tt.new_file('h1', this_b, 'h1', 'h')
671
 
 
672
 
        base.tt.new_file('i', base.root, 'i', 'i')
673
 
        other.tt.new_directory('i1', this_b, 'i')
674
 
 
675
 
        for tg in [this, base, other]:
676
 
            tg.tt.apply()
677
 
        Merge3Merger(this.wt, this.wt, base.wt, other.wt)
678
 
 
679
 
        self.assertEqual(this.wt.id2path('g'), pathjoin('b/g1.OTHER'))
680
 
        self.assertIs(os.path.lexists(this.wt.abspath('b/g1.BASE')), True)
681
 
        self.assertIs(os.path.lexists(this.wt.abspath('b/g1.THIS')), False)
682
 
        self.assertEqual(this.wt.id2path('h'), pathjoin('b/h1.THIS'))
683
 
        self.assertIs(os.path.lexists(this.wt.abspath('b/h1.BASE')), True)
684
 
        self.assertIs(os.path.lexists(this.wt.abspath('b/h1.OTHER')), False)
685
 
        self.assertEqual(this.wt.id2path('i'), pathjoin('b/i1.OTHER'))
686
 
 
687
 
class TestBuildTree(TestCaseInTempDir):
688
 
    def test_build_tree(self):
689
 
        if not has_symlinks():
690
 
            raise TestSkipped('Test requires symlink support')
691
 
        os.mkdir('a')
692
 
        a = BzrDir.create_standalone_workingtree('a')
693
 
        os.mkdir('a/foo')
694
 
        file('a/foo/bar', 'wb').write('contents')
695
 
        os.symlink('a/foo/bar', 'a/foo/baz')
696
 
        a.add(['foo', 'foo/bar', 'foo/baz'])
697
 
        a.commit('initial commit')
698
 
        b = BzrDir.create_standalone_workingtree('b')
699
 
        build_tree(a.basis_tree(), b)
700
 
        self.assertIs(os.path.isdir('b/foo'), True)
701
 
        self.assertEqual(file('b/foo/bar', 'rb').read(), "contents")
702
 
        self.assertEqual(os.readlink('b/foo/baz'), 'a/foo/bar')
703
 
        
704
 
class MockTransform(object):
705
 
 
706
 
    def has_named_child(self, by_parent, parent_id, name):
707
 
        for child_id in by_parent[parent_id]:
708
 
            if child_id == '0':
709
 
                if name == "name~":
710
 
                    return True
711
 
            elif name == "name.~%s~" % child_id:
712
 
                return True
713
 
        return False
714
 
 
715
 
class MockEntry(object):
716
 
    def __init__(self):
717
 
        object.__init__(self)
718
 
        self.name = "name"
719
 
 
720
 
class TestGetBackupName(TestCase):
721
 
    def test_get_backup_name(self):
722
 
        tt = MockTransform()
723
 
        name = get_backup_name(MockEntry(), {'a':[]}, 'a', tt)
724
 
        self.assertEqual(name, 'name.~1~')
725
 
        name = get_backup_name(MockEntry(), {'a':['1']}, 'a', tt)
726
 
        self.assertEqual(name, 'name.~2~')
727
 
        name = get_backup_name(MockEntry(), {'a':['2']}, 'a', tt)
728
 
        self.assertEqual(name, 'name.~1~')
729
 
        name = get_backup_name(MockEntry(), {'a':['2'], 'b':[]}, 'b', tt)
730
 
        self.assertEqual(name, 'name.~1~')
731
 
        name = get_backup_name(MockEntry(), {'a':['1', '2', '3']}, 'a', tt)
732
 
        self.assertEqual(name, 'name.~4~')