~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_transform.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2006-08-14 04:29:57 UTC
  • mfrom: (1912.1.2 bzr.mbp.integration)
  • Revision ID: pqm@pqm.ubuntu.com-20060814042957-3fa43995db57512a
(mbp) merge from 0.9, update for 0.10

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