/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_transform.py

  • Committer: Aaron Bentley
  • Date: 2006-06-06 22:47:43 UTC
  • mto: This revision was merged to the branch mainline in revision 1745.
  • Revision ID: aaron.bentley@utoronto.ca-20060606224743-fae7fc6407ed27f8
Update test for new interface

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