~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_transform.py

  • Committer: Olaf Conradi
  • Date: 2006-03-28 23:30:02 UTC
  • mto: (1661.1.1 bzr.mbp.remember)
  • mto: This revision was merged to the branch mainline in revision 1663.
  • Revision ID: olaf@conradi.org-20060328233002-f6262df0e19c1963
Added testcases for using pull with --remember. Moved remember code to
beginning of cmd_pull. This remembers the location in case of a failure
during pull.

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