~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_transform.py

  • Committer: Aaron Bentley
  • Date: 2006-09-21 20:21:36 UTC
  • mto: (2027.1.2 revert-subpath-56549)
  • mto: This revision was merged to the branch mainline in revision 2031.
  • Revision ID: abentley@panoramicfeedback.com-20060921202136-e5a8deaadfa00021
Added test for preserving file mode

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