~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_transform.py

  • Committer: John Arbash Meinel
  • Date: 2006-07-03 18:27:35 UTC
  • mto: This revision was merged to the branch mainline in revision 1851.
  • Revision ID: john@arbash-meinel.com-20060703182735-3081f13e92d7f657
WorkingTree.open_containing() was directly calling os.getcwdu(), which on mac returns the wrong normalization, and on win32 would have the wrong slashes

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