~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-05-03 07:48:54 UTC
  • Revision ID: mbp@sourcefrog.net-20050503074854-adb6f9d6382e27a9
- sketchy experiments in bash and zsh completion

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