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