/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_transform.py

  • Committer: Martin Pool
  • Date: 2007-03-06 00:15:39 UTC
  • mto: (2255.2.200 subtree)
  • mto: This revision was merged to the branch mainline in revision 2322.
  • Revision ID: mbp@sourcefrog.net-20070306001539-u50spfvz7v2c1pun
review fixes from robert

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