~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 08:00:27 UTC
  • Revision ID: mbp@sourcefrog.net-20050503080027-908edb5b39982198
doc

Show diffs side-by-side

added added

removed removed

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